From cf2fa796e27183bf1c6e16973b69a5416786fdec Mon Sep 17 00:00:00 2001
From: McSchneidar <262792195+McSchneidar@users.noreply.github.com>
Date: Mon, 2 Nov 2026 08:11:20 +0100
Subject: [PATCH] .NET10 Migration (#946)
Runtime and build
- Move all projects to .NET 10 with SDK-style project files and a shared
Directory.Build.props (WIN/LINUX, x86/x64 defines)
- Load managed and unmanaged dependencies from libs/managed and
libs/unmanaged through assembly and native library resolvers
- Compile party modes with Roslyn instead of CodeDom
- Update AppVeyor to the .NET 10 SDK and dotnet test
- Download the FFmpeg and gstreamer runtimes from the dependencies
repository during the build
- Remove the VC++ 2015-2022 runtime check, no dependency needs it anymore
Graphics and windowing
- Replace the Direct3D 9 renderer with Direct3D 11 (Vortice)
- Pace Direct3D 11 frames with a frame latency waitable swap chain
- Move window, input and message boxes to SDL3
- Replace System.Drawing with SkiaSharp and WinForms with SDL3 and a
software renderer
Video and audio
- Replace Acinerella with FFmpeg 8.1.3 (FFmpeg.AutoGen)
- Add hardware accelerated video decoding (D3D11VA, DXVA2, VideoToolbox,
VAAPI, VDPAU) with software fallback
- Convert YUV video frames to RGB on the GPU in Direct3D 11 and OpenGL
- Add the video options HardwareDecoding, GpuColorConversion,
DecoderThreads and VideoDownscaleResolution
- Update OpenAL to OpenTK 4 and NAudio recording to WasapiRecorder
Fixes
- Fix the song preview start and shorten the reporter buttons
- Fix a crash when opening the highscore without a selected song
- Use the options slide highlight in the party mode screens to avoid
brightness flicker on VRR displays
- Disable GameInput in the x86 build, SDL3 crashes in GameInputCreate
on 32-bit systems with the GameInput runtime installed
- Fix a truncation warning in PitchTracker
---
.appveyor/runGitLink.ps1 | 13 -
.gitignore | 8 +
Directory.Build.props | 57 +
HowToBuildLinux.md | 38 +-
HowToBuildWin.md | 2 +-
Output/Languages/en.xml | 9 +-
Output/Licenses/FFmpeg.AutoGen/LICENSE.txt | 21 +
.../FFmpeg.AutoGen/SourceAndVersion.txt | 7 +
Output/Licenses/FFmpeg/LICENSE.txt | 674 +++++++
Output/Licenses/FFmpeg/SourceAndVersion.txt | 13 +
Output/Licenses/NAudio/SourceAndVersion.txt | 2 +-
Output/Licenses/OpenTK/LICENSE.txt | 21 +
Output/Licenses/OpenTK/SourceAndVersion.txt | 9 +
Output/Licenses/QR Coder/QRCoder.txt | 5 +-
Output/Licenses/QR Coder/SourceAndVersion.txt | 4 +-
Output/Licenses/Roslyn/LICENSE.txt | 21 +
Output/Licenses/Roslyn/SourceAndVersion.txt | 7 +
Output/Licenses/SDL3/LICENSE-SDL3-CS.txt | 21 +
Output/Licenses/SDL3/LICENSE.txt | 17 +
Output/Licenses/SDL3/SourceAndVersion.txt | 8 +
Output/Licenses/SkiaSharp/LICENSE.txt | 19 +
.../Licenses/SkiaSharp/SourceAndVersion.txt | 7 +
Output/Licenses/Vortice/LICENSE.txt | 2 +-
Output/Licenses/Vortice/SourceAndVersion.txt | 9 +-
.../Screens/PartyScreenChallengeConfig.xml | 12 +-
.../Screens/PartyScreenChallengeNames.xml | 2 +-
.../Screens/PartyScreenChallengeSongs.xml | 12 +-
.../Screens/PartyScreenPartnerSwapConfig.xml | 10 +-
.../Screens/PartyScreenPartnerSwapNames.xml | 2 +-
.../Screens/PartyScreenPartnerSwapSongs.xml | 12 +-
.../Screens/PartyScreenRuleRouletteConfig.xml | 10 +-
.../Screens/PartyScreenRuleRouletteNames.xml | 2 +-
.../Screens/PartyScreenRuleRouletteSongs.xml | 12 +-
.../Screens/PartyScreenTeamSurvivalConfig.xml | 6 +-
.../Screens/PartyScreenTeamSurvivalNames.xml | 2 +-
.../Screens/PartyScreenTeamSurvivalSongs.xml | 12 +-
.../Screens/PartyScreenTicTacToeConfig.xml | 8 +-
.../Screens/PartyScreenTicTacToeNames.xml | 2 +-
.../Screens/PartyScreenTicTacToeSongs.xml | 12 +-
.../Screens/PartyScreenTournamentConfig.xml | 4 +-
.../Screens/PartyScreenTournamentNames.xml | 2 +-
.../Screens/PartyScreenTournamentSongs.xml | 12 +-
Output/Readme.txt | 4 +-
PartyModes/Directory.Build.targets | 15 +
.../CPartyScreenChallengeConfig.cs | 1 -
.../CPartyScreenChallengeMain.cs | 1 -
.../PartyModeChallenge.csproj | 120 +-
.../CPartyScreenPartnerSwapConfig.cs | 1 -
.../CPartyScreenPartnerSwapMain.cs | 1 -
.../PartyModePartnerSwap.csproj | 111 +-
.../CPartyScreenRuleRouletteConfig.cs | 1 -
.../CPartyScreenRuleRouletteMain.cs | 1 -
.../CPartyScreenRuleRouletteRules.cs | 1 -
.../PartyModeRuleRoulette.csproj | 112 +-
.../CPartyScreenTeamSurvivalConfig.cs | 1 -
.../CPartyScreenTeamSurvivalHearts.cs | 1 -
.../CPartyScreenTeamSurvivalMain.cs | 1 -
.../PartyModeTeamSurvival.csproj | 112 +-
.../CPartyScreenTicTacToeConfig.cs | 1 -
.../CPartyScreenTicTacToeMain.cs | 1 -
.../PartyModeTicTacToe.csproj | 120 +-
.../CPartyScreenTournamentConfig.cs | 1 -
.../CPartyScreenTournamentMain.cs | 1 -
.../PartyModeTournament.csproj | 111 +-
PitchTracker/PitchWrapper.h | 2 +-
PitchTracker/makefile | 7 +-
PitchTracker/performous/libda/sample.hpp | 2 +-
README.md | 4 +-
Tests/Tests.csproj | 136 +-
Tests/VocaluxeLib/XML/TestFiles/SConfig.xml | 4 +
Tests/app.config | 18 -
Vocaluxe.sln | 40 +-
Vocaluxe/Base/CBackgroundMusic.cs | 2 +-
Vocaluxe/Base/CConfig.cs | 24 +-
Vocaluxe/Base/CCover.cs | 9 +-
Vocaluxe/Base/CCoverGenerator.cs | 142 +-
Vocaluxe/Base/CDraw.cs | 23 +-
Vocaluxe/Base/CGraphics.cs | 1 -
Vocaluxe/Base/CParty.cs | 86 +-
Vocaluxe/Base/CPlaylists.cs | 2 +-
Vocaluxe/Base/CSettings.cs | 5 +-
Vocaluxe/Base/CUtility.cs | 66 +-
Vocaluxe/Base/CWebcam.cs | 8 +-
Vocaluxe/Base/Fonts/CFontStyle.cs | 49 +-
Vocaluxe/Base/Fonts/CFonts.cs | 13 +-
Vocaluxe/Base/Fonts/CGlyph.cs | 68 +-
Vocaluxe/Base/Web/CVocaluxeRemote.cs | 1 -
Vocaluxe/COSFunctions.cs | 3 +-
Vocaluxe/CProgrammHelper.cs | 54 +-
Vocaluxe/CSplashScreen.cs | 105 +-
Vocaluxe/Lib/CSdl.cs | 393 ++++
Vocaluxe/Lib/Database/CCoverDB.cs | 25 +-
Vocaluxe/Lib/Draw/CDirect3D.cs | 1011 ++++++-----
Vocaluxe/Lib/Draw/CDrawBaseSDL.cs | 254 +++
Vocaluxe/Lib/Draw/CDrawBaseWindows.cs | 227 ---
Vocaluxe/Lib/Draw/CDrawSoftware.cs | 275 +++
Vocaluxe/Lib/Draw/CDrawWinForm.cs | 497 -----
Vocaluxe/Lib/Draw/COpenGL.cs | 782 ++++----
Vocaluxe/Lib/Draw/CTextureProvider.cs | 78 +-
Vocaluxe/Lib/Draw/IDraw.cs | 11 +-
Vocaluxe/Lib/Input/CGamePad.cs | 167 +-
Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs | 1 -
.../Playback/Decoder/CAudioDecoderFFmpeg.cs | 121 +-
.../Lib/Sound/Playback/OpenAL/COpenALPlay.cs | 22 +-
.../Sound/Playback/OpenAL/COpenAlStream.cs | 6 +-
.../Lib/Sound/Record/NAudio/CNAudioRecord.cs | 31 +-
Vocaluxe/Lib/Video/Acinerella/CAcinerella.cs | 453 -----
Vocaluxe/Lib/Video/Acinerella/acinerella.c | 1248 -------------
Vocaluxe/Lib/Video/Acinerella/acinerella.h | 518 ------
Vocaluxe/Lib/Video/Acinerella/makefile | 14 -
Vocaluxe/Lib/Video/CVideoDecoderFFmpeg.cs | 2 +-
.../Video/{Acinerella => FFmpeg}/CDecoder.cs | 2 +-
.../{Acinerella => FFmpeg}/CDecoderThread.cs | 99 +-
Vocaluxe/Lib/Video/FFmpeg/CFFmpegDecoder.cs | 734 ++++++++
.../{Acinerella => FFmpeg}/CFramebuffer.cs | 2 +-
Vocaluxe/Lib/Webcam/CAForgeNet.cs | 9 +-
Vocaluxe/Lib/Webcam/IWebcam.cs | 4 +-
Vocaluxe/Program.cs | 269 ++-
Vocaluxe/Reporting/Reporter.Designer.cs | 220 ---
Vocaluxe/Reporting/Reporter.cs | 192 +-
Vocaluxe/Reporting/Reporter.resx | 1615 -----------------
Vocaluxe/Screens/CPopupScreenPlayerControl.cs | 1 -
Vocaluxe/Screens/CPopupScreenServerQR.cs | 7 +-
Vocaluxe/Screens/CPopupScreenVolumeControl.cs | 1 -
Vocaluxe/Screens/CScreenCredits.cs | 1 -
Vocaluxe/Screens/CScreenHighscore.cs | 1 -
Vocaluxe/Screens/CScreenJukebox.cs | 1 -
Vocaluxe/Screens/CScreenJukeboxPlayer.cs | 1 -
Vocaluxe/Screens/CScreenLoad.cs | 1 -
Vocaluxe/Screens/CScreenMain.cs | 1 -
Vocaluxe/Screens/CScreenNames.cs | 1 -
Vocaluxe/Screens/CScreenOptionsBase.cs | 1 -
Vocaluxe/Screens/CScreenOptionsGame.cs | 1 -
Vocaluxe/Screens/CScreenOptionsGraphics.cs | 1 -
Vocaluxe/Screens/CScreenOptionsLyrics.cs | 1 -
Vocaluxe/Screens/CScreenOptionsRecord.cs | 1 -
Vocaluxe/Screens/CScreenOptionsServer.cs | 1 -
Vocaluxe/Screens/CScreenOptionsSongFolders.cs | 15 +-
Vocaluxe/Screens/CScreenOptionsSound.cs | 1 -
Vocaluxe/Screens/CScreenOptionsTheme.cs | 1 -
Vocaluxe/Screens/CScreenOptionsVideo.cs | 1 -
.../Screens/CScreenOptionsVideoAdjustments.cs | 1 -
Vocaluxe/Screens/CScreenParty.cs | 1 -
Vocaluxe/Screens/CScreenProfiles.cs | 10 +-
Vocaluxe/Screens/CScreenScore.cs | 1 -
Vocaluxe/Screens/CScreenSing.cs | 1 -
Vocaluxe/Screens/CScreenSong.cs | 11 +-
Vocaluxe/Screens/CScreenStatistics.cs | 1 -
Vocaluxe/Screens/CScreenStatisticsDetail.cs | 1 -
Vocaluxe/Screens/CScreenTest.cs | 1 -
Vocaluxe/Screens/CScreenWebQueue.cs | 1 -
Vocaluxe/Vocaluxe.csproj | 483 +----
Vocaluxe/app.config | 39 -
VocaluxeLib/CEncoding.cs | 17 +-
VocaluxeLib/CHelper.cs | 12 +-
VocaluxeLib/Enums.cs | 11 +
VocaluxeLib/Extensions.cs | 46 +-
VocaluxeLib/Keys.cs | 161 ++
VocaluxeLib/Menu/CMenu.cs | 1 -
VocaluxeLib/Menu/CMenuPartyNameSelection.cs | 1 -
VocaluxeLib/Menu/CMenuPartySongSelection.cs | 1 -
VocaluxeLib/Menu/CNameSelection.cs | 1 -
VocaluxeLib/Menu/CObjectInteractions.cs | 1 -
VocaluxeLib/Menu/CPlaylist.cs | 1 -
VocaluxeLib/Menu/SongMenu/CSongMenuList.cs | 1 -
.../Menu/SongMenu/CSongMenuTileBoard.cs | 1 -
VocaluxeLib/Structs.cs | 1 -
VocaluxeLib/VocaluxeLib.csproj | 200 +-
VocaluxeWeb/Hosting/CCertificateProvider.cs | 4 +-
VocaluxeWeb/Hosting/CStartup.cs | 11 +-
VocaluxeWeb/Hosting/CWebHost.cs | 12 +-
VocaluxeWeb/Services/CImageService.cs | 113 +-
VocaluxeWeb/Services/CPinHasher.cs | 5 +-
VocaluxeWeb/VocaluxeWeb.csproj | 31 +-
appveyor.yml | 27 +-
makefile | 7 +-
176 files changed, 5218 insertions(+), 8042 deletions(-)
delete mode 100644 .appveyor/runGitLink.ps1
create mode 100644 Directory.Build.props
create mode 100644 Output/Licenses/FFmpeg.AutoGen/LICENSE.txt
create mode 100644 Output/Licenses/FFmpeg.AutoGen/SourceAndVersion.txt
create mode 100644 Output/Licenses/FFmpeg/LICENSE.txt
create mode 100644 Output/Licenses/FFmpeg/SourceAndVersion.txt
create mode 100644 Output/Licenses/OpenTK/LICENSE.txt
create mode 100644 Output/Licenses/OpenTK/SourceAndVersion.txt
create mode 100644 Output/Licenses/Roslyn/LICENSE.txt
create mode 100644 Output/Licenses/Roslyn/SourceAndVersion.txt
create mode 100644 Output/Licenses/SDL3/LICENSE-SDL3-CS.txt
create mode 100644 Output/Licenses/SDL3/LICENSE.txt
create mode 100644 Output/Licenses/SDL3/SourceAndVersion.txt
create mode 100644 Output/Licenses/SkiaSharp/LICENSE.txt
create mode 100644 Output/Licenses/SkiaSharp/SourceAndVersion.txt
create mode 100644 PartyModes/Directory.Build.targets
delete mode 100644 Tests/app.config
create mode 100644 Vocaluxe/Lib/CSdl.cs
create mode 100644 Vocaluxe/Lib/Draw/CDrawBaseSDL.cs
delete mode 100644 Vocaluxe/Lib/Draw/CDrawBaseWindows.cs
create mode 100644 Vocaluxe/Lib/Draw/CDrawSoftware.cs
delete mode 100644 Vocaluxe/Lib/Draw/CDrawWinForm.cs
delete mode 100644 Vocaluxe/Lib/Video/Acinerella/CAcinerella.cs
delete mode 100644 Vocaluxe/Lib/Video/Acinerella/acinerella.c
delete mode 100644 Vocaluxe/Lib/Video/Acinerella/acinerella.h
delete mode 100644 Vocaluxe/Lib/Video/Acinerella/makefile
rename Vocaluxe/Lib/Video/{Acinerella => FFmpeg}/CDecoder.cs (99%)
rename Vocaluxe/Lib/Video/{Acinerella => FFmpeg}/CDecoderThread.cs (85%)
create mode 100644 Vocaluxe/Lib/Video/FFmpeg/CFFmpegDecoder.cs
rename Vocaluxe/Lib/Video/{Acinerella => FFmpeg}/CFramebuffer.cs (98%)
delete mode 100644 Vocaluxe/Reporting/Reporter.Designer.cs
delete mode 100644 Vocaluxe/Reporting/Reporter.resx
delete mode 100644 Vocaluxe/app.config
create mode 100644 VocaluxeLib/Keys.cs
diff --git a/.appveyor/runGitLink.ps1 b/.appveyor/runGitLink.ps1
deleted file mode 100644
index d7956fe0..00000000
--- a/.appveyor/runGitLink.ps1
+++ /dev/null
@@ -1,13 +0,0 @@
-choco install gitlink --version 3.1.0-unstable0017 --pre --no-progress --limit-output
-
-Get-ChildItem -Path ".\Output" -Recurse "*.pdb" | %{
- If ($_.Fullname -contains 'PitchTracker.pdb'){
- & gitlink -u "`"https://github.com/$($Env:APPVEYOR_REPO_NAME)`"" -a --baseDir "`"$Env:APPVEYOR_BUILD_FOLDER`"" --commit "`"$Env:APPVEYOR_REPO_COMMIT`"" "`"$($_.Fullname)`""
- }
- else{
- & gitlink -u "`"https://github.com/$($Env:APPVEYOR_REPO_NAME)`"" --baseDir "`"$Env:APPVEYOR_BUILD_FOLDER`"" --commit "`"$Env:APPVEYOR_REPO_COMMIT`"" "`"$($_.Fullname)`""
- }
-}
-
-# srcsrv were integrated in the pdb files -> delete the srcsrv files
-Remove-Item ".\Output\*.pdb.srcsrv"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0cec5db4..6381898a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
obj
+bin
PitchTracker.dll
Thumbs.db
.transifexrc
@@ -43,6 +44,13 @@ tx.exe
/Output/Vocaluxe_x64.exe*
/Output/Vocaluxe.vshost.exe*
/Output/VocaluxeLib.dll
+/Output/Vocaluxe
+/Output/Vocaluxe.dll
+/Output/Vocaluxe.runtimeconfig.json
+/Output/*.so
+/Output/*.dylib
+/Output/runtimes
+/Output/PartyModes/*/*.dll
/Output/Photos
/Output/Screenshots
/Output/Website
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000..bb660b27
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,57 @@
+
+
+ net10.0
+ DebugWin;ReleaseWin;InstallerWin;DebugLinux;ReleaseLinux
+ AnyCPU;x86;x64
+ false
+ false
+ en
+ false
+ prompt
+ 4
+ portable
+ true
+ $(MSBuildThisFileDirectory)CheckingRules.ruleset
+
+
+
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+
+
+
+ false
+ $(DefineConstants);TRACE;DEBUG
+
+
+ true
+
+
+
+ $(DefineConstants);WIN
+ $(NoWarn);CA1416
+
+
+ $(DefineConstants);LINUX
+
+
+ $(DefineConstants);INSTALLER
+
+
+
+ x86
+ $(DefineConstants);ARCH_X86
+
+
+ x64
+ $(DefineConstants);ARCH_X64
+
+
diff --git a/HowToBuildLinux.md b/HowToBuildLinux.md
index 9eace736..39668f3e 100644
--- a/HowToBuildLinux.md
+++ b/HowToBuildLinux.md
@@ -4,26 +4,19 @@ Runing Vocaluxe on Linux is not offically supported yet -but you can try it your
(the problematic part is fullfilling the runtime dependencies)
-* Install ffmpeg 3.2 (a quick and dirty compile script can be found here: [installFFmpeg.sh](https://github.com/lukeIam/Vocaluxe/blob/travis/.travis/installFFmpeg.sh) )
-* Get sure your gcc and g++ version is >= 4.8
-* Install at least mono 5.8.0 ([HowTo install mono](http://www.mono-project.com/download/stable/))
-* Clone the repository
-You want to use this branch as it contains some linux specific changes
-(compiles on Ubuntu 14.04): [travis@lukeIam/Vocaluxe](https://github.com/lukeIam/Vocaluxe/tree/travis)
-* Navigate to the travis branch and execute (commands from [.travis.yml](https://github.com/lukeIam/Vocaluxe/blob/travis/.travis.yml)):
+* Install the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+* Get sure gcc, g++, make and pkg-config are installed
+* Install the runtime dependencies:
+ - FFmpeg 8 (libavformat 62, libavcodec 62, libavutil 60, libswscale 9, libswresample 6)
+ - portaudio
+ - hidapi (libhidapi-libusb)
+ - openal (optional, only needed for the OpenAL playback)
+ - gstreamer 1.0 (optional, only needed for the GStreamer playback)
+* Clone the repository and execute in its folder:
```bash
chmod ugo+x ./.build/linuxPostBuildEvent.sh
chmod ugo+x ./.build/linuxPreBuildEvent.sh
-chmod ugo+x ./.travis/gitDescribe.sh
-wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -P ./.travis/
-
-config=Linux platform=x64
-
-# ./.travis/gitDescribe.sh
-mono ./.travis/nuget.exe restore
-make -C PitchTracker
-make -C Vocaluxe/Lib/Video/Acinerella
-msbuild /p:Configuration=Release$config /p:Platform=$platform /p:TargetFrameworkVersion=v4.7 Vocaluxe.sln
+make
```
* The build should complete without errors
* change to the Output directory:
@@ -32,15 +25,8 @@ cd Output
```
* Start Vocaluxe:
```bash
-mono Vocaluxe.exe
+./Vocaluxe
```
-* Vocaluxe will start (you will see the splash screen) and then crash because runtime dependencies are missing
-Required dependencies (by heart - can be incorrect and incomplete):
- - gstreamer 1.0
- - ffmpeg 3.2 (maybe we can use the build script which builds the libs at the moment)
- - hidapi
- - libgstreamersharpglue-1.0.0
- - portaudio
- - ?
+* If GStreamer is not installed, set `PlayBackLib` to `PortAudio` in the `Config.xml`
* If yo make some progress we would be happy if you share it with us in a pull request/issue.
diff --git a/HowToBuildWin.md b/HowToBuildWin.md
index 09bb02cf..05dc65f8 100644
--- a/HowToBuildWin.md
+++ b/HowToBuildWin.md
@@ -9,7 +9,7 @@

-* Make sure [Visual Studio 2012/2013(?)](http://www.visualstudio.com/downloads/download-visual-studio-vs#d-express-windows-desktop) is installed
+* Make sure [Visual Studio 2026](https://visualstudio.microsoft.com/downloads/) with the workloads `.NET desktop development` and `Desktop development with C++` and the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) are installed
* Make sure [Node.js (LTS)](https://nodejs.org) is installed. It is needed to build the WebApp (folder `WebApp`), which is built automatically together with the project `VocaluxeWeb`. Restart Visual Studio after installing it.
diff --git a/Output/Languages/en.xml b/Output/Languages/en.xml
index 7e9151e3..7c0c3c6b 100644
--- a/Output/Languages/en.xml
+++ b/Output/Languages/en.xml
@@ -389,11 +389,16 @@
Server address
Server is not running. Please activate it and restart the game!
We are sorry
- that Vocaluxe run into an error and crashed.\r\nTo help us fix the problem please send us a report.\r\nEdit the following log before submitting to remove possibly sensitive information.
- that you experienced an error.\r\nTo help us fix the problem please send us a report.\r\nEdit the following log before submitting to remove possibly sensitive information.
+ that Vocaluxe run into an error and crashed.\r\nTo help us fix the problem please send us a report.\r\nOpen the log to check it and remove possibly sensitive information before submitting.
+ that you experienced an error.\r\nTo help us fix the problem please send us a report.\r\nOpen the log to check it and remove possibly sensitive information before submitting.
Do not upload anything (you can still copy the error message above)
Upload and open an issue (publicly visible + requires a github account)
Upload and get a link to your report (publicly visible)
+ Open the log to check or edit it
+ Don't upload
+ Upload + issue
+ Upload
+ Open log
Submit
Uploading
Exit
diff --git a/Output/Licenses/FFmpeg.AutoGen/LICENSE.txt b/Output/Licenses/FFmpeg.AutoGen/LICENSE.txt
new file mode 100644
index 00000000..bf5ff3a5
--- /dev/null
+++ b/Output/Licenses/FFmpeg.AutoGen/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Ruslan Balanukhin (Rationale One)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/Output/Licenses/FFmpeg.AutoGen/SourceAndVersion.txt b/Output/Licenses/FFmpeg.AutoGen/SourceAndVersion.txt
new file mode 100644
index 00000000..412b1e7e
--- /dev/null
+++ b/Output/Licenses/FFmpeg.AutoGen/SourceAndVersion.txt
@@ -0,0 +1,7 @@
+https://github.com/Ruslan-B/FFmpeg.AutoGen
+
+Version: FFmpeg.AutoGen.Abstractions 8.1.0, FFmpeg.AutoGen.Bindings.DynamicallyLoaded 8.1.0 (NuGet)
+
+Files:
+FFmpeg.AutoGen.Abstractions.dll
+FFmpeg.AutoGen.Bindings.DynamicallyLoaded.dll
diff --git a/Output/Licenses/FFmpeg/LICENSE.txt b/Output/Licenses/FFmpeg/LICENSE.txt
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/Output/Licenses/FFmpeg/LICENSE.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Output/Licenses/FFmpeg/SourceAndVersion.txt b/Output/Licenses/FFmpeg/SourceAndVersion.txt
new file mode 100644
index 00000000..8c604334
--- /dev/null
+++ b/Output/Licenses/FFmpeg/SourceAndVersion.txt
@@ -0,0 +1,13 @@
+https://ffmpeg.org/
+https://github.com/System233/ffmpeg-msvc-prebuilt (Windows builds)
+
+Version: FFmpeg 8.1.3 (shared, GPL)
+
+Files:
+avcodec-62.dll
+avdevice-62.dll
+avfilter-11.dll
+avformat-62.dll
+avutil-60.dll
+swresample-6.dll
+swscale-9.dll
diff --git a/Output/Licenses/NAudio/SourceAndVersion.txt b/Output/Licenses/NAudio/SourceAndVersion.txt
index b653f3fa..d6047f14 100644
--- a/Output/Licenses/NAudio/SourceAndVersion.txt
+++ b/Output/Licenses/NAudio/SourceAndVersion.txt
@@ -1,6 +1,6 @@
https://github.com/naudio/NAudio
-Version: NAudio.Wasapi 2.2.1, NAudio.Core 2.2.1 (NuGet, netstandard2.0)
+Version: NAudio.Wasapi 3.3.0, NAudio.Core 3.3.0 (NuGet, net9.0)
Files:
NAudio.Wasapi.dll
diff --git a/Output/Licenses/OpenTK/LICENSE.txt b/Output/Licenses/OpenTK/LICENSE.txt
new file mode 100644
index 00000000..78105525
--- /dev/null
+++ b/Output/Licenses/OpenTK/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2006-2020 Stefanos Apostolopoulos for the Open Toolkit project.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Output/Licenses/OpenTK/SourceAndVersion.txt b/Output/Licenses/OpenTK/SourceAndVersion.txt
new file mode 100644
index 00000000..f45e6216
--- /dev/null
+++ b/Output/Licenses/OpenTK/SourceAndVersion.txt
@@ -0,0 +1,9 @@
+https://github.com/opentk/opentk
+
+Version: OpenTK.Graphics 4.9.4, OpenTK.Audio.OpenAL 4.9.4, OpenTK.Core 4.9.4, OpenTK.Mathematics 4.9.4 (NuGet, netstandard2.1)
+
+Files:
+OpenTK.Graphics.dll
+OpenTK.Audio.OpenAL.dll
+OpenTK.Core.dll
+OpenTK.Mathematics.dll
diff --git a/Output/Licenses/QR Coder/QRCoder.txt b/Output/Licenses/QR Coder/QRCoder.txt
index e331740c..90b272f9 100644
--- a/Output/Licenses/QR Coder/QRCoder.txt
+++ b/Output/Licenses/QR Coder/QRCoder.txt
@@ -1,6 +1,7 @@
The MIT License (MIT)
-Copyright (c) 2013 Raffael Herrmann
+Copyright (c) 2013-2025 Raffael Herrmann
+Copyright (c) 2024-2025 Shane Krueger
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@@ -17,4 +18,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Output/Licenses/QR Coder/SourceAndVersion.txt b/Output/Licenses/QR Coder/SourceAndVersion.txt
index 556eb351..1c34e119 100644
--- a/Output/Licenses/QR Coder/SourceAndVersion.txt
+++ b/Output/Licenses/QR Coder/SourceAndVersion.txt
@@ -1,6 +1,6 @@
https://github.com/codebude/QRCoder/
-Version: master 05.08.2014
+Version: QRCoder 1.8.1 (NuGet, net6.0)
Files:
-QRCoder.dll
\ No newline at end of file
+QRCoder.dll
diff --git a/Output/Licenses/Roslyn/LICENSE.txt b/Output/Licenses/Roslyn/LICENSE.txt
new file mode 100644
index 00000000..94286c94
--- /dev/null
+++ b/Output/Licenses/Roslyn/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) .NET Foundation and Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Output/Licenses/Roslyn/SourceAndVersion.txt b/Output/Licenses/Roslyn/SourceAndVersion.txt
new file mode 100644
index 00000000..ae5072cd
--- /dev/null
+++ b/Output/Licenses/Roslyn/SourceAndVersion.txt
@@ -0,0 +1,7 @@
+https://github.com/dotnet/roslyn
+
+Version: Microsoft.CodeAnalysis.CSharp 5.9.0 (NuGet, net10.0)
+
+Files:
+Microsoft.CodeAnalysis.dll
+Microsoft.CodeAnalysis.CSharp.dll
diff --git a/Output/Licenses/SDL3/LICENSE-SDL3-CS.txt b/Output/Licenses/SDL3/LICENSE-SDL3-CS.txt
new file mode 100644
index 00000000..a070d473
--- /dev/null
+++ b/Output/Licenses/SDL3/LICENSE-SDL3-CS.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 ppy Pty Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Output/Licenses/SDL3/LICENSE.txt b/Output/Licenses/SDL3/LICENSE.txt
new file mode 100644
index 00000000..acaf5b21
--- /dev/null
+++ b/Output/Licenses/SDL3/LICENSE.txt
@@ -0,0 +1,17 @@
+Copyright (C) 1997-2026 Sam Lantinga
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
diff --git a/Output/Licenses/SDL3/SourceAndVersion.txt b/Output/Licenses/SDL3/SourceAndVersion.txt
new file mode 100644
index 00000000..3c4122f8
--- /dev/null
+++ b/Output/Licenses/SDL3/SourceAndVersion.txt
@@ -0,0 +1,8 @@
+https://github.com/libsdl-org/SDL
+https://github.com/ppy/SDL3-CS
+
+Version: ppy.SDL3-CS 2026.1002.1 (NuGet, net8.0)
+
+Files:
+SDL3-CS.dll
+SDL3.dll / libSDL3.so / libSDL3.dylib
diff --git a/Output/Licenses/SkiaSharp/LICENSE.txt b/Output/Licenses/SkiaSharp/LICENSE.txt
new file mode 100644
index 00000000..8532c34b
--- /dev/null
+++ b/Output/Licenses/SkiaSharp/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2015-2016 Xamarin, Inc.
+Copyright (c) 2017-2018 Microsoft Corporation.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Output/Licenses/SkiaSharp/SourceAndVersion.txt b/Output/Licenses/SkiaSharp/SourceAndVersion.txt
new file mode 100644
index 00000000..0f2c0d82
--- /dev/null
+++ b/Output/Licenses/SkiaSharp/SourceAndVersion.txt
@@ -0,0 +1,7 @@
+https://github.com/mono/SkiaSharp
+
+Version: SkiaSharp 4.154.0 (NuGet, net10.0)
+
+Files:
+SkiaSharp.dll
+libSkiaSharp.dll / libSkiaSharp.so / libSkiaSharp.dylib
diff --git a/Output/Licenses/Vortice/LICENSE.txt b/Output/Licenses/Vortice/LICENSE.txt
index eb9a6fa0..aefc93ac 100644
--- a/Output/Licenses/Vortice/LICENSE.txt
+++ b/Output/Licenses/Vortice/LICENSE.txt
@@ -1,4 +1,4 @@
-Vortice.Direct3D9, Vortice.Mathematics
+Vortice.Direct3D11, Vortice.DXGI, Vortice.D3DCompiler, Vortice.DirectX, Vortice.Mathematics
MIT License
Copyright (c) Amer Koleci and Contributors
diff --git a/Output/Licenses/Vortice/SourceAndVersion.txt b/Output/Licenses/Vortice/SourceAndVersion.txt
index b5791bd0..839d7ac4 100644
--- a/Output/Licenses/Vortice/SourceAndVersion.txt
+++ b/Output/Licenses/Vortice/SourceAndVersion.txt
@@ -2,11 +2,14 @@ https://github.com/amerkoleci/Vortice.Windows
https://github.com/amerkoleci/Vortice.Mathematics
https://github.com/SharpGenTools/SharpGenTools
-Version: Vortice.Direct3D9 2.4.2 (NuGet, netstandard2.0), Vortice.Mathematics 1.4.25,
-SharpGen.Runtime 2.0.0-beta.13, SharpGen.Runtime.COM 2.0.0-beta.13
+Version: Vortice.Direct3D11 3.8.3, Vortice.DXGI 3.8.3, Vortice.D3DCompiler 3.8.3, Vortice.DirectX 3.8.3 (NuGet, net10.0),
+Vortice.Mathematics 2.1.0, SharpGen.Runtime 2.4.2-beta, SharpGen.Runtime.COM 2.4.2-beta
Files:
-Vortice.Direct3D9.dll
+Vortice.Direct3D11.dll
+Vortice.DXGI.dll
+Vortice.D3DCompiler.dll
+Vortice.DirectX.dll
Vortice.Mathematics.dll
SharpGen.Runtime.dll
SharpGen.Runtime.COM.dll
diff --git a/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeConfig.xml b/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeConfig.xml
index 8e24ddae..25286b2c 100644
--- a/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeConfig.xml
+++ b/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeConfig.xml
@@ -205,7 +205,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -247,7 +247,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -289,7 +289,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -331,7 +331,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -373,7 +373,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -415,7 +415,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeNames.xml b/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeNames.xml
index 4b219b32..5dc10928 100644
--- a/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeNames.xml
+++ b/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeNames.xml
@@ -264,7 +264,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeSongs.xml b/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeSongs.xml
index 9822c11d..cfd5fe33 100644
--- a/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeSongs.xml
+++ b/Output/PartyModes/Challenge/Themes/Default/Screens/PartyScreenChallengeSongs.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml
index bf22385a..a9ce77ef 100644
--- a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapConfig.xml
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml
index 3d8c2be7..f2e3d4ce 100644
--- a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapNames.xml
@@ -264,7 +264,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml
index d3129f36..29883a14 100644
--- a/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml
+++ b/Output/PartyModes/PartnerSwap/Themes/Default/Screens/PartyScreenPartnerSwapSongs.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml
index 409603fa..047346cb 100644
--- a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteConfig.xml
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml
index 1ccd61ed..0fd63d14 100644
--- a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteNames.xml
@@ -264,7 +264,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml
index c647f8ee..5e7c44bc 100644
--- a/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml
+++ b/Output/PartyModes/RuleRoulette/Themes/Default/Screens/PartyScreenRuleRouletteSongs.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalConfig.xml b/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalConfig.xml
index 987a31b1..3f4ec5cf 100644
--- a/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalConfig.xml
+++ b/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalConfig.xml
@@ -163,7 +163,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -205,7 +205,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -247,7 +247,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalNames.xml b/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalNames.xml
index 79390c4e..43ec9a7f 100644
--- a/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalNames.xml
+++ b/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalNames.xml
@@ -292,7 +292,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalSongs.xml b/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalSongs.xml
index 27b45756..bdbfcc1e 100644
--- a/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalSongs.xml
+++ b/Output/PartyModes/TeamSurvival/Themes/Default/Screens/PartyScreenTeamSurvivalSongs.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeConfig.xml b/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeConfig.xml
index 8de5e6d0..07f3b054 100644
--- a/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeConfig.xml
+++ b/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeConfig.xml
@@ -177,7 +177,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -219,7 +219,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -261,7 +261,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -303,7 +303,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeNames.xml b/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeNames.xml
index 5cb20693..6154dd4a 100644
--- a/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeNames.xml
+++ b/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeNames.xml
@@ -292,7 +292,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeSongs.xml b/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeSongs.xml
index 3406ac9f..50d22386 100644
--- a/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeSongs.xml
+++ b/Output/PartyModes/TicTacToe/Themes/Default/Screens/PartyScreenTicTacToeSongs.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentConfig.xml b/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentConfig.xml
index 3c14c60e..f0062bd3 100644
--- a/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentConfig.xml
+++ b/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentConfig.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentNames.xml b/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentNames.xml
index 2550c469..e8743c29 100644
--- a/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentNames.xml
+++ b/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentNames.xml
@@ -264,7 +264,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentSongs.xml b/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentSongs.xml
index 8819866f..ec2ae7a5 100644
--- a/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentSongs.xml
+++ b/Output/PartyModes/Tournament/Themes/Default/Screens/PartyScreenTournamentSongs.xml
@@ -149,7 +149,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -191,7 +191,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -233,7 +233,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -275,7 +275,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -317,7 +317,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
@@ -359,7 +359,7 @@
SelectSlideArrowLeft
SelectSlideArrowRight
- SelectSlide
+ OptionsSlideSelected
SelectSlideArrowLeft
SelectSlideArrowRight
diff --git a/Output/Readme.txt b/Output/Readme.txt
index baf3519e..53b13373 100644
--- a/Output/Readme.txt
+++ b/Output/Readme.txt
@@ -29,8 +29,8 @@ Supported Operating Systems / Requirements:
- Visual C++ Redistributable Package 2010
Nightly Builds:
- - Windows 10 (version 20H2 or later) and Windows 11 with .NET Framework 4.8.1
- - Visual C++ Redistributable 2015–2022
+ - Windows 10 (version 20H2 or later) and Windows 11 with the .NET 10 Runtime and the ASP.NET Core 10 Runtime
+ - Graphics card with OpenGL 3.3 or Direct3D 11 (feature level 10.0) support
=================================
diff --git a/PartyModes/Directory.Build.targets b/PartyModes/Directory.Build.targets
new file mode 100644
index 00000000..a44bbd31
--- /dev/null
+++ b/PartyModes/Directory.Build.targets
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)..\Output\PartyModes\$(TargetName)\Code\
+
+
+
+
+
+
+
+
diff --git a/PartyModes/PartyModeChallenge/CPartyScreenChallengeConfig.cs b/PartyModes/PartyModeChallenge/CPartyScreenChallengeConfig.cs
index 02db1c2a..7d0c35c3 100644
--- a/PartyModes/PartyModeChallenge/CPartyScreenChallengeConfig.cs
+++ b/PartyModes/PartyModeChallenge/CPartyScreenChallengeConfig.cs
@@ -16,7 +16,6 @@
#endregion
using System;
-using System.Windows.Forms;
namespace VocaluxeLib.PartyModes.Challenge
{
diff --git a/PartyModes/PartyModeChallenge/CPartyScreenChallengeMain.cs b/PartyModes/PartyModeChallenge/CPartyScreenChallengeMain.cs
index 1dce502b..c9562034 100644
--- a/PartyModes/PartyModeChallenge/CPartyScreenChallengeMain.cs
+++ b/PartyModes/PartyModeChallenge/CPartyScreenChallengeMain.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.Challenge
diff --git a/PartyModes/PartyModeChallenge/PartyModeChallenge.csproj b/PartyModes/PartyModeChallenge/PartyModeChallenge.csproj
index a77c5b4b..d712d302 100644
--- a/PartyModes/PartyModeChallenge/PartyModeChallenge.csproj
+++ b/PartyModes/PartyModeChallenge/PartyModeChallenge.csproj
@@ -1,127 +1,13 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {5A4053F1-5AD5-441F-B254-D97A088127B6}
Library
- Properties
VocaluxeLib.PartyModes.Challenge
Challenge
- v4.8.1
-
-
- 512
+ bin\
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- bin
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
-
-
-
-
-
-
-
-
-
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
False
-
-
-
-
-
-
-
- if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
-cd $(SolutionDir)Partymodes\
-xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
- rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
-mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
-
-
-
-
\ No newline at end of file
+
diff --git a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs
index f087a5a6..e87c0a5c 100644
--- a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs
+++ b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapConfig.cs
@@ -16,7 +16,6 @@
#endregion
using System;
-using System.Windows.Forms;
namespace VocaluxeLib.PartyModes.PartnerSwap
{
diff --git a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs
index f5a1bfd9..1c8ba342 100644
--- a/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs
+++ b/PartyModes/PartyModePartnerSwap/CPartyScreenPartnerSwapMain.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.PartnerSwap
diff --git a/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj b/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj
index f015ffb3..17fe4b78 100644
--- a/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj
+++ b/PartyModes/PartyModePartnerSwap/PartyModePartnerSwap.csproj
@@ -1,118 +1,13 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {9D4F1C27-3B8E-4A62-B5D0-7E1A2C6F9B53}
Library
- Properties
VocaluxeLib.PartyModes.PartnerSwap
PartnerSwap
- v4.8.1
-
-
- 512
+ bin\
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- bin
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
-
-
-
-
-
-
-
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
False
-
-
-
-
-
-
-
- if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
-cd $(SolutionDir)Partymodes\
-xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
- rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
-mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
-
-
-
\ No newline at end of file
+
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs
index 05a0f2c2..c85470b5 100644
--- a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteConfig.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
namespace VocaluxeLib.PartyModes.RuleRoulette
{
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs
index 3c006652..980fbb5d 100644
--- a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteMain.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.RuleRoulette
diff --git a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs
index 324d37d7..a429bade 100644
--- a/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs
+++ b/PartyModes/PartyModeRuleRoulette/CPartyScreenRuleRouletteRules.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.RuleRoulette
diff --git a/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj b/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj
index efadd403..82b12f47 100644
--- a/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj
+++ b/PartyModes/PartyModeRuleRoulette/PartyModeRuleRoulette.csproj
@@ -1,119 +1,13 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {2E6A9B48-71C3-4D05-8F2B-C4A7D19E6E3F}
Library
- Properties
VocaluxeLib.PartyModes.RuleRoulette
RuleRoulette
- v4.8.1
-
-
- 512
+ bin\
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- bin
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
-
-
-
-
-
-
-
-
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
False
-
-
-
-
-
-
-
- if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
-cd $(SolutionDir)Partymodes\
-xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
- rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
-mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
-
-
-
\ No newline at end of file
+
diff --git a/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalConfig.cs b/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalConfig.cs
index 01a9ffc7..30a358b7 100644
--- a/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalConfig.cs
+++ b/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalConfig.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
namespace VocaluxeLib.PartyModes.TeamSurvival
{
diff --git a/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalHearts.cs b/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalHearts.cs
index a00a0bfa..8a52e545 100644
--- a/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalHearts.cs
+++ b/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalHearts.cs
@@ -17,7 +17,6 @@
using System;
using System.Collections.Generic;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.TeamSurvival
diff --git a/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalMain.cs b/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalMain.cs
index 8b8c7410..26eaef87 100644
--- a/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalMain.cs
+++ b/PartyModes/PartyModeTeamSurvival/CPartyScreenTeamSurvivalMain.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.TeamSurvival
diff --git a/PartyModes/PartyModeTeamSurvival/PartyModeTeamSurvival.csproj b/PartyModes/PartyModeTeamSurvival/PartyModeTeamSurvival.csproj
index be08e00b..3612bede 100644
--- a/PartyModes/PartyModeTeamSurvival/PartyModeTeamSurvival.csproj
+++ b/PartyModes/PartyModeTeamSurvival/PartyModeTeamSurvival.csproj
@@ -1,119 +1,13 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {7B2E4A19-5C3D-4E8F-A16B-2D9C0F3E8B41}
Library
- Properties
VocaluxeLib.PartyModes.TeamSurvival
TeamSurvival
- v4.8.1
-
-
- 512
+ bin\
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- bin
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
-
-
-
-
-
-
-
-
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
False
-
-
-
-
-
-
-
- if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
-cd $(SolutionDir)Partymodes\
-xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
- rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
-mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
-
-
-
\ No newline at end of file
+
diff --git a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeConfig.cs b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeConfig.cs
index ddb702e8..6d65fc94 100644
--- a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeConfig.cs
+++ b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeConfig.cs
@@ -17,7 +17,6 @@
using System;
using System.Diagnostics;
-using System.Windows.Forms;
namespace VocaluxeLib.PartyModes.TicTacToe
{
diff --git a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs
index bdfa5b67..a84f5e38 100644
--- a/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs
+++ b/PartyModes/PartyModeTicTacToe/CPartyScreenTicTacToeMain.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.TicTacToe
diff --git a/PartyModes/PartyModeTicTacToe/PartyModeTicTacToe.csproj b/PartyModes/PartyModeTicTacToe/PartyModeTicTacToe.csproj
index df23bd6d..45a8bb34 100644
--- a/PartyModes/PartyModeTicTacToe/PartyModeTicTacToe.csproj
+++ b/PartyModes/PartyModeTicTacToe/PartyModeTicTacToe.csproj
@@ -1,127 +1,13 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {6F3E1874-F28A-40E9-AFBD-31934D593679}
Library
- Properties
VocaluxeLib.PartyModes.TicTacToe
TicTacToe
- v4.8.1
-
-
- 512
+ bin\
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- bin
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
-
-
-
-
-
-
-
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
False
-
-
-
-
-
-
-
-
-
- if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
-cd $(SolutionDir)Partymodes\
-xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
- rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
-mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
-
-
-
-
\ No newline at end of file
+
diff --git a/PartyModes/PartyModeTournament/CPartyScreenTournamentConfig.cs b/PartyModes/PartyModeTournament/CPartyScreenTournamentConfig.cs
index 48a5230b..c407adf1 100644
--- a/PartyModes/PartyModeTournament/CPartyScreenTournamentConfig.cs
+++ b/PartyModes/PartyModeTournament/CPartyScreenTournamentConfig.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
namespace VocaluxeLib.PartyModes.Tournament
{
diff --git a/PartyModes/PartyModeTournament/CPartyScreenTournamentMain.cs b/PartyModes/PartyModeTournament/CPartyScreenTournamentMain.cs
index 59e2dbb6..fe81d793 100644
--- a/PartyModes/PartyModeTournament/CPartyScreenTournamentMain.cs
+++ b/PartyModes/PartyModeTournament/CPartyScreenTournamentMain.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib.Menu;
namespace VocaluxeLib.PartyModes.Tournament
diff --git a/PartyModes/PartyModeTournament/PartyModeTournament.csproj b/PartyModes/PartyModeTournament/PartyModeTournament.csproj
index 6404e834..a7f67f26 100644
--- a/PartyModes/PartyModeTournament/PartyModeTournament.csproj
+++ b/PartyModes/PartyModeTournament/PartyModeTournament.csproj
@@ -1,118 +1,13 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {3C7D2E51-8A64-4F0B-9E2A-6B1D4C8F7A20}
Library
- Properties
VocaluxeLib.PartyModes.Tournament
Tournament
- v4.8.1
-
-
- 512
+ bin\
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- bin
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- bin
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- bin
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
-
-
-
-
-
-
-
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
False
-
-
-
-
-
-
-
- if exist "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" rd "$(SolutionDir)Output\PartyModes\$(TargetName)\Code" /S /Q
-cd $(SolutionDir)Partymodes\
-xcopy "$(ProjectDir)*.cs" "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\" /EXCLUDE:Excludes.txt /I /Y
- rm -rf "$(SolutionDir)Output\PartyModes\$(TargetName)\Code"
-mkdir -p "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-cp -R $(ProjectDir)*.cs "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\"
-rm -f "$(SolutionDir)Output\PartyModes\$(TargetName)\Code\GlobalSuppressions.cs"
-
-
-
\ No newline at end of file
+
diff --git a/PitchTracker/PitchWrapper.h b/PitchTracker/PitchWrapper.h
index bff9c12d..8085fb27 100644
--- a/PitchTracker/PitchWrapper.h
+++ b/PitchTracker/PitchWrapper.h
@@ -4,7 +4,7 @@
#include "ptAKF.h"
#include "dywapitchtrack/ptDyWa.h"
-#ifdef __linux__
+#if defined(__linux__) || defined(__APPLE__)
#define DllExport extern "C"
#else
#define DllExport extern "C" __declspec(dllexport)
diff --git a/PitchTracker/makefile b/PitchTracker/makefile
index 6d2a9f63..17e608c3 100644
--- a/PitchTracker/makefile
+++ b/PitchTracker/makefile
@@ -12,9 +12,14 @@ objects = \
CPPFLAGS = -std=gnu++11 -fPIC
PitchTracker: $(objects)
+ifeq ($(shell uname),Darwin)
+ $(CXX) -shared -o libPitchTracker.dylib -fPIC $(objects)
+ cp libPitchTracker.dylib ../Output/
+else
gcc -shared -o libPitchTracker.dll.so -fPIC $(objects)
strip libPitchTracker.dll.so
cp libPitchTracker.dll.so ../Output/
+endif
clean:
- rm -f ../Output/libPitchTracker.dll.so libPitchTracker.dll.so $(objects)
\ No newline at end of file
+ rm -f ../Output/libPitchTracker.dll.so libPitchTracker.dll.so ../Output/libPitchTracker.dylib libPitchTracker.dylib $(objects)
\ No newline at end of file
diff --git a/PitchTracker/performous/libda/sample.hpp b/PitchTracker/performous/libda/sample.hpp
index da55bda7..b67487e7 100644
--- a/PitchTracker/performous/libda/sample.hpp
+++ b/PitchTracker/performous/libda/sample.hpp
@@ -22,7 +22,7 @@ namespace da {
constexpr sample_t max_s16(32767), min_s16 = -max_s16 - sample_t(1);
constexpr sample_t max_s24(8388607), min_s24 = -max_s24 - sample_t(1);
- constexpr sample_t max_s32(2147483647), min_s32= -max_s32 - sample_t(1);
+ constexpr sample_t max_s32(2147483647.0f), min_s32= -max_s32 - sample_t(1);
constexpr double tau(6.2831853071795864769252867665590057683943387987502116); ///< One full circle
constexpr double pi = tau / sample_t(2); ///< Half circle
constexpr double eps(1e-10); ///< Tiny positive value
diff --git a/README.md b/README.md
index 47adfa1c..87aaff8e 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,8 @@ Supported Operating Systems / Requirements:
- Visual C++ Redistributable Package 2010
Nightly Builds:
- - Windows 10 (version 20H2 or later) and Windows 11 with .NET Framework 4.8.1
- - Visual C++ Redistributable 2015–2022
+ - Windows 10 (version 20H2 or later) and Windows 11 with the .NET 10 Runtime and the ASP.NET Core 10 Runtime
+ - Graphics card with OpenGL 3.3 or Direct3D 11 (feature level 10.0) support
## 2. Download
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index d4cc5b5d..cb452ec9 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -1,132 +1,36 @@
-
-
-
+
- Debug
- x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}
Library
- Properties
Tests
Tests
- v4.8.1
- 512
-
-
-
-
-
- true
- true
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- x86
- 9.0
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- x86
- 9.0
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- x64
- 9.0
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- x64
- 9.0
+ x86;x64
+ bin\$(Configuration)\
+ false
+ true
-
-
-
-
+
+
-
+
+
+
-
-
- TextTemplatingFileGenerator
- CLogTest.cs
-
-
-
+
True
True
CLogTest.tt
-
-
-
-
-
-
-
-
+
+ TextTemplatingFileGenerator
+ CLogTest.cs
+
-
-
+
+
+
+
-
-
- {5a4053f1-5ad5-441f-b254-d97a088127b6}
- PartyModeChallenge
-
-
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
- VocaluxeLib
-
-
- {2a1e0f2d-5392-4bb7-b0a1-a9dfa13e79ff}
- Vocaluxe
-
-
- {4E2C9A71-6B3D-4F85-9C1E-8A7D5B2F3C60}
- VocaluxeWeb
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/Tests/VocaluxeLib/XML/TestFiles/SConfig.xml b/Tests/VocaluxeLib/XML/TestFiles/SConfig.xml
index dba080aa..ef09a8b2 100644
--- a/Tests/VocaluxeLib/XML/TestFiles/SConfig.xml
+++ b/Tests/VocaluxeLib/XML/TestFiles/SConfig.xml
@@ -91,6 +91,10 @@
TR_CONFIG_ON
TR_CONFIG_ON
TR_CONFIG_OFF
+ TR_CONFIG_ON
+ TR_CONFIG_ON
+ 0
+ TR_CONFIG_SCALING_DISABLED
AForgeNet
diff --git a/Tests/app.config b/Tests/app.config
deleted file mode 100644
index dcd3c5a2..00000000
--- a/Tests/app.config
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Vocaluxe.sln b/Vocaluxe.sln
index 78838776..fde0473b 100644
--- a/Vocaluxe.sln
+++ b/Vocaluxe.sln
@@ -208,26 +208,26 @@ Global
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x64.Build.0 = Release|x64
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x86.ActiveCfg = Release|Win32
{54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x86.Build.0 = Release|Win32
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x64.ActiveCfg = Debug|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x64.Build.0 = Debug|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x86.ActiveCfg = Debug|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x86.Build.0 = Debug|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x64.ActiveCfg = Debug|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x64.Build.0 = Debug|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x86.ActiveCfg = Debug|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x86.Build.0 = Debug|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x64.ActiveCfg = Release|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x64.Build.0 = Release|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x86.ActiveCfg = Release|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x86.Build.0 = Release|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x64.ActiveCfg = Release|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x64.Build.0 = Release|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x86.ActiveCfg = Release|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x86.Build.0 = Release|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x64.ActiveCfg = Release|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x64.Build.0 = Release|x64
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x86.ActiveCfg = Release|x86
- {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x86.Build.0 = Release|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x64.ActiveCfg = DebugLinux|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x64.Build.0 = DebugLinux|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x86.ActiveCfg = DebugLinux|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugLinux|x86.Build.0 = DebugLinux|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x64.ActiveCfg = DebugWin|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x64.Build.0 = DebugWin|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x86.ActiveCfg = DebugWin|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.DebugWin|x86.Build.0 = DebugWin|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x64.ActiveCfg = InstallerWin|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x64.Build.0 = InstallerWin|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x86.ActiveCfg = InstallerWin|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.InstallerWin|x86.Build.0 = InstallerWin|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x64.ActiveCfg = ReleaseLinux|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x64.Build.0 = ReleaseLinux|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x86.ActiveCfg = ReleaseLinux|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseLinux|x86.Build.0 = ReleaseLinux|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x64.Build.0 = ReleaseWin|x64
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86
+ {8EDF4429-251A-416D-BB68-93F227191BCF}.ReleaseWin|x86.Build.0 = ReleaseWin|x86
{7B2E4A19-5C3D-4E8F-A16B-2D9C0F3E8B41}.DebugLinux|x64.ActiveCfg = DebugLinux|Any CPU
{7B2E4A19-5C3D-4E8F-A16B-2D9C0F3E8B41}.DebugLinux|x64.Build.0 = DebugLinux|Any CPU
{7B2E4A19-5C3D-4E8F-A16B-2D9C0F3E8B41}.DebugLinux|x86.ActiveCfg = DebugLinux|Any CPU
diff --git a/Vocaluxe/Base/CBackgroundMusic.cs b/Vocaluxe/Base/CBackgroundMusic.cs
index 547df50e..22574e3a 100644
--- a/Vocaluxe/Base/CBackgroundMusic.cs
+++ b/Vocaluxe/Base/CBackgroundMusic.cs
@@ -316,7 +316,7 @@ namespace Vocaluxe.Base
// Recheck the condition as it cloud have change before we got the lock
if (_PreviewStartHelperTask != null && (_PreviewPlayer.Length > 0 || _PreviewStartWaitCounter++ >= _PreviewStartWaitMaxTries))
{
- _PreviewStartHelperTask.RunSynchronously(TaskScheduler.FromCurrentSynchronizationContext());
+ _PreviewStartHelperTask.RunSynchronously();
_PreviewStartHelperTask = null;
_PreviewStartWaitCounter = 0;
}
diff --git a/Vocaluxe/Base/CConfig.cs b/Vocaluxe/Base/CConfig.cs
index cb338521..405843ce 100644
--- a/Vocaluxe/Base/CConfig.cs
+++ b/Vocaluxe/Base/CConfig.cs
@@ -21,8 +21,8 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
-using System.Windows.Forms;
using System.Xml.Serialization;
+using Vocaluxe.Lib;
using Vocaluxe.Lib.Sound.Record;
using Vocaluxe.Lib.Webcam;
using VocaluxeLib;
@@ -218,6 +218,14 @@ namespace Vocaluxe.Base
public EOffOn VideosInSongs;
[DefaultValue(EOffOn.TR_CONFIG_OFF)]
public EOffOn VideosToBackground;
+ [DefaultValue(EOffOn.TR_CONFIG_ON)]
+ public EOffOn HardwareDecoding;
+ [DefaultValue(EOffOn.TR_CONFIG_ON)]
+ public EOffOn GpuColorConversion;
+ [XmlRanged(0, 64), DefaultValue(0)]
+ public int DecoderThreads;
+ [DefaultValue(EVideoDownscaleResolution.TR_CONFIG_SCALING_DISABLED)]
+ public EVideoDownscaleResolution VideoDownscaleResolution;
[DefaultValue(EWebcamLib.AForgeNet)]
public EWebcamLib WebcamLib;
public SWebcamConfig? WebcamConfig;
@@ -268,7 +276,7 @@ namespace Vocaluxe.Base
{
#if INSTALLER
Path.Combine(CSettings.DataFolder, CSettings.FolderNameSongs),
- Path.Combine(CSettings.ProgramFolder, FolderNameSongs)
+ Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameSongs)
#elif WIN
Path.Combine(CSettings.ProgramFolder, CSettings.FolderNameSongs)
#elif LINUX
@@ -483,8 +491,8 @@ namespace Vocaluxe.Base
Platform = Environment.OSVersion.Platform.ToString(),
OSVersion = Environment.OSVersion.ToString(),
ProcessorCount = Environment.ProcessorCount,
- Screens = Screen.AllScreens.Length,
- PrimaryScreenResolution = Screen.PrimaryScreen.Bounds.Size.ToString(),
+ Screens = CSdl.GetDisplayCount(),
+ PrimaryScreenResolution = CSdl.GetPrimaryDisplaySize().ToString(),
Directory = CSettings.ProgramFolder
};
}
@@ -614,6 +622,14 @@ namespace Vocaluxe.Base
return "Show Videos while singing: " + CHelper.ListStrings(Enum.GetNames(typeof(EOffOn)));
case "VideosToBackground":
return "Show backgroundmusic videos as background: " + CHelper.ListStrings(Enum.GetNames(typeof(EOffOn)));
+ case "HardwareDecoding":
+ return "Decode videos on the graphics card if supported, falls back to software decoding: " + CHelper.ListStrings(Enum.GetNames(typeof(EOffOn)));
+ case "GpuColorConversion":
+ return "Upload videos as YUV and convert them to RGB on the graphics card (Direct3D/OpenGL): " + CHelper.ListStrings(Enum.GetNames(typeof(EOffOn)));
+ case "DecoderThreads":
+ return "Number of threads used for decoding a video, 0 = automatic (all cores) [0..64]";
+ case "VideoDownscaleResolution":
+ return "The resolution to which you want to downscale videos: " + CHelper.ListStrings(Enum.GetNames(typeof(EVideoDownscaleResolution)));
case "WebcamLib":
return "WebcamLib: " + CHelper.ListStrings(Enum.GetNames(typeof(EWebcamLib)));
case "MicDelay":
diff --git a/Vocaluxe/Base/CCover.cs b/Vocaluxe/Base/CCover.cs
index 66964987..5f349bea 100644
--- a/Vocaluxe/Base/CCover.cs
+++ b/Vocaluxe/Base/CCover.cs
@@ -19,13 +19,13 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using SkiaSharp;
using VocaluxeLib;
using VocaluxeLib.Draw;
using VocaluxeLib.Log;
@@ -391,7 +391,7 @@ namespace Vocaluxe.Base
}
}
- private static void _TrySaveCachedCover(string text, ECoverGeneratorType type, CSong firstSong, Bitmap bmp)
+ private static void _TrySaveCachedCover(string text, ECoverGeneratorType type, CSong firstSong, SKBitmap bmp)
{
try
{
@@ -404,7 +404,10 @@ namespace Vocaluxe.Base
Directory.CreateDirectory(cacheFolder);
var tmpPath = cachePath + ".tmp";
- bmp.Save(tmpPath, ImageFormat.Png);
+ using (var stream = File.Create(tmpPath))
+ {
+ bmp.Encode(stream, SKEncodedImageFormat.Png, 100);
+ }
if (File.Exists(cachePath))
File.Delete(cachePath);
diff --git a/Vocaluxe/Base/CCoverGenerator.cs b/Vocaluxe/Base/CCoverGenerator.cs
index 2a10831e..bb754b5f 100644
--- a/Vocaluxe/Base/CCoverGenerator.cs
+++ b/Vocaluxe/Base/CCoverGenerator.cs
@@ -19,11 +19,9 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
-using System.Drawing.Text;
using System.IO;
using System.Linq;
+using SkiaSharp;
using Vocaluxe.Base.Fonts;
using VocaluxeLib;
@@ -75,8 +73,7 @@ namespace Vocaluxe.Base
{
public readonly string Text;
public float Width, Height;
- private readonly Graphics _Graphics;
- private readonly Font _Font;
+ private readonly SKFont _Font;
private float _WidthTrimmed = -1;
public float WidthTrimmed
{
@@ -85,7 +82,7 @@ namespace Vocaluxe.Base
if (_WidthTrimmed < 0f)
{
var text = Text.TrimEnd(null);
- _WidthTrimmed = text.Length == Text.Length ? Width : _Graphics.MeasureString(text, _Font).Width;
+ _WidthTrimmed = text.Length == Text.Length ? Width : _MeasureString(text, _Font).Width;
}
return _WidthTrimmed;
@@ -93,13 +90,12 @@ namespace Vocaluxe.Base
}
public int Line;
- public CTextElement(string text, Graphics g, Font font)
+ public CTextElement(string text, SKFont font)
{
Text = text;
- var dimensions = g.MeasureString(text, font);
+ var dimensions = _MeasureString(text, font);
Width = dimensions.Width;
- Height = (dimensions.Height + font.Height) / 2;
- _Graphics = g;
+ Height = (dimensions.Height + (float)Math.Ceiling(font.Spacing)) / 2;
_Font = font;
}
@@ -111,44 +107,67 @@ namespace Vocaluxe.Base
}
}
- private void _DrawBackground(Graphics g, Bitmap bmpBackground, String firstCoverPath)
+ private static SizeF _MeasureString(string text, SKFont font)
{
- g.Clear(_BGColor.AsColor());
-
- ImageAttributes ia = null;
- if (_Theme.ShowFirstCover && !string.IsNullOrEmpty(firstCoverPath) && File.Exists(firstCoverPath))
- {
- using (var bmp2 = new Bitmap(firstCoverPath))
- {
- g.DrawImage(bmp2, bmpBackground.GetRect(), 0, 0, bmp2.Width, bmp2.Height, GraphicsUnit.Pixel);
- }
-
- var cm = new ColorMatrix { Matrix33 = _Theme.ImageAlpha };
- ia = new ImageAttributes();
- ia.SetColorMatrix(cm);
- }
-
- g.DrawImage(bmpBackground, bmpBackground.GetRect(), 0, 0, bmpBackground.Width, bmpBackground.Height, GraphicsUnit.Pixel, ia);
+ return new SizeF(font.MeasureText(text.TrimEnd(' ')) + font.Size / 3, font.Spacing);
}
- private void _DrawText(Graphics g, Size bmpSize, CFont font, List elements)
+ private static SKColor _ToSKColor(SColorF color)
{
- var fo = CFonts.GetSystemFont(font);
+ var c = color.AsColor();
+ return new SKColor(c.R, c.G, c.B, c.A);
+ }
+
+ private void _DrawBackground(SKCanvas g, SKBitmap bmpBackground, String firstCoverPath)
+ {
+ g.Clear(_ToSKColor(_BGColor));
+
+ var rect = new SKRect(0, 0, bmpBackground.Width, bmpBackground.Height);
+ var alpha = 1f;
+ if (_Theme.ShowFirstCover && !string.IsNullOrEmpty(firstCoverPath) && File.Exists(firstCoverPath))
+ {
+ using (var bmp2 = SKBitmap.Decode(firstCoverPath))
+ using (var image2 = bmp2 == null ? null : SKImage.FromBitmap(bmp2))
+ {
+ if (image2 != null)
+ {
+ g.DrawImage(image2, rect, new SKSamplingOptions(SKCubicResampler.Mitchell));
+ }
+ }
+
+ alpha = _Theme.ImageAlpha;
+ }
+
+ using (var image = SKImage.FromBitmap(bmpBackground))
+ using (var paint = new SKPaint { Color = SKColors.White.WithAlpha((byte)Math.Round(alpha.Clamp(0f, 1f) * 255)) })
+ {
+ g.DrawImage(image, rect, new SKSamplingOptions(SKCubicResampler.Mitchell), paint);
+ }
+ }
+
+ private void _DrawText(SKCanvas g, Size bmpSize, CFont font, List elements)
+ {
+ var typeface = CFonts.GetTypeface(font);
var maxHeight = elements.Select(el => el.Height).Max();
var lineCount = elements.Last().Line + 1;
- //Have to use size in em not pixels!
- var emSize = fo.Size * fo.FontFamily.GetCellAscent(fo.Style) / fo.FontFamily.GetEmHeight(fo.Style);
+ float emSize;
+ using (var fo = new SKFont(typeface, font.Height))
+ {
+ //Have to use size in em not pixels!
+ emSize = -fo.Metrics.Ascent;
+ }
+
var outlineSize = CFonts.GetOutlineSize(font) * font.Height;
var outlineColorF = CFonts.GetOutlineColor(font);
outlineColorF.A = outlineColorF.A * _TextColor.A;
- using (var path = new GraphicsPath())
- using (var pen = new Pen(outlineColorF.AsColor(), outlineSize / 2))
+ var paths = new List();
+ using (var fo = new SKFont(typeface, emSize) { Edging = SKFontEdging.Antialias, Subpixel = true })
+ using (var pen = new SKPaint { Style = SKPaintStyle.Stroke, StrokeWidth = outlineSize / 2, StrokeJoin = SKStrokeJoin.Round, IsAntialias = true, Color = _ToSKColor(outlineColorF) })
+ using (var brush = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true, Color = _ToSKColor(_TextColor) })
{
- pen.LineJoin = LineJoin.Round;
- pen.Alignment = PenAlignment.Outset;
var top = (bmpSize.Height - _MarginBottom - _MarginTop - maxHeight * lineCount) / 2 + _MarginTop;
var nextLineEl = 0;
for (var i = 0; i < lineCount; i++)
@@ -181,17 +200,25 @@ namespace Vocaluxe.Base
left = _MarginLeft;
}
- //g.DrawString(line, fo, new SolidBrush(_TextColor.AsColor()), left, top, StringFormat.GenericTypographic);
- path.AddString(line, fo.FontFamily, (int)fo.Style, emSize, new PointF(left, top), StringFormat.GenericTypographic);
+ paths.Add(fo.GetTextPath(line, new SKPoint(left, top - fo.Metrics.Ascent)));
+
top += maxHeight + _LineSpace;
}
- g.DrawPath(pen, path);
- g.FillPath(new SolidBrush(_TextColor.AsColor()), path);
+ foreach (var path in paths)
+ {
+ g.DrawPath(path, pen);
+ }
+
+ foreach (var path in paths)
+ {
+ g.DrawPath(path, brush);
+ path.Dispose();
+ }
}
}
- public Bitmap GetCover(string text, string firstCoverPath)
+ public SKBitmap GetCover(string text, string firstCoverPath)
{
if (!_Valid)
{
@@ -199,33 +226,36 @@ namespace Vocaluxe.Base
}
text = CLanguage.Translate(_Theme.Text.Text.Replace("%TEXT%", text));
- using (var bmpImage = new Bitmap(_Image))
+ using (var bmpImage = SKBitmap.Decode(_Image))
{
- var bmp = new Bitmap(bmpImage.Width, bmpImage.Height, PixelFormat.Format32bppArgb);
+ if (bmpImage == null)
+ {
+ return null;
+ }
+
+ var bmp = new SKBitmap(bmpImage.Width, bmpImage.Height, SKColorType.Bgra8888, SKAlphaType.Premul);
try
{
- using (var g = Graphics.FromImage(bmp))
+ using (var g = new SKCanvas(bmp))
{
- g.SmoothingMode = SmoothingMode.AntiAlias;
- g.InterpolationMode = InterpolationMode.HighQualityBicubic;
- g.TextRenderingHint = TextRenderingHint.AntiAlias;
-
_DrawBackground(g, bmpImage, firstCoverPath);
if (text != "")
{
var font = new CFont(_Theme.Text.Font);
- var fo = CFonts.GetSystemFont(font);
- var textParts = _SplitText(text);
- var elements = textParts.Select(line => new CTextElement(line, g, fo)).ToList();
- var factor = _DistributeText(elements, bmp.Width, bmp.Height);
- foreach (var element in elements)
+ using (var fo = new SKFont(CFonts.GetTypeface(font), font.Height))
{
- element.AdjustSize(factor);
- }
+ var textParts = _SplitText(text);
+ var elements = textParts.Select(line => new CTextElement(line, fo)).ToList();
+ var factor = _DistributeText(elements, bmp.Width, bmp.Height);
+ foreach (var element in elements)
+ {
+ element.AdjustSize(factor);
+ }
- font.Height *= factor / (1f + CFonts.GetOutlineSize(font)); //Adjust for outline size
- _DrawText(g, bmp.GetSize(), font, elements);
+ font.Height *= factor / (1f + CFonts.GetOutlineSize(font)); //Adjust for outline size
+ _DrawText(g, bmp.GetSize(), font, elements);
+ }
}
}
diff --git a/Vocaluxe/Base/CDraw.cs b/Vocaluxe/Base/CDraw.cs
index 9f0d2b6f..09224bed 100644
--- a/Vocaluxe/Base/CDraw.cs
+++ b/Vocaluxe/Base/CDraw.cs
@@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using SkiaSharp;
using Vocaluxe.Lib.Draw;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -101,7 +102,7 @@ namespace Vocaluxe.Base
switch (CConfig.Config.Graphics.Renderer)
{
case ERenderer.TR_CONFIG_SOFTWARE:
- _Draw = new CDrawWinForm();
+ _Draw = new CDrawSoftware();
break;
case ERenderer.TR_CONFIG_OPENGL:
@@ -133,7 +134,7 @@ namespace Vocaluxe.Base
#endif
default:
- _Draw = new CDrawWinForm();
+ _Draw = new CDrawSoftware();
break;
}
@@ -213,7 +214,7 @@ namespace Vocaluxe.Base
///
///
/// Reference to texture
- public static CTextureRef AddTexture(Bitmap bitmap)
+ public static CTextureRef AddTexture(SKBitmap bitmap)
{
return _Draw.AddTexture(bitmap);
}
@@ -261,7 +262,7 @@ namespace Vocaluxe.Base
///
/// Bitmap to add, gets disposed after adding!
/// Reference to texture
- public static CTextureRef EnqueueTexture(Bitmap bmp)
+ public static CTextureRef EnqueueTexture(SKBitmap bmp)
{
return _Draw.EnqueueTexture(bmp);
}
@@ -284,7 +285,7 @@ namespace Vocaluxe.Base
///
/// Reference to the texture to update
///
- public static void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp)
+ public static void EnqueueTextureUpdate(CTextureRef textureRef, SKBitmap bmp)
{
_Draw.EnqueueTextureUpdate(textureRef, bmp);
}
@@ -312,12 +313,22 @@ namespace Vocaluxe.Base
_Draw.UpdateTexture(textureRef, w, h, data);
}
+ public static bool SupportsYuvVideo
+ {
+ get { return _Draw != null && _Draw.SupportsYuvVideo; }
+ }
+
+ public static void UpdateYuvTexture(ref CTextureRef textureRef, int w, int h, byte[] data, float[] yuvToRgbRows)
+ {
+ _Draw.UpdateYuvTexture(ref textureRef, w, h, data, yuvToRgbRows);
+ }
+
///
/// Updates a texture, filling it with the new data from the bitmap
///
///
///
- public static void UpdateTexture(CTextureRef textureRef, Bitmap bmp)
+ public static void UpdateTexture(CTextureRef textureRef, SKBitmap bmp)
{
_Draw.UpdateTexture(textureRef, bmp);
}
diff --git a/Vocaluxe/Base/CGraphics.cs b/Vocaluxe/Base/CGraphics.cs
index b1c12853..234bf1ec 100644
--- a/Vocaluxe/Base/CGraphics.cs
+++ b/Vocaluxe/Base/CGraphics.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Windows.Forms;
using Vocaluxe.Base.Fonts;
using Vocaluxe.Base.ThemeSystem;
using Vocaluxe.Screens;
diff --git a/Vocaluxe/Base/CParty.cs b/Vocaluxe/Base/CParty.cs
index 95c2989d..954c9039 100644
--- a/Vocaluxe/Base/CParty.cs
+++ b/Vocaluxe/Base/CParty.cs
@@ -16,14 +16,17 @@
#endregion
using System;
-using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Emit;
using Vocaluxe.Base.ThemeSystem;
using VocaluxeLib;
using VocaluxeLib.Log;
@@ -387,32 +390,36 @@ namespace Vocaluxe.Base
private static Assembly _CompileFiles(string[] files, string outputPath)
{
- var compilerParams = new CompilerParameters();
+ SyntaxTree[] syntaxTrees;
- compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");
- compilerParams.ReferencedAssemblies.Add("System.dll");
- compilerParams.ReferencedAssemblies.Add("System.Core.dll");
- compilerParams.ReferencedAssemblies.Add(Path.Combine("libs", "managed", "VocaluxeLib.dll"));
- if (outputPath == null)
+ try
{
- compilerParams.GenerateInMemory = true;
+ syntaxTrees = files.Select(f => CSharpSyntaxTree.ParseText(File.ReadAllText(f), path: f, encoding: Encoding.UTF8)).ToArray();
}
- else
+ catch (Exception e)
{
- compilerParams.GenerateInMemory = false;
- compilerParams.OutputAssembly = outputPath;
+ CLog.Error("Error Compiling Source (" + CHelper.ListStrings(files) + "): " + e.Message);
+ return null;
}
+
+ var assemblyName = outputPath == null ? "PartyMode_" + Guid.NewGuid().ToString("N") : Path.GetFileNameWithoutExtension(outputPath);
#if DEBUG
- compilerParams.IncludeDebugInformation = true;
+ var optimization = OptimizationLevel.Debug;
+ var emitOptions = new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded);
+#else
+ var optimization = OptimizationLevel.Release;
+ var emitOptions = new EmitOptions();
#endif
+ var compilation = CSharpCompilation.Create(assemblyName, syntaxTrees, _GetCompilerReferences(),
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: optimization));
- using (var cdp = CodeDomProvider.CreateProvider("CSharp"))
+ using (var peStream = new MemoryStream())
{
- CompilerResults compileResult;
+ EmitResult compileResult;
try
{
- compileResult = cdp.CompileAssemblyFromFile(compilerParams, files);
+ compileResult = compilation.Emit(peStream, options: emitOptions);
}
catch (Exception e)
{
@@ -420,11 +427,13 @@ namespace Vocaluxe.Base
return null;
}
- if (compileResult.Errors.Count > 0)
+ if (!compileResult.Success)
{
- foreach (CompilerError e in compileResult.Errors)
+ foreach (var e in compileResult.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
{
- CLog.Error("Error Compiling Source (" + CHelper.ListStrings(files) + "): " + e.ErrorText + " in '" + e.FileName + "' (" + e.Line + ")");
+ var position = e.Location.GetLineSpan();
+ CLog.Error("Error Compiling Source (" + CHelper.ListStrings(files) + "): " + e.GetMessage() + " in '" + position.Path + "' (" +
+ (position.StartLinePosition.Line + 1) + ")");
}
return null;
@@ -432,11 +441,12 @@ namespace Vocaluxe.Base
if (outputPath == null)
{
- return compileResult.CompiledAssembly;
+ return Assembly.Load(peStream.ToArray());
}
try
{
+ File.WriteAllBytes(outputPath, peStream.ToArray());
return Assembly.LoadFrom(outputPath);
}
catch (Exception e)
@@ -447,6 +457,44 @@ namespace Vocaluxe.Base
}
}
+ private static IEnumerable _GetCompilerReferences()
+ {
+ var paths = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ var platformAssemblies = AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string;
+ if (platformAssemblies != null)
+ {
+ foreach (var path in platformAssemblies.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ paths[Path.GetFileName(path)] = path;
+ }
+ }
+
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location)))
+ {
+ paths[Path.GetFileName(assembly.Location)] = assembly.Location;
+ }
+
+ paths[Path.GetFileName(typeof(IPartyMode).Assembly.Location)] = typeof(IPartyMode).Assembly.Location;
+
+ return paths.Values.Where(_HasMetadata).Select(p => MetadataReference.CreateFromFile(p));
+ }
+
+ private static bool _HasMetadata(string path)
+ {
+ try
+ {
+ using (var stream = File.OpenRead(path))
+ using (var reader = new PEReader(stream))
+ {
+ return reader.HasMetadata;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
private static CMenuParty _GetPartyScreenInstance(Assembly assembly, string screenName, string partyModeName)
{
if (assembly == null)
diff --git a/Vocaluxe/Base/CPlaylists.cs b/Vocaluxe/Base/CPlaylists.cs
index 96b68427..aee57acf 100644
--- a/Vocaluxe/Base/CPlaylists.cs
+++ b/Vocaluxe/Base/CPlaylists.cs
@@ -259,7 +259,7 @@ namespace Vocaluxe.Base
try
{
StreamReader sr;
- using (sr = new StreamReader(file, Encoding.Default, true))
+ using (sr = new StreamReader(file, CEncoding.Locale, true))
{
string line;
while ((line = sr.ReadLine()) != null)
diff --git a/Vocaluxe/Base/CSettings.cs b/Vocaluxe/Base/CSettings.cs
index 9656f2f7..8924e834 100644
--- a/Vocaluxe/Base/CSettings.cs
+++ b/Vocaluxe/Base/CSettings.cs
@@ -20,7 +20,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Windows.Forms;
using VocaluxeLib;
using VocaluxeLib.Log;
@@ -205,12 +204,12 @@ namespace Vocaluxe.Base
public static string Version
{
- get { return Application.ProductVersion.Split('-').First(); }
+ get { return FullVersion.Split('-').First(); }
}
public static string FullVersion
{
- get { return Application.ProductVersion; }
+ get { return Assembly.GetExecutingAssembly().GetCustomAttribute().InformationalVersion; }
}
public static string GetFullVersionText()
diff --git a/Vocaluxe/Base/CUtility.cs b/Vocaluxe/Base/CUtility.cs
index ff8825c8..9e5837df 100644
--- a/Vocaluxe/Base/CUtility.cs
+++ b/Vocaluxe/Base/CUtility.cs
@@ -18,11 +18,18 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Windows.Forms;
using VocaluxeLib;
namespace Vocaluxe.Base
{
+ enum EMouseButton
+ {
+ None,
+ Left,
+ Right,
+ Middle
+ }
+
static class CTime
{
private static readonly Stopwatch _Stopwatch = new Stopwatch();
@@ -150,42 +157,38 @@ namespace Vocaluxe.Base
_ActualPool.RemoveAt(index);
}
- private void _CheckModifiers()
+ private void _CheckModifiers(Keys keys)
{
- var keys = Control.ModifierKeys;
-
_ModShift = (keys & Keys.Shift) == Keys.Shift;
_ModAlt = (keys & Keys.Alt) == Keys.Alt;
_ModCtrl = (keys & Keys.Control) == Keys.Control;
}
- public void KeyDown(KeyEventArgs e)
+ public void KeyDown(Keys keyCode, Keys modifiers)
{
- _CheckModifiers();
+ _CheckModifiers(modifiers);
- var repeat = _Keys == e.KeyCode;
+ var repeat = _Keys == keyCode;
if (!_Timer.IsRunning || _Timer.ElapsedMilliseconds > 75 || !repeat)
{
- _Keys = e.KeyCode;
+ _Keys = keyCode;
_Add(_ModAlt, _ModShift, _ModCtrl, _KeyPressed, repeat ? _Char : Char.MinValue, _Keys);
}
}
- public void KeyPress(KeyPressEventArgs e)
+ public void KeyPress(char keyChar, Keys modifiers)
{
- _CheckModifiers();
+ _CheckModifiers(modifiers);
- _Add(_ModAlt, _ModShift, _ModCtrl, true, e.KeyChar, Keys.None);
- _Char = e.KeyChar;
+ _Add(_ModAlt, _ModShift, _ModCtrl, true, keyChar, Keys.None);
+ _Char = keyChar;
_KeyPressed = true;
}
- // ReSharper disable UnusedParameter.Global
- public void KeyUp(KeyEventArgs e)
- // ReSharper restore UnusedParameter.Global
+ public void KeyUp(Keys modifiers)
{
- _CheckModifiers();
+ _CheckModifiers(modifiers);
_KeyPressed = false;
}
@@ -236,9 +239,9 @@ namespace Vocaluxe.Base
_Timer = new Stopwatch();
}
- private void _Add(int x, int y, bool lb, bool ld, bool rb, int wheel, bool lbh, bool rbh, bool mb, bool mbh)
+ private void _Add(Keys modifiers, int x, int y, bool lb, bool ld, bool rb, int wheel, bool lbh, bool rbh, bool mb, bool mbh)
{
- _CheckModifiers();
+ _CheckModifiers(modifiers);
x = (int)(x * (float)CSettings.RenderW * CConfig.Config.Graphics.NumScreens / CDraw.GetScreenWidth());
while (x > CSettings.RenderW)
{
@@ -260,9 +263,8 @@ namespace Vocaluxe.Base
_CurrentPool.RemoveAt(index);
}
- private void _CheckModifiers()
+ private void _CheckModifiers(Keys keys)
{
- var keys = Control.ModifierKeys;
_Mod = EModifier.None;
if ((keys & Keys.Shift) != 0)
@@ -281,19 +283,19 @@ namespace Vocaluxe.Base
}
}
- public void MouseMove(MouseEventArgs e)
+ public void MouseMove(int x, int y, EMouseButton button, Keys modifiers)
{
- _Add(e.X, e.Y, false, false, false, 0, e.Button == MouseButtons.Left, e.Button == MouseButtons.Right, false, e.Button == MouseButtons.Middle);
+ _Add(modifiers, x, y, false, false, false, 0, button == EMouseButton.Left, button == EMouseButton.Right, false, button == EMouseButton.Middle);
}
- public void MouseWheel(MouseEventArgs e)
+ public void MouseWheel(int x, int y, int delta, Keys modifiers)
{
- _Add(e.X, e.Y, false, false, false, e.Delta, false, false, false, false);
+ _Add(modifiers, x, y, false, false, false, delta, false, false, false, false);
}
- public void MouseDown(MouseEventArgs e)
+ public void MouseDown(int x, int y, EMouseButton button, Keys modifiers)
{
- var lb = e.Button == MouseButtons.Left;
+ var lb = button == EMouseButton.Left;
var ld = false;
if (lb)
{
@@ -313,16 +315,8 @@ namespace Vocaluxe.Base
_Timer.Reset();
}
- _Add(e.X, e.Y, lb, ld, e.Button == MouseButtons.Right, e.Delta, false, false,
- e.Button == MouseButtons.Middle, false);
- }
-
- // ReSharper disable UnusedParameter.Global
- public void MouseUp(MouseEventArgs e)
- // ReSharper restore UnusedParameter.Global
- {
- //CheckModifiers();
- //Add(_ModALT, _ModSHIFT, _ModCTRL, e.X, e.Y, e.Button == MouseButtons.Left, e.Button == MouseButtons.Right, e.Delta);
+ _Add(modifiers, x, y, lb, ld, button == EMouseButton.Right, 0, false, false,
+ button == EMouseButton.Middle, false);
}
public bool PollEvent(ref SMouseEvent mouseEvent)
diff --git a/Vocaluxe/Base/CWebcam.cs b/Vocaluxe/Base/CWebcam.cs
index 04fd4706..e86bce29 100644
--- a/Vocaluxe/Base/CWebcam.cs
+++ b/Vocaluxe/Base/CWebcam.cs
@@ -15,7 +15,7 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Drawing;
+using SkiaSharp;
using Vocaluxe.Lib.Webcam;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -33,6 +33,7 @@ namespace Vocaluxe.Base
return false;
}
+#if WIN
switch (CConfig.Config.Video.WebcamLib)
{
case EWebcamLib.AForgeNet:
@@ -43,6 +44,9 @@ namespace Vocaluxe.Base
_Webcam = new CAForgeNet();
break;
}
+#else
+ return true;
+#endif
if (!_Webcam.Init())
{
@@ -72,7 +76,7 @@ namespace Vocaluxe.Base
return _Webcam != null && _Webcam.GetFrame(ref tex);
}
- public static Bitmap GetBitmap()
+ public static SKBitmap GetBitmap()
{
return _Webcam == null ? null : _Webcam.GetBitmap();
}
diff --git a/Vocaluxe/Base/Fonts/CFontStyle.cs b/Vocaluxe/Base/Fonts/CFontStyle.cs
index d8631d83..acaf6bdc 100644
--- a/Vocaluxe/Base/Fonts/CFontStyle.cs
+++ b/Vocaluxe/Base/Fonts/CFontStyle.cs
@@ -17,8 +17,7 @@
using System;
using System.Collections.Generic;
-using System.Drawing;
-using System.Drawing.Text;
+using SkiaSharp;
using VocaluxeLib;
using VocaluxeLib.Draw;
using VocaluxeLib.Log;
@@ -28,18 +27,16 @@ namespace Vocaluxe.Base.Fonts
class CFontStyle : IDisposable
{
private readonly string _FilePath;
- private readonly EStyle _Style;
public readonly float Outline; //0..1, 0=not outline 1=100% outline
public readonly SColorF OutlineColor;
private readonly float _MaxGlyphHeight;
private readonly Dictionary _Glyphs = new Dictionary();
- private PrivateFontCollection _Fonts;
- private FontFamily _Family;
+ private SKTypeface _Typeface;
+ private bool _TypefaceLoaded;
- public CFontStyle(string file, EStyle style, float outline, SColorF outlineColor)
+ public CFontStyle(string file, float outline, SColorF outlineColor)
{
_FilePath = file;
- _Style = style;
Outline = outline;
OutlineColor = outlineColor;
@@ -66,32 +63,18 @@ namespace Vocaluxe.Base.Fonts
}
}
- private FontStyle _GetSystemFontStyle()
+ public SKTypeface GetTypeface()
{
- switch (_Style)
+ if (!_TypefaceLoaded)
{
- case EStyle.Normal:
- return FontStyle.Regular;
- case EStyle.Italic:
- return FontStyle.Italic;
- case EStyle.Bold:
- return FontStyle.Bold;
- case EStyle.BoldItalic:
- return FontStyle.Bold | FontStyle.Italic;
- }
-
- throw new ArgumentException("Invalid style: " + _Style);
- }
-
- public Font GetSystemFont(float height)
- {
- if (_Fonts == null)
- {
- _Fonts = new PrivateFontCollection();
+ _TypefaceLoaded = true;
try
{
- _Fonts.AddFontFile(_FilePath);
- _Family = _Fonts.Families[0];
+ _Typeface = SKTypeface.FromFile(_FilePath);
+ if (_Typeface == null)
+ {
+ CLog.Error("Error opening font file " + _FilePath);
+ }
}
catch (Exception e)
{
@@ -99,7 +82,7 @@ namespace Vocaluxe.Base.Fonts
}
}
- return new Font(_Family, height, _GetSystemFontStyle(), GraphicsUnit.Pixel);
+ return _Typeface ?? SKTypeface.Default;
}
public void DrawGlyph(char chr, float fontHeight, float x, float y, float z, SColorF color, bool allMonitors = true)
@@ -172,10 +155,10 @@ namespace Vocaluxe.Base.Fonts
{
if (!_Disposed)
{
- if (_Fonts != null)
+ if (_Typeface != null)
{
- _Fonts.Dispose();
- _Fonts = null;
+ _Typeface.Dispose();
+ _Typeface = null;
}
_UnloadGlyphs();
diff --git a/Vocaluxe/Base/Fonts/CFonts.cs b/Vocaluxe/Base/Fonts/CFonts.cs
index 1702c363..ac528bc0 100644
--- a/Vocaluxe/Base/Fonts/CFonts.cs
+++ b/Vocaluxe/Base/Fonts/CFonts.cs
@@ -21,6 +21,7 @@ using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
+using SkiaSharp;
using VocaluxeLib;
using VocaluxeLib.Log;
using VocaluxeLib.Menu;
@@ -173,9 +174,9 @@ namespace Vocaluxe.Base.Fonts
return new RectangleF(text.X, text.Y, GetTextWidth(text.TranslatedText, text.CalculatedFont), GetTextHeight(text.TranslatedText, text.CalculatedFont));
}
- public static Font GetSystemFont(CFont font)
+ public static SKTypeface GetTypeface(CFont font)
{
- return _GetFontStyle(font).GetSystemFont(font.Height);
+ return _GetFontStyle(font).GetTypeface();
}
public static float GetOutlineSize(CFont font)
@@ -343,10 +344,10 @@ namespace Vocaluxe.Base.Fonts
{
fontFamily.ThemeName = themeName;
fontFamily.PartyModeId = partyModeId;
- fontFamily.Normal = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileNormal), EStyle.Normal, fontFamily.Outline, fontFamily.OutlineColor);
- fontFamily.Italic = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileItalic), EStyle.Italic, fontFamily.Outline, fontFamily.OutlineColor);
- fontFamily.Bold = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileBold), EStyle.Bold, fontFamily.Outline, fontFamily.OutlineColor);
- fontFamily.BoldItalic = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileBoldItalic), EStyle.BoldItalic, fontFamily.Outline,
+ fontFamily.Normal = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileNormal), fontFamily.Outline, fontFamily.OutlineColor);
+ fontFamily.Italic = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileItalic), fontFamily.Outline, fontFamily.OutlineColor);
+ fontFamily.Bold = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileBold), fontFamily.Outline, fontFamily.OutlineColor);
+ fontFamily.BoldItalic = new CFontStyle(Path.Combine(fontFolder, fontFamily.Folder, fontFamily.FileBoldItalic), fontFamily.Outline,
fontFamily.OutlineColor);
_FontFamilies.Add(fontFamily);
return true;
diff --git a/Vocaluxe/Base/Fonts/CGlyph.cs b/Vocaluxe/Base/Fonts/CGlyph.cs
index e3a54da2..328fd374 100644
--- a/Vocaluxe/Base/Fonts/CGlyph.cs
+++ b/Vocaluxe/Base/Fonts/CGlyph.cs
@@ -20,10 +20,8 @@
using System;
using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
-using System.Drawing.Text;
using System.Runtime.InteropServices;
+using SkiaSharp;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -42,21 +40,18 @@ namespace Vocaluxe.Base.Fonts
var outlineSize = fontStyle.Outline * maxHeight;
var chrString = chr.ToString();
- var fo = fontStyle.GetSystemFont(maxHeight);
+ var typeface = fontStyle.GetTypeface();
SizeF fullSize;
Size bmpSize;
- using (var g = Graphics.FromHwnd(IntPtr.Zero))
+ float ascent;
+ using (var fo = new SKFont(typeface, maxHeight))
{
- fullSize = g.MeasureString(chrString, fo);
+ ascent = -fo.Metrics.Ascent;
+ var lineSpacing = fo.Spacing;
+ fullSize = new SizeF(fo.MeasureText(chrString) + maxHeight / 3, lineSpacing);
if (chr != ' ')
{
- //Gets exact height and width for drawing more than 1 char. But width is to small to draw char on bitmap as e.g. italic chars will get cropped
- //See https://stackoverflow.com/questions/11708621/how-to-measure-width-of-a-string-precisely
- var format = StringFormat.GenericTypographic;
- var rect = new RectangleF(0, 0, 2000, 2000);
- CharacterRange[] ranges = { new CharacterRange(0, chrString.Length) };
- format.SetMeasurableCharacterRanges(ranges);
- _BoundingBox = g.MeasureCharacterRanges(chrString, fo, rect, format)[0].GetBounds(g).Size;
+ _BoundingBox = new SizeF(fo.MeasureText(chrString), lineSpacing);
// ReSharper disable CompareOfFloatsByEqualityOperator
if (_BoundingBox.Height == 0)
@@ -72,16 +67,17 @@ namespace Vocaluxe.Base.Fonts
}
else
{
+ fullSize.Width = maxHeight / 3;
_BoundingBox = fullSize;
_BoundingBox.Height += outlineSize;
bmpSize = new Size(1, 1);
}
}
- using (var bmp = new Bitmap(bmpSize.Width, bmpSize.Height, PixelFormat.Format32bppArgb))
- using (var g = Graphics.FromImage(bmp))
+ using (var bmp = new SKBitmap(bmpSize.Width, bmpSize.Height, SKColorType.Bgra8888, SKAlphaType.Premul))
+ using (var g = new SKCanvas(bmp))
{
- g.Clear(Color.Transparent);
+ g.Clear(SKColors.Transparent);
if (chr == ' ')
{
@@ -90,29 +86,26 @@ namespace Vocaluxe.Base.Fonts
}
else
{
- g.SmoothingMode = SmoothingMode.AntiAlias;
- g.InterpolationMode = InterpolationMode.HighQualityBicubic;
- g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
-
- var point = new PointF(outlineSize / 2, outlineSize / 4);
-
- using (var path = new GraphicsPath())
+ //Have to use size in em not pixels!
+ using (var drawFont = new SKFont(typeface, ascent) { Edging = SKFontEdging.Antialias, Subpixel = true })
{
- //Have to use size in em not pixels!
- var emSize = fo.Size * fo.FontFamily.GetCellAscent(fo.Style) / fo.FontFamily.GetEmHeight(fo.Style);
- path.AddString(chrString, fo.FontFamily, (int)fo.Style, emSize, point, new StringFormat());
+ var point = new SKPoint(outlineSize / 2 + ascent / 6, outlineSize / 4 - drawFont.Metrics.Ascent);
- using (var pen = new Pen(fontStyle.OutlineColor.AsColor(), outlineSize))
+ using (var path = drawFont.GetTextPath(chrString, point))
+ using (var pen = new SKPaint { Style = SKPaintStyle.Stroke, StrokeWidth = outlineSize, StrokeJoin = SKStrokeJoin.Round, IsAntialias = true, Color = _ToSKColor(fontStyle.OutlineColor) })
+ using (var brush = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true, Color = SKColors.White })
{
- pen.LineJoin = LineJoin.Round;
- g.DrawPath(pen, path);
- g.FillPath(Brushes.White, path);
+ g.DrawPath(path, pen);
+ g.DrawPath(path, brush);
}
}
+ g.Flush();
_DrawBounding = _GetRealBounds(bmp);
- using (var bmpCropped = bmp.Clone(_DrawBounding, PixelFormat.Format32bppArgb))
+ using (var bmpCropped = new SKBitmap())
{
+ var bounds = Rectangle.Round(_DrawBounding);
+ bmp.ExtractSubset(bmpCropped, new SKRectI(bounds.Left, bounds.Top, bounds.Right, bounds.Bottom));
var dx = (fullSize.Width - _BoundingBox.Width - 1) / 2;
_DrawBounding.X -= dx;
_Texture = CDraw.AddTexture(bmpCropped);
@@ -134,6 +127,12 @@ namespace Vocaluxe.Base.Fonts
}
}
+ private static SKColor _ToSKColor(SColorF color)
+ {
+ var c = color.AsColor();
+ return new SKColor(c.R, c.G, c.B, c.A);
+ }
+
public void UnloadTexture()
{
CDraw.RemoveTexture(ref _Texture);
@@ -165,13 +164,12 @@ namespace Vocaluxe.Base.Fonts
rect = new SRectF(x, y, w, h, z);
}
- private static Rectangle _GetRealBounds(Bitmap bmp)
+ private static Rectangle _GetRealBounds(SKBitmap bmp)
{
int minX = 0, maxX = bmp.Width - 1, minY = 0;
- var bmpData = bmp.LockBits(bmp.GetRect(), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
- var values = bmpData.Width * bmp.Height;
+ var values = bmp.Width * bmp.Height;
var rgbValues = new Int32[values];
- Marshal.Copy(bmpData.Scan0, rgbValues, 0, values);
+ Marshal.Copy(bmp.GetPixels(), rgbValues, 0, values);
var index = 0;
var found = false;
//find from top
diff --git a/Vocaluxe/Base/Web/CVocaluxeRemote.cs b/Vocaluxe/Base/Web/CVocaluxeRemote.cs
index 4a490471..e387a197 100644
--- a/Vocaluxe/Base/Web/CVocaluxeRemote.cs
+++ b/Vocaluxe/Base/Web/CVocaluxeRemote.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Windows.Forms;
using Vocaluxe.Lib.Input;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/COSFunctions.cs b/Vocaluxe/COSFunctions.cs
index 063c21cb..11c465db 100644
--- a/Vocaluxe/COSFunctions.cs
+++ b/Vocaluxe/COSFunctions.cs
@@ -16,6 +16,7 @@
#endregion
using System;
+using System.IO;
using System.Runtime.InteropServices;
namespace Vocaluxe
@@ -39,7 +40,7 @@ namespace Vocaluxe
var newPath = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
if (newPath.Length > 0)
{
- newPath += ";";
+ newPath += Path.PathSeparator;
}
newPath += path;
diff --git a/Vocaluxe/CProgrammHelper.cs b/Vocaluxe/CProgrammHelper.cs
index 8dd187bd..2533be1a 100644
--- a/Vocaluxe/CProgrammHelper.cs
+++ b/Vocaluxe/CProgrammHelper.cs
@@ -17,9 +17,7 @@
using System.IO;
using System.Reflection;
-using Microsoft.Win32;
using Vocaluxe.Base;
-using VocaluxeLib.Log;
namespace Vocaluxe
{
@@ -28,46 +26,6 @@ namespace Vocaluxe
///
static class CProgrammHelper
{
- private static bool _IsVC2015To2022Installed()
- {
- const string baseKey = @"SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\";
- const string wow6432BaseKey = @"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\";
-
-#if ARCH_X64
- string[] arches = { "x64" };
-#elif ARCH_X86
- string[] arches = { "x86" };
-#else
- string[] arches = { "x86", "x64" };
-#endif
-
- foreach (var arch in arches)
- {
- using (var rk = Registry.LocalMachine.OpenSubKey(baseKey + arch) ??
- Registry.LocalMachine.OpenSubKey(wow6432BaseKey + arch))
- {
- if (rk == null)
- {
- continue;
- }
-
- var installed = rk.GetValue("Installed");
- if (installed == null)
- {
- continue;
- }
-
- int installedValue;
- if (int.TryParse(installed.ToString(), out installedValue) && installedValue == 1)
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
private static void _EnsureDataFolderExists()
{
if (!Directory.Exists(CSettings.DataFolder))
@@ -89,17 +47,7 @@ namespace Vocaluxe
public static bool CheckRequirements()
{
-#if WIN
- if (!_IsVC2015To2022Installed())
- {
- CLog.Fatal(
- "VC++ 2015-2022 Redistributables are missing. Please install them first.\r\n" +
- "Download x64: https://aka.ms/vc14/vc_redist.x64.exe\r\n" +
- "Download x86: https://aka.ms/vc14/vc_redist.x86.exe");
- return false;
- }
-
-#endif //TODO: check for dependencies on linux?
+ //TODO: check for dependencies on linux?
return true;
}
diff --git a/Vocaluxe/CSplashScreen.cs b/Vocaluxe/CSplashScreen.cs
index 3360fdf9..4ae1b892 100644
--- a/Vocaluxe/CSplashScreen.cs
+++ b/Vocaluxe/CSplashScreen.cs
@@ -16,17 +16,19 @@
#endregion
using System;
-using System.Drawing;
using System.IO;
-using System.Windows.Forms;
+using SDL;
+using SkiaSharp;
using Vocaluxe.Base;
+using Vocaluxe.Lib;
using VocaluxeLib.Log;
namespace Vocaluxe
{
- class CSplashScreen : Form
+ unsafe class CSplashScreen
{
- private readonly Bitmap _Logo;
+ private readonly SKBitmap _Logo;
+ private SDL_Window* _Window;
public CSplashScreen()
{
@@ -35,8 +37,7 @@ namespace Vocaluxe
{
try
{
- _Logo = new Bitmap(path);
- ClientSize = new Size(_Logo.Width, _Logo.Height);
+ _Logo = SKBitmap.Decode(path);
}
catch (Exception e)
{
@@ -48,55 +49,57 @@ namespace Vocaluxe
CLog.Error("Can't find " + path);
}
- path = Path.Combine(CSettings.ProgramFolder, CSettings.FileNameIcon);
- if (File.Exists(path))
- {
- try
- {
- Icon = new Icon(path);
- }
- catch (Exception e)
- {
- CLog.Error("Error loading icon: " + e.Message);
- }
- }
- else
- {
- CLog.Error("Can't find " + path);
- }
-
- SetStyle(ControlStyles.SupportsTransparentBackColor, true);
- BackColor = Color.Transparent;
-
- FormBorderStyle = FormBorderStyle.None;
- Text = CSettings.ProgramName;
- CenterToScreen();
- Show();
- }
-
- public override sealed string Text
- {
- get { return base.Text; }
- set { base.Text = value; }
- }
-
- public override sealed Color BackColor
- {
- get { return base.BackColor; }
- set { base.BackColor = value; }
- }
-
- protected override void OnPaint(PaintEventArgs e) { }
-
- protected override void OnPaintBackground(PaintEventArgs e)
- {
if (_Logo == null)
{
return;
}
- var g = e.Graphics;
- g.DrawImage(_Logo, new Rectangle(0, 0, Width, Height));
+ _Window = SDL3.SDL_CreateWindow(CSettings.ProgramName, _Logo.Width, _Logo.Height, SDL_WindowFlags.SDL_WINDOW_BORDERLESS | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
+ if (_Window == null)
+ {
+ CLog.Error("Error creating the splash screen: " + SDL3.SDL_GetError());
+ return;
+ }
+
+ CSdl.SetWindowIcon(_Window, Path.Combine(CSettings.ProgramFolder, CSettings.FileNameIcon));
+ SDL3.SDL_SetWindowPosition(_Window, (int)SDL3.SDL_WINDOWPOS_CENTERED, (int)SDL3.SDL_WINDOWPOS_CENTERED);
+ SDL3.SDL_ShowWindow(_Window);
+ _Paint();
+ }
+
+ private void _Paint()
+ {
+ var surface = SDL3.SDL_GetWindowSurface(_Window);
+ if (surface == null)
+ {
+ return;
+ }
+
+ var colorType = surface->format == SDL_PixelFormat.SDL_PIXELFORMAT_ABGR8888 || surface->format == SDL_PixelFormat.SDL_PIXELFORMAT_XBGR8888
+ ? SKColorType.Rgba8888 : SKColorType.Bgra8888;
+ using (var target = SKSurface.Create(new SKImageInfo(surface->w, surface->h, colorType, SKAlphaType.Premul), surface->pixels, surface->pitch))
+ using (var image = SKImage.FromBitmap(_Logo))
+ {
+ if (target != null)
+ {
+ target.Canvas.Clear(SKColors.Black);
+ target.Canvas.DrawImage(image, SKRect.Create(surface->w, surface->h), new SKSamplingOptions(SKCubicResampler.Mitchell));
+ target.Canvas.Flush();
+ }
+ }
+
+ SDL3.SDL_UpdateWindowSurface(_Window);
+ }
+
+ public void Close()
+ {
+ if (_Window != null)
+ {
+ SDL3.SDL_DestroyWindow(_Window);
+ _Window = null;
+ }
+
+ _Logo?.Dispose();
}
}
-}
\ No newline at end of file
+}
diff --git a/Vocaluxe/Lib/CSdl.cs b/Vocaluxe/Lib/CSdl.cs
new file mode 100644
index 00000000..702c27e1
--- /dev/null
+++ b/Vocaluxe/Lib/CSdl.cs
@@ -0,0 +1,393 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using SDL;
+using SkiaSharp;
+using Vocaluxe.Base;
+using VocaluxeLib;
+using VocaluxeLib.Log;
+
+namespace Vocaluxe.Lib
+{
+ static unsafe class CSdl
+ {
+ private static bool _Initialized;
+ private static string _FolderDialogResult;
+ private static int _FolderDialogDone;
+
+ public static SDL_Window* MainWindow { get; set; }
+
+ public static bool Init()
+ {
+ if (_Initialized)
+ {
+ return true;
+ }
+
+ SDL3.SDL_SetHint(SDL3.SDL_HINT_APP_NAME, CSettings.ProgramName);
+#if ARCH_X86
+ SDL3.SDL_SetHint(SDL3.SDL_HINT_JOYSTICK_GAMEINPUT, "0");
+ SDL3.SDL_SetHint(SDL3.SDL_HINT_JOYSTICK_GAMEINPUT_RAW, "0");
+ SDL3.SDL_SetHint(SDL3.SDL_HINT_WINDOWS_GAMEINPUT, "0");
+#endif
+ if (!SDL3.SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO | SDL_InitFlags.SDL_INIT_GAMEPAD))
+ {
+ CLog.Error("Failed to initialize SDL: " + SDL3.SDL_GetError());
+ return false;
+ }
+
+ _Initialized = true;
+ return true;
+ }
+
+ public static void Close()
+ {
+ if (!_Initialized)
+ {
+ return;
+ }
+
+ SDL3.SDL_Quit();
+ _Initialized = false;
+ }
+
+ public static void DoEvents()
+ {
+ if (_Initialized)
+ {
+ SDL3.SDL_PumpEvents();
+ }
+ }
+
+ public static void ShowMessage(string title, string message, bool error = false)
+ {
+ SDL3.SDL_ShowSimpleMessageBox(error ? SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR : SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, title, message, MainWindow);
+ }
+
+ public static int ShowMessage(string title, string message, bool error, params string[] buttons)
+ {
+ var allocated = new List();
+ try
+ {
+ var buttonData = stackalloc SDL_MessageBoxButtonData[buttons.Length];
+ for (var i = 0; i < buttons.Length; i++)
+ {
+ buttonData[i].buttonID = i;
+ buttonData[i].text = _ToUtf8(buttons[i], allocated);
+ buttonData[i].flags = i == 0 ? SDL_MessageBoxButtonFlags.SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT : 0;
+ }
+
+ var data = new SDL_MessageBoxData
+ {
+ flags = (error ? SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR : SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION) | SDL_MessageBoxFlags.SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT,
+ window = MainWindow,
+ title = _ToUtf8(title, allocated),
+ message = _ToUtf8(message, allocated),
+ numbuttons = buttons.Length,
+ buttons = buttonData
+ };
+
+ int buttonId;
+ if (!SDL3.SDL_ShowMessageBox(&data, &buttonId))
+ {
+ CLog.Error("Failed to show message box: " + SDL3.SDL_GetError());
+ return -1;
+ }
+
+ return buttonId;
+ }
+ finally
+ {
+ foreach (var ptr in allocated)
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+ }
+
+ private static byte* _ToUtf8(string text, List allocated)
+ {
+ var bytes = Encoding.UTF8.GetBytes((text ?? "") + "\0");
+ var ptr = Marshal.AllocHGlobal(bytes.Length);
+ Marshal.Copy(bytes, 0, ptr, bytes.Length);
+ allocated.Add(ptr);
+ return (byte*)ptr;
+ }
+
+ public static string ShowFolderDialog(string title)
+ {
+ _FolderDialogResult = null;
+ Interlocked.Exchange(ref _FolderDialogDone, 0);
+
+ var props = SDL3.SDL_CreateProperties();
+ SDL3.SDL_SetStringProperty(props, SDL3.SDL_PROP_FILE_DIALOG_TITLE_STRING, title);
+ SDL3.SDL_SetPointerProperty(props, SDL3.SDL_PROP_FILE_DIALOG_WINDOW_POINTER, (IntPtr)MainWindow);
+ SDL3.SDL_SetBooleanProperty(props, SDL3.SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, false);
+ SDL3.SDL_ShowFileDialogWithProperties(SDL_FileDialogType.SDL_FILEDIALOG_OPENFOLDER, &_OnFolderSelected, IntPtr.Zero, props);
+ SDL3.SDL_DestroyProperties(props);
+
+ while (Volatile.Read(ref _FolderDialogDone) == 0)
+ {
+ SDL3.SDL_PumpEvents();
+ Thread.Sleep(10);
+ }
+
+ return _FolderDialogResult;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
+ private static void _OnFolderSelected(IntPtr userdata, byte** fileList, int filter)
+ {
+ if (fileList != null && fileList[0] != null)
+ {
+ _FolderDialogResult = Marshal.PtrToStringUTF8((IntPtr)fileList[0]);
+ }
+
+ Volatile.Write(ref _FolderDialogDone, 1);
+ }
+
+ public static void OpenUrl(string url)
+ {
+ SDL3.SDL_OpenURL(url);
+ }
+
+ public static void SetClipboardText(string text)
+ {
+ SDL3.SDL_SetClipboardText(text);
+ }
+
+ public static int GetDisplayCount()
+ {
+ using (var displays = SDL3.SDL_GetDisplays())
+ {
+ return displays == null ? 0 : displays.Count;
+ }
+ }
+
+ public static System.Drawing.Size GetPrimaryDisplaySize()
+ {
+ SDL_Rect bounds;
+ if (!SDL3.SDL_GetDisplayBounds(SDL3.SDL_GetPrimaryDisplay(), &bounds))
+ {
+ return System.Drawing.Size.Empty;
+ }
+
+ return new System.Drawing.Size(bounds.w, bounds.h);
+ }
+
+ public static void SetWindowIcon(SDL_Window* window, string iconPath)
+ {
+ if (!File.Exists(iconPath))
+ {
+ CLog.Error("Can't find " + iconPath);
+ return;
+ }
+
+ try
+ {
+ using (var icon = SKBitmap.Decode(iconPath))
+ using (var converted = new SKBitmap(new SKImageInfo(icon.Width, icon.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul)))
+ {
+ icon.CopyPixelsTo(converted);
+ var surface = SDL3.SDL_CreateSurfaceFrom(converted.Width, converted.Height, SDL_PixelFormat.SDL_PIXELFORMAT_ARGB8888, converted.GetPixels(),
+ converted.RowBytes);
+ if (surface == null)
+ {
+ return;
+ }
+
+ SDL3.SDL_SetWindowIcon(window, surface);
+ SDL3.SDL_DestroySurface(surface);
+ }
+ }
+ catch (Exception e)
+ {
+ CLog.Error("Error loading icon: " + e.Message);
+ }
+ }
+
+ public static Keys GetModifiers(SDL_Keymod mod)
+ {
+ var result = Keys.None;
+ if ((mod & SDL_Keymod.SDL_KMOD_SHIFT) != 0)
+ {
+ result |= Keys.Shift;
+ }
+
+ if ((mod & SDL_Keymod.SDL_KMOD_CTRL) != 0)
+ {
+ result |= Keys.Control;
+ }
+
+ if ((mod & SDL_Keymod.SDL_KMOD_ALT) != 0)
+ {
+ result |= Keys.Alt;
+ }
+
+ return result;
+ }
+
+ public static Keys GetModifiers()
+ {
+ return GetModifiers(SDL3.SDL_GetModState());
+ }
+
+ public static Keys GetKey(SDL_Keycode key)
+ {
+ if (key >= SDL_Keycode.SDLK_A && key <= SDL_Keycode.SDLK_Z)
+ {
+ return Keys.A + (int)(key - SDL_Keycode.SDLK_A);
+ }
+
+ if (key >= SDL_Keycode.SDLK_0 && key <= SDL_Keycode.SDLK_9)
+ {
+ return Keys.D0 + (int)(key - SDL_Keycode.SDLK_0);
+ }
+
+ if (key >= SDL_Keycode.SDLK_F1 && key <= SDL_Keycode.SDLK_F12)
+ {
+ return Keys.F1 + (int)(key - SDL_Keycode.SDLK_F1);
+ }
+
+ if (key >= SDL_Keycode.SDLK_KP_1 && key <= SDL_Keycode.SDLK_KP_9)
+ {
+ return Keys.NumPad1 + (int)(key - SDL_Keycode.SDLK_KP_1);
+ }
+
+ switch (key)
+ {
+ case SDL_Keycode.SDLK_BACKSPACE:
+ return Keys.Back;
+ case SDL_Keycode.SDLK_TAB:
+ return Keys.Tab;
+ case SDL_Keycode.SDLK_RETURN:
+ case SDL_Keycode.SDLK_KP_ENTER:
+ return Keys.Enter;
+ case SDL_Keycode.SDLK_ESCAPE:
+ return Keys.Escape;
+ case SDL_Keycode.SDLK_SPACE:
+ return Keys.Space;
+ case SDL_Keycode.SDLK_PAGEUP:
+ return Keys.PageUp;
+ case SDL_Keycode.SDLK_PAGEDOWN:
+ return Keys.PageDown;
+ case SDL_Keycode.SDLK_END:
+ return Keys.End;
+ case SDL_Keycode.SDLK_HOME:
+ return Keys.Home;
+ case SDL_Keycode.SDLK_LEFT:
+ return Keys.Left;
+ case SDL_Keycode.SDLK_UP:
+ return Keys.Up;
+ case SDL_Keycode.SDLK_RIGHT:
+ return Keys.Right;
+ case SDL_Keycode.SDLK_DOWN:
+ return Keys.Down;
+ case SDL_Keycode.SDLK_PRINTSCREEN:
+ return Keys.PrintScreen;
+ case SDL_Keycode.SDLK_INSERT:
+ return Keys.Insert;
+ case SDL_Keycode.SDLK_DELETE:
+ return Keys.Delete;
+ case SDL_Keycode.SDLK_PAUSE:
+ return Keys.Pause;
+ case SDL_Keycode.SDLK_CAPSLOCK:
+ return Keys.CapsLock;
+ case SDL_Keycode.SDLK_NUMLOCKCLEAR:
+ return Keys.NumLock;
+ case SDL_Keycode.SDLK_SCROLLLOCK:
+ return Keys.Scroll;
+ case SDL_Keycode.SDLK_KP_0:
+ return Keys.NumPad0;
+ case SDL_Keycode.SDLK_KP_MULTIPLY:
+ return Keys.Multiply;
+ case SDL_Keycode.SDLK_KP_PLUS:
+ return Keys.Add;
+ case SDL_Keycode.SDLK_KP_MINUS:
+ return Keys.Subtract;
+ case SDL_Keycode.SDLK_KP_PERIOD:
+ case SDL_Keycode.SDLK_KP_DECIMAL:
+ return Keys.Decimal;
+ case SDL_Keycode.SDLK_KP_DIVIDE:
+ return Keys.Divide;
+ case SDL_Keycode.SDLK_LSHIFT:
+ case SDL_Keycode.SDLK_RSHIFT:
+ return Keys.ShiftKey;
+ case SDL_Keycode.SDLK_LCTRL:
+ case SDL_Keycode.SDLK_RCTRL:
+ return Keys.ControlKey;
+ case SDL_Keycode.SDLK_LALT:
+ case SDL_Keycode.SDLK_RALT:
+ return Keys.Menu;
+ case SDL_Keycode.SDLK_LGUI:
+ return Keys.LWin;
+ case SDL_Keycode.SDLK_RGUI:
+ return Keys.RWin;
+ case SDL_Keycode.SDLK_APPLICATION:
+ return Keys.Apps;
+ case SDL_Keycode.SDLK_MEDIA_NEXT_TRACK:
+ return Keys.MediaNextTrack;
+ case SDL_Keycode.SDLK_MEDIA_PREVIOUS_TRACK:
+ return Keys.MediaPreviousTrack;
+ case SDL_Keycode.SDLK_MEDIA_STOP:
+ return Keys.MediaStop;
+ case SDL_Keycode.SDLK_MEDIA_PLAY_PAUSE:
+ case SDL_Keycode.SDLK_MEDIA_PLAY:
+ case SDL_Keycode.SDLK_MEDIA_PAUSE:
+ return Keys.MediaPlayPause;
+ case SDL_Keycode.SDLK_MUTE:
+ return Keys.VolumeMute;
+ case SDL_Keycode.SDLK_VOLUMEDOWN:
+ return Keys.VolumeDown;
+ case SDL_Keycode.SDLK_VOLUMEUP:
+ return Keys.VolumeUp;
+ case SDL_Keycode.SDLK_SEMICOLON:
+ return Keys.OemSemicolon;
+ case SDL_Keycode.SDLK_PLUS:
+ case SDL_Keycode.SDLK_EQUALS:
+ return Keys.Oemplus;
+ case SDL_Keycode.SDLK_COMMA:
+ return Keys.Oemcomma;
+ case SDL_Keycode.SDLK_MINUS:
+ return Keys.OemMinus;
+ case SDL_Keycode.SDLK_PERIOD:
+ return Keys.OemPeriod;
+ case SDL_Keycode.SDLK_SLASH:
+ return Keys.OemQuestion;
+ case SDL_Keycode.SDLK_GRAVE:
+ return Keys.Oemtilde;
+ case SDL_Keycode.SDLK_LEFTBRACKET:
+ return Keys.OemOpenBrackets;
+ case SDL_Keycode.SDLK_BACKSLASH:
+ return Keys.OemPipe;
+ case SDL_Keycode.SDLK_RIGHTBRACKET:
+ return Keys.OemCloseBrackets;
+ case SDL_Keycode.SDLK_APOSTROPHE:
+ return Keys.OemQuotes;
+ }
+
+ return Keys.None;
+ }
+ }
+}
diff --git a/Vocaluxe/Lib/Database/CCoverDB.cs b/Vocaluxe/Lib/Database/CCoverDB.cs
index 74e6074d..db4f7f8f 100644
--- a/Vocaluxe/Lib/Database/CCoverDB.cs
+++ b/Vocaluxe/Lib/Database/CCoverDB.cs
@@ -16,11 +16,11 @@
#endregion
using System;
-using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Data.Sqlite;
+using SkiaSharp;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -30,8 +30,7 @@ namespace Vocaluxe.Lib.Database
{
public class CCoverDB : CDatabaseBase
{
- private const long _JpegQuality = 90;
- private static readonly ImageCodecInfo _JpegCodec = ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid);
+ private const int _JpegQuality = 90;
private SqliteTransaction _TransactionCover;
@@ -94,7 +93,7 @@ namespace Vocaluxe.Lib.Database
try
{
using (var stream = new MemoryStream(cachedData))
- using (var source = new System.Drawing.Bitmap(stream))
+ using (var source = SKBitmap.Decode(stream) ?? throw new InvalidDataException("Invalid image data"))
tex = CDraw.EnqueueTexture(_FitToMaxSize(source, maxSize));
}
catch (Exception e)
@@ -126,7 +125,7 @@ namespace Vocaluxe.Lib.Database
height = bitmap.Height;
data = source.Width <= maxSize && source.Height <= maxSize
? File.ReadAllBytes(coverPath)
- : _EncodeCover(bitmap, source.PixelFormat);
+ : _EncodeCover(bitmap, source.AlphaType != SKAlphaType.Opaque);
}
catch
{
@@ -147,11 +146,11 @@ namespace Vocaluxe.Lib.Database
return true;
}
- private static System.Drawing.Bitmap _FitToMaxSize(System.Drawing.Bitmap source, int maxSize)
+ private static SKBitmap _FitToMaxSize(SKBitmap source, int maxSize)
{
var sourceSize = source.GetSize();
if (sourceSize.Width <= maxSize && sourceSize.Height <= maxSize)
- return new System.Drawing.Bitmap(source);
+ return source.Copy();
var targetSize = CHelper.FitInBounds(
new SRectF(0, 0, maxSize, maxSize, 0),
@@ -162,21 +161,17 @@ namespace Vocaluxe.Lib.Database
return source.Resize(targetSize) ?? throw new InvalidOperationException("Resizing the cover failed");
}
- private static byte[] _EncodeCover(System.Drawing.Bitmap bitmap, PixelFormat sourceFormat)
+ private static byte[] _EncodeCover(SKBitmap bitmap, bool hasAlpha)
{
using (var stream = new MemoryStream())
{
- if (System.Drawing.Image.IsAlphaPixelFormat(sourceFormat))
+ if (hasAlpha)
{
- bitmap.Save(stream, ImageFormat.Png);
+ bitmap.Encode(stream, SKEncodedImageFormat.Png, 100);
}
else
{
- using (var parameters = new EncoderParameters(1))
- {
- parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, _JpegQuality);
- bitmap.Save(stream, _JpegCodec, parameters);
- }
+ bitmap.Encode(stream, SKEncodedImageFormat.Jpeg, _JpegQuality);
}
return stream.ToArray();
diff --git a/Vocaluxe/Lib/Draw/CDirect3D.cs b/Vocaluxe/Lib/Draw/CDirect3D.cs
index 75652838..cb5c2217 100644
--- a/Vocaluxe/Lib/Draw/CDirect3D.cs
+++ b/Vocaluxe/Lib/Draw/CDirect3D.cs
@@ -18,60 +18,52 @@
using System;
using System.Collections.Generic;
using System.Drawing;
-using System.Drawing.Imaging;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
-using System.Windows.Forms;
+using System.Threading;
+using Microsoft.Win32.SafeHandles;
+using SDL;
using SharpGen.Runtime;
-using Vortice.Direct3D9;
+using SkiaSharp;
+using Vortice.D3DCompiler;
+using Vortice.Direct3D;
+using Vortice.Direct3D11;
+using Vortice.DXGI;
+using Vortice.Mathematics;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
using VocaluxeLib.Log;
+using Color = System.Drawing.Color;
+using Format = Vortice.DXGI.Format;
+using MapFlags = Vortice.Direct3D11.MapFlags;
+using Size = System.Drawing.Size;
namespace Vocaluxe.Lib.Draw
{
- class CRenderFormHook : Form, IFormHook
+ class CD3DTexture : CTextureBase
{
- private const int _WmMenuChar = 0x0120;
- private const int _MncClose = 1;
+ public readonly ID3D11Texture2D D3DTexture;
+ public readonly ID3D11ShaderResourceView View;
+ public readonly bool HasMips;
+ public ID3D11Texture2D[] Planes;
+ public ID3D11ShaderResourceView[] PlaneViews;
+ public ID3D11RenderTargetView TargetView;
- public MessageEventHandler OnMessage { private get; set; }
-
- public CRenderFormHook()
+ public CD3DTexture(ID3D11Device device, Size dataSize, int texWidth = 0, int texHeight = 0) : base(dataSize, new Size(texWidth, texHeight))
{
- SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.Opaque, true);
- }
-
- protected override void OnPaintBackground(PaintEventArgs e) { }
-
- protected override void WndProc(ref Message m)
- {
- if (m.Msg == _WmMenuChar)
+ if (device == null)
{
- m.Result = new IntPtr(_MncClose << 16);
return;
}
- if (OnMessage == null || OnMessage(ref m))
- {
- base.WndProc(ref m);
- }
- }
- }
-
- class CD3DTexture : CTextureBase
- {
- public readonly IDirect3DTexture9 D3DTexture;
-
- public CD3DTexture(IDirect3DDevice9 device, Size dataSize, int texWidth = 0, int texHeight = 0) : base(dataSize, new Size(texWidth, texHeight))
- {
- var noMips = texWidth >= 1280 || texHeight >= 720;
- var usage = noMips ? Usage.None : Usage.AutoGenerateMipMap;
- var levels = noMips ? 1 : 0;
-
- D3DTexture = device?.CreateTexture(texWidth, texHeight, levels, usage, Format.A8R8G8B8, Pool.Managed);
+ HasMips = texWidth < 1280 && texHeight < 720;
+ var description = new Texture2DDescription(Format.B8G8R8A8_UNorm, (uint)texWidth, (uint)texHeight, 1, HasMips ? 0u : 1u,
+ BindFlags.ShaderResource | BindFlags.RenderTarget, ResourceUsage.Default, CpuAccessFlags.None, 1, 0,
+ HasMips ? ResourceOptionFlags.GenerateMips : ResourceOptionFlags.None);
+ D3DTexture = device.CreateTexture2D(description);
+ View = device.CreateShaderResourceView(D3DTexture);
}
public override bool IsLoaded
@@ -87,147 +79,194 @@ namespace Vocaluxe.Lib.Draw
}
base.Dispose();
- if (D3DTexture != null)
+ TargetView?.Dispose();
+ if (Planes != null)
{
- D3DTexture.Dispose();
+ for (var i = 0; i < Planes.Length; i++)
+ {
+ PlaneViews[i]?.Dispose();
+ Planes[i]?.Dispose();
+ }
}
+
+ View?.Dispose();
+ D3DTexture?.Dispose();
}
}
- class CDirect3D : CDrawBaseWindows, IDraw
+ unsafe class CDirect3D : CDrawBaseSDL, IDraw
{
- private const int _D3DErrDeviceLost = unchecked((int)0x88760868);
- private const int _D3DErrDeviceNotReset = unchecked((int)0x88760869);
+ private const Format _BackBufferFormat = Format.B8G8R8A8_UNorm;
+ private const string _YuvShader = @"
+cbuffer CYuv : register(b0)
+{
+ float4 RowR;
+ float4 RowG;
+ float4 RowB;
+ float4 Size;
+};
- private readonly IDirect3D9 _D3D;
- private readonly IDirect3DDevice9 _Device;
- private PresentParameters _PresentParameters;
+Texture2D TexY : register(t0);
+Texture2D TexU : register(t1);
+Texture2D TexV : register(t2);
+SamplerState Sampler : register(s0);
- private IDirect3DVertexBuffer9 _VertexBuffer;
- private IDirect3DIndexBuffer9 _IndexBuffer;
+float4 VS(uint id : SV_VertexID) : SV_POSITION
+{
+ float2 position = float2((id << 1) & 2, id & 2);
+ return float4(position * float2(2, -2) + float2(-1, 1), 0, 1);
+}
+
+float4 PS(float4 position : SV_POSITION) : SV_TARGET
+{
+ float2 uv = position.xy / Size.xy;
+ float4 yuv = float4(TexY.Sample(Sampler, uv).r, TexU.Sample(Sampler, uv).r, TexV.Sample(Sampler, uv).r, 1);
+ return float4(saturate(dot(RowR, yuv)), saturate(dot(RowG, yuv)), saturate(dot(RowB, yuv)), 1);
+}";
+ private const string _Shader = @"
+cbuffer CTransform : register(b0)
+{
+ row_major float4x4 Projection;
+};
+
+Texture2D Tex : register(t0);
+SamplerState Sampler : register(s0);
+
+struct SVertex
+{
+ float3 Position : POSITION;
+ float2 TexCoord : TEXCOORD0;
+ float4 Color : COLOR0;
+};
+
+struct SPixel
+{
+ float4 Position : SV_POSITION;
+ float2 TexCoord : TEXCOORD0;
+ float4 Color : COLOR0;
+};
+
+SPixel VS(SVertex input)
+{
+ SPixel output;
+ output.Position = mul(float4(input.Position, 1), Projection);
+ output.TexCoord = input.TexCoord;
+ output.Color = input.Color;
+ return output;
+}
+
+float4 PS(SPixel input) : SV_TARGET
+{
+ return Tex.Sample(Sampler, input.TexCoord) * input.Color;
+}";
+
+ private readonly IDXGIFactory2 _Factory;
+ private readonly ID3D11Device _Device;
+ private readonly ID3D11DeviceContext _Context;
+ private IDXGISwapChain1 _SwapChain;
+ private WaitHandle _FrameLatencyWaitHandle;
+ private readonly bool _TearingSupported;
+ private readonly uint _SampleCount = 1;
+ private readonly uint _SampleQuality;
+
+ private ID3D11Texture2D _BackBuffer;
+ private ID3D11Texture2D _RenderTarget;
+ private ID3D11RenderTargetView _RenderTargetView;
+ private ID3D11Texture2D _DepthBuffer;
+ private ID3D11DepthStencilView _DepthStencilView;
+ private int _BufferWidth;
+ private int _BufferHeight;
+
+ private ID3D11VertexShader _VertexShader;
+ private ID3D11PixelShader _PixelShader;
+ private ID3D11InputLayout _InputLayout;
+ private ID3D11Buffer _VertexBuffer;
+ private ID3D11Buffer _IndexBuffer;
+ private ID3D11Buffer _ConstantBuffer;
+ private ID3D11BlendState _BlendState;
+ private ID3D11DepthStencilState _DepthStencilState;
+ private ID3D11RasterizerState _RasterizerState;
+ private ID3D11SamplerState _SamplerState;
+ private ID3D11VertexShader _YuvVertexShader;
+ private ID3D11PixelShader _YuvPixelShader;
+ private ID3D11Buffer _YuvConstantBuffer;
private CTextureRef _BlankTexture;
- private readonly Queue _Vertices = new Queue();
- private readonly Queue _VerticesTextures = new Queue();
- private readonly Queue _VerticesRotationMatrices = new Queue();
+ private readonly List _Vertices = new List();
+ private readonly List _VerticesTextures = new List();
///
/// Creates a new Instance of the CDirect3D Class
///
public CDirect3D()
{
- _Form = new CRenderFormHook { ClientSize = new Size(CConfig.Config.Graphics.ScreenW * CConfig.Config.Graphics.NumScreens, CConfig.Config.Graphics.ScreenH) };
-
- try
+ if (!_CreateWindow(0))
{
- _D3D = D3D9.Direct3DCreate9();
- }
- catch (Exception e)
- {
- CLog.Error(e, "Direct3D 9 could not be initialized, please check if your graphic card drivers are up to date.");
- }
-
- if (_D3D == null)
- {
- CLog.Fatal("Direct3D 9 is not available on this system.");
return;
}
- _Form.KeyDown += _OnKeyDown;
- _Form.PreviewKeyDown += _OnPreviewKeyDown;
- _Form.KeyPress += _OnKeyPress;
- _Form.KeyUp += _OnKeyUp;
-
- _Form.MouseMove += _OnMouseMove;
- _Form.MouseWheel += _OnMouseWheel;
- _Form.MouseDown += _OnMouseDown;
- _Form.MouseUp += _OnMouseUp;
- _Form.MouseLeave += _OnMouseLeave;
- _Form.MouseEnter += _OnMouseEnter;
-
- const int adapter = 0;
- var displayFormat = _D3D.GetAdapterDisplayMode(adapter).Format;
-
- var depthFormat = _D3D.CheckDepthStencilMatch(adapter, DeviceType.Hardware, displayFormat, displayFormat, Format.D24X8).Success ? Format.D24X8 : Format.D16;
-
- _PresentParameters = new PresentParameters
- {
- Windowed = true,
- SwapEffect = SwapEffect.Discard,
- BackBufferHeight = CConfig.Config.Graphics.ScreenH,
- BackBufferWidth = CConfig.Config.Graphics.ScreenW * CConfig.Config.Graphics.NumScreens,
- BackBufferFormat = displayFormat,
- EnableAutoDepthStencil = true,
- AutoDepthStencilFormat = depthFormat,
- MultiSampleType = MultisampleType.None,
- MultiSampleQuality = 0
- };
-
- //Apply antialiasing and check if antialiasing mode is supported
-
- #region Antialiasing
- int quality;
- MultisampleType msType;
- switch (CConfig.Config.Graphics.AAMode)
- {
- case EAntiAliasingModes.X2:
- msType = MultisampleType.TwoSamples;
- break;
- case EAntiAliasingModes.X4:
- msType = MultisampleType.FourSamples;
- break;
- case EAntiAliasingModes.X8:
- msType = MultisampleType.EightSamples;
- break;
- case EAntiAliasingModes.X16:
- case EAntiAliasingModes.X32: //x32 is not supported, fallback to x16
- msType = MultisampleType.SixteenSamples;
- break;
- default:
- msType = MultisampleType.None;
- break;
- }
-
- if (_D3D.CheckDeviceMultiSampleType(adapter, DeviceType.Hardware, displayFormat, false, msType, out quality).Failure)
- {
- CLog.Error("[Direct3D] This AAMode is not supported by this device or driver, fallback to no AA");
- msType = MultisampleType.None;
- quality = 1;
- }
-
- _PresentParameters.MultiSampleType = msType;
- _PresentParameters.MultiSampleQuality = quality - 1;
- #endregion Antialiasing
-
- //Apply the VSync configuration
- _PresentParameters.PresentationInterval = CConfig.Config.Graphics.VSync == EOffOn.TR_CONFIG_ON ? PresentInterval.Default : PresentInterval.Immediate;
-
- //GMA 950 graphics devices can only process vertices in software mode
- var caps = _D3D.GetDeviceCaps(adapter, DeviceType.Hardware);
- var flags = (caps.DeviceCaps & DeviceCaps.HWTransformAndLight) != 0 ? CreateFlags.HardwareVertexProcessing : CreateFlags.SoftwareVertexProcessing;
-
- //Check if Pow2 textures are needed
- _NonPowerOf2TextureSupported = true;
- _NonPowerOf2TextureSupported &= (caps.TextureCaps & TextureCaps.Pow2) == 0;
- _NonPowerOf2TextureSupported &= (caps.TextureCaps & TextureCaps.NonPow2Conditional) == 0;
- _NonPowerOf2TextureSupported &= (caps.TextureCaps & TextureCaps.SquareOnly) == 0;
-
try
{
- _Device = _D3D.CreateDevice(adapter, DeviceType.Hardware, _Form.Handle, flags, _PresentParameters);
+ _Factory = DXGI.CreateDXGIFactory1();
+ var featureLevels = new[] { FeatureLevel.Level_11_0, FeatureLevel.Level_10_1, FeatureLevel.Level_10_0 };
+ D3D11.D3D11CreateDevice(IntPtr.Zero, DriverType.Hardware, DeviceCreationFlags.BgraSupport, featureLevels, out _Device, out _Context).CheckError();
}
catch (Exception e)
{
- CLog.Error(e, "Error during D3D device creation.");
+ CLog.Error(e, "Direct3D 11 could not be initialized, please check if your graphic card drivers are up to date.");
}
- finally
+
+ if (_Device == null)
{
- if (_Device == null)
+ CLog.Fatal("Direct3D 11 is not available on this system.");
+ return;
+ }
+
+ using (var factory5 = _Factory.QueryInterfaceOrNull())
+ {
+ _TearingSupported = factory5 != null && factory5.PresentAllowTearing;
+ }
+
+ //Apply antialiasing and check if antialiasing mode is supported
+ #region Antialiasing
+ uint sampleCount;
+ switch (CConfig.Config.Graphics.AAMode)
+ {
+ case EAntiAliasingModes.X2:
+ sampleCount = 2;
+ break;
+ case EAntiAliasingModes.X4:
+ sampleCount = 4;
+ break;
+ case EAntiAliasingModes.X8:
+ sampleCount = 8;
+ break;
+ case EAntiAliasingModes.X16:
+ case EAntiAliasingModes.X32: //x32 is not supported, fallback to x16
+ sampleCount = 16;
+ break;
+ default:
+ sampleCount = 1;
+ break;
+ }
+
+ if (sampleCount > 1)
+ {
+ var quality = _Device.CheckMultisampleQualityLevels(_BackBufferFormat, sampleCount);
+ if (quality == 0)
{
- CLog.Fatal("Something went wrong during device creating, please check if your grafic card drivers are up to date.");
+ CLog.Error("[Direct3D] This AAMode is not supported by this device or driver, fallback to no AA");
+ }
+ else
+ {
+ _SampleCount = sampleCount;
+ _SampleQuality = quality - 1;
}
}
+ #endregion Antialiasing
+
+ _NonPowerOf2TextureSupported = true;
}
#region resize
@@ -236,38 +275,34 @@ namespace Vocaluxe.Lib.Draw
///
protected override void _DoResize()
{
- // The window was minimized, so restore it to the last known size
- if (_Form.ClientSize.Width == 0 || _Form.ClientSize.Height == 0)
+ var clientSize = _GetClientSize();
+
+ // The window was minimized, so keep the last known size
+ if (clientSize.Width == 0 || clientSize.Height == 0)
{
- _Form.ClientSize = _SizeBeforeMinimize;
+ return;
}
- if (_H == _Form.ClientSize.Height && _W == _Form.ClientSize.Width && CConfig.Config.Graphics.ScreenAlignment == _CurrentAlignment)
+ if (_H == clientSize.Height && _W == clientSize.Width && CConfig.Config.Graphics.ScreenAlignment == _CurrentAlignment)
{
return;
}
_CurrentAlignment = CConfig.Config.Graphics.ScreenAlignment;
- _H = _Form.ClientSize.Height;
- _W = _Form.ClientSize.Width;
+ _H = clientSize.Height;
+ _W = clientSize.Width;
+ _X = 0;
+ _Y = 0;
if (CConfig.Config.Graphics.Stretch != EOffOn.TR_CONFIG_ON)
{
_AdjustAspect(false);
}
- //Apply the new sizes to the PresentParameters
- _PresentParameters.BackBufferWidth = _Form.ClientSize.Width;
- _PresentParameters.BackBufferHeight = _Form.ClientSize.Height;
- if (_Run)
+ if (_SwapChain != null)
{
- _ClearScreen();
- //To set new PresentParameters the device has to be resetted
- _Reset();
+ _CreateSizeDependentResources(clientSize.Width, clientSize.Height);
}
-
- //Store size so it can get restored after the window gets minimized
- _SizeBeforeMinimize = _Form.ClientSize;
}
// ReSharper disable RedundantOverridenMember
@@ -277,8 +312,6 @@ namespace Vocaluxe.Lib.Draw
protected override void _EnterFullScreen()
{
//This currently not using real fullscreen mode but a borderless window
- //Real fullscreen could be gained setting _PresentParameters.Windowed = true
- //And calling Reset() and Init() after
base._EnterFullScreen();
}
@@ -302,171 +335,240 @@ namespace Vocaluxe.Lib.Draw
return false;
}
- _InitDevice();
+ try
+ {
+ _InitDevice();
+ }
+ catch (Exception e)
+ {
+ CLog.Error(e, "Error during D3D device initialization.");
+ return false;
+ }
//This creates a new white texture and adds it to the texture pool
//This texture is used for the DrawRect method
- using (var blankMap = new Bitmap(1, 1))
- using (var g = Graphics.FromImage(blankMap))
- {
- g.Clear(Color.White);
- _BlankTexture = AddTexture(blankMap);
- }
+ _BlankTexture = AddTexture(1, 1, new byte[] { 255, 255, 255, 255 });
return true;
}
private void _InitDevice()
{
- _AdjustNewBorders();
-
- var vertexSize = Marshal.SizeOf(typeof(STexturedColoredVertex));
- _VertexBuffer = _Device.CreateVertexBuffer(CSettings.VertexBufferElements * 4 * vertexSize, Usage.WriteOnly | Usage.Dynamic,
- VertexFormat.Position | VertexFormat.Texture1 | VertexFormat.Diffuse, Pool.Default);
-
- _Device.SetStreamSource(0, _VertexBuffer, 0, vertexSize);
- _Device.VertexDeclaration = STexturedColoredVertex.GetDeclaration(_Device);
-
- _Device.SetRenderState(RenderState.CullMode, (int)Cull.None);
- _Device.SetRenderState(RenderState.ZEnable, (int)ZBufferType.UseZBuffer);
- _Device.SetRenderState(RenderState.ZFunc, (int)Compare.LessEqual);
- _Device.SetRenderState(RenderState.ZWriteEnable, true);
- _Device.SetRenderState(RenderState.AlphaBlendEnable, true);
- _Device.SetRenderState(RenderState.Lighting, false);
- _Device.SetRenderState(RenderState.DestinationBlend, (int)Blend.InverseSourceAlpha);
- _Device.SetRenderState(RenderState.SourceBlend, (int)Blend.SourceAlpha);
-
- if (_PresentParameters.MultiSampleType != MultisampleType.None)
+ var clientSize = _GetClientSize();
+ var hwnd = SDL3.SDL_GetPointerProperty(SDL3.SDL_GetWindowProperties(_Window), SDL3.SDL_PROP_WINDOW_WIN32_HWND_POINTER, IntPtr.Zero);
+ var description = new SwapChainDescription1
{
- _Device.SetRenderState(RenderState.MultisampleAntialias, true);
+ Width = (uint)clientSize.Width,
+ Height = (uint)clientSize.Height,
+ Format = _BackBufferFormat,
+ SampleDescription = new SampleDescription(1, 0),
+ BufferUsage = Usage.RenderTargetOutput,
+ BufferCount = 2,
+ Scaling = Scaling.Stretch,
+ SwapEffect = SwapEffect.FlipDiscard,
+ AlphaMode = AlphaMode.Ignore,
+ Flags = _SwapChainFlags
+ };
+ _SwapChain = _Factory.CreateSwapChainForHwnd(_Device, hwnd, description);
+ using (var swapChain2 = _SwapChain.QueryInterface())
+ {
+ swapChain2.MaximumFrameLatency = 1;
+ _FrameLatencyWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset) { SafeWaitHandle = new SafeWaitHandle(swapChain2.FrameLatencyWaitableObject, true) };
}
- _Device.SetSamplerState(0, SamplerState.MinFilter, (int)TextureFilter.Linear);
- _Device.SetSamplerState(0, SamplerState.MagFilter, (int)TextureFilter.Linear);
- _Device.SetSamplerState(0, SamplerState.MipFilter, (int)TextureFilter.Linear);
- _Device.SetSamplerState(0, SamplerState.AddressU, (int)TextureAddress.Clamp);
- _Device.SetSamplerState(0, SamplerState.AddressV, (int)TextureAddress.Clamp);
+ _Factory.MakeWindowAssociation(hwnd, WindowAssociationFlags.IgnoreAltEnter);
+ _CreateSizeDependentResources(clientSize.Width, clientSize.Height);
- _Device.SetTextureStageState(0, TextureStage.AlphaArg1, (int)TextureArgument.Texture);
- _Device.SetTextureStageState(0, TextureStage.AlphaArg2, (int)TextureArgument.Diffuse);
- _Device.SetTextureStageState(0, TextureStage.AlphaOperation, (int)TextureOperation.Modulate);
+ var vertexShader = _CompileShader(_Shader, "VS", "vs_4_0");
+ var pixelShader = _CompileShader(_Shader, "PS", "ps_4_0");
+ _VertexShader = _Device.CreateVertexShader(vertexShader);
+ _PixelShader = _Device.CreatePixelShader(pixelShader);
+ _InputLayout = _Device.CreateInputLayout(new[]
+ {
+ new InputElementDescription("POSITION", 0, Format.R32G32B32_Float, 0, 0),
+ new InputElementDescription("TEXCOORD", 0, Format.R32G32_Float, sizeof(float) * 3, 0),
+ new InputElementDescription("COLOR", 0, Format.B8G8R8A8_UNorm, sizeof(float) * 5, 0)
+ }, vertexShader);
- var indices = new Int16[6];
- indices[0] = 0;
- indices[1] = 1;
- indices[2] = 2;
- indices[3] = 0;
- indices[4] = 2;
- indices[5] = 3;
+ var vertexSize = (uint)Marshal.SizeOf();
+ _VertexBuffer = _Device.CreateBuffer(CSettings.VertexBufferElements * 4 * vertexSize, BindFlags.VertexBuffer, ResourceUsage.Dynamic, CpuAccessFlags.Write,
+ ResourceOptionFlags.None, 0);
- _IndexBuffer = _Device.CreateIndexBuffer(6 * sizeof(Int16), Usage.WriteOnly, true, Pool.Managed);
+ var indices = new ushort[CSettings.VertexBufferElements * 6];
+ for (var i = 0; i < CSettings.VertexBufferElements; i++)
+ {
+ var vertex = (ushort)(i * 4);
+ indices[i * 6] = vertex;
+ indices[i * 6 + 1] = (ushort)(vertex + 1);
+ indices[i * 6 + 2] = (ushort)(vertex + 2);
+ indices[i * 6 + 3] = vertex;
+ indices[i * 6 + 4] = (ushort)(vertex + 2);
+ indices[i * 6 + 5] = (ushort)(vertex + 3);
+ }
- var indexData = _IndexBuffer.Lock(0, 6 * sizeof(Int16), LockFlags.Discard);
- MemoryMarshal.AsBytes(indices.AsSpan()).CopyTo(indexData);
- _IndexBuffer.Unlock();
- _Device.Indices = _IndexBuffer;
+ _IndexBuffer = _Device.CreateBuffer(indices, BindFlags.IndexBuffer, ResourceUsage.Immutable, CpuAccessFlags.None, ResourceOptionFlags.None, 0, 0);
+ _ConstantBuffer = _Device.CreateBuffer((uint)Marshal.SizeOf(), BindFlags.ConstantBuffer, ResourceUsage.Default, CpuAccessFlags.None,
+ ResourceOptionFlags.None, 0);
+
+ _BlendState = _Device.CreateBlendState(new BlendDescription(Blend.SourceAlpha, Blend.InverseSourceAlpha, Blend.Zero, Blend.One));
+ _DepthStencilState = _Device.CreateDepthStencilState(new DepthStencilDescription(true, DepthWriteMask.All, ComparisonFunction.LessEqual));
+ _RasterizerState = _Device.CreateRasterizerState(new RasterizerDescription(CullMode.None, FillMode.Solid) { DepthClipEnable = true, MultisampleEnable = _SampleCount > 1 });
+ _SamplerState = _Device.CreateSamplerState(new SamplerDescription(Filter.MinMagMipLinear, TextureAddressMode.Clamp, 0, 1, ComparisonFunction.Never, 0, float.MaxValue));
+
+ try
+ {
+ _YuvVertexShader = _Device.CreateVertexShader(_CompileShader(_YuvShader, "VS", "vs_4_0"));
+ _YuvPixelShader = _Device.CreatePixelShader(_CompileShader(_YuvShader, "PS", "ps_4_0"));
+ _YuvConstantBuffer = _Device.CreateBuffer(16 * sizeof(float), BindFlags.ConstantBuffer, ResourceUsage.Default, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
+ }
+ catch (Exception e)
+ {
+ CLog.Error(e, "[Direct3D] Unable to compile the YUV shader, using CPU conversion for videos");
+ _YuvPixelShader?.Dispose();
+ _YuvPixelShader = null;
+ }
+
+ _AdjustNewBorders();
+ }
+
+ private SwapChainFlags _SwapChainFlags
+ {
+ get { return SwapChainFlags.FrameLatencyWaitableObject | (_TearingSupported ? SwapChainFlags.AllowTearing : SwapChainFlags.None); }
+ }
+
+ private static byte[] _CompileShader(string source, string entryPoint, string profile)
+ {
+ Blob code = null, errors = null;
+ try
+ {
+ var result = Compiler.Compile(source, entryPoint, "Vocaluxe", profile, out code, out errors);
+ if (result.Failure)
+ {
+ throw new Exception("Shader compilation failed: " + (errors != null ? errors.AsString() : result.ToString()));
+ }
+
+ return code.AsBytes();
+ }
+ finally
+ {
+ code?.Dispose();
+ errors?.Dispose();
+ }
+ }
+
+ private void _CreateSizeDependentResources(int width, int height)
+ {
+ width = Math.Max(1, width);
+ height = Math.Max(1, height);
+ if (_BackBuffer != null && width == _BufferWidth && height == _BufferHeight)
+ {
+ return;
+ }
+
+ _Context.OMSetRenderTargets((ID3D11RenderTargetView)null, null);
+ _DisposeSizeDependentResources();
+
+ _SwapChain.ResizeBuffers(2, (uint)width, (uint)height, _BackBufferFormat, _SwapChainFlags).CheckError();
+ _BufferWidth = width;
+ _BufferHeight = height;
+
+ _BackBuffer = _SwapChain.GetBuffer(0);
+ if (_SampleCount > 1)
+ {
+ _RenderTarget = _Device.CreateTexture2D(new Texture2DDescription(_BackBufferFormat, (uint)width, (uint)height, 1, 1, BindFlags.RenderTarget,
+ ResourceUsage.Default, CpuAccessFlags.None, _SampleCount, _SampleQuality, ResourceOptionFlags.None));
+ }
+
+ _RenderTargetView = _Device.CreateRenderTargetView(_RenderTarget ?? _BackBuffer);
+ _DepthBuffer = _Device.CreateTexture2D(new Texture2DDescription(Format.D24_UNorm_S8_UInt, (uint)width, (uint)height, 1, 1, BindFlags.DepthStencil,
+ ResourceUsage.Default, CpuAccessFlags.None, _SampleCount, _SampleQuality, ResourceOptionFlags.None));
+ _DepthStencilView = _Device.CreateDepthStencilView(_DepthBuffer);
+ _ClearScreen();
+ }
+
+ private void _DisposeSizeDependentResources()
+ {
+ _DepthStencilView?.Dispose();
+ _DepthStencilView = null;
+ _DepthBuffer?.Dispose();
+ _DepthBuffer = null;
+ _RenderTargetView?.Dispose();
+ _RenderTargetView = null;
+ _RenderTarget?.Dispose();
+ _RenderTarget = null;
+ _BackBuffer?.Dispose();
+ _BackBuffer = null;
}
protected override void _OnBeforeDraw()
{
- try
- {
- _Device.BeginScene();
- }
- catch (SharpGenException e)
- {
- CLog.Error(e, "Failed to begin scene");
- }
+ _FrameLatencyWaitHandle?.WaitOne(1000);
+ _ApplyPipeline();
+ }
+
+ private void _ApplyPipeline()
+ {
+ _Context.OMSetRenderTargets(_RenderTargetView, _DepthStencilView);
+ _Context.RSSetViewport(new Viewport(_X, _Y, _W, _H, 0f, 1f));
+ _Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
+ _Context.IASetInputLayout(_InputLayout);
+ _Context.IASetVertexBuffer(0, _VertexBuffer, (uint)Marshal.SizeOf(), 0);
+ _Context.IASetIndexBuffer(_IndexBuffer, Format.R16_UInt, 0);
+ _Context.VSSetShader(_VertexShader);
+ _Context.VSSetConstantBuffer(0, _ConstantBuffer);
+ _Context.PSSetShader(_PixelShader);
+ _Context.PSSetSampler(0, _SamplerState);
+ _Context.OMSetBlendState(_BlendState);
+ _Context.OMSetDepthStencilState(_DepthStencilState, 0);
+ _Context.RSSetState(_RasterizerState);
}
protected override void _OnAfterDraw()
{
_RenderVertexBuffer();
- try
- {
- _Device.EndScene();
- }
- catch (SharpGenException e)
- {
- CLog.Error(e, "Failed to end scene");
- }
+ _ResolveRenderTarget();
//Now push the frame to the Viewport
- var result = _Device.Present();
+ var vSync = CConfig.Config.Graphics.VSync == EOffOn.TR_CONFIG_ON;
+ var result = _SwapChain.Present(vSync ? 1u : 0u, !vSync && _TearingSupported ? PresentFlags.AllowTearing : PresentFlags.None);
if (result.Failure)
{
- //In Direct3D devices can get lost.
- //This happens for example when opening the Task Manager in Vista/Windows 7 or if a UAC message is opening
- //We need to reset the device to get it to workable state again
- //After a reset Init() needs to be called because all data in the Direct3D default pool are lost and need to be recreated
- if (_GetCooperativeLevel() == _D3DErrDeviceNotReset)
+ if (result.Code == Vortice.DXGI.ResultCode.DeviceRemoved.Code || result.Code == Vortice.DXGI.ResultCode.DeviceReset.Code)
{
- _Reset();
+ CLog.Error("The graphics device was removed: " + _Device.DeviceRemovedReason);
}
- else if (result.Code != _D3DErrDeviceLost)
+ else
{
CLog.Error("Failed to present the frame: " + result);
}
}
- Application.DoEvents();
+ _HandleEvents();
}
- private int _GetCooperativeLevel()
+ private void _ResolveRenderTarget()
{
- try
+ if (_RenderTarget != null)
{
- _Device.TestCooperativeLevel();
- return 0;
+ _Context.ResolveSubresource(_BackBuffer, 0, _RenderTarget, 0, _BackBufferFormat);
}
- catch (SharpGenException e)
- {
- return e.ResultCode.Code;
- }
- }
-
- ///
- /// Resets the device, all objects in the Direct3D default pool get flushed and need to be recreated
- ///
- private void _Reset()
- {
- //Dispose all objects in the default pool, those need to be recreated
- _DisposeDeviceResources();
- try
- {
- _Device.Reset(ref _PresentParameters);
- //All configurations got flushed due to Reset(), so apply them again
- _InitDevice();
- _Device.Viewport = new Viewport { X = _X, Y = _Y, Width = _W, Height = _H, MinZ = 0f, MaxZ = 1f };
- }
- catch (SharpGenException e)
- {
- CLog.Error(e, "Failed to reset the device");
- _DisposeDeviceResources();
- }
- }
-
- private void _DisposeDeviceResources()
- {
- STexturedColoredVertex.DisposeDeclaration();
- _VertexBuffer?.Dispose();
- _VertexBuffer = null;
- _IndexBuffer?.Dispose();
- _IndexBuffer = null;
}
protected override void _AdjustNewBorders()
{
+ if (_ConstantBuffer == null)
+ {
+ return;
+ }
+
const float dx = (float)CSettings.RenderW / 2;
const float dy = (float)CSettings.RenderH / 2;
- var translate = Matrix4x4.CreateTranslation(-dx, dy, 0);
var projection = _CreateOrthoOffCenterLH(
-dx - _BorderLeft, CSettings.RenderW * CConfig.Config.Graphics.NumScreens - dx + _BorderRight,
-dy - _BorderBottom, dy + _BorderTop,
CSettings.ZNear, CSettings.ZFar);
- _Device.SetTransform(TransformState.Projection, projection);
- _Device.SetTransform(0, translate);
+ _Context.UpdateSubresource(in projection, _ConstantBuffer);
}
private static Matrix4x4 _CreateOrthoOffCenterLH(float left, float right, float bottom, float top, float zNear, float zFar)
@@ -485,9 +587,26 @@ namespace Vocaluxe.Lib.Draw
public override void Close()
{
base.Close();
- _DisposeDeviceResources();
+ _Context?.ClearState();
+ _DisposeSizeDependentResources();
+ _YuvConstantBuffer?.Dispose();
+ _YuvPixelShader?.Dispose();
+ _YuvVertexShader?.Dispose();
+ _SamplerState?.Dispose();
+ _RasterizerState?.Dispose();
+ _DepthStencilState?.Dispose();
+ _BlendState?.Dispose();
+ _ConstantBuffer?.Dispose();
+ _IndexBuffer?.Dispose();
+ _VertexBuffer?.Dispose();
+ _InputLayout?.Dispose();
+ _PixelShader?.Dispose();
+ _VertexShader?.Dispose();
+ _FrameLatencyWaitHandle?.Dispose();
+ _SwapChain?.Dispose();
+ _Context?.Dispose();
_Device?.Dispose();
- _D3D?.Dispose();
+ _Factory?.Dispose();
}
///
@@ -496,7 +615,7 @@ namespace Vocaluxe.Lib.Draw
/// The current viewport width
public int GetScreenWidth()
{
- return _Device.Viewport.Width;
+ return _W;
}
///
@@ -505,7 +624,7 @@ namespace Vocaluxe.Lib.Draw
/// The current viewport height
public int GetScreenHeight()
{
- return _Device.Viewport.Height;
+ return _H;
}
///
@@ -517,12 +636,6 @@ namespace Vocaluxe.Lib.Draw
/// If true, then color is faded out in y direction
protected override void _DrawTexture(CD3DTexture texture, SDrawCoords dc, SColorF color, bool isReflection = false)
{
- //Align the pixels because Direct3D expects the pixels to be the left top corner
- dc.Wx1 -= 0.5f;
- dc.Wy1 -= 0.5f;
- dc.Wx2 -= 0.5f;
- dc.Wy2 -= 0.5f;
-
color.A *= CGraphics.GlobalAlpha;
var c = color.AsColor().ToArgb();
int c2;
@@ -536,12 +649,13 @@ namespace Vocaluxe.Lib.Draw
c2 = c;
}
+ var rotation = _CalculateRotationMatrix(dc.Rotation, dc.Wx1, dc.Wx2, dc.Wy1, dc.Wy2);
var vert = new STexturedColoredVertex[4];
- vert[0] = new STexturedColoredVertex(new Vector3(dc.Wx1, -dc.Wy1, dc.Wz), new Vector2(dc.Tx1, dc.Ty1), c);
- vert[1] = new STexturedColoredVertex(new Vector3(dc.Wx1, -dc.Wy2, dc.Wz), new Vector2(dc.Tx1, dc.Ty2), c2);
- vert[2] = new STexturedColoredVertex(new Vector3(dc.Wx2, -dc.Wy2, dc.Wz), new Vector2(dc.Tx2, dc.Ty2), c2);
- vert[3] = new STexturedColoredVertex(new Vector3(dc.Wx2, -dc.Wy1, dc.Wz), new Vector2(dc.Tx2, dc.Ty1), c);
- _AddToVertexBuffer(vert, texture.D3DTexture, _CalculateRotationMatrix(dc.Rotation, dc.Wx1, dc.Wx2, dc.Wy1, dc.Wy2));
+ vert[0] = new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx1, -dc.Wy1, dc.Wz), rotation), new Vector2(dc.Tx1, dc.Ty1), c);
+ vert[1] = new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx1, -dc.Wy2, dc.Wz), rotation), new Vector2(dc.Tx1, dc.Ty2), c2);
+ vert[2] = new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx2, -dc.Wy2, dc.Wz), rotation), new Vector2(dc.Tx2, dc.Ty2), c2);
+ vert[3] = new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx2, -dc.Wy1, dc.Wz), rotation), new Vector2(dc.Tx2, dc.Ty1), c);
+ _AddToVertexBuffer(vert, texture.View);
}
///
@@ -549,8 +663,7 @@ namespace Vocaluxe.Lib.Draw
///
/// A TexturedColoredVertex array containg 4 vertices
/// The texture the vertex should be textured with
- /// The vertices' rotation
- private void _AddToVertexBuffer(STexturedColoredVertex[] vertices, IDirect3DTexture9 tex, Matrix4x4 rotation)
+ private void _AddToVertexBuffer(STexturedColoredVertex[] vertices, ID3D11ShaderResourceView tex)
{
//The vertexbuffer is full, so we need to flush it before we can continue
if (_Vertices.Count >= CSettings.VertexBufferElements)
@@ -558,12 +671,8 @@ namespace Vocaluxe.Lib.Draw
_RenderVertexBuffer();
}
- _Vertices.Enqueue(vertices[0]);
- _Vertices.Enqueue(vertices[1]);
- _Vertices.Enqueue(vertices[2]);
- _Vertices.Enqueue(vertices[3]);
- _VerticesTextures.Enqueue(tex);
- _VerticesRotationMatrices.Enqueue(rotation);
+ _Vertices.AddRange(vertices);
+ _VerticesTextures.Add(tex);
}
///
@@ -581,21 +690,34 @@ namespace Vocaluxe.Lib.Draw
try
{
//The vertex buffer locks are slow actions, its better to lock once per frame and write all vertices to the buffer at once
- var vertexData = MemoryMarshal.AsBytes(_Vertices.ToArray().AsSpan());
- var bufferData = _VertexBuffer.Lock(0, vertexData.Length, LockFlags.Discard);
- vertexData.CopyTo(bufferData);
- _VertexBuffer.Unlock();
-
- for (var i = 0; i < _Vertices.Count; i += 4)
+ var mapped = _Context.Map(_VertexBuffer, MapMode.WriteDiscard, MapFlags.None);
+ try
{
- //Apply rotation
- _Device.SetTransform(0, _VerticesRotationMatrices.Dequeue());
+ var vertexData = MemoryMarshal.AsBytes(CollectionsMarshal.AsSpan(_Vertices));
+ unsafe
+ {
+ vertexData.CopyTo(new Span(mapped.DataPointer.ToPointer(), vertexData.Length));
+ }
+ }
+ finally
+ {
+ _Context.Unmap(_VertexBuffer, 0);
+ }
- //Apply texture
- _Device.SetTexture(0, _VerticesTextures.Dequeue());
+ var quads = _VerticesTextures.Count;
+ var start = 0;
+ while (start < quads)
+ {
+ var texture = _VerticesTextures[start];
+ var end = start + 1;
+ while (end < quads && _VerticesTextures[end] == texture)
+ {
+ end++;
+ }
- //Draw 2 triangles from vertexbuffer
- _Device.DrawIndexedPrimitive(PrimitiveType.TriangleList, i, 0, 4, 0, 2);
+ _Context.PSSetShaderResource(0, texture);
+ _Context.DrawIndexed((uint)(end - start) * 6, (uint)start * 6, 0);
+ start = end;
}
}
catch (SharpGenException e)
@@ -607,7 +729,6 @@ namespace Vocaluxe.Lib.Draw
//Clear the queues for the next frame
_Vertices.Clear();
_VerticesTextures.Clear();
- _VerticesRotationMatrices.Clear();
}
#endregion main stuff
@@ -616,62 +737,53 @@ namespace Vocaluxe.Lib.Draw
///
protected override void _ClearScreen()
{
- try
+ if (_RenderTargetView == null)
{
- _Device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, new Vortice.Mathematics.Color(0, 0, 0, 255), 1.0f, 0);
- }
- catch (SharpGenException e)
- {
- CLog.Error(e, "Failed to clear the backbuffer");
+ return;
}
+
+ _Context.ClearRenderTargetView(_RenderTargetView, new Color4(0f, 0f, 0f, 1f));
+ _Context.ClearDepthStencilView(_DepthStencilView, DepthStencilClearFlags.Depth, 1.0f, 0);
+ }
+
+ private ID3D11Texture2D _GetCurrentFrame()
+ {
+ _RenderVertexBuffer();
+ _ResolveRenderTarget();
+ return _BackBuffer;
}
private int[] _ReadBackBuffer(out int width, out int height)
{
- width = 0;
- height = 0;
+ width = _BufferWidth;
+ height = _BufferHeight;
try
{
- using (var backBuffer = _Device.GetBackBuffer(0, 0))
+ var frame = _GetCurrentFrame();
+ using (var staging = _Device.CreateTexture2D(new Texture2DDescription(_BackBufferFormat, (uint)width, (uint)height, 1, 1, BindFlags.None,
+ ResourceUsage.Staging, CpuAccessFlags.Read, 1, 0, ResourceOptionFlags.None)))
{
- var desc = backBuffer.Description;
- if (desc.Format != Format.X8R8G8B8 && desc.Format != Format.A8R8G8B8)
+ _Context.CopyResource(staging, frame);
+ var mapped = _Context.Map(staging, 0, MapMode.Read, MapFlags.None);
+ var pixels = new int[width * height];
+ try
{
- CLog.Error("Reading the backbuffer is not supported for format " + desc.Format);
- return null;
+ for (var y = 0; y < height; y++)
+ {
+ Marshal.Copy(IntPtr.Add(mapped.DataPointer, y * (int)mapped.RowPitch), pixels, y * width, width);
+ }
+ }
+ finally
+ {
+ _Context.Unmap(staging, 0);
}
- width = desc.Width;
- height = desc.Height;
- var rect = new Rect(0, 0, width, height);
-
- using (var renderTarget = _Device.CreateRenderTarget(width, height, desc.Format, MultisampleType.None, 0, false))
- using (var sysMemSurface = _Device.CreateOffscreenPlainSurface(width, height, desc.Format, Pool.SystemMemory))
+ for (var i = 0; i < pixels.Length; i++)
{
- _Device.StretchRect(backBuffer, rect, renderTarget, rect, TextureFilter.None);
- _Device.GetRenderTargetData(renderTarget, sysMemSurface);
-
- var pixels = new int[width * height];
- var locked = sysMemSurface.LockRect(LockFlags.ReadOnly);
- try
- {
- for (var y = 0; y < height; y++)
- {
- Marshal.Copy(IntPtr.Add(locked.DataPointer, y * locked.Pitch), pixels, y * width, width);
- }
- }
- finally
- {
- sysMemSurface.UnlockRect();
- }
-
- for (var i = 0; i < pixels.Length; i++)
- {
- pixels[i] |= unchecked((int)0xFF000000);
- }
-
- return pixels;
+ pixels[i] |= unchecked((int)0xFF000000);
}
+
+ return pixels;
}
}
catch (SharpGenException e)
@@ -683,27 +795,25 @@ namespace Vocaluxe.Lib.Draw
private void _CopyBackBufferToTexture(CD3DTexture texture)
{
- int bufferWidth, bufferHeight;
- var pixels = _ReadBackBuffer(out bufferWidth, out bufferHeight);
- if (pixels == null)
- {
- return;
- }
-
- var width = Math.Min(Math.Min(_W, bufferWidth), texture.Size.Width);
- var height = Math.Min(Math.Min(_H, bufferHeight), texture.Size.Height);
-
- var locked = texture.D3DTexture.LockRect(0, LockFlags.Discard);
try
{
- for (var y = 0; y < height; y++)
+ var frame = _GetCurrentFrame();
+ var width = Math.Min(Math.Min(_W, _BufferWidth - _X), texture.Size.Width);
+ var height = Math.Min(Math.Min(_H, _BufferHeight - _Y), texture.Size.Height);
+ if (width <= 0 || height <= 0)
{
- Marshal.Copy(pixels, y * bufferWidth, IntPtr.Add(locked.DataPointer, y * locked.Pitch), width);
+ return;
+ }
+
+ _Context.CopySubresourceRegion(texture.D3DTexture, 0, 0, 0, 0, frame, 0, new Box(_X, _Y, 0, _X + width, _Y + height, 1));
+ if (texture.HasMips)
+ {
+ _Context.GenerateMips(texture.View);
}
}
- finally
+ catch (SharpGenException e)
{
- texture.D3DTexture.UnlockRect(0);
+ CLog.Error(e, "Failed to copy the backbuffer");
}
}
@@ -751,22 +861,17 @@ namespace Vocaluxe.Lib.Draw
return;
}
- using (var screen = new Bitmap(width, height, PixelFormat.Format32bppArgb))
+ using (var screen = new SKBitmap(new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Opaque)))
{
- var bmpData = screen.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
- try
+ for (var y = 0; y < height; y++)
{
- for (var y = 0; y < height; y++)
- {
- Marshal.Copy(pixels, y * width, IntPtr.Add(bmpData.Scan0, y * bmpData.Stride), width);
- }
- }
- finally
- {
- screen.UnlockBits(bmpData);
+ Marshal.Copy(pixels, y * width, IntPtr.Add(screen.GetPixels(), y * screen.RowBytes), width);
}
- screen.Save(file, ImageFormat.Png);
+ using (var stream = File.Create(file))
+ {
+ screen.Encode(stream, SKEncodedImageFormat.Png, 100);
+ }
}
}
@@ -802,23 +907,112 @@ namespace Vocaluxe.Lib.Draw
return new CD3DTexture(_Device, dataSize, _CheckForNextPowerOf2(dataSize.Width), _CheckForNextPowerOf2(dataSize.Height));
}
- protected override void _WriteDataToTexture(CD3DTexture texture, byte[] data)
+ public override bool SupportsYuvVideo
{
- var rect = texture.D3DTexture.LockRect(0, LockFlags.Discard);
- try
+ get { return _YuvPixelShader != null; }
+ }
+
+ protected override void _WriteYuvDataToTexture(CD3DTexture texture, byte[] data, float[] yuvToRgbRows)
+ {
+ if (!texture.IsLoaded)
{
- var rowWidth = 4 * texture.DataSize.Width;
- var dst = rect.DataPointer;
- if (rowWidth == rect.Pitch)
- Marshal.Copy(data, 0, dst, data.Length);
- else
+ return;
+ }
+
+ var width = texture.DataSize.Width;
+ var height = texture.DataSize.Height;
+ var planeWidths = new[] { width, (width + 1) / 2, (width + 1) / 2 };
+ var planeHeights = new[] { height, (height + 1) / 2, (height + 1) / 2 };
+ if (texture.Planes == null)
+ {
+ texture.Planes = new ID3D11Texture2D[3];
+ texture.PlaneViews = new ID3D11ShaderResourceView[3];
+ for (var i = 0; i < 3; i++)
{
- var rows = data.Length / rowWidth;
- for (var y = 0; y < rows; y++)
- Marshal.Copy(data, y * rowWidth, IntPtr.Add(dst, y * rect.Pitch), rowWidth);
+ texture.Planes[i] = _Device.CreateTexture2D(new Texture2DDescription(Format.R8_UNorm, (uint)planeWidths[i], (uint)planeHeights[i], 1, 1,
+ BindFlags.ShaderResource, ResourceUsage.Default, CpuAccessFlags.None, 1, 0, ResourceOptionFlags.None));
+ texture.PlaneViews[i] = _Device.CreateShaderResourceView(texture.Planes[i]);
+ }
+
+ texture.TargetView = _Device.CreateRenderTargetView(texture.D3DTexture,
+ new RenderTargetViewDescription(RenderTargetViewDimension.Texture2D, Format.B8G8R8A8_UNorm, 0));
+ }
+
+ fixed (byte* source = data)
+ {
+ var offset = 0;
+ for (var i = 0; i < 3; i++)
+ {
+ _Context.UpdateSubresource(texture.Planes[i], 0, null, (IntPtr)(source + offset), (uint)planeWidths[i], 0);
+ offset += planeWidths[i] * planeHeights[i];
}
}
- finally { texture.D3DTexture.UnlockRect(0); }
+
+ var constants = new float[16];
+ Array.Copy(yuvToRgbRows, constants, 12);
+ constants[12] = width;
+ constants[13] = height;
+ _Context.UpdateSubresource(constants, _YuvConstantBuffer);
+
+ _Context.OMSetRenderTargets(texture.TargetView, null);
+ _Context.RSSetViewport(new Viewport(0, 0, width, height, 0f, 1f));
+ _Context.IASetInputLayout(null);
+ _Context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
+ _Context.VSSetShader(_YuvVertexShader);
+ _Context.PSSetShader(_YuvPixelShader);
+ _Context.PSSetConstantBuffer(0, _YuvConstantBuffer);
+ _Context.PSSetShaderResources(0, texture.PlaneViews);
+ _Context.PSSetSampler(0, _SamplerState);
+ _Context.OMSetBlendState(null);
+ _Context.OMSetDepthStencilState(null, 0);
+ _Context.Draw(3, 0);
+ _Context.PSSetShaderResources(0, new ID3D11ShaderResourceView[3]);
+ if (texture.HasMips)
+ {
+ _Context.GenerateMips(texture.View);
+ }
+
+ _ApplyPipeline();
+ }
+
+ protected override void _WriteDataToTexture(CD3DTexture texture, byte[] data)
+ {
+ var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
+ try
+ {
+ _WriteDataToTexture(texture, handle.AddrOfPinnedObject(), data.Length);
+ }
+ finally
+ {
+ handle.Free();
+ }
+ }
+
+ protected override void _WriteDataToTexture(CD3DTexture texture, IntPtr data)
+ {
+ _WriteDataToTexture(texture, data, 4 * texture.DataSize.Width * texture.DataSize.Height);
+ }
+
+ private void _WriteDataToTexture(CD3DTexture texture, IntPtr data, int length)
+ {
+ if (!texture.IsLoaded)
+ {
+ return;
+ }
+
+ var rowWidth = 4 * texture.DataSize.Width;
+ var width = Math.Min(texture.DataSize.Width, texture.Size.Width);
+ var height = Math.Min(length / rowWidth, texture.Size.Height);
+ if (width <= 0 || height <= 0)
+ {
+ return;
+ }
+
+ _Context.UpdateSubresource(texture.D3DTexture, 0, new Box(0, 0, 0, width, height, 1), data, (uint)rowWidth, 0);
+ if (texture.HasMips)
+ {
+ _Context.GenerateMips(texture.View);
+ }
}
private static Matrix4x4 _CalculateRotationMatrix(float rot, float rx1, float rx2, float ry1, float ry2)
@@ -845,17 +1039,9 @@ namespace Vocaluxe.Lib.Draw
return originTranslation;
}
+ [StructLayout(LayoutKind.Sequential)]
private struct STexturedColoredVertex
{
- private static IDirect3DVertexDeclaration9 _Declaration;
- private static readonly VertexElement[] _Elements =
- {
- new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
- new VertexElement(0, sizeof(float) * 3, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
- new VertexElement(0, sizeof(float) * 3 + sizeof(float) * 2, DeclarationType.Color, DeclarationMethod.Default, DeclarationUsage.Color, 0),
- VertexElement.VertexDeclarationEnd
- };
-
// ReSharper disable NotAccessedField.Local
private Vector3 _Position;
private Vector2 _Texture;
@@ -868,25 +1054,6 @@ namespace Vocaluxe.Lib.Draw
_Texture = texture;
_Color = color;
}
-
- public static IDirect3DVertexDeclaration9 GetDeclaration(IDirect3DDevice9 device)
- {
- if (_Declaration == null)
- {
- _Declaration = device.CreateVertexDeclaration(_Elements);
- }
-
- return _Declaration;
- }
-
- public static void DisposeDeclaration()
- {
- if (_Declaration != null)
- {
- _Declaration.Dispose();
- _Declaration = null;
- }
- }
}
}
}
diff --git a/Vocaluxe/Lib/Draw/CDrawBaseSDL.cs b/Vocaluxe/Lib/Draw/CDrawBaseSDL.cs
new file mode 100644
index 00000000..4e065a56
--- /dev/null
+++ b/Vocaluxe/Lib/Draw/CDrawBaseSDL.cs
@@ -0,0 +1,254 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using SDL;
+using Vocaluxe.Base;
+using VocaluxeLib;
+using VocaluxeLib.Log;
+
+namespace Vocaluxe.Lib.Draw
+{
+ abstract unsafe class CDrawBaseSDL : CDrawBase where TTextureType : CTextureBase, IDisposable
+ {
+ protected SDL_Window* _Window;
+
+ protected bool _CreateWindow(SDL_WindowFlags flags)
+ {
+ _Window = SDL3.SDL_CreateWindow(CSettings.GetFullVersionText(), CConfig.Config.Graphics.ScreenW * CConfig.Config.Graphics.NumScreens, CConfig.Config.Graphics.ScreenH,
+ flags | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_HIDDEN | SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY);
+ if (_Window == null)
+ {
+ CLog.Error("Failed to create the window: " + SDL3.SDL_GetError());
+ return false;
+ }
+
+ CSdl.MainWindow = _Window;
+ return true;
+ }
+
+ protected Size _GetClientSize()
+ {
+ int w, h;
+ if (_Window == null || !SDL3.SDL_GetWindowSizeInPixels(_Window, &w, &h))
+ {
+ return Size.Empty;
+ }
+
+ return new Size(w, h);
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ if (_Window != null)
+ {
+ if (CSdl.MainWindow == _Window)
+ {
+ CSdl.MainWindow = null;
+ }
+
+ SDL3.SDL_DestroyWindow(_Window);
+ _Window = null;
+ }
+ }
+
+ protected override void _EnterFullScreen()
+ {
+ Debug.Assert(!_Fullscreen);
+ _Fullscreen = true;
+
+ SDL3.SDL_SetWindowFullscreen(_Window, true);
+ SDL3.SDL_SyncWindow(_Window);
+ _DoResize();
+ }
+
+ protected override void _LeaveFullScreen()
+ {
+ Debug.Assert(_Fullscreen);
+ _Fullscreen = false;
+
+ SDL3.SDL_SetWindowFullscreen(_Window, false);
+ SDL3.SDL_SyncWindow(_Window);
+ _DoResize();
+ }
+
+ #region event handlers
+ protected void _HandleEvents()
+ {
+ SDL_Event e;
+ while (SDL3.SDL_PollEvent(&e))
+ {
+ switch (e.Type)
+ {
+ case SDL_EventType.SDL_EVENT_QUIT:
+ case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+ _Run = false;
+ break;
+ case SDL_EventType.SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
+ _DoResize();
+ break;
+ case SDL_EventType.SDL_EVENT_WINDOW_MOUSE_ENTER:
+ SDL3.SDL_HideCursor();
+ _Mouse.Visible = true;
+ break;
+ case SDL_EventType.SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ _Mouse.Visible = false;
+ SDL3.SDL_ShowCursor();
+ break;
+ case SDL_EventType.SDL_EVENT_KEY_DOWN:
+ _OnKeyDown(e.key);
+ break;
+ case SDL_EventType.SDL_EVENT_KEY_UP:
+ _Keys.KeyUp(CSdl.GetModifiers(e.key.mod));
+ break;
+ case SDL_EventType.SDL_EVENT_TEXT_INPUT:
+ var text = e.text.GetText();
+ if (!string.IsNullOrEmpty(text))
+ {
+ var modifiers = CSdl.GetModifiers();
+ foreach (var chr in text)
+ {
+ _Keys.KeyPress(chr, modifiers);
+ }
+ }
+
+ break;
+ case SDL_EventType.SDL_EVENT_MOUSE_MOTION:
+ _Mouse.MouseMove(_ToPixelX(e.motion.x), _ToPixelY(e.motion.y), _GetHeldButton(e.motion.state), CSdl.GetModifiers());
+ break;
+ case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN:
+ _Mouse.MouseDown(_ToPixelX(e.button.x), _ToPixelY(e.button.y), _GetButton(e.button.Button), CSdl.GetModifiers());
+ break;
+ case SDL_EventType.SDL_EVENT_MOUSE_WHEEL:
+ var delta = e.wheel.direction == SDL_MouseWheelDirection.SDL_MOUSEWHEEL_FLIPPED ? -e.wheel.y : e.wheel.y;
+ _Mouse.MouseWheel(_ToPixelX(e.wheel.mouse_x), _ToPixelY(e.wheel.mouse_y), (int)Math.Round(delta * 120), CSdl.GetModifiers());
+ break;
+ }
+ }
+ }
+
+ private void _OnKeyDown(SDL_KeyboardEvent key)
+ {
+ var keyCode = CSdl.GetKey(key.key);
+ var modifiers = CSdl.GetModifiers(key.mod);
+ if (keyCode == Keys.None)
+ {
+ return;
+ }
+
+ _Keys.KeyDown(keyCode, modifiers);
+
+ switch (keyCode)
+ {
+ case Keys.Enter:
+ _Keys.KeyPress('\r', modifiers);
+ break;
+ case Keys.Back:
+ _Keys.KeyPress('\b', modifiers);
+ break;
+ case Keys.Escape:
+ _Keys.KeyPress((char)27, modifiers);
+ break;
+ case Keys.Tab:
+ _Keys.KeyPress('\t', modifiers);
+ break;
+ }
+ }
+
+ private int _ToPixelX(float x)
+ {
+ int w, h;
+ SDL3.SDL_GetWindowSize(_Window, &w, &h);
+ return w > 0 ? (int)(x * _GetClientSize().Width / w) : (int)x;
+ }
+
+ private int _ToPixelY(float y)
+ {
+ int w, h;
+ SDL3.SDL_GetWindowSize(_Window, &w, &h);
+ return h > 0 ? (int)(y * _GetClientSize().Height / h) : (int)y;
+ }
+
+ private static EMouseButton _GetButton(SDLButton button)
+ {
+ switch (button)
+ {
+ case SDLButton.SDL_BUTTON_LEFT:
+ return EMouseButton.Left;
+ case SDLButton.SDL_BUTTON_RIGHT:
+ return EMouseButton.Right;
+ case SDLButton.SDL_BUTTON_MIDDLE:
+ return EMouseButton.Middle;
+ }
+
+ return EMouseButton.None;
+ }
+
+ private static EMouseButton _GetHeldButton(SDL_MouseButtonFlags state)
+ {
+ if ((state & SDL_MouseButtonFlags.SDL_BUTTON_LMASK) != 0)
+ {
+ return EMouseButton.Left;
+ }
+
+ if ((state & SDL_MouseButtonFlags.SDL_BUTTON_RMASK) != 0)
+ {
+ return EMouseButton.Right;
+ }
+
+ if ((state & SDL_MouseButtonFlags.SDL_BUTTON_MMASK) != 0)
+ {
+ return EMouseButton.Middle;
+ }
+
+ return EMouseButton.None;
+ }
+ #endregion
+
+ public override bool Init()
+ {
+ if (!base.Init())
+ {
+ return false;
+ }
+
+ if (_Window == null)
+ {
+ return false;
+ }
+
+ CSdl.SetWindowIcon(_Window, Path.Combine(CSettings.ProgramFolder, CSettings.FileNameIcon));
+ SDL3.SDL_SetWindowPosition(_Window, (int)SDL3.SDL_WINDOWPOS_CENTERED, (int)SDL3.SDL_WINDOWPOS_CENTERED);
+ SDL3.SDL_StartTextInput(_Window);
+
+ return true;
+ }
+
+ public override void MainLoop()
+ {
+ SDL3.SDL_ShowWindow(_Window);
+ SDL3.SDL_RaiseWindow(_Window);
+ _ClearScreen();
+ base.MainLoop();
+ SDL3.SDL_HideWindow(_Window);
+ }
+ }
+}
diff --git a/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs b/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs
deleted file mode 100644
index eee882e6..00000000
--- a/Vocaluxe/Lib/Draw/CDrawBaseWindows.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-#region license
-// This file is part of Vocaluxe.
-//
-// Vocaluxe is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Vocaluxe is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Vocaluxe. If not, see .
-#endregion
-
-using System;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Drawing;
-using System.IO;
-using System.Windows.Forms;
-using Vocaluxe.Base;
-
-namespace Vocaluxe.Lib.Draw
-{
- public delegate bool MessageEventHandler(ref Message m);
-
- public interface IFormHook
- {
- MessageEventHandler OnMessage { set; }
- }
-
- abstract class CDrawBaseWindows : CDrawBase where TTextureType : CTextureBase, IDisposable
- {
- private struct SClientRect
- {
- public Point Location;
- public int Width;
- public int Height;
- }
-
- protected Form _Form;
- private SClientRect _Restore;
- protected Size _SizeBeforeMinimize;
-
- public override void Close()
- {
- base.Close();
- try
- {
- _Form.Close();
- }
- catch { }
- }
-
- protected void _CenterToScreen()
- {
- var screen = Screen.FromControl(_Form);
- _Form.Location = new Point((screen.WorkingArea.Width - _Form.Width) / 2,
- (screen.WorkingArea.Height - _Form.Height) / 2);
- }
-
- private static bool _OnMessageAvoidScreenOff(ref Message m)
- {
- switch (m.Msg)
- {
- case 0x112: // WM_SYSCOMMAND
- switch ((int)m.WParam & 0xFFF0)
- {
- case 0xF100: // SC_KEYMENU
- m.Result = IntPtr.Zero;
- return false;
- case 0xF140: // SC_SCREENSAVER
- case 0xF170: // SC_MONITORPOWER
- return false;
- }
-
- break;
- }
-
- return true;
- }
-
- protected override void _EnterFullScreen()
- {
- Debug.Assert(!_Fullscreen);
- _Fullscreen = true;
-
- _Restore.Location = _Form.Location;
- _Restore.Width = _Form.Width;
- _Restore.Height = _Form.Height;
-
- _Form.FormBorderStyle = FormBorderStyle.None;
-
- var screen = Screen.FromControl(_Form);
- _Form.DesktopBounds = new Rectangle(screen.Bounds.Location, new Size(screen.Bounds.Width, screen.Bounds.Height));
-
- if (_Form.WindowState == FormWindowState.Maximized)
- {
- _Form.WindowState = FormWindowState.Normal;
- _DoResize();
- _Form.WindowState = FormWindowState.Maximized;
- }
- else
- {
- _DoResize();
- }
- }
-
- protected override void _LeaveFullScreen()
- {
- Debug.Assert(_Fullscreen);
- _Fullscreen = false;
-
- _Form.FormBorderStyle = FormBorderStyle.Sizable;
- _Form.DesktopBounds = new Rectangle(_Restore.Location, new Size(_Restore.Width, _Restore.Height));
- }
-
- #region form event handlers
- private void _OnClose(object sender, CancelEventArgs e)
- {
- _Run = false;
- }
-
- private void _OnLoad(object sender, EventArgs e)
- {
- _ClearScreen();
- }
-
- protected virtual void _OnResize(object sender, EventArgs e)
- {
- _DoResize();
- }
-
- #region mouse event handlers
- protected void _OnMouseMove(object sender, MouseEventArgs e)
- {
- _Mouse.MouseMove(e);
- }
-
- protected void _OnMouseWheel(object sender, MouseEventArgs e)
- {
- _Mouse.MouseWheel(e);
- }
-
- protected void _OnMouseDown(object sender, MouseEventArgs e)
- {
- _Mouse.MouseDown(e);
- }
-
- protected void _OnMouseUp(object sender, MouseEventArgs e)
- {
- _Mouse.MouseUp(e);
- }
-
- protected void _OnMouseLeave(object sender, EventArgs e)
- {
- _Mouse.Visible = false;
-#if !WIN && !DEBUG
- _Form.Cursor = Cursors.Default;
-#endif
- Cursor.Show();
- }
-
- protected void _OnMouseEnter(object sender, EventArgs e)
- {
- Cursor.Hide();
- _Mouse.Visible = true;
-#if !WIN && !DEBUG //don't want to be stuck without a cursor when debugging
- _Form.Cursor = new Cursor("Linux/blank.cur"); //Cursor.Hide() doesn't work in Mono
-#endif
- }
- #endregion
-
- #region keyboard event handlers
- protected void _OnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
- {
- _OnKeyDown(sender, new KeyEventArgs(e.KeyData));
- }
-
- protected void _OnKeyDown(object sender, KeyEventArgs e)
- {
- _Keys.KeyDown(e);
- }
-
- protected void _OnKeyPress(object sender, KeyPressEventArgs e)
- {
- _Keys.KeyPress(e);
- }
-
- protected void _OnKeyUp(object sender, KeyEventArgs e)
- {
- _Keys.KeyUp(e);
- }
- #endregion keyboard event handlers
- #endregion
-
- public override bool Init()
- {
- if (!base.Init())
- {
- return false;
- }
-
- _Form.Icon = new Icon(Path.Combine(CSettings.ProgramFolder, CSettings.FileNameIcon));
- _Form.Text = CSettings.GetFullVersionText();
- ((IFormHook)_Form).OnMessage = _OnMessageAvoidScreenOff;
- _Form.Closing += _OnClose;
- _Form.Resize += _OnResize;
- _Form.Load += _OnLoad;
-
- _SizeBeforeMinimize = _Form.ClientSize;
- _CenterToScreen();
-
- return true;
- }
-
- public override void MainLoop()
- {
- _Form.Show();
- base.MainLoop();
- _Form.Hide();
- }
- }
-}
\ No newline at end of file
diff --git a/Vocaluxe/Lib/Draw/CDrawSoftware.cs b/Vocaluxe/Lib/Draw/CDrawSoftware.cs
new file mode 100644
index 00000000..8a13b273
--- /dev/null
+++ b/Vocaluxe/Lib/Draw/CDrawSoftware.cs
@@ -0,0 +1,275 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Drawing;
+using System.IO;
+using SDL;
+using SkiaSharp;
+using Vocaluxe.Base;
+using VocaluxeLib;
+using VocaluxeLib.Draw;
+
+namespace Vocaluxe.Lib.Draw
+{
+ class CSoftwareTexture : CTextureBase
+ {
+ public readonly SKBitmap Bitmap;
+ public readonly SKImage Image;
+
+ public CSoftwareTexture(Size dataSize) : base(dataSize, dataSize)
+ {
+ if (dataSize.Width <= 0 || dataSize.Height <= 0)
+ {
+ return;
+ }
+
+ Bitmap = new SKBitmap(new SKImageInfo(dataSize.Width, dataSize.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul));
+ Image = SKImage.FromPixels(Bitmap.PeekPixels());
+ }
+
+ public override bool IsLoaded
+ {
+ get { return Bitmap != null; }
+ }
+
+ public override void Dispose()
+ {
+ if (IsDisposed)
+ {
+ return;
+ }
+
+ base.Dispose();
+ Image?.Dispose();
+ Bitmap?.Dispose();
+ }
+ }
+
+ unsafe class CDrawSoftware : CDrawBaseSDL, IDraw
+ {
+ private readonly SKBitmap _Backbuffer;
+ private readonly SKCanvas _Canvas;
+ private readonly SKColor _ClearColor = new SKColor(0, 0, 139);
+ private CTextureRef _BlankTexture;
+
+ public CDrawSoftware()
+ {
+ _CreateWindow(0);
+ _Backbuffer = new SKBitmap(CSettings.RenderW * CConfig.Config.Graphics.NumScreens, CSettings.RenderH, SKColorType.Bgra8888, SKAlphaType.Premul);
+ _Canvas = new SKCanvas(_Backbuffer);
+ _Canvas.Clear(_ClearColor);
+ _NonPowerOf2TextureSupported = true;
+ }
+
+ public override bool Init()
+ {
+ if (!base.Init())
+ {
+ return false;
+ }
+
+ _BlankTexture = AddTexture(1, 1, new byte[] { 255, 255, 255, 255 });
+ return true;
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ _Canvas.Dispose();
+ _Backbuffer.Dispose();
+ }
+
+ protected override void _DoResize()
+ {
+ var clientSize = _GetClientSize();
+ if (clientSize.Width == 0 || clientSize.Height == 0)
+ {
+ return;
+ }
+
+ _CurrentAlignment = CConfig.Config.Graphics.ScreenAlignment;
+ _W = clientSize.Width;
+ _H = clientSize.Height;
+ _X = 0;
+ _Y = 0;
+ _AdjustAspect(false);
+ }
+
+ protected override void _AdjustNewBorders() { }
+
+ protected override void _OnBeforeDraw() { }
+
+ protected override void _OnAfterDraw()
+ {
+ var surface = SDL3.SDL_GetWindowSurface(_Window);
+ if (surface != null)
+ {
+ var colorType = surface->format == SDL_PixelFormat.SDL_PIXELFORMAT_ABGR8888 || surface->format == SDL_PixelFormat.SDL_PIXELFORMAT_XBGR8888
+ ? SKColorType.Rgba8888 : SKColorType.Bgra8888;
+ var info = new SKImageInfo(surface->w, surface->h, colorType, SKAlphaType.Premul);
+ using (var windowSurface = SKSurface.Create(info, surface->pixels, surface->pitch))
+ using (var image = SKImage.FromBitmap(_Backbuffer))
+ {
+ if (windowSurface != null)
+ {
+ windowSurface.Canvas.Clear(SKColors.Black);
+ windowSurface.Canvas.DrawImage(image, SKRect.Create(_X, _Y, _W, _H), new SKSamplingOptions(SKFilterMode.Linear));
+ windowSurface.Canvas.Flush();
+ }
+ }
+
+ SDL3.SDL_UpdateWindowSurface(_Window);
+ }
+
+ _HandleEvents();
+ }
+
+ protected override void _ClearScreen()
+ {
+ _Canvas.Clear(_ClearColor);
+ }
+
+ public int GetScreenWidth()
+ {
+ return _W;
+ }
+
+ public int GetScreenHeight()
+ {
+ return _H;
+ }
+
+ public CTextureRef CopyScreen()
+ {
+ var texture = _CreateTexture(new Size(_Backbuffer.Width, _Backbuffer.Height));
+ _Backbuffer.CopyPixelsTo(texture.Bitmap);
+ return _GetTextureReference(_Backbuffer.Width, _Backbuffer.Height, texture);
+ }
+
+ public void CopyScreen(ref CTextureRef texture)
+ {
+ CSoftwareTexture softwareTexture;
+ if (!_GetTexture(texture, out softwareTexture) || softwareTexture.DataSize.Width != _Backbuffer.Width || softwareTexture.DataSize.Height != _Backbuffer.Height)
+ {
+ RemoveTexture(ref texture);
+ texture = CopyScreen();
+ }
+ else
+ {
+ _Backbuffer.CopyPixelsTo(softwareTexture.Bitmap);
+ }
+ }
+
+ public void MakeScreenShot()
+ {
+ var file = CHelper.GetUniqueFileName(Path.Combine(CSettings.DataFolder, CSettings.FolderNameScreenshots), "Screenshot.png");
+ using (var stream = File.Create(file))
+ {
+ _Backbuffer.Encode(stream, SKEncodedImageFormat.Png, 100);
+ }
+ }
+
+ public void DrawRect(SColorF color, SRectF rect, bool allMonitors = true)
+ {
+ DrawTexture(_BlankTexture, rect, color, false, allMonitors);
+ }
+
+ public void DrawRectReflection(SColorF color, SRectF rect, float space, float height)
+ {
+ DrawTextureReflection(_BlankTexture, rect, color, rect, space, height);
+ }
+
+ protected override CSoftwareTexture _CreateTexture(Size dataSize)
+ {
+ return new CSoftwareTexture(dataSize);
+ }
+
+ protected override void _WriteDataToTexture(CSoftwareTexture texture, byte[] data)
+ {
+ if (!texture.IsLoaded)
+ {
+ return;
+ }
+
+ var length = Math.Min(data.Length, texture.Bitmap.ByteCount);
+ System.Runtime.InteropServices.Marshal.Copy(data, 0, texture.Bitmap.GetPixels(), length);
+ texture.Bitmap.NotifyPixelsChanged();
+ }
+
+ protected override void _DrawTexture(CSoftwareTexture texture, SDrawCoords dc, SColorF color, bool isReflection = false)
+ {
+ if (!texture.IsLoaded)
+ {
+ return;
+ }
+
+ var alpha = color.A * CGraphics.GlobalAlpha;
+ var width = texture.Bitmap.Width;
+ var height = texture.Bitmap.Height;
+ var source = new SKRect(Math.Min(dc.Tx1, dc.Tx2) * width, Math.Min(dc.Ty1, dc.Ty2) * height, Math.Max(dc.Tx1, dc.Tx2) * width,
+ Math.Max(dc.Ty1, dc.Ty2) * height);
+ var destination = new SKRect(dc.Wx1, dc.Wy1, dc.Wx2, dc.Wy2);
+ if (source.Width <= 0 || source.Height <= 0 || destination.Width <= 0 || destination.Height <= 0)
+ {
+ return;
+ }
+
+ var matrix = new[]
+ {
+ color.R, 0, 0, 0, 0,
+ 0, color.G, 0, 0, 0,
+ 0, 0, color.B, 0, 0,
+ 0, 0, 0, alpha, 0
+ };
+
+ _Canvas.Save();
+ if (Math.Abs(dc.Rotation) > float.Epsilon)
+ {
+ _Canvas.RotateDegrees(dc.Rotation, destination.MidX, destination.MidY);
+ }
+
+ if (dc.Ty1 > dc.Ty2)
+ {
+ _Canvas.Scale(1, -1, destination.MidX, destination.MidY);
+ }
+
+ using (var filter = SKColorFilter.CreateColorMatrix(matrix))
+ using (var paint = new SKPaint { ColorFilter = filter, IsAntialias = true })
+ {
+ if (isReflection)
+ {
+ using (var mask = SKShader.CreateLinearGradient(new SKPoint(0, dc.Ty1 > dc.Ty2 ? destination.Bottom : destination.Top),
+ new SKPoint(0, dc.Ty1 > dc.Ty2 ? destination.Top : destination.Bottom), new[] { SKColors.White, SKColors.Transparent }, SKShaderTileMode.Clamp))
+ using (var maskPaint = new SKPaint { Shader = mask, BlendMode = SKBlendMode.DstIn })
+ {
+ _Canvas.SaveLayer(destination, null);
+ _Canvas.DrawImage(texture.Image, source, destination, new SKSamplingOptions(SKFilterMode.Linear), paint);
+ _Canvas.DrawRect(destination, maskPaint);
+ _Canvas.Restore();
+ }
+ }
+ else
+ {
+ _Canvas.DrawImage(texture.Image, source, destination, new SKSamplingOptions(SKFilterMode.Linear), paint);
+ }
+ }
+
+ _Canvas.Restore();
+ }
+ }
+}
diff --git a/Vocaluxe/Lib/Draw/CDrawWinForm.cs b/Vocaluxe/Lib/Draw/CDrawWinForm.cs
deleted file mode 100644
index f8066774..00000000
--- a/Vocaluxe/Lib/Draw/CDrawWinForm.cs
+++ /dev/null
@@ -1,497 +0,0 @@
-#region license
-// This file is part of Vocaluxe.
-//
-// Vocaluxe is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Vocaluxe is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Vocaluxe. If not, see .
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Windows.Forms;
-using Vocaluxe.Base;
-using VocaluxeLib;
-using VocaluxeLib.Draw;
-
-namespace Vocaluxe.Lib.Draw
-{
- class CDrawWinForm : Form, IDraw
- {
- private bool _Run;
- private readonly Bitmap _Backbuffer;
- private readonly Graphics _G;
- private bool _Fullscreen;
-
- private readonly CKeys _Keys;
- private readonly CMouse _Mouse;
-
- private FormBorderStyle _BrdStyle;
- private bool _TopMost;
- private Rectangle _Bounds;
-
- private readonly Dictionary _Textures = new Dictionary();
- private readonly Dictionary _TextureCache = new Dictionary();
- private readonly List _Bitmaps = new List();
-
- private readonly Color _ClearColor = Color.DarkBlue;
-
- public CDrawWinForm()
- {
- Icon = new Icon(Path.Combine(CSettings.ProgramFolder, CSettings.FileNameIcon));
-
- _Keys = new CKeys();
- _Mouse = new CMouse();
- ClientSize = new Size(1280, 720);
-
- SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.Opaque, true);
-
- // Create the backbuffer
- _Backbuffer = new Bitmap(CSettings.RenderW, CSettings.RenderH);
- _G = Graphics.FromImage(_Backbuffer);
- _G.Clear(Color.DarkBlue);
-
- Closing += _OnClosingEvent;
- KeyDown += _OnKeyDownEvent;
- KeyPress += _OnKeyPressEvent;
- KeyUp += _OnKeyUpEvent;
- Resize += _OnResizeEvent;
-
- MouseMove += _OnMouseMove;
- MouseDown += _OnMouseDown;
- MouseUp += _OnMouseUp;
-
- _FlipBuffer();
- Cursor.Show();
- }
-
- private void _FlipBuffer()
- {
- _DrawBuffer();
- _G.Clear(_ClearColor);
- }
-
- private void _DrawBuffer()
- {
- var frontBuffer = Graphics.FromHwnd(Handle);
- var h = ClientSize.Height;
- var w = ClientSize.Width;
- var y = 0;
- var x = 0;
-
- if (ClientSize.Width / (float)ClientSize.Height > CSettings.GetRenderAspect())
- {
- w = (int)Math.Round(ClientSize.Height * CSettings.GetRenderAspect());
- x = (ClientSize.Width - w) / 2;
- }
- else
- {
- h = (int)Math.Round(ClientSize.Width / CSettings.GetRenderAspect());
- y = (ClientSize.Height - h) / 2;
- }
-
- frontBuffer.DrawImage(_Backbuffer, new Rectangle(x, y, w, h), new Rectangle(0, 0, _Backbuffer.Width, _Backbuffer.Height), GraphicsUnit.Pixel);
- }
-
- #region FullScreenStuff
- private void _ToggleFullScreen()
- {
- if (!_Fullscreen)
- {
- _Maximize(this);
- }
- else
- {
- _Restore(this);
- }
- }
-
- private void _Maximize(Form targetForm)
- {
- if (!_Fullscreen)
- {
- _Save(targetForm);
- targetForm.FormBorderStyle = FormBorderStyle.None;
- targetForm.Bounds = Screen.PrimaryScreen.Bounds;
- targetForm.TopMost = true;
- _Fullscreen = true;
- }
- }
-
- private void _Save(Form targetForm)
- {
- _BrdStyle = targetForm.FormBorderStyle;
- _TopMost = targetForm.TopMost;
- _Bounds = targetForm.Bounds;
- }
-
- private void _Restore(Form targetForm)
- {
- targetForm.FormBorderStyle = _BrdStyle;
- targetForm.TopMost = _TopMost;
- targetForm.Bounds = _Bounds;
- _Fullscreen = false;
- }
- #endregion FullScreenStuff
-
- private void _OnResizeEvent(object sender, EventArgs e)
- {
- var frontBuffer = Graphics.FromHwnd(Handle);
- frontBuffer.Clear(Color.Black);
- }
-
- private void _OnClosingEvent(object sender, CancelEventArgs e)
- {
- _Run = false;
- }
-
- protected override void WndProc(ref Message m)
- {
- switch (m.Msg)
- {
- case 0x112: // WM_SYSCOMMAND
- switch ((int)m.WParam & 0xFFF0)
- {
- case 0xF100: // SC_KEYMENU
- m.Result = IntPtr.Zero;
- break;
- case 0xF140: // SC_SCREENSAVER
- case 0xF170: // SC_MONITORPOWER
- break;
- default:
- base.WndProc(ref m);
- break;
- }
-
- break;
- default:
- base.WndProc(ref m);
- break;
- }
- }
-
- private void _OnKeyDownEvent(object sender, KeyEventArgs e)
- {
- _Keys.KeyDown(e);
- }
-
- private void _OnKeyPressEvent(object sender, KeyPressEventArgs e)
- {
- _Keys.KeyPress(e);
- }
-
- private void _OnKeyUpEvent(object sender, KeyEventArgs e)
- {
- _Keys.KeyUp(e);
- }
-
- #region mouse event handlers
- private void _OnMouseMove(object sender, MouseEventArgs e)
- {
- _Mouse.MouseMove(e);
- }
-
- private void _OnMouseDown(object sender, MouseEventArgs e)
- {
- _Mouse.MouseDown(e);
- }
-
- private void _OnMouseUp(object sender, MouseEventArgs e)
- {
- _Mouse.MouseUp(e);
- }
- #endregion
-
- public bool Init()
- {
- Text = CSettings.GetFullVersionText();
- return true;
- }
-
- public void MainLoop()
- {
- _Run = true;
- var delay = 0;
- Show();
-
- if (CConfig.Config.Graphics.FullScreen == EOffOn.TR_CONFIG_ON)
- {
- _Maximize(this);
- }
-
- while (_Run)
- {
- Application.DoEvents();
-
- if (_Run)
- {
- ClearScreen();
- _Run = _Run && CGraphics.Draw();
- _Run = CGraphics.UpdateGameLogic(_Keys, _Mouse);
- _FlipBuffer();
-
- if (CConfig.Config.Graphics.FullScreen == EOffOn.TR_CONFIG_ON != _Fullscreen)
- {
- _ToggleFullScreen();
- }
-
- if (CTime.IsRunning())
- {
- delay = (int)Math.Floor(CConfig.CalcCycleTime() - CTime.GetMilliseconds());
- }
-
- if (delay >= 1)
- {
- Thread.Sleep(delay);
- }
-
- CTime.CalculateFPS();
- CTime.Restart();
- }
- }
-
- Close();
- }
-
- public new void Close()
- {
- try
- {
- base.Close();
- }
- catch { }
-
- Dispose();
- }
-
- public int GetScreenWidth()
- {
- return ClientSize.Width;
- }
-
- public int GetScreenHeight()
- {
- return ClientSize.Height;
- }
-
- public void ClearScreen()
- {
- _G.Clear(_ClearColor);
- }
-
- public CTextureRef CopyScreen()
- {
- var bmp = new Bitmap(_Backbuffer);
- _Bitmaps.Add(bmp);
- var texture = _GetNewTexture(bmp.GetSize());
-
- // Add to Texture List
- _Textures[texture.Id] = texture;
-
- return texture;
- }
-
- public void CopyScreen(ref CTextureRef texture)
- {
- if (!_TextureExists(texture) || texture.OrigSize.Width != GetScreenWidth() || texture.OrigSize.Height != GetScreenHeight())
- {
- RemoveTexture(ref texture);
- texture = CopyScreen();
- }
- else
- {
- _Bitmaps[texture.Id] = new Bitmap(_Backbuffer);
- }
- }
-
- public void MakeScreenShot()
- {
- var file = CHelper.GetUniqueFileName(Path.Combine(CSettings.DataFolder, CSettings.FolderNameScreenshots), "Screenshot.png");
-
- _Backbuffer.Save(file, ImageFormat.Png);
- }
-
- public void DrawRect(SColorF color, SRectF rect, bool allMonitors = true) { }
-
- public void DrawRectReflection(SColorF color, SRectF rect, float space, float height) { }
-
- public CTextureRef AddTexture(Bitmap bmp)
- {
- var bmp2 = new Bitmap(bmp);
- _Bitmaps.Add(bmp2);
- var texture = _GetNewTexture(bmp.GetSize());
-
- // Add to Texture List
- _Textures[texture.Id] = texture;
-
- return texture;
- }
-
- private CTextureRef _GetNewTexture(Size size)
- {
- return new CTextureRef(_Bitmaps.Count - 1, size);
- }
-
- public CTextureRef AddTexture(string texturePath)
- {
- if (File.Exists(texturePath))
- {
- CTextureRef texture;
- if (_TextureCache.TryGetValue(texturePath, out texture))
- {
- return texture;
- }
-
- using (var bmp = new Bitmap(texturePath))
- {
- texture = AddTexture(bmp);
- _TextureCache.Add(texturePath, texture);
- }
-
- return texture;
- }
-
- return null;
- }
-
- private bool _TextureExists(CTextureRef texture)
- {
- return texture != null && _Textures.ContainsKey(texture.Id);
- }
-
- public CTextureRef CopyTexture(CTextureRef textureRef)
- {
- if (_TextureExists(textureRef))
- {
- return AddTexture(_Bitmaps[textureRef.Id]);
- }
-
- return null;
- }
-
- public void RemoveTexture(ref CTextureRef texture)
- {
- if (_TextureExists(texture))
- {
- _Bitmaps[texture.Id].Dispose();
- _Textures.Remove(texture.Id);
- texture.SetRemoved();
- }
-
- texture = null;
- }
-
- public CTextureRef EnqueueTexture(int w, int h, byte[] data)
- {
- return AddTexture(w, h, data);
- }
-
- public CTextureRef EnqueueTexture(string texturePath)
- {
- return AddTexture(texturePath);
- }
-
- public CTextureRef EnqueueTexture(Bitmap bmp)
- {
- return AddTexture(bmp);
- }
-
- public void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp)
- {
- UpdateTexture(textureRef, bmp);
- }
-
- public CTextureRef AddTexture(int w, int h, byte[] data)
- {
- using (var bmp = new Bitmap(w, h))
- {
- var bmpData = bmp.LockBits(bmp.GetRect(), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
- Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
- bmp.UnlockBits(bmpData);
- return AddTexture(bmp);
- }
- }
-
- public void UpdateTexture(CTextureRef texture, int w, int h, byte[] data)
- {
- if (_TextureExists(texture))
- {
- var bmpData = _Bitmaps[texture.Id].LockBits(new Rectangle(0, 0, _Bitmaps[texture.Id].Width, _Bitmaps[texture.Id].Height),
- ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
- Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
- _Bitmaps[texture.Id].UnlockBits(bmpData);
- }
- }
-
- public void UpdateTexture(CTextureRef textureRef, Bitmap bmp)
- {
- if (_TextureExists(textureRef))
- {
- _Bitmaps[textureRef.Id] = bmp;
- }
- }
-
- public void DrawTexture(CTextureRef texture, SRectF rect, SColorF color, bool mirrored = false, bool allMonitors = true)
- {
- if (texture != null)
- {
- DrawTexture(texture, rect, color, rect, mirrored);
- }
- }
-
- public void DrawTexture(CTextureRef texture, SRectF rect, SColorF color, SRectF bounds, bool mirrored = false, bool allMonitors = true)
- {
- if (!_TextureExists(texture))
- {
- return;
- }
-
- var coloredBitmap = _ColorizeBitmap(_Bitmaps[texture.Id], color);
- _G.DrawImage(coloredBitmap, new RectangleF(rect.X, rect.Y, rect.W, rect.H));
- coloredBitmap.Dispose();
- }
-
- public void DrawTexture(CTextureRef texture, SRectF rect, SColorF color, float begin, float end, bool allMonitors = true) { }
-
- public void DrawTextureReflection(CTextureRef texture, SRectF rect, SColorF color, SRectF bounds, float space, float height, bool allMonitors = true) { }
-
- public int GetTextureCount()
- {
- return _Textures.Count;
- }
-
- private static Bitmap _ColorizeBitmap(Bitmap original, SColorF color)
- {
- var newBitmap = new Bitmap(original.Width, original.Height);
-
- using (var g = Graphics.FromImage(newBitmap))
- {
- var cm = new ColorMatrix { Matrix33 = color.A, Matrix00 = color.R, Matrix11 = color.G, Matrix22 = color.B, Matrix44 = 1 };
-
- using (var ia = new ImageAttributes())
- {
- ia.SetColorMatrix(cm);
-
- g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
- 0, 0, original.Width, original.Height, GraphicsUnit.Pixel, ia);
- }
- }
-
- return newBitmap;
- }
- }
-}
\ No newline at end of file
diff --git a/Vocaluxe/Lib/Draw/COpenGL.cs b/Vocaluxe/Lib/Draw/COpenGL.cs
index a84791ac..0ef95ae4 100644
--- a/Vocaluxe/Lib/Draw/COpenGL.cs
+++ b/Vocaluxe/Lib/Draw/COpenGL.cs
@@ -16,46 +16,30 @@
#endregion
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Drawing.Imaging;
using System.IO;
-using System.Windows.Forms;
+using System.Numerics;
+using System.Runtime.InteropServices;
using OpenTK;
-using OpenTK.Graphics;
-using OpenTK.Graphics.OpenGL;
+using OpenTK.Graphics.OpenGL4;
+using SDL;
+using SkiaSharp;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
-using BlendingFactorDest = OpenTK.Graphics.OpenGL.BlendingFactorDest;
-using BlendingFactorSrc = OpenTK.Graphics.OpenGL.BlendingFactorSrc;
-using ClearBufferMask = OpenTK.Graphics.OpenGL.ClearBufferMask;
-using DepthFunction = OpenTK.Graphics.OpenGL.DepthFunction;
-using EnableCap = OpenTK.Graphics.OpenGL.EnableCap;
-using GL = OpenTK.Graphics.OpenGL.GL;
-using GenerateMipmapTarget = OpenTK.Graphics.OpenGL.GenerateMipmapTarget;
-using MatrixMode = OpenTK.Graphics.OpenGL.MatrixMode;
-using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat;
-using PixelInternalFormat = OpenTK.Graphics.OpenGL.PixelInternalFormat;
-using PixelStoreParameter = OpenTK.Graphics.OpenGL.PixelStoreParameter;
-using PixelType = OpenTK.Graphics.OpenGL.PixelType;
-using TextureMagFilter = OpenTK.Graphics.OpenGL.TextureMagFilter;
-using TextureMinFilter = OpenTK.Graphics.OpenGL.TextureMinFilter;
-using TextureParameterName = OpenTK.Graphics.OpenGL.TextureParameterName;
-using TextureTarget = OpenTK.Graphics.OpenGL.TextureTarget;
+using VocaluxeLib.Log;
+using PixelFormat = OpenTK.Graphics.OpenGL4.PixelFormat;
+using Size = System.Drawing.Size;
namespace Vocaluxe.Lib.Draw
{
- class CFormHook : Form, IFormHook
+ class CSdlBindingsContext : IBindingsContext
{
- public MessageEventHandler OnMessage { private get; set; }
-
- protected override void WndProc(ref Message m)
+ public IntPtr GetProcAddress(string procName)
{
- if (OnMessage == null || OnMessage(ref m))
- {
- base.WndProc(ref m);
- }
+ return SDL3.SDL_GL_GetProcAddress(procName);
}
}
@@ -63,6 +47,7 @@ namespace Vocaluxe.Lib.Draw
{
//The texture "name" according to the specs
public readonly int Name;
+ public int[] Planes;
public COGLTexture(int name, Size dataSize, int texWidth = 0, int texHeight = 0) : base(dataSize, new Size(texWidth, texHeight))
{
@@ -74,10 +59,10 @@ namespace Vocaluxe.Lib.Draw
GL.BindTexture(TextureTarget.Texture2D, Name);
- GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, texWidth, texHeight, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, texWidth, texHeight, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
- GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)OpenTK.Graphics.TextureWrapMode.ClampToEdge);
- GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)OpenTK.Graphics.TextureWrapMode.ClampToEdge);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
@@ -97,6 +82,11 @@ namespace Vocaluxe.Lib.Draw
}
base.Dispose();
+ if (Planes != null)
+ {
+ GL.DeleteTextures(Planes.Length, Planes);
+ }
+
if (Name != 0)
{
GL.DeleteTexture(Name);
@@ -104,146 +94,130 @@ namespace Vocaluxe.Lib.Draw
}
}
- class COpenGL : CDrawBaseWindows, IDraw
+ unsafe class COpenGL : CDrawBaseSDL, IDraw
{
- private readonly GLControl _Control;
+ private const string _VertexShaderSource = @"#version 330 core
+uniform mat4 Projection;
+layout(location = 0) in vec3 Position;
+layout(location = 1) in vec2 TexCoord;
+layout(location = 2) in vec4 Color;
+out vec2 vTexCoord;
+out vec4 vColor;
+void main()
+{
+ gl_Position = Projection * vec4(Position, 1.0);
+ vTexCoord = TexCoord;
+ vColor = Color.bgra;
+}";
+ private const string _YuvVertexShaderSource = @"#version 330 core
+void main()
+{
+ vec2 position = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);
+ gl_Position = vec4(position * 2.0 - 1.0, 0.0, 1.0);
+}";
+ private const string _YuvFragmentShaderSource = @"#version 330 core
+uniform sampler2D TexY;
+uniform sampler2D TexU;
+uniform sampler2D TexV;
+uniform vec4 RowR;
+uniform vec4 RowG;
+uniform vec4 RowB;
+uniform vec2 Size;
+out vec4 FragColor;
+void main()
+{
+ vec2 uv = gl_FragCoord.xy / Size;
+ vec4 yuv = vec4(texture(TexY, uv).r, texture(TexU, uv).r, texture(TexV, uv).r, 1.0);
+ FragColor = vec4(clamp(dot(RowR, yuv), 0.0, 1.0), clamp(dot(RowG, yuv), 0.0, 1.0), clamp(dot(RowB, yuv), 0.0, 1.0), 1.0);
+}";
+ private const string _FragmentShaderSource = @"#version 330 core
+uniform sampler2D Tex;
+in vec2 vTexCoord;
+in vec4 vColor;
+out vec4 FragColor;
+void main()
+{
+ FragColor = texture(Tex, vTexCoord) * vColor;
+}";
+
+ private readonly SDL_GLContextState* _Context;
private int _FBO;
+ private int _ResolveFBO;
+ private int _ResolveTexture;
+ private Size _ResolveSize;
+ private int _Program;
+ private int _YuvProgram;
+ private int _ProjectionLocation;
+ private int _VertexArray;
+ private int _VertexBuffer;
+ private int _IndexBuffer;
+ private CTextureRef _BlankTexture;
+
+ private readonly List _Vertices = new List();
+ private readonly List _VerticesTextures = new List();
public COpenGL()
{
- _Form = new CFormHook { ClientSize = new Size(CConfig.Config.Graphics.ScreenW * CConfig.Config.Graphics.NumScreens, CConfig.Config.Graphics.ScreenH) };
- //OpenGL needs that here but D3D needs it in the constructor, so do NOT unify!
-
//Check AA Mode
- CConfig.Config.Graphics.AAMode = (EAntiAliasingModes)_CheckAntiAliasingMode((int)CConfig.Config.Graphics.AAMode);
-
- var ok = false;
- try
+ var samples = (int)CConfig.Config.Graphics.AAMode;
+ if (samples > 16)
{
-#if WIN
- var gm = new GraphicsMode(32, 24, 0, (int)CConfig.Config.Graphics.AAMode);
-#else
- var gm = new GraphicsMode(24, 24, 0, (int)CConfig.Config.Graphics.AAMode);
-#endif
- _Control = new GLControl(gm, 2, 1, GraphicsContextFlags.Default);
- if (_Control.GraphicsMode != null)
- {
- ok = true;
- }
- }
- catch (Exception)
- {
- ok = false;
+ samples = 16;
}
- if (!ok)
+ _SetAttributes(samples);
+ if (!_CreateWindow(SDL_WindowFlags.SDL_WINDOW_OPENGL) && samples > 0)
{
- _Control = new GLControl();
+ samples = 0;
+ _SetAttributes(samples);
+ _CreateWindow(SDL_WindowFlags.SDL_WINDOW_OPENGL);
}
- _Control.MakeCurrent();
- _Control.VSync = CConfig.Config.Graphics.VSync == EOffOn.TR_CONFIG_ON;
+ if (_Window == null)
+ {
+ throw new Exception("Could not create an OpenGL window: " + SDL3.SDL_GetError());
+ }
- _Form.Controls.Add(_Control);
- _Control.ClientSize = _Form.ClientSize;
+ CConfig.Config.Graphics.AAMode = (EAntiAliasingModes)samples;
- _Control.KeyDown += _OnKeyDown;
- _Control.PreviewKeyDown += _OnPreviewKeyDown;
- _Control.KeyPress += _OnKeyPress;
- _Control.KeyUp += _OnKeyUp;
+ _Context = SDL3.SDL_GL_CreateContext(_Window);
+ if (_Context == null)
+ {
+ throw new Exception("Could not create an OpenGL 3.3 context: " + SDL3.SDL_GetError());
+ }
- _Control.MouseMove += _OnMouseMove;
- _Control.MouseWheel += _OnMouseWheel;
- _Control.MouseDown += _OnMouseDown;
- _Control.MouseUp += _OnMouseUp;
- _Control.MouseLeave += _OnMouseLeave;
- _Control.MouseEnter += _OnMouseEnter;
+ SDL3.SDL_GL_MakeCurrent(_Window, _Context);
+ GL.LoadBindings(new CSdlBindingsContext());
+ SDL3.SDL_GL_SetSwapInterval(CConfig.Config.Graphics.VSync == EOffOn.TR_CONFIG_ON ? 1 : 0);
- _NonPowerOf2TextureSupported = false;
+ _NonPowerOf2TextureSupported = true;
}
- private static int _CheckAntiAliasingMode(int setValue)
+ private static void _SetAttributes(int samples)
{
- var samples = 0;
-
- if (setValue > 32)
- {
- setValue = 32;
- }
-
- while (samples <= setValue)
- {
- GraphicsMode mode;
- try
- {
- mode = new GraphicsMode(16, 0, 0, samples);
- }
- catch (Exception)
- {
- break;
- }
-
- if (mode.Samples != samples)
- {
- break;
- }
-
- if (samples == 0)
- {
- samples = 2;
- }
- else
- {
- samples *= 2;
- }
- }
-
- if (samples == 2)
- {
- return 0;
- }
-
- return samples / 2;
- }
-
- /*
- private int _CheckColorDepth(int setValue)
- {
- int result = 16;
-
- if (setValue > 32)
- setValue = 32;
-
- while (result <= setValue)
- {
- GraphicsMode mode;
- try
- {
- mode = new GraphicsMode(result, 0, 0, 0);
- }
- catch (Exception)
- {
- break;
- }
- if (mode.ColorFormat != result)
- break;
- result += 8;
- }
-
- return result - 8;
- }
-*/
-
- protected override void _OnResize(object sender, EventArgs e)
- {
- _Control.ClientSize = _Form.ClientSize;
- base._OnResize(sender, e);
+ SDL3.SDL_GL_ResetAttributes();
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, 3);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, SDL3.SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_FLAGS, SDL3.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DOUBLEBUFFER, 1);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DEPTH_SIZE, 24);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_MULTISAMPLEBUFFERS, samples > 0 ? 1 : 0);
+ SDL3.SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_MULTISAMPLESAMPLES, samples);
}
protected override void _DoResize()
{
- _H = _Control.Height;
- _W = _Control.Width;
+ var clientSize = _GetClientSize();
+ if (clientSize.Width == 0 || clientSize.Height == 0)
+ {
+ return;
+ }
+
+ _H = clientSize.Height;
+ _W = clientSize.Width;
+ _X = 0;
+ _Y = 0;
_CurrentAlignment = CConfig.Config.Graphics.ScreenAlignment;
if (CConfig.Config.Graphics.Stretch != EOffOn.TR_CONFIG_ON)
@@ -262,47 +236,165 @@ namespace Vocaluxe.Lib.Draw
return false;
}
- // Init Texturing
- GL.Enable(EnableCap.Texture2D);
+ try
+ {
+ _Program = _CreateProgram(_VertexShaderSource, _FragmentShaderSource);
+ }
+ catch (Exception e)
+ {
+ CLog.Error(e, "Error during OpenGL initialization.");
+ return false;
+ }
- GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
+ try
+ {
+ _YuvProgram = _CreateProgram(_YuvVertexShaderSource, _YuvFragmentShaderSource);
+ GL.UseProgram(_YuvProgram);
+ GL.Uniform1(GL.GetUniformLocation(_YuvProgram, "TexY"), 0);
+ GL.Uniform1(GL.GetUniformLocation(_YuvProgram, "TexU"), 1);
+ GL.Uniform1(GL.GetUniformLocation(_YuvProgram, "TexV"), 2);
+ }
+ catch (Exception e)
+ {
+ CLog.Error(e, "[OpenGL] Unable to compile the YUV shader, using CPU conversion for videos");
+ _YuvProgram = 0;
+ }
+
+ _ProjectionLocation = GL.GetUniformLocation(_Program, "Projection");
+ GL.UseProgram(_Program);
+ GL.Uniform1(GL.GetUniformLocation(_Program, "Tex"), 0);
+
+ _VertexArray = GL.GenVertexArray();
+ GL.BindVertexArray(_VertexArray);
+
+ var vertexSize = Marshal.SizeOf();
+ _VertexBuffer = GL.GenBuffer();
+ GL.BindBuffer(BufferTarget.ArrayBuffer, _VertexBuffer);
+ GL.BufferData(BufferTarget.ArrayBuffer, CSettings.VertexBufferElements * 4 * vertexSize, IntPtr.Zero, BufferUsageHint.StreamDraw);
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, vertexSize, 0);
+ GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, vertexSize, sizeof(float) * 3);
+ GL.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, vertexSize, sizeof(float) * 5);
+ GL.EnableVertexAttribArray(0);
+ GL.EnableVertexAttribArray(1);
+ GL.EnableVertexAttribArray(2);
+
+ var indices = new ushort[CSettings.VertexBufferElements * 6];
+ for (var i = 0; i < CSettings.VertexBufferElements; i++)
+ {
+ var vertex = (ushort)(i * 4);
+ indices[i * 6] = vertex;
+ indices[i * 6 + 1] = (ushort)(vertex + 1);
+ indices[i * 6 + 2] = (ushort)(vertex + 2);
+ indices[i * 6 + 3] = vertex;
+ indices[i * 6 + 4] = (ushort)(vertex + 2);
+ indices[i * 6 + 5] = (ushort)(vertex + 3);
+ }
+
+ _IndexBuffer = GL.GenBuffer();
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, _IndexBuffer);
+ GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(ushort), indices, BufferUsageHint.StaticDraw);
+
+ GL.Enable(EnableCap.Blend);
+ GL.BlendFuncSeparate(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha, BlendingFactorSrc.Zero, BlendingFactorDest.One);
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
- GL.DepthRange(CSettings.ZFar, CSettings.ZNear);
GL.DepthFunc(DepthFunction.Lequal);
GL.Enable(EnableCap.DepthTest);
- GL.ClearColor(Color.Black);
+ GL.ClearColor(0f, 0f, 0f, 1f);
- GL.GenFramebuffers(1, out _FBO);
+ _FBO = GL.GenFramebuffer();
+ _ResolveFBO = GL.GenFramebuffer();
+
+ _BlankTexture = AddTexture(1, 1, new byte[] { 255, 255, 255, 255 });
return true;
}
+ private static int _CreateProgram(string vertexShaderSource, string fragmentShaderSource)
+ {
+ var vertexShader = _CompileShader(ShaderType.VertexShader, vertexShaderSource);
+ var fragmentShader = _CompileShader(ShaderType.FragmentShader, fragmentShaderSource);
+ var program = GL.CreateProgram();
+ GL.AttachShader(program, vertexShader);
+ GL.AttachShader(program, fragmentShader);
+ GL.LinkProgram(program);
+ GL.DeleteShader(vertexShader);
+ GL.DeleteShader(fragmentShader);
+
+ int status;
+ GL.GetProgram(program, GetProgramParameterName.LinkStatus, out status);
+ if (status == 0)
+ {
+ throw new Exception("Shader linking failed: " + GL.GetProgramInfoLog(program));
+ }
+
+ return program;
+ }
+
+ private static int _CompileShader(ShaderType type, string source)
+ {
+ var shader = GL.CreateShader(type);
+ GL.ShaderSource(shader, source);
+ GL.CompileShader(shader);
+
+ int status;
+ GL.GetShader(shader, ShaderParameter.CompileStatus, out status);
+ if (status == 0)
+ {
+ throw new Exception("Shader compilation failed: " + GL.GetShaderInfoLog(shader));
+ }
+
+ return shader;
+ }
+
public override void Close()
{
base.Close();
- GL.DeleteFramebuffers(1, ref _FBO);
+ if (_Context == null)
+ {
+ return;
+ }
+
+ GL.DeleteFramebuffer(_FBO);
+ GL.DeleteFramebuffer(_ResolveFBO);
+ if (_ResolveTexture != 0)
+ {
+ GL.DeleteTexture(_ResolveTexture);
+ }
+
+ GL.DeleteBuffer(_VertexBuffer);
+ GL.DeleteBuffer(_IndexBuffer);
+ GL.DeleteVertexArray(_VertexArray);
+ GL.DeleteProgram(_Program);
+ if (_YuvProgram != 0)
+ {
+ GL.DeleteProgram(_YuvProgram);
+ }
+ SDL3.SDL_GL_DestroyContext(_Context);
}
protected override void _OnBeforeDraw()
{
- //Nothing to do
+ GL.UseProgram(_Program);
+ GL.BindVertexArray(_VertexArray);
+ GL.ActiveTexture(TextureUnit.Texture0);
}
protected override void _OnAfterDraw()
{
- _Control.SwapBuffers();
- Application.DoEvents();
+ _RenderVertexBuffer();
+ SDL3.SDL_GL_SwapWindow(_Window);
+ _HandleEvents();
}
public int GetScreenWidth()
{
- return _Control.Width;
+ return _GetClientSize().Width;
}
public int GetScreenHeight()
{
- return _Control.Height;
+ return _GetClientSize().Height;
}
protected override void _ClearScreen()
@@ -312,41 +404,91 @@ namespace Vocaluxe.Lib.Draw
protected override void _AdjustNewBorders()
{
- GL.MatrixMode(MatrixMode.Projection);
- GL.LoadIdentity();
- GL.Ortho(-CConfig.Config.Graphics.BorderLeft, CConfig.Config.Graphics.BorderRight + CSettings.RenderW * CConfig.Config.Graphics.NumScreens,
- CConfig.Config.Graphics.BorderBottom + CSettings.RenderH,
- -CConfig.Config.Graphics.BorderTop, CSettings.ZNear, CSettings.ZFar);
+ float left = -CConfig.Config.Graphics.BorderLeft;
+ float right = CConfig.Config.Graphics.BorderRight + CSettings.RenderW * CConfig.Config.Graphics.NumScreens;
+ float bottom = CConfig.Config.Graphics.BorderBottom + CSettings.RenderH;
+ float top = -CConfig.Config.Graphics.BorderTop;
+ float zNear = CSettings.ZNear;
+ float zFar = CSettings.ZFar;
+ var projection = new Matrix4x4(
+ 2f / (right - left), 0f, 0f, 0f,
+ 0f, 2f / (top - bottom), 0f, 0f,
+ 0f, 0f, 2f / (zFar - zNear), 0f,
+ -(right + left) / (right - left), -(top + bottom) / (top - bottom), -(zFar + zNear) / (zFar - zNear), 1f);
+
+ GL.UseProgram(_Program);
+ GL.UniformMatrix4(_ProjectionLocation, 1, false, &projection.M11);
}
public void MakeScreenShot()
{
+ _RenderVertexBuffer();
var file = CHelper.GetUniqueFileName(Path.Combine(CSettings.DataFolder, CSettings.FolderNameScreenshots), "Screenshot.png");
- var width = GetScreenWidth();
- var height = GetScreenHeight();
-
- using (var screen = new Bitmap(width, height))
+ var size = _GetClientSize();
+ using (var screen = new SKBitmap(new SKImageInfo(size.Width, size.Height, SKColorType.Bgra8888, SKAlphaType.Opaque)))
{
- var bmpData = screen.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+ var pixels = new byte[size.Width * size.Height * 4];
+ GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
+ GL.ReadPixels(0, 0, size.Width, size.Height, PixelFormat.Bgra, PixelType.UnsignedByte, pixels);
- GL.ReadPixels(0, 0, width, height, PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0);
- screen.UnlockBits(bmpData);
+ var rowLength = size.Width * 4;
+ var target = screen.GetPixels();
+ for (var y = 0; y < size.Height; y++)
+ {
+ Marshal.Copy(pixels, (size.Height - 1 - y) * rowLength, IntPtr.Add(target, y * screen.RowBytes), rowLength);
+ }
- screen.RotateFlip(RotateFlipType.RotateNoneFlipY);
- screen.Save(file, ImageFormat.Png);
+ using (var stream = File.Create(file))
+ {
+ screen.Encode(stream, SKEncodedImageFormat.Png, 100);
+ }
}
}
+ private void _CopyFrameToTexture(COGLTexture texture)
+ {
+ _RenderVertexBuffer();
+
+ if (_ResolveTexture == 0 || _ResolveSize.Width != _W || _ResolveSize.Height != _H)
+ {
+ if (_ResolveTexture != 0)
+ {
+ GL.DeleteTexture(_ResolveTexture);
+ }
+
+ _ResolveSize = new Size(_W, _H);
+ _ResolveTexture = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, _ResolveTexture);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, _W, _H, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
+ GL.BindTexture(TextureTarget.Texture2D, 0);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, _ResolveFBO);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, _ResolveTexture, 0);
+ }
+
+ var width = Math.Min(_W, texture.Size.Width);
+ var height = Math.Min(_H, texture.Size.Height);
+
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, 0);
+ GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _ResolveFBO);
+ GL.BlitFramebuffer(_X, _Y, _X + _W, _Y + _H, 0, 0, _W, _H, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest);
+
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, _FBO);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texture.Name, 0);
+ GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _ResolveFBO);
+ GL.BlitFramebuffer(0, _H - height, width, _H, 0, height, width, 0, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+
+ GL.BindTexture(TextureTarget.Texture2D, texture.Name);
+ GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+ GL.BindTexture(TextureTarget.Texture2D, 0);
+ }
+
public CTextureRef CopyScreen()
{
- //TODO: Check if _W,_H needs to be used or not
- var size = new Size(GetScreenWidth(), GetScreenHeight());
+ var size = new Size(_W, _H);
var texture = _CreateTexture(size);
-
- GL.BindTexture(TextureTarget.Texture2D, texture.Name);
- GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, size.Width, size.Height); //TODO: Use _X,_Y and _W,_H?
- GL.BindTexture(TextureTarget.Texture2D, 0);
+ _CopyFrameToTexture(texture);
return _GetTextureReference(size, texture);
}
@@ -355,113 +497,25 @@ namespace Vocaluxe.Lib.Draw
{
COGLTexture texture;
//Check for actual texture sizes as it may be downsized compared to OrigSize
- if (!_GetTexture(textureRef, out texture) || texture.DataSize.Width != GetScreenWidth() || texture.DataSize.Height != GetScreenHeight())
+ if (!_GetTexture(textureRef, out texture) || texture.DataSize.Width != _W || texture.DataSize.Height != _H)
{
RemoveTexture(ref textureRef);
textureRef = CopyScreen();
}
else
{
- GL.BindTexture(TextureTarget.Texture2D, texture.Name);
- GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, GetScreenWidth(), GetScreenHeight());
- GL.BindTexture(TextureTarget.Texture2D, 0);
+ _CopyFrameToTexture(texture);
}
}
public void DrawRect(SColorF color, SRectF rect, bool allMonitors = true)
{
- var loops = 1;
- if (allMonitors)
- {
- loops = CConfig.Config.Graphics.NumScreens;
- }
-
- for (var i = 0; i < loops; i++)
- {
- var newrect = rect;
- newrect.X += CSettings.RenderW * i;
-
- GL.Enable(EnableCap.Blend);
- GL.Color4(color.R, color.G, color.B, color.A * CGraphics.GlobalAlpha);
-
- GL.Begin(PrimitiveType.Quads);
- GL.MatrixMode(MatrixMode.Color);
- GL.PushMatrix();
- if (Math.Abs(newrect.Rotation) > 0.001)
- {
- GL.Translate(0.5f, 0.5f, 0);
- GL.Rotate(-newrect.Rotation, 0f, 0f, 1f);
- GL.Translate(-0.5f, -0.5f, 0);
- }
-
- GL.Vertex3(newrect.X, newrect.Y, newrect.Z + CGraphics.ZOffset);
- GL.Vertex3(newrect.X, newrect.Y + newrect.H, newrect.Z + CGraphics.ZOffset);
- GL.Vertex3(newrect.X + newrect.W, newrect.Y + newrect.H, newrect.Z + CGraphics.ZOffset);
- GL.Vertex3(newrect.X + newrect.W, newrect.Y, newrect.Z + CGraphics.ZOffset);
- GL.End();
- GL.PopMatrix();
- GL.Disable(EnableCap.Blend);
- }
+ DrawTexture(_BlankTexture, rect, color, false, allMonitors);
}
public void DrawRectReflection(SColorF color, SRectF rect, float space, float height)
{
- if (rect.H < height)
- {
- height = rect.H;
- }
-
- var rx1 = rect.X;
- var rx2 = rect.X + rect.W;
- var ry1 = rect.Y + rect.H + space;
- var ry2 = rect.Y + rect.H + space + height;
-
- if (rx1 < rect.X)
- {
- rx1 = rect.X;
- }
-
- if (rx2 > rect.X + rect.W)
- {
- rx2 = rect.X + rect.W;
- }
-
- if (ry1 < rect.Y + space)
- {
- ry1 = rect.Y + space;
- }
-
- if (ry2 > rect.Y + rect.H + space + height)
- {
- ry2 = rect.Y + rect.H + space + height;
- }
-
-
- GL.Enable(EnableCap.Blend);
- GL.MatrixMode(MatrixMode.Color);
- GL.PushMatrix();
- if (Math.Abs(rect.Rotation) > 0.001)
- {
- GL.Translate(0.5f, 0.5f, 0);
- GL.Rotate(-rect.Rotation, 0f, 0f, 1f);
- GL.Translate(-0.5f, -0.5f, 0);
- }
-
- GL.Begin(PrimitiveType.Quads);
-
- GL.Color4(color.R, color.G, color.B, color.A * CGraphics.GlobalAlpha);
- GL.Vertex3(rx2, ry1, rect.Z + CGraphics.ZOffset);
-
- GL.Color4(color.R, color.G, color.B, 0f);
- GL.Vertex3(rx2, ry2, rect.Z + CGraphics.ZOffset);
- GL.Vertex3(rx1, ry2, rect.Z + CGraphics.ZOffset);
-
- GL.Color4(color.R, color.G, color.B, color.A * CGraphics.GlobalAlpha);
- GL.Vertex3(rx1, ry1, rect.Z + CGraphics.ZOffset);
-
- GL.End();
- GL.PopMatrix();
- GL.Disable(EnableCap.Blend);
+ DrawTextureReflection(_BlankTexture, rect, color, rect, space, height);
}
protected override COGLTexture _CreateTexture(Size dataSize)
@@ -483,13 +537,90 @@ namespace Vocaluxe.Lib.Draw
return;
}
- GL.BindFramebuffer(OpenTK.Graphics.OpenGL.FramebufferTarget.Framebuffer, _FBO);
- GL.FramebufferTexture2D(OpenTK.Graphics.OpenGL.FramebufferTarget.Framebuffer, OpenTK.Graphics.OpenGL.FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D,
- texture.Name, 0);
- GL.ClearColor(Color.FromArgb(0));
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, _FBO);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texture.Name, 0);
+ GL.ClearColor(0f, 0f, 0f, 0f);
GL.Clear(ClearBufferMask.ColorBufferBit);
- GL.ClearColor(Color.Black);
- GL.BindFramebuffer(OpenTK.Graphics.OpenGL.FramebufferTarget.Framebuffer, 0);
+ GL.ClearColor(0f, 0f, 0f, 1f);
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+ }
+
+ public override bool SupportsYuvVideo
+ {
+ get { return _YuvProgram != 0; }
+ }
+
+ protected override void _WriteYuvDataToTexture(COGLTexture texture, byte[] data, float[] yuvToRgbRows)
+ {
+ if (!texture.IsLoaded)
+ {
+ return;
+ }
+
+ var width = texture.DataSize.Width;
+ var height = texture.DataSize.Height;
+ var planeWidths = new[] { width, (width + 1) / 2, (width + 1) / 2 };
+ var planeHeights = new[] { height, (height + 1) / 2, (height + 1) / 2 };
+ if (texture.Planes == null)
+ {
+ texture.Planes = new int[3];
+ GL.GenTextures(3, texture.Planes);
+ for (var i = 0; i < 3; i++)
+ {
+ GL.BindTexture(TextureTarget.Texture2D, texture.Planes[i]);
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.R8, planeWidths[i], planeHeights[i], 0, PixelFormat.Red, PixelType.UnsignedByte, IntPtr.Zero);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+ }
+ }
+
+ fixed (byte* source = data)
+ {
+ var offset = 0;
+ for (var i = 0; i < 3; i++)
+ {
+ GL.BindTexture(TextureTarget.Texture2D, texture.Planes[i]);
+ GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, planeWidths[i], planeHeights[i], PixelFormat.Red, PixelType.UnsignedByte, (IntPtr)(source + offset));
+ offset += planeWidths[i] * planeHeights[i];
+ }
+ }
+
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, _FBO);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texture.Name, 0);
+ GL.Viewport(0, 0, width, height);
+ GL.Disable(EnableCap.Blend);
+ GL.Disable(EnableCap.DepthTest);
+ GL.UseProgram(_YuvProgram);
+ GL.Uniform4(GL.GetUniformLocation(_YuvProgram, "RowR"), yuvToRgbRows[0], yuvToRgbRows[1], yuvToRgbRows[2], yuvToRgbRows[3]);
+ GL.Uniform4(GL.GetUniformLocation(_YuvProgram, "RowG"), yuvToRgbRows[4], yuvToRgbRows[5], yuvToRgbRows[6], yuvToRgbRows[7]);
+ GL.Uniform4(GL.GetUniformLocation(_YuvProgram, "RowB"), yuvToRgbRows[8], yuvToRgbRows[9], yuvToRgbRows[10], yuvToRgbRows[11]);
+ GL.Uniform2(GL.GetUniformLocation(_YuvProgram, "Size"), (float)width, height);
+ for (var i = 0; i < 3; i++)
+ {
+ GL.ActiveTexture(TextureUnit.Texture0 + i);
+ GL.BindTexture(TextureTarget.Texture2D, texture.Planes[i]);
+ }
+
+ GL.BindVertexArray(_VertexArray);
+ GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
+
+ for (var i = 2; i >= 0; i--)
+ {
+ GL.ActiveTexture(TextureUnit.Texture0 + i);
+ GL.BindTexture(TextureTarget.Texture2D, 0);
+ }
+
+ GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+ GL.Viewport(_X, _Y, _W, _H);
+ GL.Enable(EnableCap.Blend);
+ GL.Enable(EnableCap.DepthTest);
+ GL.UseProgram(_Program);
+
+ GL.BindTexture(TextureTarget.Texture2D, texture.Name);
+ GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+ GL.BindTexture(TextureTarget.Texture2D, 0);
}
protected override void _WriteDataToTexture(COGLTexture texture, byte[] data)
@@ -499,7 +630,7 @@ namespace Vocaluxe.Lib.Draw
GL.BindTexture(TextureTarget.Texture2D, texture.Name);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, texture.DataSize.Width, texture.DataSize.Height, PixelFormat.Bgra, PixelType.UnsignedByte, data);
- GL.Ext.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+ GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
GL.BindTexture(TextureTarget.Texture2D, 0);
}
@@ -511,7 +642,7 @@ namespace Vocaluxe.Lib.Draw
GL.BindTexture(TextureTarget.Texture2D, texture.Name);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, texture.DataSize.Width, texture.DataSize.Height, PixelFormat.Bgra, PixelType.UnsignedByte, data);
- GL.Ext.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+ GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
GL.BindTexture(TextureTarget.Texture2D, 0);
}
@@ -524,51 +655,82 @@ namespace Vocaluxe.Lib.Draw
dc.Wx2 = (float)Math.Round(dc.Wx2);
dc.Wy2 = (float)Math.Round(dc.Wy2);
- GL.BindTexture(TextureTarget.Texture2D, texture.Name);
-
- GL.Enable(EnableCap.Blend);
-
- GL.MatrixMode(MatrixMode.Texture);
- GL.PushMatrix();
+ color.A *= CGraphics.GlobalAlpha;
+ var c = color.AsColor().ToArgb();
+ var c2 = c;
+ if (isReflection)
+ {
+ color.A = 0;
+ c2 = color.AsColor().ToArgb();
+ }
+ var rotation = Matrix4x4.Identity;
if (Math.Abs(dc.Rotation) > float.Epsilon)
{
- GL.Translate(0.5f, 0.5f, 0);
- GL.Rotate(-dc.Rotation, 0f, 0f, 1f);
- GL.Translate(-0.5f, -0.5f, 0);
+ var center = new Vector3((dc.Wx1 + dc.Wx2) / 2f, (dc.Wy1 + dc.Wy2) / 2f, 0);
+ rotation = Matrix4x4.CreateTranslation(-center) * Matrix4x4.CreateRotationZ(dc.Rotation * (float)Math.PI / 180) * Matrix4x4.CreateTranslation(center);
}
- GL.Begin(PrimitiveType.Quads);
-
- GL.Color4(color.R, color.G, color.B, color.A * CGraphics.GlobalAlpha);
- GL.TexCoord2(dc.Tx1, dc.Ty1);
- GL.Vertex3(dc.Wx1, dc.Wy1, dc.Wz);
-
- if (isReflection)
+ if (_Vertices.Count >= CSettings.VertexBufferElements)
{
- GL.Color4(color.R, color.G, color.B, 0);
+ _RenderVertexBuffer();
}
- GL.TexCoord2(dc.Tx1, dc.Ty2);
- GL.Vertex3(dc.Wx1, dc.Wy2, dc.Wz);
+ _Vertices.Add(new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx1, dc.Wy1, dc.Wz), rotation), new Vector2(dc.Tx1, dc.Ty1), c));
+ _Vertices.Add(new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx1, dc.Wy2, dc.Wz), rotation), new Vector2(dc.Tx1, dc.Ty2), c2));
+ _Vertices.Add(new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx2, dc.Wy2, dc.Wz), rotation), new Vector2(dc.Tx2, dc.Ty2), c2));
+ _Vertices.Add(new STexturedColoredVertex(Vector3.Transform(new Vector3(dc.Wx2, dc.Wy1, dc.Wz), rotation), new Vector2(dc.Tx2, dc.Ty1), c));
+ _VerticesTextures.Add(texture.Name);
+ }
- GL.TexCoord2(dc.Tx2, dc.Ty2);
- GL.Vertex3(dc.Wx2, dc.Wy2, dc.Wz);
-
- if (isReflection)
+ private void _RenderVertexBuffer()
+ {
+ if (_Vertices.Count <= 0)
{
- GL.Color4(color.R, color.G, color.B, color.A * CGraphics.GlobalAlpha);
+ return;
}
- GL.TexCoord2(dc.Tx2, dc.Ty1);
- GL.Vertex3(dc.Wx2, dc.Wy1, dc.Wz);
+ var vertexData = MemoryMarshal.AsBytes(CollectionsMarshal.AsSpan(_Vertices));
+ GL.BindBuffer(BufferTarget.ArrayBuffer, _VertexBuffer);
+ fixed (byte* data = vertexData)
+ {
+ GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, vertexData.Length, (IntPtr)data);
+ }
- GL.End();
+ var quads = _VerticesTextures.Count;
+ var start = 0;
+ while (start < quads)
+ {
+ var texture = _VerticesTextures[start];
+ var end = start + 1;
+ while (end < quads && _VerticesTextures[end] == texture)
+ {
+ end++;
+ }
- GL.PopMatrix();
+ GL.BindTexture(TextureTarget.Texture2D, texture);
+ GL.DrawElements(PrimitiveType.Triangles, (end - start) * 6, DrawElementsType.UnsignedShort, start * 6 * sizeof(ushort));
+ start = end;
+ }
- GL.Disable(EnableCap.Blend);
GL.BindTexture(TextureTarget.Texture2D, 0);
+ _Vertices.Clear();
+ _VerticesTextures.Clear();
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct STexturedColoredVertex
+ {
+ private Vector3 _Position;
+ private Vector2 _Texture;
+ private readonly int _Color;
+
+ public STexturedColoredVertex(Vector3 position, Vector2 texture, int color)
+ {
+ _Position = position;
+ _Texture = texture;
+ _Color = color;
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/Vocaluxe/Lib/Draw/CTextureProvider.cs b/Vocaluxe/Lib/Draw/CTextureProvider.cs
index d8c46c02..2d617833 100644
--- a/Vocaluxe/Lib/Draw/CTextureProvider.cs
+++ b/Vocaluxe/Lib/Draw/CTextureProvider.cs
@@ -19,12 +19,12 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
+using SkiaSharp;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -63,7 +63,7 @@ namespace Vocaluxe.Lib.Draw
/// TTextureType or CTextureRef
///
/// Texture data as a bitmap
- public STextureQueue(object textureOrRef, EQueueAction action, Bitmap data)
+ public STextureQueue(object textureOrRef, EQueueAction action, SKBitmap data)
{
TextureOrRef = textureOrRef;
Action = action;
@@ -280,9 +280,9 @@ namespace Vocaluxe.Lib.Draw
///
///
///
- private void _WriteBitmapToTexture(ref TTextureType texture, Bitmap bmp)
+ private void _WriteBitmapToTexture(ref TTextureType texture, SKBitmap bmp)
{
- Bitmap bmp2 = null;
+ SKBitmap bmp2 = null;
try
{
var size = _GetNewTextureSize(bmp.GetSize());
@@ -293,7 +293,7 @@ namespace Vocaluxe.Lib.Draw
}
//Fill the new Bitmap with the texture data
- var bmpData = bmp.LockBits(bmp.GetRect(), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ var bmpData = bmp.ToBgra();
if (texture == null)
{
texture = _CreateTexture(size);
@@ -303,8 +303,11 @@ namespace Vocaluxe.Lib.Draw
texture.DataSize = size;
}
- _WriteDataToTexture(texture, bmpData.Scan0);
- bmp.UnlockBits(bmpData);
+ _WriteDataToTexture(texture, bmpData.GetPixels());
+ if (bmpData != bmp)
+ {
+ bmpData.Dispose();
+ }
}
finally
{
@@ -506,7 +509,7 @@ namespace Vocaluxe.Lib.Draw
Debug.Assert(!oldTexture.IsLoaded);
if (oldTexture.RefCount <= 0)
{
- var bmp = q.Data as Bitmap;
+ var bmp = q.Data as SKBitmap;
if (bmp != null)
{
bmp.Dispose();
@@ -517,9 +520,9 @@ namespace Vocaluxe.Lib.Draw
TTextureType texture = null;
// ReSharper disable CanBeReplacedWithTryCastAndCheckForNull
- if (q.Data is Bitmap)
+ if (q.Data is SKBitmap)
{
- var bmp = (Bitmap)q.Data;
+ var bmp = (SKBitmap)q.Data;
_WriteBitmapToTexture(ref texture, bmp);
bmp.Dispose();
}
@@ -541,7 +544,7 @@ namespace Vocaluxe.Lib.Draw
Debug.Assert(textureRef != null, "Queued type is wrong");
if (!_Textures.ContainsKey(textureRef.Id))
{
- var bmp = q.Data as Bitmap;
+ var bmp = q.Data as SKBitmap;
if (bmp != null)
{
bmp.Dispose();
@@ -553,9 +556,9 @@ namespace Vocaluxe.Lib.Draw
if (q.Action == EQueueAction.Update)
{
// ReSharper disable CanBeReplacedWithTryCastAndCheckForNull
- if (q.Data is Bitmap)
+ if (q.Data is SKBitmap)
{
- var bmp = (Bitmap)q.Data;
+ var bmp = (SKBitmap)q.Data;
UpdateTexture(textureRef, bmp);
bmp.Dispose();
}
@@ -618,7 +621,7 @@ namespace Vocaluxe.Lib.Draw
///
///
/// True if resizing should be done in an extra thread
- private void _EnqueueTextureAddOrUpdate(Object texture, Bitmap bmp, EQueueAction action, bool asyncResize)
+ private void _EnqueueTextureAddOrUpdate(Object texture, SKBitmap bmp, EQueueAction action, bool asyncResize)
{
Debug.Assert(action == EQueueAction.Add || action == EQueueAction.Update);
Debug.Assert(action != EQueueAction.Add || texture is TTextureType);
@@ -649,9 +652,9 @@ namespace Vocaluxe.Lib.Draw
///
///
///
- private void _ResizeTextureAndEnqueue(object texture, Bitmap bmp, EQueueAction action)
+ private void _ResizeTextureAndEnqueue(object texture, SKBitmap bmp, EQueueAction action)
{
- var bmp2 = bmp.Resize(_GetNewTextureSize(bmp.Size));
+ var bmp2 = bmp.Resize(_GetNewTextureSize(bmp.GetSize()));
bmp.Dispose();
lock (_TextureQueue)
{
@@ -750,7 +753,7 @@ namespace Vocaluxe.Lib.Draw
return textureRef;
}
- public CTextureRef AddTexture(Bitmap bmp)
+ public CTextureRef AddTexture(SKBitmap bmp)
{
_EnsureMainThread();
return AddTexture(bmp, null);
@@ -762,7 +765,7 @@ namespace Vocaluxe.Lib.Draw
/// The Bitmap of which the texure will be created from
///
/// A reference to the texture
- public CTextureRef AddTexture(Bitmap bmp, string texturePath)
+ public CTextureRef AddTexture(SKBitmap bmp, string texturePath)
{
_EnsureMainThread();
if (bmp.Height == 0 || bmp.Width == 0)
@@ -817,7 +820,7 @@ namespace Vocaluxe.Lib.Draw
}
}
- public CTextureRef EnqueueTexture(Bitmap bmp)
+ public CTextureRef EnqueueTexture(SKBitmap bmp)
{
var texture = _CreateTexture(new Size(-1, -1));
var textureRef = _GetTextureReference(bmp.GetSize(), texture);
@@ -882,7 +885,7 @@ namespace Vocaluxe.Lib.Draw
return textureRef;
}
- public void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp)
+ public void EnqueueTextureUpdate(CTextureRef textureRef, SKBitmap bmp)
{
_EnqueueTextureAddOrUpdate(textureRef, bmp, EQueueAction.Update, true);
}
@@ -923,7 +926,7 @@ namespace Vocaluxe.Lib.Draw
}
}
- public void UpdateTexture(CTextureRef textureRef, Bitmap bmp)
+ public void UpdateTexture(CTextureRef textureRef, SKBitmap bmp)
{
_EnsureMainThread();
TTextureType texture;
@@ -932,7 +935,7 @@ namespace Vocaluxe.Lib.Draw
return;
}
- var reuseTexture = _IsTextureUsable(texture, bmp.Size);
+ var reuseTexture = _IsTextureUsable(texture, bmp.GetSize());
if (reuseTexture && texture.RefCount == 1)
{
_WriteBitmapToTexture(ref texture, bmp);
@@ -947,6 +950,37 @@ namespace Vocaluxe.Lib.Draw
}
}
+ public virtual bool SupportsYuvVideo
+ {
+ get { return false; }
+ }
+
+ protected virtual void _WriteYuvDataToTexture(TTextureType texture, byte[] data, float[] yuvToRgbRows)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void UpdateYuvTexture(ref CTextureRef textureRef, int w, int h, byte[] data, float[] yuvToRgbRows)
+ {
+ _EnsureMainThread();
+ var size = new Size(w, h);
+ TTextureType texture;
+ if (_GetTexture(textureRef, out texture) && texture.DataSize.Equals(size) && texture.Size.Equals(size))
+ {
+ _WriteYuvDataToTexture(texture, data, yuvToRgbRows);
+ return;
+ }
+
+ if (textureRef != null)
+ {
+ RemoveTexture(ref textureRef);
+ }
+
+ texture = _CreateTexture(size);
+ _WriteYuvDataToTexture(texture, data, yuvToRgbRows);
+ textureRef = _GetTextureReference(size, texture);
+ }
+
public CTextureRef CopyTexture(CTextureRef textureRef)
{
// Lock to make sure the texture is not disposed while we are in this method
diff --git a/Vocaluxe/Lib/Draw/IDraw.cs b/Vocaluxe/Lib/Draw/IDraw.cs
index 1fc3fd7d..27e4bd75 100644
--- a/Vocaluxe/Lib/Draw/IDraw.cs
+++ b/Vocaluxe/Lib/Draw/IDraw.cs
@@ -16,6 +16,7 @@
#endregion
using System.Drawing;
+using SkiaSharp;
using VocaluxeLib;
using VocaluxeLib.Draw;
@@ -37,15 +38,17 @@ namespace Vocaluxe.Lib.Draw
void MakeScreenShot();
CTextureRef AddTexture(string texturePath);
- CTextureRef AddTexture(Bitmap bitmap);
+ CTextureRef AddTexture(SKBitmap bitmap);
CTextureRef AddTexture(int w, int h, byte[] data);
- void UpdateTexture(CTextureRef texture, Bitmap bmp);
+ void UpdateTexture(CTextureRef texture, SKBitmap bmp);
void UpdateTexture(CTextureRef texture, int w, int h, byte[] data);
CTextureRef EnqueueTexture(string texturePath);
- CTextureRef EnqueueTexture(Bitmap bmp);
+ CTextureRef EnqueueTexture(SKBitmap bmp);
CTextureRef EnqueueTexture(int w, int h, byte[] data);
- void EnqueueTextureUpdate(CTextureRef textureRef, Bitmap bmp);
+ void EnqueueTextureUpdate(CTextureRef textureRef, SKBitmap bmp);
CTextureRef CopyTexture(CTextureRef textureRef);
+ bool SupportsYuvVideo { get; }
+ void UpdateYuvTexture(ref CTextureRef textureRef, int w, int h, byte[] data, float[] yuvToRgbRows);
void RemoveTexture(ref CTextureRef texture);
// Basic Draw Methods
diff --git a/Vocaluxe/Lib/Input/CGamePad.cs b/Vocaluxe/Lib/Input/CGamePad.cs
index 24587297..cc1f981b 100644
--- a/Vocaluxe/Lib/Input/CGamePad.cs
+++ b/Vocaluxe/Lib/Input/CGamePad.cs
@@ -19,17 +19,14 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
-using System.Windows.Forms;
-using OpenTK.Input;
+using SDL;
using Vocaluxe.Base;
using VocaluxeLib;
-using ButtonState = OpenTK.Input.ButtonState;
namespace Vocaluxe.Lib.Input
{
- class CGamePad : CControllerFramework
+ unsafe class CGamePad : CControllerFramework
{
- private const int MaxGamePads = 4;
private const int PollIntervalMs = 5;
private const int ReconnectWaitMs = 1000;
private const int ErrorBackoffMs = 2000;
@@ -67,7 +64,8 @@ namespace Vocaluxe.Lib.Input
private volatile int _GamePadIndex = -1;
private volatile bool _Active;
private int _LastConnectedIndex = -1;
- private GamePadState _OldState;
+ private SDL_Gamepad* _GamePad;
+ private SGamePadState _OldState;
private Thread _HandlerThread;
private ManualResetEvent _EvTerminate;
private CRumbleTimer _RumbleTimer;
@@ -221,8 +219,8 @@ namespace Vocaluxe.Lib.Input
}
finally
{
- _StopVibration(_GamePadIndex);
- _GamePadIndex = -1;
+ _StopVibration(_GamePad);
+ _CloseGamePad();
}
}
@@ -250,14 +248,21 @@ namespace Vocaluxe.Lib.Input
return false;
}
+ var gamePad = SDL3.SDL_OpenGamepad((SDL_JoystickID)index);
+ if (gamePad == null)
+ {
+ return false;
+ }
+
_ResetInputState();
+ _GamePad = gamePad;
_GamePadIndex = index;
var isNewPad = index != _LastConnectedIndex;
_LastConnectedIndex = index;
if (isNewPad)
{
- _PlayConnectRumble(index, evTerminate);
+ _PlayConnectRumble(gamePad, evTerminate);
}
return true;
@@ -265,37 +270,37 @@ namespace Vocaluxe.Lib.Input
private static int _FindConnectedPad()
{
- for (var i = 0; i < MaxGamePads; i++)
+ try
{
- try
+ using (var gamePads = SDL3.SDL_GetGamepads())
{
- if (GamePad.GetCapabilities(i).IsConnected)
+ if (gamePads != null && gamePads.Count > 0)
{
- return i;
+ return (int)gamePads[0];
}
}
- catch (Exception ex)
- {
- Debug.WriteLine("CGamePad: Failed to query capabilities for pad " + i + ": " + ex);
- }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine("CGamePad: Failed to query the connected pads: " + ex);
}
return -1;
}
- private static void _PlayConnectRumble(int index, WaitHandle evTerminate)
+ private static void _PlayConnectRumble(SDL_Gamepad* gamePad, WaitHandle evTerminate)
{
try
{
for (var i = 0; i < ConnectRumblePulses; i++)
{
- GamePad.SetVibration(index, 1.0f, 1.0f);
+ _SetVibration(gamePad, true);
if (evTerminate.WaitOne(ConnectRumblePulseMs))
{
break;
}
- GamePad.SetVibration(index, 0.0f, 0.0f);
+ _SetVibration(gamePad, false);
if (evTerminate.WaitOne(ConnectRumblePulseMs))
{
break;
@@ -308,30 +313,34 @@ namespace Vocaluxe.Lib.Input
}
finally
{
- _StopVibration(index);
+ _StopVibration(gamePad);
}
}
+ private static void _SetVibration(SDL_Gamepad* gamePad, bool on)
+ {
+ SDL3.SDL_RumbleGamepad(gamePad, on ? ushort.MaxValue : (ushort)0, on ? ushort.MaxValue : (ushort)0, on ? 10000u : 0u);
+ }
+
private void _ProcessCurrentGamePad()
{
- var index = _GamePadIndex;
- if (index == -1)
+ var gamePad = _GamePad;
+ if (_GamePadIndex == -1 || gamePad == null)
{
return;
}
- var state = GamePad.GetState(index);
- if (!state.IsConnected)
+ if (!SDL3.SDL_GamepadConnected(gamePad))
{
_HandleDisconnect(true);
return;
}
- _UpdateRumble(index);
- _HandleInput(state);
+ _UpdateRumble(gamePad);
+ _HandleInput(SGamePadState.Get(gamePad));
}
- private void _UpdateRumble(int index)
+ private void _UpdateRumble(SDL_Gamepad* gamePad)
{
bool startRumble;
bool stopRumble;
@@ -344,18 +353,18 @@ namespace Vocaluxe.Lib.Input
if (startRumble)
{
- GamePad.SetVibration(index, 1.0f, 1.0f);
+ _SetVibration(gamePad, true);
}
else if (stopRumble)
{
- GamePad.SetVibration(index, 0.0f, 0.0f);
+ _SetVibration(gamePad, false);
}
}
private void _HandleDisconnect(bool padRemoved)
{
- _StopVibration(_GamePadIndex);
- _GamePadIndex = -1;
+ _StopVibration(_GamePad);
+ _CloseGamePad();
if (padRemoved)
{
@@ -365,16 +374,27 @@ namespace Vocaluxe.Lib.Input
_ResetInputState();
}
- private static void _StopVibration(int gamePadIndex)
+ private void _CloseGamePad()
{
- if (gamePadIndex == -1)
+ var gamePad = _GamePad;
+ _GamePadIndex = -1;
+ _GamePad = null;
+ if (gamePad != null)
+ {
+ SDL3.SDL_CloseGamepad(gamePad);
+ }
+ }
+
+ private static void _StopVibration(SDL_Gamepad* gamePad)
+ {
+ if (gamePad == null)
{
return;
}
try
{
- GamePad.SetVibration(gamePadIndex, 0.0f, 0.0f);
+ _SetVibration(gamePad, false);
}
catch (Exception ex)
{
@@ -384,7 +404,7 @@ namespace Vocaluxe.Lib.Input
private void _ResetInputState()
{
- _OldState = new GamePadState();
+ _OldState = new SGamePadState();
foreach (var input in _RepeatingInputs)
{
input.Reset();
@@ -393,7 +413,7 @@ namespace Vocaluxe.Lib.Input
_MouseTimer.Reset();
}
- private void _HandleInput(GamePadState state)
+ private void _HandleInput(SGamePadState state)
{
_PendingKeys.Clear();
@@ -432,7 +452,7 @@ namespace Vocaluxe.Lib.Input
_OldState = state;
}
- private void _HandleMouse(GamePadState state)
+ private void _HandleMouse(SGamePadState state)
{
var leftClick =
_IsNewlyPressed(state.Buttons.LeftShoulder, _OldState.Buttons.LeftShoulder) ||
@@ -483,9 +503,9 @@ namespace Vocaluxe.Lib.Input
return Math.Max(min, Math.Min(max, value));
}
- private static bool _IsNewlyPressed(ButtonState now, ButtonState old)
+ private static bool _IsNewlyPressed(bool now, bool old)
{
- return now == ButtonState.Pressed && old == ButtonState.Released;
+ return now && !old;
}
private static bool _IsAxisPressed(float value, bool wasPressed)
@@ -498,21 +518,82 @@ namespace Vocaluxe.Lib.Input
return value >= (wasPressed ? TriggerReleaseThreshold : TriggerPressThreshold);
}
+ private struct SGamePadState
+ {
+ public struct SButtons
+ {
+ public bool A, B, Back, Start, LeftShoulder, RightShoulder, RightStick;
+ }
+
+ public struct SDPad
+ {
+ public bool IsUp, IsDown, IsLeft, IsRight;
+ }
+
+ public struct SStick
+ {
+ public float X, Y;
+ }
+
+ public struct SThumbSticks
+ {
+ public SStick Left, Right;
+ }
+
+ public struct STriggers
+ {
+ public float Left, Right;
+ }
+
+ public SButtons Buttons;
+ public SDPad DPad;
+ public SThumbSticks ThumbSticks;
+ public STriggers Triggers;
+
+ public static SGamePadState Get(SDL_Gamepad* gamePad)
+ {
+ var state = new SGamePadState();
+ state.Buttons.A = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH);
+ state.Buttons.B = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST);
+ state.Buttons.Back = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_BACK);
+ state.Buttons.Start = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START);
+ state.Buttons.LeftShoulder = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
+ state.Buttons.RightShoulder = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
+ state.Buttons.RightStick = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_STICK);
+ state.DPad.IsUp = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_UP);
+ state.DPad.IsDown = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_DOWN);
+ state.DPad.IsLeft = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_LEFT);
+ state.DPad.IsRight = SDL3.SDL_GetGamepadButton(gamePad, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
+ state.ThumbSticks.Left.X = _GetAxis(gamePad, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX);
+ state.ThumbSticks.Left.Y = -_GetAxis(gamePad, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY);
+ state.ThumbSticks.Right.X = _GetAxis(gamePad, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTX);
+ state.ThumbSticks.Right.Y = -_GetAxis(gamePad, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTY);
+ state.Triggers.Left = _GetAxis(gamePad, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
+ state.Triggers.Right = _GetAxis(gamePad, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
+ return state;
+ }
+
+ private static float _GetAxis(SDL_Gamepad* gamePad, SDL_GamepadAxis axis)
+ {
+ return Math.Max(-1f, SDL3.SDL_GetGamepadAxis(gamePad, axis) / 32767f);
+ }
+ }
+
private sealed class CRepeatingInput
{
private readonly Keys _Key;
- private readonly Func _IsPressed;
+ private readonly Func _IsPressed;
private readonly Stopwatch _Timer = new Stopwatch();
private bool _Pressed;
private bool _Repeating;
- public CRepeatingInput(Keys key, Func isPressed)
+ public CRepeatingInput(Keys key, Func isPressed)
{
_Key = key;
_IsPressed = isPressed;
}
- public void Update(GamePadState state, ICollection keys)
+ public void Update(SGamePadState state, ICollection keys)
{
if (!_IsPressed(state, _Pressed))
{
diff --git a/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs b/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs
index a30b79d9..be00160a 100644
--- a/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs
+++ b/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs
@@ -18,7 +18,6 @@
using System;
using System.Drawing;
using System.Threading;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
diff --git a/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs b/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs
index 83a1caea..6ca9cf86 100644
--- a/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs
+++ b/Vocaluxe/Lib/Sound/Playback/Decoder/CAudioDecoderFFmpeg.cs
@@ -17,21 +17,18 @@
using System;
using System.Runtime.InteropServices;
-using Vocaluxe.Lib.Video.Acinerella;
+using Vocaluxe.Lib.Video.FFmpeg;
using VocaluxeLib.Log;
namespace Vocaluxe.Lib.Sound.Playback.Decoder
{
class CAudioDecoderFFmpeg : IAudioDecoder, IDisposable
{
- private IntPtr _InstancePtr = IntPtr.Zero;
- private IntPtr _Audiodecoder = IntPtr.Zero;
+ private CFFmpegDecoder _Decoder;
- private SACInstance _Instance;
private SFormatInfo _FormatInfo;
private float _CurrentTime;
private const int _MaxDecodeTries = 10;
- private bool _ConvertFromFloat;
private string _FileName;
private bool _FileOpened;
@@ -42,9 +39,13 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
try
{
- _InstancePtr = CAcinerella.AcInit();
- CAcinerella.AcOpen2(_InstancePtr, _FileName);
- _Instance = (SACInstance)Marshal.PtrToStructure(_InstancePtr, typeof(SACInstance));
+ _Decoder = new CFFmpegDecoder(EFFmpegStreamType.Audio);
+ if (!_Decoder.Open(_FileName))
+ {
+ CLog.Error("Error opening audio file: " + _FileName);
+ Close();
+ return false;
+ }
}
catch (Exception)
{
@@ -53,73 +54,25 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
return false;
}
-
- if (!_Instance.Opened)
- {
- Close();
- return false;
- }
-
- int audioStreamIndex;
- SACDecoder audiodecoder;
- try
- {
- _Audiodecoder = CAcinerella.AcCreateAudioDecoder(_InstancePtr);
- audiodecoder = (SACDecoder)Marshal.PtrToStructure(_Audiodecoder, typeof(SACDecoder));
- audioStreamIndex = audiodecoder.StreamIndex;
- }
- catch (Exception)
- {
- CLog.Error("Error opening audio file (can't find decoder): " + _FileName);
- Close();
- return false;
- }
-
- if (audioStreamIndex < 0)
- {
- Close();
- return false;
- }
-
_FormatInfo = new SFormatInfo
{
- SamplesPerSecond = audiodecoder.StreamInfo.AudioInfo.SamplesPerSecond,
- BitDepth = audiodecoder.StreamInfo.AudioInfo.BitDepth,
- ChannelCount = audiodecoder.StreamInfo.AudioInfo.ChannelCount
+ SamplesPerSecond = _Decoder.SamplesPerSecond,
+ BitDepth = 16,
+ ChannelCount = _Decoder.ChannelCount
};
_CurrentTime = 0f;
- _ConvertFromFloat = _FormatInfo.BitDepth == 32;
- if (_ConvertFromFloat)
- {
- _FormatInfo.BitDepth = 16;
- }
-
- if (_FormatInfo.BitDepth != 16)
- {
- CLog.Error("Unsupported BitDepth in file " + fileName);
- Close();
- return false;
- }
-
_FileOpened = true;
return true;
}
public void Close()
{
- if (_Audiodecoder != IntPtr.Zero)
+ if (_Decoder != null)
{
- CAcinerella.AcFreeDecoder(_Audiodecoder);
- _Audiodecoder = IntPtr.Zero;
- }
-
- if (_InstancePtr != IntPtr.Zero)
- {
- CAcinerella.AcClose(_InstancePtr);
- CAcinerella.AcFree(_InstancePtr);
- _InstancePtr = IntPtr.Zero;
+ _Decoder.Dispose();
+ _Decoder = null;
}
_FileOpened = false;
@@ -132,7 +85,7 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
public float GetLength()
{
- return _FileOpened ? _Instance.Info.Duration / 1000f : 0;
+ return _FileOpened ? _Decoder.Length : 0;
}
public void SetPosition(float time)
@@ -144,7 +97,7 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
try
{
- CAcinerella.AcSeek(_Audiodecoder, time > _CurrentTime ? 0 : -1, (Int64)(time * 1000f));
+ _Decoder.Seek(time <= _CurrentTime, (Int64)(time * 1000f));
}
catch (Exception)
{
@@ -158,25 +111,6 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
return _FileOpened ? _CurrentTime : 0f;
}
- private static byte[] _ConvertFloatToInt16(IntPtr source, int sourceSize)
- {
- var samples = new float[sourceSize / sizeof(float)];
- if (samples.Length > 0)
- {
- Marshal.Copy(source, samples, 0, samples.Length);
- }
-
- var buffer = new byte[samples.Length * sizeof(Int16)];
- for (var i = 0; i < samples.Length; i++)
- {
- var sample = (Int16)(Math.Max(-1f, Math.Min(1f, samples[i])) * Int16.MaxValue);
- buffer[2 * i] = (byte)sample;
- buffer[2 * i + 1] = (byte)(sample >> 8);
- }
-
- return buffer;
- }
-
public void Decode(out byte[] buffer, out float timeStamp)
{
if (!_FileOpened)
@@ -189,27 +123,18 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
var frameFinished = false;
for (var i = 0; i < _MaxDecodeTries && !frameFinished; i++)
{
- frameFinished = CAcinerella.AcGetAudioFrame(_InstancePtr, _Audiodecoder);
+ frameFinished = _Decoder.GetFrame();
}
if (frameFinished)
{
- var decoder = (SACDecoder)Marshal.PtrToStructure(_Audiodecoder, typeof(SACDecoder));
-
- timeStamp = (float)decoder.Timecode;
+ timeStamp = (float)_Decoder.Timecode;
_CurrentTime = timeStamp;
- //Console.WriteLine(_CurrentTime.ToString("#0.000") + " Buffer size: " + Decoder.buffer_size.ToString());
- if (_ConvertFromFloat)
- {
- buffer = _ConvertFloatToInt16(decoder.Buffer, decoder.BufferSize);
- return;
- }
+ buffer = new byte[_Decoder.BufferSize];
- buffer = new byte[decoder.BufferSize];
-
- if (decoder.BufferSize > 0)
+ if (_Decoder.BufferSize > 0)
{
- Marshal.Copy(decoder.Buffer, buffer, 0, buffer.Length);
+ Marshal.Copy(_Decoder.Buffer, buffer, 0, buffer.Length);
}
return;
@@ -221,4 +146,4 @@ namespace Vocaluxe.Lib.Sound.Playback.Decoder
public void Dispose() { }
}
-}
\ No newline at end of file
+}
diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs
index 79685469..4bc589e2 100644
--- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs
+++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenALPlay.cs
@@ -15,13 +15,14 @@
// along with Vocaluxe. If not, see .
#endregion
-using OpenTK.Audio;
+using OpenTK.Audio.OpenAL;
namespace Vocaluxe.Lib.Sound.Playback.OpenAL
{
class COpenALPlay : CPlaybackBase
{
- private AudioContext _Context;
+ private ALDevice _Device;
+ private ALContext _Context;
public override bool Init()
{
@@ -30,8 +31,14 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
return false;
}
- _Context = new AudioContext();
- _Context.MakeCurrent();
+ _Device = ALC.OpenDevice(null);
+ if (_Device == ALDevice.Null)
+ {
+ return false;
+ }
+
+ _Context = ALC.CreateContext(_Device, (int[])null);
+ ALC.MakeContextCurrent(_Context);
_Initialized = true;
return true;
@@ -45,8 +52,11 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
}
base.Close();
- _Context.Dispose();
- _Context = null;
+ ALC.MakeContextCurrent(ALContext.Null);
+ ALC.DestroyContext(_Context);
+ ALC.CloseDevice(_Device);
+ _Context = ALContext.Null;
+ _Device = ALDevice.Null;
}
protected override IAudioStream _CreateStream(int id, string media, bool loop, EAudioEffect effect = EAudioEffect.None)
diff --git a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs
index 0b1939f7..6942b952 100644
--- a/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs
+++ b/Vocaluxe/Lib/Sound/Playback/OpenAL/COpenAlStream.cs
@@ -19,7 +19,7 @@ using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
-using OpenTK.Audio;
+using OpenTK.Audio.OpenAL;
using Vocaluxe.Base;
using Vocaluxe.Lib.Sound.Playback.Decoder;
using VocaluxeLib.Log;
@@ -450,7 +450,7 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
if (buffer != 0)
{
var alFormat = _Format.ChannelCount == 2 ? ALFormat.Stereo16 : ALFormat.Mono16;
- AL.BufferData(buffer, alFormat, _SampleBuf, _SampleBuf.Length, _Format.SamplesPerSecond);
+ AL.BufferData(buffer, alFormat, _SampleBuf, _Format.SamplesPerSecond);
AL.SourceQueueBuffer(_Source, buffer);
}
@@ -463,7 +463,7 @@ namespace Vocaluxe.Lib.Sound.Playback.OpenAL
}
}
- if (queuedCount > 0 && AL.GetSourceState(_Source) != ALSourceState.Playing)
+ if (queuedCount > 0 && (ALSourceState)AL.GetSource(_Source, ALGetSourcei.SourceState) != ALSourceState.Playing)
{
AL.SourcePlay(_Source);
}
diff --git a/Vocaluxe/Lib/Sound/Record/NAudio/CNAudioRecord.cs b/Vocaluxe/Lib/Sound/Record/NAudio/CNAudioRecord.cs
index 08b51ed0..d50a6672 100644
--- a/Vocaluxe/Lib/Sound/Record/NAudio/CNAudioRecord.cs
+++ b/Vocaluxe/Lib/Sound/Record/NAudio/CNAudioRecord.cs
@@ -31,7 +31,7 @@ namespace Vocaluxe.Lib.Sound.Record.NAudio
private bool _Initialized;
private MMDeviceEnumerator _Enumerator;
- private readonly List _Captures = new List();
+ private readonly List _Captures = new List();
public override bool Init()
{
@@ -48,7 +48,7 @@ namespace Vocaluxe.Lib.Sound.Record.NAudio
try
{
using (device)
- using (var client = device.AudioClient)
+ using (var client = device.CreateAudioClient())
{
_Devices.Add(new CRecordDevice(_Devices.Count, device.FriendlyName, device.ID, client.MixFormat.Channels));
}
@@ -102,19 +102,22 @@ namespace Vocaluxe.Lib.Sound.Record.NAudio
continue;
}
- WasapiCapture capture = null;
+ WasapiRecorder capture = null;
try
{
using (var mmDevice = _Enumerator.GetDevice(dev.Driver))
{
- capture = new WasapiCapture(mmDevice, true, _BufferMilliseconds)
- {
- WaveFormat = new WaveFormat(_SampleRate, _BitsPerSample, dev.Channels)
- };
+ capture = new WasapiRecorderBuilder()
+ .WithDevice(mmDevice)
+ .WithSharedMode()
+ .WithEventSync()
+ .WithBufferLength(_BufferMilliseconds)
+ .WithFormat(new WaveFormat(_SampleRate, _BitsPerSample, dev.Channels))
+ .Build();
}
var device = dev;
- capture.DataAvailable += (sender, e) => _OnDataAvailable(device, e);
+ capture.DataAvailable += (buffer, flags, devicePosition, qpcPosition) => _OnDataAvailable(device, buffer, flags);
capture.RecordingStopped += (sender, e) => _OnRecordingStopped(device, e);
capture.StartRecording();
_Captures.Add(capture);
@@ -176,17 +179,21 @@ namespace Vocaluxe.Lib.Sound.Record.NAudio
}
}
- private void _OnDataAvailable(CRecordDevice device, WaveInEventArgs e)
+ private void _OnDataAvailable(CRecordDevice device, ReadOnlySpan buffer, AudioClientBufferFlags flags)
{
- if (e.BytesRecorded <= 0)
+ if (buffer.Length <= 0)
{
return;
}
try
{
- var recbuffer = new byte[e.BytesRecorded];
- Array.Copy(e.Buffer, recbuffer, e.BytesRecorded);
+ var recbuffer = new byte[buffer.Length];
+ if ((flags & AudioClientBufferFlags.Silent) == 0)
+ {
+ buffer.CopyTo(recbuffer);
+ }
+
_HandleData(device, recbuffer);
}
catch (Exception ex)
diff --git a/Vocaluxe/Lib/Video/Acinerella/CAcinerella.cs b/Vocaluxe/Lib/Video/Acinerella/CAcinerella.cs
deleted file mode 100644
index e460b96a..00000000
--- a/Vocaluxe/Lib/Video/Acinerella/CAcinerella.cs
+++ /dev/null
@@ -1,453 +0,0 @@
-#region license
-// This file is part of Vocaluxe.
-//
-// Vocaluxe is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Vocaluxe is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Vocaluxe. If not, see .
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-
-namespace Vocaluxe.Lib.Video.Acinerella
-{
- // ReSharper disable UnusedMember.Global
- public enum EACStreamType : sbyte
- {
- /*The type of the media stream is not known. This kind of stream can not be
- decoded.*/
- ACStreamTypeUnknown = -1,
- //This media stream is a video stream.
- ACStreamTypeVideo = 0,
- //This media stream is an audio stream.
- ACStreamTypeAudio = 1
- }
-
- //Defines the type of an Acinerella media decoder.
- public enum EACDecoderType : byte
- {
- //This decoder is used to decode a video stream.
- ACDecoderTypeVideo = 0,
- //This decoder is used to decode an audio stram.
- ACDecoderTypeAudio = 1
- }
-
- //Defines the format video/image data is outputted in
- public enum EACOutputFormat : byte
- {
- ACOutputRGB24 = 0,
- ACOutputBGR24 = 1,
- ACOutputRGBA32 = 2,
- ACOutputBGra32 = 3
- }
-
- // ReSharper disable MemberCanBePrivate.Global
-
- // Contains information about the whole file/stream that has been opened. Default
- // values are "" for strings and -1 for integer values.
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
- public struct SACFileInfo
- {
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
- public readonly string title;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
- public readonly string author;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
- public readonly string copyright;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
- public readonly string comment;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
- public readonly string album;
- public readonly int year;
- public readonly int track;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
- public readonly string genre;
- public readonly Int64 Duration;
- public readonly int Bitrate;
- }
-
- // TAc_instance represents an Acinerella instance. Each instance can open and
- // decode one file at once. There can be only 26 Acinerella instances opened at
- // once.
- [StructLayout(LayoutKind.Sequential)]
- public struct SACInstance
- {
- //If true, the instance currently opened a media file
- public readonly bool Opened;
- //Contains the count of streams the media file has. This value is available
- //after calling the ac_open function.
- public readonly Int32 StreamCount;
- //Set this value to change the image output format
- public readonly EACOutputFormat OutputFormat;
- //Contains information about the opened stream/file
- public SACFileInfo Info;
- }
-
- // Contains information about an Acinerella audio stream.
- [StructLayout(LayoutKind.Sequential)]
- public struct SACAudioStreamInfo
- {
- //Samples per second. Default values are 44100 or 48000.
- public readonly Int32 SamplesPerSecond;
- //Bits per sample. Can be 8 or 16 Bit.
- public readonly Int32 BitDepth;
- //Count of channels in the audio stream.
- public readonly Int32 ChannelCount;
- }
-
- // Contains information about an Acinerella video stream.
- [StructLayout(LayoutKind.Sequential)]
- public struct SACVideoStreamInfo
- {
- //The width of one frame.
- public readonly Int32 FrameWidth;
- //The height of one frame.
- public readonly Int32 FrameHeight;
- //The width of one pixel. 1.07 for 4:3 format, 1,42 for the 16:9 format
- public readonly float PixelAspect;
- //Frames per second that should be played.
- public readonly double FramesPerSecond;
- }
-
- // Contains information about an Acinerella stream.
- [StructLayout(LayoutKind.Explicit)]
- public struct SACStreamInfo
- {
- //Contains the type of the stream.
- [FieldOffset(0)]
- public readonly EACStreamType StreamType;
-
- //Additional info about the stream
- [FieldOffset(8)]
- public SACAudioStreamInfo AudioInfo;
- [FieldOffset(8)]
- public SACVideoStreamInfo VideoInfo;
- }
-
- // Contains information about an Acinerella video/audio decoder.
- [StructLayout(LayoutKind.Sequential)]
- public struct SACDecoder
- {
- //Pointer on the Acinerella instance
- private readonly IntPtr _ACInstancePtr;
- //Contains the type of the decoder.
- public readonly EACDecoderType DecType;
-
- //The timecode of the currently decoded picture in seconds.
- public readonly double Timecode;
-
- //public readonly double VideoClock;
-
- //Contains information about the stream the decoder is attached to.
- public SACStreamInfo StreamInfo;
- //The index of the stream the decoder is attached to.
- public readonly Int32 StreamIndex;
-
- //Pointer to the buffer which contains the data.
- internal IntPtr Buffer;
- //Size of the data in the buffer.
- public readonly Int32 BufferSize;
- }
-
- // Contains information about an Acinerella package.
- [StructLayout(LayoutKind.Sequential)]
- public struct SACPackage
- {
- //The stream the package belongs to.
- public readonly Int32 StreamIndex;
- }
-
- // ReSharper restore MemberCanBePrivate.Global
-
- // Callback function used to ask the application to read data. Should return
- // the number of bytes read or an value smaller than zero if an error occured.
- //TAc_read_callback = function(sender: Pointer; byte[] buf, int size): integer; cdecl;
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate Int32 ACReadCallback(IntPtr sender, IntPtr buf, Int32 size);
-
- // Callback function used to ask the application to seek. return 0 if succeed , -1 on failure.
- //TAc_seek_callback = function(sender: Pointer; pos: int64; whence: integer): int64; cdecl;
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate Int64 ACSeekCallback(IntPtr sender, Int64 pos, Int32 whence);
-
- // Callback function that is used to notify the application when the data stream
- // is opened or closed. For example the file pointer should be resetted to zero
- // when the "open" function is called.
- // TAc_openclose_callback = function(sender: Pointer): integer; cdecl;
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate Int32 ACOpencloseCallback(IntPtr sender);
-
- public static class CAcinerella
- {
-#if ARCH_X86
-#if WIN
- private const string _AcDll = "acinerella.dll";
-#endif
-
-#if LINUX
- private const string _AcDll = "libacinerella.so";
-#endif
-#endif
-
-#if ARCH_X64
-#if WIN
- private const string _AcDll = "acinerella.dll";
-#endif
-
-#if LINUX
- private const string _AcDll = "libacinerella.so";
-#endif
-#endif
-
- private static readonly Object _Lock = new Object();
- private static readonly Object _DictionaryLock = new Object();
- private static readonly Dictionary _AcInstanceLocks = new Dictionary(20);
-
- private static object _GetLockToken(IntPtr pAcDecoder)
- {
- object l;
-
- lock (_DictionaryLock)
- {
- _AcInstanceLocks.TryGetValue(pAcDecoder.ToInt64(), out l);
- if (l == null)
- {
- l = new object();
- _AcInstanceLocks.Add(pAcDecoder.ToInt64(), l);
- }
- }
-
- return l;
- }
-
- // Defines the type of an Acinerella media stream. Currently only video and
- // audio streams are supported, subtitle and data streams will be marked as
- // "unknown".
-
- // Initializes an Acinerella instance.
- //function ac_init(): PAc_instance; cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_init_of", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern IntPtr _ac_init(EACOutputFormat outputFormat);
-
- public static IntPtr AcInit()
- {
- lock (_Lock)
- {
- return _ac_init(EACOutputFormat.ACOutputRGBA32);
- }
- }
-
- // Frees an Acinerella instance.
- //procedure ac_free(inst: PAc_instance); cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_free", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern void _ac_free(IntPtr pAcInstance);
-
- public static void AcFree(IntPtr pAcInstance)
- {
- lock (_GetLockToken(pAcInstance))
- {
- _ac_free(pAcInstance);
- }
- }
-
- [DllImport(_AcDll, EntryPoint = "ac_open_file", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- private static extern Int32 _ac_open2(IntPtr pAcInstance, string filename);
-
- // ReSharper disable UnusedMethodReturnValue.Global
- public static Int32 AcOpen2(IntPtr pAcInstance, string filename)
- // ReSharper restore UnusedMethodReturnValue.Global
- {
- lock (_GetLockToken(pAcInstance))
- {
- return _ac_open2(pAcInstance, filename);
- }
- }
-
- // Closes an opened media file.
- //procedure ac_close(inst: PAc_instance);cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_close", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern void _ac_close(IntPtr pAcInstance);
-
- public static void AcClose(IntPtr pAcInstance)
- {
- lock (_GetLockToken(pAcInstance))
- {
- _ac_close(pAcInstance);
- }
- }
-
- // Stores information in "pInfo" about stream number "nb".
- //procedure ac_get_stream_info(inst: PAc_instance; nb: integer; pinfo: PAc_stream_info); cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_get_stream_info", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern void _ac_get_stream_info(IntPtr pAcInstance, Int32 nb, out SACStreamInfo info);
-
- public static void AcGetStreamInfo(IntPtr pAcInstance, Int32 nb, out SACStreamInfo info)
- {
- lock (_GetLockToken(pAcInstance))
- {
- _ac_get_stream_info(pAcInstance, nb, out info);
- }
- }
-
- // Reads a package from an opened media file.
- //function ac_read_package(inst: PAc_instance): PAc_package; cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_read_package", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- public static extern IntPtr ac_read_package(IntPtr inst);
-
- // Frees a package that has been read.
- //procedure ac_free_package(package: PAc_package); cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_free_package", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- public static extern void ac_free_package(IntPtr pAcPackage);
-
- [DllImport(_AcDll, EntryPoint = "ac_create_decoder", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern IntPtr _ac_create_decoder(IntPtr pAcInstance, int i);
-
- public static IntPtr AcCreateVideoDecoder(IntPtr pAcInstance)
- {
- lock (_GetLockToken(pAcInstance))
- {
- var firstVideoStreamId = _GetFirstStreamByType(pAcInstance, EACStreamType.ACStreamTypeVideo);
-
- if (firstVideoStreamId < 0)
- {
- throw new ArgumentException("The instace does not contain a video stream.");
- }
-
- return _ac_create_decoder(pAcInstance, firstVideoStreamId);
- }
- }
-
- public static IntPtr AcCreateAudioDecoder(IntPtr pAcInstance)
- {
- lock (_GetLockToken(pAcInstance))
- {
- var firstAudioStreamId = _GetFirstStreamByType(pAcInstance, EACStreamType.ACStreamTypeAudio);
-
- if (firstAudioStreamId < 0)
- {
- throw new ArgumentException("The instace does not contain a audio stream.");
- }
-
- return _ac_create_decoder(pAcInstance, firstAudioStreamId);
- }
- }
-
- private static int _GetFirstStreamByType(IntPtr pAcInstance, EACStreamType type)
- {
- var instance = (SACInstance)Marshal.PtrToStructure(pAcInstance, typeof(SACInstance));
-
- var currentStream = 0;
- SACStreamInfo currentStreamInfo;
-
- while (currentStream < instance.StreamCount)
- {
- AcGetStreamInfo(pAcInstance, currentStream, out currentStreamInfo);
- if (currentStreamInfo.StreamType == type)
- {
- return currentStream;
- }
-
- currentStream++;
- }
-
- return -1;
- }
-
- // Frees an created decoder.
- //procedure ac_free_decoder(pDecoder: PAc_decoder); cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_free_decoder", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern void _ac_free_decoder(IntPtr pAcDecoder);
-
- public static void AcFreeDecoder(IntPtr pAcDecoder)
- {
- lock (_Lock)
- {
- lock (_GetLockToken(pAcDecoder))
- {
- _ac_free_decoder(pAcDecoder);
- }
- }
- }
-
- // Decodes a package using the specified decoder. The decodec data is stored in the
- // "buffer" property of the decoder.
- //function ac_decode_package(pPackage: PAc_package; pDecoder: PAc_decoder): integer; cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_decode_package", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern Int32 _ac_decode_package(IntPtr pAcPackage, IntPtr pAcDecoder);
-
- public static bool AcDecodePackage(IntPtr pAcPackage, IntPtr pAcDecoder)
- {
- lock (_GetLockToken(pAcDecoder))
- {
- return _ac_decode_package(pAcPackage, pAcDecoder) != 0;
- }
- }
-
- [DllImport(_AcDll, EntryPoint = "ac_get_audio_frame", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern Int32 _ac_get_audio_frame(IntPtr pAcInstance, IntPtr pAcDecoder);
-
- public static bool AcGetAudioFrame(IntPtr pAcInstance, IntPtr pAcDecoder)
- {
- lock (_GetLockToken(pAcDecoder))
- {
- return _ac_get_audio_frame(pAcInstance, pAcDecoder) == 0;
- }
- }
-
- [DllImport(_AcDll, EntryPoint = "ac_get_frame", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern Int32 _ac_get_frame(IntPtr pAcInstance, IntPtr pAcDecoder);
-
- public static bool AcGetFrame(IntPtr pAcInstance, IntPtr pAcDecoder)
- {
- lock (_GetLockToken(pAcDecoder))
- {
- var asd = _ac_get_frame(pAcInstance, pAcDecoder);
- return asd == 0;
- }
- }
-
- [DllImport(_AcDll, EntryPoint = "ac_skip_frames", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern Int32 _ac_skip_frames(IntPtr pAcInstance, IntPtr pAcDecoder, Int32 num);
-
- public static bool AcSkipFrames(IntPtr pAcInstance, IntPtr pAcDecoder, Int32 num)
- {
- lock (_GetLockToken(pAcDecoder))
- {
- return _ac_skip_frames(pAcInstance, pAcDecoder, num) == 0;
- }
- }
-
- // Seeks to the given target position in the file. The seek funtion is not able to seek a single audio/video stream
- // but seeks the whole file forward. The deocder parameter is only used as an timecode reference.
- // The parameter "dir" specifies the seek direction: 0 for forward, -1 for backward.
- // The target_pos paremeter is in milliseconds. Returns 0 if the functions succeded.}
- //function ac_seek(pDecoder: PAc_decoder; dir: integer; target_pos: int64): integer; cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_seek", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- private static extern Int32 _ac_seek(IntPtr pAcDecoder, Int32 dir, Int64 targetPos);
-
- public static bool AcSeek(IntPtr pAcDecoder, Int32 dir, Int64 targetPos)
- {
- lock (_GetLockToken(pAcDecoder))
- {
- return _ac_seek(pAcDecoder, dir, targetPos) == 0;
- }
- }
-
- //function ac_probe_input_buffer(buf: PChar; bufsize: Integer; filename: PChar;
- //var score_max: Integer): PAc_proberesult; cdecl; external ac_dll;
- [DllImport(_AcDll, EntryPoint = "ac_probe_input_buffer", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
- public static extern IntPtr ac_probe_input_buffer(IntPtr buf, Int32 bufsize, IntPtr filename, out Int32 scoreMax);
- }
-
- // ReSharper restore UnusedMember.Global
-}
\ No newline at end of file
diff --git a/Vocaluxe/Lib/Video/Acinerella/acinerella.c b/Vocaluxe/Lib/Video/Acinerella/acinerella.c
deleted file mode 100644
index 73961cd0..00000000
--- a/Vocaluxe/Lib/Video/Acinerella/acinerella.c
+++ /dev/null
@@ -1,1248 +0,0 @@
-/*
- This file is part of Acinerella.
-
- Acinerella is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Acinerella is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Acinerella. If not, see .
-*/
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "acinerella.h"
-
-#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
-#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
-
-#define AC_BUFSIZE 1024 * 64
-#define PROBE_BUF_MIN 1024
-#define PROBE_BUF_MAX (1 << 20)
-
-#define ERR(E) \
- do { \
- if (!(E)) { \
- goto error; \
- } \
- } while (0)
-#define AV_ERR(E) \
- do { \
- if ((E) < 0) { \
- goto error; \
- } \
- } while (0)
-
-struct _ac_data {
- ac_instance instance;
-
- AVFormatContext *pFormatCtx;
- AVIOContext *pIo;
-
- void *sender;
- ac_openclose_callback open_proc;
- ac_read_callback read_proc;
- ac_seek_callback seek_proc;
- ac_openclose_callback close_proc;
-
- uint8_t *buffer;
- uint8_t *probe_buffer;
- size_t probe_buffer_size;
- size_t probe_buffer_offs;
-};
-
-typedef struct _ac_data ac_data;
-typedef ac_data *lp_ac_data;
-
-struct _ac_decoder_data {
- ac_decoder decoder;
- int sought;
- double last_timecode;
-};
-
-typedef struct _ac_decoder_data ac_decoder_data;
-typedef ac_decoder_data *lp_ac_decoder_data;
-
-struct _ac_video_decoder {
- ac_decoder decoder;
- int sought;
- double last_timecode;
- AVCodec *pCodec;
- AVCodecContext *pCodecCtx;
- AVFrame *pFrame;
- AVFrame *pFrameRGB;
- struct SwsContext *pSwsCtx;
-};
-
-typedef struct _ac_video_decoder ac_video_decoder;
-typedef ac_video_decoder *lp_ac_video_decoder;
-
-struct _ac_audio_decoder {
- ac_decoder decoder;
- int sought;
- double last_timecode;
- AVCodec *pCodec;
- AVCodecContext *pCodecCtx;
- AVFrame *pFrame;
- SwrContext *pSwrCtx;
- size_t own_buffer_size;
-};
-
-typedef struct _ac_audio_decoder ac_audio_decoder;
-typedef ac_audio_decoder *lp_ac_audio_decoder;
-
-struct _ac_package_data {
- ac_package package;
- AVPacket *pPack;
- int pts;
-};
-
-typedef struct _ac_package_data ac_package_data;
-typedef ac_package_data *lp_ac_package_data;
-
-//
-//--- Forward declarations ---
-//
-
-void ac_free_video_decoder(lp_ac_video_decoder pDecoder);
-void ac_free_audio_decoder(lp_ac_audio_decoder pDecoder);
-
-//
-//--- Initialization and Stream opening---
-//
-
-void init_info(lp_ac_file_info info)
-{
- info->title[0] = 0;
- info->author[0] = 0;
- info->copyright[0] = 0;
- info->comment[0] = 0;
- info->album[0] = 0;
- info->year = -1;
- info->track = -1;
- info->genre[0] = 0;
- info->duration = -1;
- info->bitrate = -1;
-}
-
-/* avcodec_register_all(), av_register_all() deprecated since lavc 58.9.100 */
-#if (LIBAVCODEC_VERSION_MAJOR < 58) || \
- ((LIBAVCODEC_VERSION_MAJOR == 58) && (LIBAVCODEC_VERSION_MINOR < 9))
-#define AC_NEED_REGISTER_ALL
-#endif
-
-#ifdef AC_NEED_REGISTER_ALL
-int av_initialized = 0;
-void ac_init_ffmpeg()
-{
- if (!av_initialized) {
- avcodec_register_all();
- av_register_all();
-
- av_initialized = 1;
- }
-}
-#endif /* AC_NEED_REGISTER_ALL */
-
-lp_ac_instance CALL_CONVT ac_init(void)
-{
-#ifdef AC_NEED_REGISTER_ALL
- ac_init_ffmpeg();
-#endif /* AC_NEED_REGISTER_ALL */
-
- // Allocate a new instance of the videoplayer data and return it
- lp_ac_data ptmp;
- ERR(ptmp = (lp_ac_data)av_malloc(sizeof(ac_data)));
-
- // Initialize the created structure
- memset(ptmp, 0, sizeof(ac_data));
-
- ptmp->instance.opened = 0;
- ptmp->instance.stream_count = 0;
- ptmp->instance.output_format = AC_OUTPUT_BGR24;
- init_info(&(ptmp->instance.info));
- return (lp_ac_instance)ptmp;
-
-error:
- return NULL;
-}
-
-lp_ac_instance CALL_CONVT ac_init_of(ac_output_format outputFormat)
-{
-#ifdef AC_NEED_REGISTER_ALL
- ac_init_ffmpeg();
-#endif /* AC_NEED_REGISTER_ALL */
-
- // Allocate a new instance of the videoplayer data and return it
- lp_ac_data ptmp;
- ptmp = (lp_ac_data)av_malloc(sizeof(ac_data));
-
- // Initialize the created structure
- memset(ptmp, 0, sizeof(ac_data));
-
- ptmp->instance.opened = 0;
- ptmp->instance.stream_count = 0;
- ptmp->instance.output_format = outputFormat;
- init_info(&(ptmp->instance.info));
- return (lp_ac_instance)ptmp;
-}
-
-void CALL_CONVT ac_free(lp_ac_instance pacInstance)
-{
- if (pacInstance) {
- // Close the decoder. If it is already closed, this won't be a problem
- // as
- // ac_close checks the streams state
- ac_close(pacInstance);
- av_free((lp_ac_data)pacInstance);
- }
-}
-
-static int io_read(void *opaque, uint8_t *buf, int buf_size)
-{
- lp_ac_data self = ((lp_ac_data)opaque);
-
- // If there is still memory in the probe buffer, consume this memory first
- if (self->probe_buffer &&
- self->probe_buffer_offs < self->probe_buffer_size) {
-
- unsigned int buf_size_u = (unsigned int)buf_size;
- // Copy as many bytes as possible from the probe buffer
- size_t cnt =
- MIN(buf_size_u, self->probe_buffer_size - self->probe_buffer_offs);
- memcpy(buf, self->probe_buffer + self->probe_buffer_offs, cnt);
-
- // Advance the read/write pointers
- self->probe_buffer_offs += cnt;
- buf += cnt;
- buf_size -= (int)cnt;
-
- // Free the probe buffer once all bytes have been read
- if (self->probe_buffer_offs == self->probe_buffer_size) {
- av_free(self->probe_buffer);
- self->probe_buffer = NULL;
- self->probe_buffer_size = 0;
- self->probe_buffer_offs = 0;
- }
-
- // Return the actual bytes obtained from the memory buffer
- return (int)cnt;
- }
-
- // Try to read the memory from the probe buffer
- if (self->read_proc != NULL) {
- return self->read_proc(self->sender, buf, buf_size);
- }
-
- return -1;
-}
-
-static int64_t io_seek(void *opaque, int64_t pos, int whence)
-{
- lp_ac_data self = ((lp_ac_data)opaque);
- if (self->seek_proc != NULL) {
- if ((whence >= 0) && (whence <= 2)) {
- // Throw away the probe buffer if seek is performed.
- // NOTE: This could be improved to check if the seek
- // is inside the probe_buffer, but it would get really
- // complicated as real seek would need to be omitted
- // then.
- if (self->probe_buffer) {
- av_free(self->probe_buffer);
- self->probe_buffer = NULL;
- self->probe_buffer_size = 0;
- self->probe_buffer_offs = 0;
- }
- return self->seek_proc(self->sender, pos, whence);
- }
- }
- return -1;
-}
-
-lp_ac_proberesult CALL_CONVT ac_probe_input_buffer(uint8_t *buf, int bufsize,
- char *filename,
- int *score_max)
-{
- AVProbeData pd;
- AVInputFormat *fmt = NULL;
-
-#ifdef AC_NEED_REGISTER_ALL
- // Initialize FFMpeg libraries
- ac_init_ffmpeg();
-#endif /* AC_NEED_REGISTER_ALL */
-
- // Set the filename and mime_type
- pd.mime_type = "";
- pd.filename = "";
- if (filename) {
- pd.filename = filename;
- }
-
- // The given buffer has to be copied to a new one, which is aligned and
- // padded
- uint8_t *aligned_buf;
- ERR(aligned_buf = av_malloc(bufsize + AVPROBE_PADDING_SIZE));
- memset(aligned_buf, 0, bufsize + AVPROBE_PADDING_SIZE);
- memcpy(aligned_buf, buf, bufsize);
-
- // Set the probe data buffer
- pd.buf = aligned_buf;
- pd.buf_size = bufsize;
-
- // Test it
- int score_ret;
- fmt = av_probe_input_format3(&pd, 1, &score_ret);
- if (score_ret > *score_max) {
- *score_max = score_ret;
- }
-
- // Free the temporary buffer
- av_free(aligned_buf);
-
- return (lp_ac_proberesult)fmt;
-
-error:
- return NULL;
-}
-
-AVInputFormat *ac_probe_input_stream(void *sender, ac_read_callback read_proc,
- char *filename, uint8_t **buf,
- size_t *buf_read)
-{
- // Initialize the result variables
- AVInputFormat *fmt = NULL;
- *buf_read = 0;
- *buf = NULL;
- int last_iteration = 0;
- int probe_size = 0;
-
- for (probe_size = PROBE_BUF_MIN;
- (probe_size <= PROBE_BUF_MAX) && !fmt && !last_iteration;
- probe_size <<= 1) {
- int score = AVPROBE_SCORE_MAX / 4;
-
- // Allocate some memory for the current probe buffer
- uint8_t *tmp_buf = av_malloc(probe_size);
- if (!tmp_buf) {
- return fmt; // An error occurred, abort
- }
- memset(tmp_buf, 0, probe_size);
-
- // Copy the old data to the new buffer
- if (*buf) {
- memcpy(tmp_buf, *buf, *buf_read);
- // Free the old data memory
- av_free(*buf);
- // Zero the pointer to avoid double free on read_proc error
- *buf = NULL;
- }
-
- // Read the new data
- uint8_t *write_ptr = tmp_buf + *buf_read;
- int read_size = probe_size - (int)*buf_read;
- int size = read_proc(sender, write_ptr, read_size);
- if (size < 0) {
- return fmt; // An error occurred, abort
- }
- if (size < read_size) {
- av_free(tmp_buf);
- last_iteration = 1;
- probe_size = (int)*buf_read + size;
- }
-
- // Probe it
- fmt = (AVInputFormat *)ac_probe_input_buffer(tmp_buf, probe_size,
- filename, &score);
-
- // Set the new buffer
- *buf = tmp_buf;
- *buf_read = probe_size;
- }
-
- // Return the result
- return fmt;
-}
-
-static void cpymetadata(const AVFormatContext *ctx, const char *key, char *tar,
- size_t len)
-{
- const AVDictionaryEntry *entry = av_dict_get(ctx->metadata, key, NULL, 0);
- if (entry) {
- strncpy_s(tar, len, entry->value, len-1);
- tar[len - 1] = '\0';
- } else {
- strncpy_s(tar, len, "", len);
- }
-}
-
-static void cpymetadatai(const AVFormatContext *ctx, const char *key, int *tar)
-{
- char buf[16];
- cpymetadata(ctx, key, buf, 16);
- *tar = atoi(buf);
-}
-
-static int finalize_open(lp_ac_instance pacInstance)
-{
- lp_ac_data self = ((lp_ac_data)pacInstance);
-
- // Assume the stream is opened
- pacInstance->opened = 1;
-
- // Retrieve stream information
- AVFormatContext *ctx = self->pFormatCtx;
- if (avformat_find_stream_info(ctx, NULL) >= 0) {
- cpymetadata(ctx, "title", pacInstance->info.title, 512);
- cpymetadata(ctx, "artist", pacInstance->info.author, 512);
- cpymetadata(ctx, "copyright", pacInstance->info.copyright, 512);
- cpymetadata(ctx, "comment", pacInstance->info.comment, 512);
- cpymetadata(ctx, "album", pacInstance->info.album, 512);
- cpymetadata(ctx, "genre", pacInstance->info.genre, 32);
- cpymetadatai(ctx, "year", &(pacInstance->info.year));
- cpymetadatai(ctx, "track", &(pacInstance->info.track));
- pacInstance->info.bitrate = (int)ctx->bit_rate;
- pacInstance->info.duration = ctx->duration * 1000 / AV_TIME_BASE;
- } else {
- ac_close(pacInstance);
- return -1;
- }
-
- // Set some information in the instance variable
- pacInstance->stream_count =
- ((lp_ac_data)pacInstance)->pFormatCtx->nb_streams;
- if (pacInstance->stream_count <= 0) {
- ac_close(pacInstance);
- return -1;
- }
- return 0;
-}
-
-int CALL_CONVT ac_open(lp_ac_instance pacInstance, void *sender,
- ac_openclose_callback open_proc,
- ac_read_callback read_proc, ac_seek_callback seek_proc,
- ac_openclose_callback close_proc,
- lp_ac_proberesult proberesult)
-{
- // Instance cannot be opened twice!
- if (pacInstance->opened) {
- return -1;
- }
-
- // Reference at the underlying lp_ac_data instance
- lp_ac_data self = ((lp_ac_data)pacInstance);
-
- // Store the given parameters in the ac Instance
- self->sender = sender;
- self->open_proc = open_proc;
- self->read_proc = read_proc;
- self->seek_proc = seek_proc;
- self->close_proc = close_proc;
-
- // Call the file open proc
- if (self->open_proc != NULL) {
- if (self->open_proc(sender) < 0) {
- ac_close(pacInstance);
- return -1;
- }
- }
-
- AVInputFormat *fmt = NULL;
-
- // Probe the input format, if no probe result is specified
- if (proberesult == NULL) {
- fmt =
- ac_probe_input_stream(sender, read_proc, "", &(self->probe_buffer),
- &(self->probe_buffer_size));
- } else {
- fmt = (AVInputFormat *)proberesult;
- }
-
- if (!fmt) {
- ac_close(pacInstance);
- return -1;
- }
-
- // Reserve the buffer memory and initialise the context
- ERR(self->buffer = av_malloc(AC_BUFSIZE));
- ERR(self->pIo = avio_alloc_context(self->buffer, AC_BUFSIZE, 0, self, io_read,
- 0, seek_proc ? io_seek : NULL));
- self->pIo->seekable = seek_proc != NULL;
-
- // Open the given input stream (the io structure) with the given format of
- // the stream (fmt) and write the pointer to the new format context to the
- // pFormatCtx variable
- ERR(self->pFormatCtx = avformat_alloc_context());
- self->pFormatCtx->pb = self->pIo;
-
- if (avformat_open_input(&(self->pFormatCtx), "", fmt, NULL) < 0) {
- ac_close(pacInstance);
- return -1;
- }
-
- return finalize_open(pacInstance);
-
-error:
- ac_close(pacInstance);
- return -1;
-}
-
-int CALL_CONVT ac_open_file(lp_ac_instance pacInstance, const char *filename)
-{
- // Instance cannot be opened twice!
- if (pacInstance->opened) {
- return -1;
- }
-
- // Reference at the underlying lp_ac_data instance
- lp_ac_data self = ((lp_ac_data)pacInstance);
-
- self->pFormatCtx = NULL;
- if (avformat_open_input(&(self->pFormatCtx), filename, NULL, NULL) < 0) {
- ac_close(pacInstance);
- return -1;
- }
-
- return finalize_open(pacInstance);
-}
-
-void CALL_CONVT ac_close(lp_ac_instance pacInstance)
-{
- lp_ac_data self = ((lp_ac_data)pacInstance);
- if (pacInstance->opened) {
- // Close the opened file
- if (self->close_proc != NULL) {
- self->close_proc(((lp_ac_data)(pacInstance))->sender);
- }
-
- avformat_close_input(&(self->pFormatCtx));
-
- pacInstance->opened = 0;
- }
-
- // Make sure all buffers are freed
- av_free(self->probe_buffer);
- av_free(self->buffer);
- av_free(self->pIo);
- self->buffer = NULL;
- self->probe_buffer = NULL;
- self->pIo = NULL;
- self->pFormatCtx = NULL;
- self->sender = NULL;
- self->open_proc = NULL;
- self->read_proc = NULL;
- self->seek_proc = NULL;
- self->close_proc = NULL;
-}
-
-void CALL_CONVT ac_get_stream_info(lp_ac_instance pacInstance, int nb,
- lp_ac_stream_info info)
-{
- // Rese the stream info structure
- memset(info, 0, sizeof(ac_stream_info));
- info->stream_type = AC_STREAM_TYPE_UNKNOWN;
-
- // Abort if the instance is not opened
- if (!(pacInstance->opened)) {
- return;
- }
-
- // Read the information
- lp_ac_data self = ((lp_ac_data)pacInstance);
- switch (self->pFormatCtx->streams[nb]->codecpar->codec_type) {
- case AVMEDIA_TYPE_VIDEO:
- // Set stream type to "VIDEO"
- info->stream_type = AC_STREAM_TYPE_VIDEO;
-
- // Store more information about the video stream
- info->additional_info.video_info.frame_width =
- self->pFormatCtx->streams[nb]->codecpar->width;
- info->additional_info.video_info.frame_height =
- self->pFormatCtx->streams[nb]->codecpar->height;
-
- double pixel_aspect_num =
- self->pFormatCtx->streams[nb]->codecpar->sample_aspect_ratio.num;
- double pixel_aspect_den =
- self->pFormatCtx->streams[nb]->codecpar->sample_aspect_ratio.den;
-
- // Sometime "pixel aspect" may be zero or have other invalid values.
- // Correct this.
- if (pixel_aspect_num <= 0.0 || pixel_aspect_den <= 0.0)
- info->additional_info.video_info.pixel_aspect = 1.0;
- else
- info->additional_info.video_info.pixel_aspect = (float)(
- pixel_aspect_num / pixel_aspect_den);
-
- info->additional_info.video_info.frames_per_second =
- (double)self->pFormatCtx->streams[nb]->r_frame_rate.num /
- (double)self->pFormatCtx->streams[nb]->r_frame_rate.den;
- break;
- case AVMEDIA_TYPE_AUDIO:
- // Set stream type to "AUDIO"
- info->stream_type = AC_STREAM_TYPE_AUDIO;
-
- // Store more information about the video stream
- info->additional_info.audio_info.samples_per_second =
- self->pFormatCtx->streams[nb]->codecpar->sample_rate;
- info->additional_info.audio_info.channel_count =
- self->pFormatCtx->streams[nb]->codecpar->channels;
-
- // Set bit depth
- switch (self->pFormatCtx->streams[nb]->codecpar->format) {
- // 8-Bit
- case AV_SAMPLE_FMT_U8:
- case AV_SAMPLE_FMT_U8P:
- info->additional_info.audio_info.bit_depth = 8;
- break;
-
- // 16-Bit
- case AV_SAMPLE_FMT_S16:
- case AV_SAMPLE_FMT_S16P:
- info->additional_info.audio_info.bit_depth = 16;
- break;
-
- /* //24-Bit (removed in the newest ffmpeg version)
- case SAMPLE_FMT_S24:
- info->additional_info.audio_info.bit_depth =
- 24;
- break; */
-
- // 32-Bit
- case AV_SAMPLE_FMT_S32:
- case AV_SAMPLE_FMT_FLT:
- case AV_SAMPLE_FMT_DBL:
- case AV_SAMPLE_FMT_S32P:
- case AV_SAMPLE_FMT_FLTP:
- case AV_SAMPLE_FMT_DBLP:
- info->additional_info.audio_info.bit_depth = 32;
- break;
-
- // Unknown format, return zero
- default:
- info->additional_info.audio_info.bit_depth = 0;
- }
-
- break;
- default: {
- // Do nothing
- }
- }
-}
-
-//
-//---Package management---
-//
-
-lp_ac_package CALL_CONVT ac_read_package(lp_ac_instance pacInstance)
-{
- // Allocate the result packet
- lp_ac_package_data pkt;
- ERR(pkt = av_malloc(sizeof(ac_package_data)));
- memset(pkt, 0, sizeof(ac_package_data));
- ERR(pkt->pPack = av_malloc(sizeof(AVPacket)));
-
- av_init_packet(pkt->pPack);
- pkt->pPack->data = NULL;
- pkt->pPack->size = 0;
-
- // Try to read package
- int av_read_frame_status =
- av_read_frame(((lp_ac_data)(pacInstance))->pFormatCtx, pkt->pPack);
- if (av_read_frame_status >=
- 0) {
- if (pkt->pPack->dts != AV_NOPTS_VALUE) {
- pkt->pts = (int)pkt->pPack->dts;
- }
- pkt->package.stream_index = pkt->pPack->stream_index;
- return (lp_ac_package)(pkt);
- }
- ac_free_package((lp_ac_package)pkt);
- return NULL;
-
-error:
- ac_free_package((lp_ac_package)pkt);
- return NULL;
-}
-
-// Frees the currently loaded package
-void CALL_CONVT ac_free_package(lp_ac_package pPackage)
-{
- if (pPackage) {
- lp_ac_package_data self = (lp_ac_package_data)pPackage;
- av_packet_unref(self->pPack);
- av_free(self->pPack);
- av_free(self);
- }
-}
-
-//
-//--- Decoder management ---
-//
-
-enum AVPixelFormat convert_pix_format(ac_output_format fmt)
-{
- switch (fmt) {
- case AC_OUTPUT_RGB24:
- return AV_PIX_FMT_RGB24;
- case AC_OUTPUT_BGR24:
- return AV_PIX_FMT_BGR24;
- case AC_OUTPUT_RGBA32:
- return AV_PIX_FMT_RGB32;
- case AC_OUTPUT_BGRA32:
- return AV_PIX_FMT_BGR32;
- case AC_OUTPUT_YUV420P:
- return AV_PIX_FMT_YUV420P;
- case AC_OUTPUT_UYVY422:
- return AV_PIX_FMT_UYVY422;
- case AC_OUTPUT_YUYV422:
- return AV_PIX_FMT_YUYV422;
- }
- return AV_PIX_FMT_RGB24;
-}
-
-// Init a video decoder
-void *ac_create_video_decoder(lp_ac_instance pacInstance,
- lp_ac_stream_info info, int nb)
-{
- // Allocate memory for a new decoder instance
- lp_ac_video_decoder pDecoder;
- ERR(pDecoder = (lp_ac_video_decoder)(av_malloc(sizeof(ac_video_decoder))));
- memset(pDecoder, 0, sizeof(ac_video_decoder));
-
- // Set a few properties
- pDecoder->decoder.pacInstance = pacInstance;
- pDecoder->decoder.type = AC_DECODER_TYPE_VIDEO;
- pDecoder->decoder.stream_index = nb;
-
- AVCodecParameters* pCodecParam = ((lp_ac_data)(pacInstance))->pFormatCtx->streams[nb]->codecpar;
- pDecoder->decoder.stream_info = *info;
-
- // Find correspondenting codec
- if (!(pDecoder->pCodec =
- avcodec_find_decoder(pCodecParam->codec_id))) {
- ac_free_video_decoder(pDecoder);
- return NULL; // Codec could not have been found
- }
-
- ERR(pDecoder->pCodecCtx = avcodec_alloc_context3(pDecoder->pCodec));
- if (avcodec_parameters_to_context(pDecoder->pCodecCtx, pCodecParam) < 0)
- {
- ac_free_video_decoder(pDecoder);
- return NULL;
- }
-
- // Open codec
- if (avcodec_open2(pDecoder->pCodecCtx, pDecoder->pCodec, NULL) < 0) {
- ac_free_video_decoder(pDecoder);
- return NULL; // Codec could not have been opened
- }
-
- // Reserve frame variables
- ERR(pDecoder->pFrame = av_frame_alloc());
- ERR(pDecoder->pFrameRGB = av_frame_alloc());
-
- pDecoder->pSwsCtx = NULL;
-
- // Reserve buffer memory
- pDecoder->decoder.buffer_size = av_image_get_buffer_size(
- convert_pix_format(pacInstance->output_format),
- pDecoder->pCodecCtx->width, pDecoder->pCodecCtx->height,
- 1);
- pDecoder->decoder.pBuffer =
- (uint8_t *)av_malloc(pDecoder->decoder.buffer_size);
-
- // Link decoder to buffer
- if(av_image_fill_arrays(pDecoder->pFrameRGB->data,
- pDecoder->pFrameRGB->linesize,
- pDecoder->decoder.pBuffer,
- convert_pix_format(pacInstance->output_format),
- pDecoder->pCodecCtx->width, pDecoder->pCodecCtx->height,
- 1) < 0)
- {
- ac_free_video_decoder(pDecoder);
- return NULL;
- }
-
- return (void *)pDecoder;
-
-error:
- ac_free_video_decoder(pDecoder);
- return NULL;
-}
-
-// Init a audio decoder
-void *ac_create_audio_decoder(lp_ac_instance pacInstance,
- lp_ac_stream_info info, int nb)
-{
- // Allocate memory for a new decoder instance
- lp_ac_data self = ((lp_ac_data)(pacInstance));
- lp_ac_audio_decoder pDecoder = NULL;
- ERR(pDecoder = (lp_ac_audio_decoder)(av_malloc(sizeof(ac_audio_decoder))));
- memset(pDecoder, 0, sizeof(ac_audio_decoder));
-
- // Set a few properties
- pDecoder->decoder.pacInstance = pacInstance;
- pDecoder->decoder.type = AC_DECODER_TYPE_AUDIO;
- pDecoder->decoder.stream_index = nb;
- pDecoder->decoder.stream_info = *info;
-
- AVCodecParameters* pCodecParam = self->pFormatCtx->streams[nb]->codecpar;
-
- // Find correspondenting codec
- ERR(pDecoder->pCodec = avcodec_find_decoder(pCodecParam->codec_id));
-
- pDecoder->pCodecCtx = avcodec_alloc_context3(pDecoder->pCodec);
- avcodec_parameters_to_context(pDecoder->pCodecCtx, pCodecParam);
-
- // Open codec
- AV_ERR(avcodec_open2(pDecoder->pCodecCtx, pDecoder->pCodec, NULL));
-
- // Initialize the buffers
- pDecoder->decoder.pBuffer = NULL; // av_malloc(AUDIO_BUFFER_BASE_SIZE);
- pDecoder->decoder.buffer_size = 0;
- ERR(pDecoder->pFrame = av_frame_alloc());
-
- // Fetch audio format, rate and channel layout -- under some circumstances,
- // the layout is not known to the decoder, then a channel layout is guessed
- // from the channel count.
- const enum AVSampleFormat fmt = pDecoder->pCodecCtx->sample_fmt;
- const int rate = pDecoder->pCodecCtx->sample_rate;
- const int64_t layout =
- pDecoder->pCodecCtx->channel_layout
- ? pDecoder->pCodecCtx->channel_layout
- : av_get_default_channel_layout(pDecoder->pCodecCtx->channels);
-
- // Initialize libswresample if needed
- if (av_sample_fmt_is_planar(fmt) || (fmt == AV_SAMPLE_FMT_S32) ||
- (fmt == AV_SAMPLE_FMT_DBL)) {
- enum AVSampleFormat out_fmt = av_get_packed_sample_fmt(fmt);
- if (out_fmt == AV_SAMPLE_FMT_DBL || out_fmt == AV_SAMPLE_FMT_S32) {
- out_fmt = AV_SAMPLE_FMT_FLT;
- }
- ERR(pDecoder->pSwrCtx = swr_alloc_set_opts(NULL, layout, out_fmt, rate,
- layout, fmt, rate, 0, NULL));
- AV_ERR(swr_init(pDecoder->pSwrCtx));
- }
- return (void *)pDecoder;
-
-error:
- ac_free_audio_decoder(pDecoder);
- return NULL;
-}
-
-lp_ac_decoder CALL_CONVT ac_create_decoder(lp_ac_instance pacInstance, int nb)
-{
- // Get information about the chosen data stream and create an decoder that
- // can handle this kind of stream.
- ac_stream_info info;
- ac_get_stream_info(pacInstance, nb, &info);
-
- lp_ac_decoder_data result = NULL;
-
- if (info.stream_type == AC_STREAM_TYPE_VIDEO) {
- result =
- (lp_ac_decoder_data)ac_create_video_decoder(pacInstance, &info, nb);
- } else if (info.stream_type == AC_STREAM_TYPE_AUDIO) {
- result =
- (lp_ac_decoder_data)ac_create_audio_decoder(pacInstance, &info, nb);
- }
-
- if (result) {
- result->decoder.timecode = 0;
- result->last_timecode = 0;
- result->sought = 1;
- }
-
- return (lp_ac_decoder)result;
-}
-
-int ac_decode_video_package(lp_ac_package pPackage,
- lp_ac_video_decoder pDecoder)
-{
- lp_ac_package_data pkt = ((lp_ac_package_data)pPackage);
-
- if(avcodec_send_packet(pDecoder->pCodecCtx, pkt->pPack) < 0)
- {
- return -1;
- }
-
- int avcodec_receive_frame_response;
-
- do
- {
- avcodec_receive_frame_response = avcodec_receive_frame(pDecoder->pCodecCtx,
- pDecoder->pFrame);
- if (avcodec_receive_frame_response == AVERROR(EAGAIN) ||
- avcodec_receive_frame_response == AVERROR_EOF) {
- return avcodec_receive_frame_response;
- }
-
- } while (avcodec_receive_frame_response > 0);
-
- if((pDecoder->pSwsCtx = sws_getCachedContext(
- pDecoder->pSwsCtx, pDecoder->pCodecCtx->width,
- pDecoder->pCodecCtx->height, pDecoder->pCodecCtx->pix_fmt,
- pDecoder->pCodecCtx->width, pDecoder->pCodecCtx->height,
- convert_pix_format(pDecoder->decoder.pacInstance->output_format),
- SWS_BICUBIC, NULL, NULL, NULL)) < 0)
- {
- return -3;
- }
-
- if(sws_scale(pDecoder->pSwsCtx,
- (const uint8_t *const *)(pDecoder->pFrame->data),
- pDecoder->pFrame->linesize,
- 0, //?
- pDecoder->pCodecCtx->height, pDecoder->pFrameRGB->data,
- pDecoder->pFrameRGB->linesize) < 0)
- {
- return -4;
- }
-
- return 0;
-}
-
-int ac_skip_video_package(lp_ac_package pPackage,
- lp_ac_video_decoder pDecoder)
-{
- lp_ac_package_data pkt = ((lp_ac_package_data)pPackage);
-
- if (avcodec_send_packet(pDecoder->pCodecCtx, pkt->pPack) < 0)
- {
- return -1;
- }
-
- if (avcodec_receive_frame(pDecoder->pCodecCtx, pDecoder->pFrame) < 0)
- {
- return -2;
- }
-
- return 0;
-}
-
-int ac_decode_audio_package(lp_ac_package pPackage,
- lp_ac_audio_decoder pDecoder)
-{
- int got_frame = 0;
- int len = 0;
- lp_ac_package_data pkt = ((lp_ac_package_data)pPackage);
-
- if (avcodec_send_packet(pDecoder->pCodecCtx, pkt->pPack) < 0)
- {
- return -1;
- }
-
- if (avcodec_receive_frame(pDecoder->pCodecCtx, pDecoder->pFrame) < 0)
- {
- return -2;
- }
-
- // Calculate the output buffer size
- const int sample_size =
- MIN(4, av_get_bytes_per_sample(pDecoder->pCodecCtx->sample_fmt));
- const int sample_count = pDecoder->pFrame->nb_samples;
- const int channel_count = pDecoder->pFrame->channels;
- const int buffer_size = sample_size * sample_count * channel_count;
- pDecoder->decoder.buffer_size = buffer_size;
-
- if (pDecoder->pSwrCtx) {
- if (pDecoder->own_buffer_size != buffer_size) {
- ERR(pDecoder->decoder.pBuffer =
- av_realloc(pDecoder->decoder.pBuffer, buffer_size));
- pDecoder->own_buffer_size = buffer_size;
- }
- if(swr_convert(pDecoder->pSwrCtx, &(pDecoder->decoder.pBuffer),
- sample_count, (const uint8_t **)(pDecoder->pFrame->data),
- sample_count) < 0)
- {
- return -3;
- }
- } else {
- // No conversion needs to be done, simply set the buffer pointer
- pDecoder->decoder.pBuffer = pDecoder->pFrame->data[0];
- }
-
- return got_frame;
-error:
- return -4;
-}
-
-int ac_skip_audio_package(lp_ac_package pPackage,
- lp_ac_audio_decoder pDecoder)
-{
- int len = 0;
- lp_ac_package_data pkt = ((lp_ac_package_data)pPackage);
- if (avcodec_send_packet(pDecoder->pCodecCtx, pkt->pPack) < 0)
- {
- return -1;
- }
-
- if (avcodec_receive_frame(pDecoder->pCodecCtx, pDecoder->pFrame) < 0)
- {
- return -2;
- }
-
- return 0;
-}
-
-int CALL_CONVT ac_decode_package(lp_ac_package pPackage, lp_ac_decoder pDecoder)
-{
- double timebase = av_q2d(((lp_ac_data)pDecoder->pacInstance)
- ->pFormatCtx->streams[pPackage->stream_index]
- ->time_base);
-
- // Create a valid timecode
- if (((lp_ac_package_data)pPackage)->pts > 0) {
- lp_ac_decoder_data dec_dat = (lp_ac_decoder_data)pDecoder;
-
- dec_dat->last_timecode = pDecoder->timecode;
- pDecoder->timecode = ((lp_ac_package_data)pPackage)->pts * timebase;
-
- double delta = pDecoder->timecode - dec_dat->last_timecode;
- double max_delta, min_delta;
-
- if (dec_dat->sought > 0) {
- max_delta = 120.0;
- min_delta = -120.0;
- --dec_dat->sought;
- } else {
- max_delta = 4.0;
- min_delta = 0.0;
- }
-
- if ((delta < min_delta) || (delta > max_delta)) {
- pDecoder->timecode = dec_dat->last_timecode;
- if (dec_dat->sought > 0) {
- ++dec_dat->sought;
- }
- }
- }
-
- if (pDecoder->type == AC_DECODER_TYPE_VIDEO) {
- return ac_decode_video_package(pPackage, (lp_ac_video_decoder)pDecoder);
- } else if (pDecoder->type == AC_DECODER_TYPE_AUDIO) {
- return ac_decode_audio_package(pPackage, (lp_ac_audio_decoder)pDecoder);
- }
- return -10;
-}
-
-int CALL_CONVT ac_skip_package(lp_ac_package pPackage, lp_ac_decoder pDecoder)
-{
- double timebase = av_q2d(((lp_ac_data)pDecoder->pacInstance)
- ->pFormatCtx->streams[pPackage->stream_index]
- ->time_base);
-
- // Create a valid timecode
- if (((lp_ac_package_data)pPackage)->pts > 0) {
- lp_ac_decoder_data dec_dat = (lp_ac_decoder_data)pDecoder;
-
- dec_dat->last_timecode = pDecoder->timecode;
- pDecoder->timecode = ((lp_ac_package_data)pPackage)->pts * timebase;
-
- double delta = pDecoder->timecode - dec_dat->last_timecode;
- double max_delta, min_delta;
-
- if (dec_dat->sought > 0) {
- max_delta = 120.0;
- min_delta = -120.0;
- --dec_dat->sought;
- }
- else {
- max_delta = 4.0;
- min_delta = 0.0;
- }
-
- if ((delta < min_delta) || (delta > max_delta)) {
- pDecoder->timecode = dec_dat->last_timecode;
- if (dec_dat->sought > 0) {
- ++dec_dat->sought;
- }
- }
- }
-
- if (pDecoder->type == AC_DECODER_TYPE_VIDEO) {
- return ac_skip_video_package(pPackage, (lp_ac_video_decoder)pDecoder);
- }
- else if (pDecoder->type == AC_DECODER_TYPE_AUDIO) {
- return ac_skip_audio_package(pPackage, (lp_ac_audio_decoder)pDecoder);
- }
- return -9;
-}
-
-int CALL_CONVT ac_skip_frames(lp_ac_instance pacInstance, lp_ac_decoder pDecoder, int num)
-{
- if (pDecoder == NULL)
- {
- return -1;
- }
-
- int skipedFrames = 0;
-
- while (skipedFrames < num)
- {
- lp_ac_package pckt = ac_read_package(pacInstance);
- if (pckt != NULL)
- {
- // Decode the package to figure out if its completing a frame
- if (pckt->stream_index == pDecoder->stream_index && ac_skip_package(pckt, pDecoder) == 0)
- {
- skipedFrames++;
- }
-
- ac_free_package(pckt);
- }
- else
- {
- return -2;
- }
- }
- return 0;
-}
-
-int CALL_CONVT ac_get_frame(lp_ac_instance pacInstance, lp_ac_decoder pDecoder)
-{
- if (pDecoder == NULL)
- {
- return -1;
- }
-
- do
- {
- lp_ac_package pckt = ac_read_package(pacInstance);
- if (pckt == NULL)
- {
- return -2;
- }
-
- if(pckt->stream_index != pDecoder->stream_index)
- {
- continue;
- }
-
- // The packet is for the video stream, try to decode it
- int _status = ac_decode_package(pckt, pDecoder);
-
- ac_free_package(pckt);
-
- if (_status > 0 || _status == AVERROR(EAGAIN))
- {
- continue;
- }
-
- return _status;
-
- } while (true);
-}
-
-int CALL_CONVT ac_get_audio_frame(lp_ac_instance pacInstance, lp_ac_decoder pDecoder)
-{
- if (pDecoder == NULL)
- {
- return -1;
- }
-
- do
- {
- lp_ac_package pckt = ac_read_package(pacInstance);
- if (pckt == NULL)
- {
- return -2;
- }
-
- if (pckt->stream_index != pDecoder->stream_index)
- {
- continue;
- }
-
- // The packet is for the video stream, try to decode it
- int _status = ac_decode_package(pckt, pDecoder);
-
- ac_free_package(pckt);
-
- if (_status > 0 || _status == AVERROR(EAGAIN)) {
- continue;
- }
-
- return _status;
- } while (true);
-}
-
-// Seek function
-int CALL_CONVT ac_seek(lp_ac_decoder pDecoder, int dir, int64_t target_pos)
-{
- AVRational timebase = ((lp_ac_data)pDecoder->pacInstance)
- ->pFormatCtx->streams[pDecoder->stream_index]
- ->time_base;
-
- int flags = dir < 0 ? AVSEEK_FLAG_BACKWARD : 0;
-
- int64_t pos = av_rescale(target_pos, AV_TIME_BASE, 1000);
-
- ((lp_ac_decoder_data)pDecoder)->sought = 100;
- pDecoder->timecode = (double)target_pos / 1000;
-
- if (av_seek_frame(((lp_ac_data)pDecoder->pacInstance)->pFormatCtx,
- pDecoder->stream_index,
- av_rescale_q(pos, AV_TIME_BASE_Q, timebase),
- flags) >= 0) {
- return 0;
- }
-
- return -1;
-}
-
-// Free video decoder
-void ac_free_video_decoder(lp_ac_video_decoder pDecoder)
-{
- if (pDecoder) {
- av_frame_free(&(pDecoder->pFrame));
- av_frame_free(&(pDecoder->pFrameRGB));
- sws_freeContext(pDecoder->pSwsCtx);
- avcodec_free_context(&pDecoder->pCodecCtx);
- av_free(pDecoder->decoder.pBuffer);
- av_free(pDecoder);
- }
-}
-
-// Free video decoder
-void ac_free_audio_decoder(lp_ac_audio_decoder pDecoder)
-{
- if (pDecoder) {
- avcodec_free_context(&pDecoder->pCodecCtx);
- av_frame_free(&(pDecoder->pFrame));
- if (pDecoder->pSwrCtx) {
- swr_free(&(pDecoder->pSwrCtx));
- }
- if (pDecoder->own_buffer_size > 0) {
- av_free(pDecoder->decoder.pBuffer);
- }
- av_free(pDecoder);
- }
-}
-
-void CALL_CONVT ac_free_decoder(lp_ac_decoder pDecoder)
-{
- if (pDecoder) {
- if (pDecoder->type == AC_DECODER_TYPE_VIDEO) {
- ac_free_video_decoder((lp_ac_video_decoder)pDecoder);
- } else if (pDecoder->type == AC_DECODER_TYPE_AUDIO) {
- ac_free_audio_decoder((lp_ac_audio_decoder)pDecoder);
- }
- }
-}
-
diff --git a/Vocaluxe/Lib/Video/Acinerella/acinerella.h b/Vocaluxe/Lib/Video/Acinerella/acinerella.h
deleted file mode 100644
index a7a228b9..00000000
--- a/Vocaluxe/Lib/Video/Acinerella/acinerella.h
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- This file is part of Acinerella.
-
- Acinerella is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Acinerella is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Acinerella. If not, see .
-*/
-
-#ifndef _ACINERELLA_H_
-#define _ACINERELLA_H_
-
-#include
-#include
-#include
-
-#ifdef _WIN32
-#define CALL_CONVT __cdecl
-#define EXTERN extern __declspec(dllexport)
-#else
-#define CALL_CONVT
-#define EXTERN extern
-#endif
-
-/**
- * Defines the type of an Acinerella media stream. Currently only video and
- * audio streams are supported, subtitle and data streams will be marked as
- * "unknown".
- */
-typedef enum _ac_stream_type {
- /**
- * The type of the media stream is not known. This kind of stream can not be
- * decoded.
- */
- AC_STREAM_TYPE_VIDEO = 0,
-
- /**
- * This media stream is a video stream.
- */
- AC_STREAM_TYPE_AUDIO = 1,
-
- /**
- * This media stream is an audio stream.
- */
- AC_STREAM_TYPE_UNKNOWN = -1
-} ac_stream_type;
-
-/**
- * Defines the type of an Acinerella media decoder.
- */
-typedef enum _ac_decoder_type {
- /**
- * This decoder is used to decode a video stream.
- */
- AC_DECODER_TYPE_VIDEO = 0,
-
- /**
- * This decoder is used to decode an audio stram.
- */
- AC_DECODER_TYPE_AUDIO = 1
-} ac_decoder_type;
-
-/**
- * Defines the format video/image data is returned in.
- */
-typedef enum _ac_output_format {
- AC_OUTPUT_RGB24 = 0,
- AC_OUTPUT_BGR24 = 1,
- AC_OUTPUT_RGBA32 = 2,
- AC_OUTPUT_BGRA32 = 3,
- AC_OUTPUT_YUV420P = 4,
- AC_OUTPUT_UYVY422 = 5,
- AC_OUTPUT_YUYV422 = 6
-} ac_output_format;
-
-/*Contains information about the whole file/stream that has been opened. Default
-values are ""
-for strings and -1 for integer values.*/
-typedef struct _ac_file_info {
- /**
- * Title of the media file
- */
- char title[512];
-
- /**
- * Author or artist of the media file.
- */
- char author[512];
-
- /**
- * Copyright information.
- */
- char copyright[512];
-
- /**
- * Comment stored in the file.
- */
- char comment[512];
-
- /**
- * Album the media file is from.
- */
- char album[512];
-
- /**
- * Year in which the media file was created.
- */
- int year;
-
- /**
- * Track number.
- */
- int track;
-
- /**
- * Genre of the media file.
- */
- char genre[32];
-
- /**
- * Length of the file in milliseconds.
- */
- int64_t duration;
-
- /**
- * Bitrate of the file.
- */
- int bitrate;
-} ac_file_info;
-
-typedef ac_file_info *lp_ac_file_info;
-
-/**
- * ac_instance represents an Acinerella instance. Each instance can open and
- * decode one file at once.
- */
-typedef struct _ac_instance {
- /**
- * If true, the instance currently opened a media file.
- */
- bool opened;
-
- /**
- * Contains the count of streams the media file has. This value is available
- * after calling the ac_open function.
- */
- int stream_count;
-
- /**
- * Set this value to change the image output format
- */
- ac_output_format output_format;
-
- /**
- * Contains information about the opened stream/file
- */
- ac_file_info info;
-} ac_instance;
-
-/**
- * Pointer on the Acinerella instance record.
- */
-typedef ac_instance *lp_ac_instance;
-
-/**
- * Contains information about an Acinerella video stream.
- */
-struct _ac_video_stream_info {
- /**
- * The width of one frame.
- */
- int frame_width;
-
- /**
- * The height of one frame.
- */
- int frame_height;
-
- /**
- * The width of one pixel. 1.07 for 4:3 format, 1,42 for the 16:9 format
- */
- float pixel_aspect;
-
- /**
- * Frames per second that should be played.
- */
- double frames_per_second;
-};
-
-/**
- * Contains information about an Acinerella audio stream.
- */
-struct _ac_audio_stream_info {
- /**
- * Samples per second. Default values are 44100 or 48000.
- */
- int samples_per_second;
-
- /**
- * Bits per sample. Can be 8, 16 or 32 Bit. In the latter case the data is
- * in floating point format.
- */
- int bit_depth;
-
- /**
- * Count of channels in the audio stream.
- */
- int channel_count;
-};
-
-/**
- * Additional info about the stream - use "video_info" when the stream
- * is an video stream, "audio_info" when the stream is an audio stream.
- */
-union _ac_additional_stream_info {
- struct _ac_audio_stream_info audio_info;
- struct _ac_video_stream_info video_info;
-};
-
-/*Contains information about an Acinerella stream.*/
-typedef struct _ac_stream_info {
- /**
- * Contains the type of the stream.
- */
- ac_stream_type stream_type;
-
- /**
- * Additional data depending on the stream type. Either access the
- * "audio_info" or the "video_info" field, depending on the value stored in
- * the "stream_type" member.
- */
- union _ac_additional_stream_info additional_info;
-} ac_stream_info;
-
-/**
- * Pointer on ac_stream_info
- */
-typedef ac_stream_info *lp_ac_stream_info;
-
-/**
- * Contains information about an Acinerella video/audio decoder.
- */
-typedef struct _ac_decoder {
- /**
- * Pointer on the Acinerella instance.
- */
- lp_ac_instance pacInstance;
-
- /**
- * Contains the type of the decoder.
- */
- ac_decoder_type type;
-
- /**
- * The timecode of the currently decoded picture in seconds.
- */
- double timecode;
-
- /**
- * Contains information about the stream the decoder is attached to.
- */
- ac_stream_info stream_info;
-
- /**
- * The index of the stream the decoder is attached to.
- */
- int stream_index;
-
- /**
- * Pointer to the buffer which contains the data.
- */
- uint8_t *pBuffer;
-
- /**
- * Size of the data in the buffer.
- */
- int buffer_size;
-} ac_decoder;
-
-typedef ac_decoder *lp_ac_decoder;
-
-/**
- * Contains information about an Acinerella package.
- */
-typedef struct _ac_package {
- /*The stream the package belongs to.*/
- int stream_index;
-} ac_package;
-
-typedef ac_package *lp_ac_package;
-
-typedef void *lp_ac_proberesult;
-
-/**
- * Callback function used to ask the application to read data. Should return
- * the number of bytes read or an value smaller than zero if an error occured.
- */
-typedef int (CALL_CONVT *ac_read_callback)(void *sender, uint8_t *buf,
- int size);
-
-/**
- * Callback function used to ask the application to seek.
- *
- * @param sender is the pointer specified in ac_open.
- * @param pos is the position to which the stream should be seeked.
- * @param whence specifies whether the seeking should be relative to the
- * beginning of the file (0, SEEK_SET), relative to the current location (1,
- * SEEK_CUR), or relative to the end of the file (2, SEEK_END)
- * @return 0 if the function succeeds, -1 on failure.
- */
-typedef int64_t (CALL_CONVT *ac_seek_callback)(void *sender, int64_t pos,
- int whence);
-
-/**
- * Callback function that is used to notify the application when the data stream
- * is opened or closed. For example the file pointer should be resetted to zero
- * when the "open" function is called.
- */
-typedef int (CALL_CONVT *ac_openclose_callback)(void *sender);
-
-/**
- * Initializes an Acinerella instance.
- *
- * @return a pointer at an acinerella instance. The instance must be freed using
- * ac_free.
- */
-EXTERN lp_ac_instance CALL_CONVT ac_init(void);
-
-/**
-* Initializes an Acinerella instance with a specific output format.
-*
-* @return a pointer at an acinerella instance. The instance must be freed using
-* ac_free.
-*/
-EXTERN lp_ac_instance CALL_CONVT ac_init_of(ac_output_format outputFormat);
-
-/**
- * Frees a previously created Acinerella instance.
- *
- * @param pacInstance is the Acinerella instance that should be freed.
- */
-EXTERN void CALL_CONVT ac_free(lp_ac_instance pacInstance);
-
-/**
- * Opens a media stream.
- *
- * @param inst specifies the Acinerella Instance the stream should be opened
- * for.
- * @param sender specifies a pointer that is sent to all callback functions to
- * allow you to do object orientated programming. May be NULL.
- * @param open_proc specifies the callback function that is called, when the
- * media file is opened. May be NULL.
- * @param seek_proc specifies the callback function that is called, when the
- * ffmpeg decoder wants to seek in the file. May be NULL.
- * @param close_proc specifies the callback function that is called when the
- * media file is closed. May be NULL.
- * @param proberesult is a pointer at a structure previously returned by
- * ac_probe_input_buffer. May be NULL.
- */
-EXTERN int CALL_CONVT
- ac_open(lp_ac_instance pacInstance, void *sender,
- ac_openclose_callback open_proc, ac_read_callback read_proc,
- ac_seek_callback seek_proc, ac_openclose_callback close_proc,
- lp_ac_proberesult proberesult);
-
-/**
- * Opens a media file.
- *
- * @param inst specifies the Acinerella Instance the stream should be opened
- * for.
- * @param file is the name of the file that should be opened.
- */
-EXTERN int CALL_CONVT
- ac_open_file(lp_ac_instance pacInstance, const char *filename);
-
-/**
- * Closes an opened media file.
- */
-EXTERN void CALL_CONVT ac_close(lp_ac_instance pacInstance);
-
-/**
- * Stores information in "pInfo" about stream number "nb".
- */
-EXTERN void CALL_CONVT ac_get_stream_info(lp_ac_instance pacInstance, int nb,
- lp_ac_stream_info info);
-
-/**
- * Reads a package from an opened media file.
- *
- * @param pacInstance is the Acinerella instance from which the data should be
- * read.
- * @return a pointer at a ac_package structure or NULL on failure. The returned
- * pointer must be freed with ac_free_package after use.
- */
-EXTERN lp_ac_package CALL_CONVT ac_read_package(lp_ac_instance pacInstance);
-
-/**
- * Frees a package that has been read.
- *
- * @param pPackage is a pointer at the ac_package that should be freed. May be
- * NULL in which case no operation is performed.
- */
-EXTERN void CALL_CONVT ac_free_package(lp_ac_package pPackage);
-
-/**
- * Creates an decoder for the specified stream number. Returns NULL if no
- * decoder could be found.
- */
-EXTERN lp_ac_decoder CALL_CONVT
- ac_create_decoder(lp_ac_instance pacInstance, int nb);
-
-/**
- * Frees an created decoder.
- */
-EXTERN void CALL_CONVT ac_free_decoder(lp_ac_decoder pDecoder);
-
-/**
- * Decodes a package using the specified decoder. The decoded data is stored in
- * the "buffer" property of the decoder.
- *
- * @param pPackage is the package that should be decoded, previously read by the
- * ac_read_package method.
- * @param pDecoder is the decoder instance that should be used to decode the
- * package -- use the stream index stored in the package to determine the
- * correct decoder.
- */
-EXTERN int CALL_CONVT
- ac_decode_package(lp_ac_package pPackage, lp_ac_decoder pDecoder);
-
-/**
-* Decodes a package using the specified decoder and skips it. Returns true if a frame was skiped.
-*
-* @param pPackage is the package that should be decoded, previously read by the
-* ac_read_package method.
-* @param pDecoder is the decoder instance that should be used to decode the
-* package -- use the stream index stored in the package to determine the
-* correct decoder.
-*/
-EXTERN int CALL_CONVT
- ac_skip_package(lp_ac_package pPackage, lp_ac_decoder pDecoder);
-
-/**
-* Decodes a packages using the specified decoder and skips num frames. Returns true if num frames where skiped.
-*
-* @param pacInstance is the Acinerella instance from which the data should be
-* read.
-* @param pDecoder is the decoder instance that should be used to decode the
-* package -- use the stream index stored in the package to determine the
-* correct decoder.
-* @num number of frames to skip.
-*/
-EXTERN int CALL_CONVT
- ac_skip_frames(lp_ac_instance pacInstance, lp_ac_decoder pDecoder, int num);
-
-/**
-* Decodes a video frame using the specified decoder and strores the result in the decoder. Returns true if a frame cloud be decoded.
-*
-* @param pacInstance is the Acinerella instance from which the data should be
-* read.
-* @param pDecoder is the decoder instance that should be used to decode the
-* package -- use the stream index stored in the package to determine the
-* correct decoder.
-*/
-EXTERN int CALL_CONVT
- ac_get_frame(lp_ac_instance pacInstance, lp_ac_decoder pDecoder);
-
-/**
-* Decodes a audio frame using the specified decoder and strores the result in the decoder. Returns true if a frame cloud be decoded.
-*
-* @param pacInstance is the Acinerella instance from which the data should be
-* read.
-* @param pDecoder is the decoder instance that should be used to decode the
-* package -- use the stream index stored in the package to determine the
-* correct decoder.
-*/
-EXTERN int CALL_CONVT
- ac_get_audio_frame(lp_ac_instance pacInstance, lp_ac_decoder pDecoder);
-
-
-/**
- * Seeks to the given target position in the file. The seek funtion is not able
- * to seek a single audio/video stream but seeks the whole file forward. The
- * stream number paremter (nb) is only used for the timecode reference.
- * The parameter "dir" specifies the seek direction: 0 for forward, -1 for
- * backward. The target_pos paremeter is in milliseconds. Returns 1 if the
- * functions succeded.
- */
-EXTERN int CALL_CONVT
- ac_seek(lp_ac_decoder pDecoder, int dir, int64_t target_pos);
-
-/**
- * Checks whether the given input buffer contains data that can potentially be
- * decoded by Acinerella. If yes, returns a pointer that may be passed to
- * ac_open in order to speed up the open process. Otherwise returns NULL.
- *
- * @param buf is a pointer to the buffer in which the data is stored.
- * @param bufsize is the size of the buffer.
- * @param filename is the name of the file the buffer originated from. May be
- * empty or NULL.
- * @param score_max is set to the maximum of the previous value of score_max
- * and an internal score value.
- * @return either a pointer at an internal "probe result" or NULL if the buffer
- * contains no decodeable data.
- */
-EXTERN lp_ac_proberesult CALL_CONVT
- ac_probe_input_buffer(uint8_t *buf, int bufsize, char *filename,
- int *score_max);
-
-#endif /* _ACINERELLA_H_ */
-
diff --git a/Vocaluxe/Lib/Video/Acinerella/makefile b/Vocaluxe/Lib/Video/Acinerella/makefile
deleted file mode 100644
index 6f391b70..00000000
--- a/Vocaluxe/Lib/Video/Acinerella/makefile
+++ /dev/null
@@ -1,14 +0,0 @@
-acinerella: acinerella.c acinerella.h
- gcc -c -fPIC acinerella.c -I /usr/local/include
-
-ifeq ($(shell uname),Linux)
- gcc -shared -o libacinerella.so acinerella.o -lavformat -lavcodec -lavutil -lm -lswscale -lswresample
- strip libacinerella.so
- cp libacinerella.so ../../../../Output/
-else
- gcc -shared -o acinerella.dll -fPIC acinerella.o -lavformat -lavcodec -lavutil -lm -lswscale -lws2_32
- strip acinerella.dll
-endif
-
-clean:
- rm -f libacinerella.so ../../../../Output/libacinerella.so
\ No newline at end of file
diff --git a/Vocaluxe/Lib/Video/CVideoDecoderFFmpeg.cs b/Vocaluxe/Lib/Video/CVideoDecoderFFmpeg.cs
index dc1e367d..98c07161 100644
--- a/Vocaluxe/Lib/Video/CVideoDecoderFFmpeg.cs
+++ b/Vocaluxe/Lib/Video/CVideoDecoderFFmpeg.cs
@@ -16,7 +16,7 @@
#endregion
using System.Collections.Generic;
-using Vocaluxe.Lib.Video.Acinerella;
+using Vocaluxe.Lib.Video.FFmpeg;
using VocaluxeLib;
namespace Vocaluxe.Lib.Video
diff --git a/Vocaluxe/Lib/Video/Acinerella/CDecoder.cs b/Vocaluxe/Lib/Video/FFmpeg/CDecoder.cs
similarity index 99%
rename from Vocaluxe/Lib/Video/Acinerella/CDecoder.cs
rename to Vocaluxe/Lib/Video/FFmpeg/CDecoder.cs
index 982e6d80..664f89f8 100644
--- a/Vocaluxe/Lib/Video/Acinerella/CDecoder.cs
+++ b/Vocaluxe/Lib/Video/FFmpeg/CDecoder.cs
@@ -19,7 +19,7 @@ using System.Diagnostics;
using System.IO;
using VocaluxeLib.Draw;
-namespace Vocaluxe.Lib.Video.Acinerella
+namespace Vocaluxe.Lib.Video.FFmpeg
{
class CDecoder
{
diff --git a/Vocaluxe/Lib/Video/Acinerella/CDecoderThread.cs b/Vocaluxe/Lib/Video/FFmpeg/CDecoderThread.cs
similarity index 85%
rename from Vocaluxe/Lib/Video/Acinerella/CDecoderThread.cs
rename to Vocaluxe/Lib/Video/FFmpeg/CDecoderThread.cs
index e6c31ba6..32af6f34 100644
--- a/Vocaluxe/Lib/Video/Acinerella/CDecoderThread.cs
+++ b/Vocaluxe/Lib/Video/FFmpeg/CDecoderThread.cs
@@ -17,13 +17,13 @@
using System;
using System.IO;
-using System.Runtime.InteropServices;
using System.Threading;
using Vocaluxe.Base;
+using VocaluxeLib;
using VocaluxeLib.Draw;
using VocaluxeLib.Log;
-namespace Vocaluxe.Lib.Video.Acinerella
+namespace Vocaluxe.Lib.Video.FFmpeg
{
//This class describes a thread decoding a video
//All public methods are meant to be called from "reader" thread only
@@ -35,8 +35,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
private Thread _Thread;
private readonly Object _BufferMutex = new Object();
- private IntPtr _Instance = IntPtr.Zero; // acinerella instance
- private IntPtr _Videodecoder = IntPtr.Zero; // acinerella video decoder instance
+ private CFFmpegDecoder _Decoder;
private readonly CFramebuffer _Framebuffer;
private float _LastDecodedTime; // time of last decoded frame
@@ -77,13 +76,18 @@ namespace Vocaluxe.Lib.Video.Acinerella
_FileName = fileName;
try
{
- _Instance = CAcinerella.AcInit();
- CAcinerella.AcOpen2(_Instance, fileName);
+ _Decoder = new CFFmpegDecoder(EFFmpegStreamType.Video)
+ {
+ HardwareDecoding = CConfig.Config.Video.HardwareDecoding == EOffOn.TR_CONFIG_ON,
+ OutputYuv = CConfig.Config.Video.GpuColorConversion == EOffOn.TR_CONFIG_ON && CDraw.SupportsYuvVideo,
+ DecoderThreads = CConfig.Config.Video.DecoderThreads,
+ MaxHeight = (int)CConfig.Config.Video.VideoDownscaleResolution
+ };
+ var opened = _Decoder.Open(fileName);
- var instance = (SACInstance)Marshal.PtrToStructure(_Instance, typeof(SACInstance));
- Length = instance.Info.Duration / 1000f;
+ Length = _Decoder.Length;
LoopLength = Length;
- var ok = instance.Opened && Length > 0.001f;
+ var ok = opened && Length > 0.001f;
_DropSeekEnabled = true;
if (ok)
{
@@ -95,13 +99,13 @@ namespace Vocaluxe.Lib.Video.Acinerella
catch (Exception) { }
CLog.Error("Error opening video file: " + _FileName);
- _Instance = IntPtr.Zero;
+ _Decoder = null;
return false;
}
public bool Start()
{
- if (_Instance == IntPtr.Zero)
+ if (_Decoder == null)
{
CLog.Error("Tried to start a video file that is not open: " + _FileName);
return false;
@@ -233,7 +237,11 @@ namespace Vocaluxe.Lib.Video.Acinerella
var curFrame = _FindFrame(time);
if (curFrame != null)
{
- if (frame == null)
+ if (_Decoder.OutputYuv)
+ {
+ CDraw.UpdateYuvTexture(ref frame, _Width, _Height, curFrame.Data, _Decoder.YuvToRgbRows);
+ }
+ else if (frame == null)
{
frame = CDraw.AddTexture(_Width, _Height, curFrame.Data);
}
@@ -314,34 +322,15 @@ namespace Vocaluxe.Lib.Video.Acinerella
private bool _OpenVideoStream()
{
- int videoStreamIndex;
- SACDecoder decoder;
- try
+ _Width = _Decoder.Width;
+ _Height = _Decoder.Height;
+
+ if (_Decoder.FramesPerSecond > 0)
{
- _Videodecoder = CAcinerella.AcCreateVideoDecoder(_Instance);
- decoder = (SACDecoder)Marshal.PtrToStructure(_Videodecoder, typeof(SACDecoder));
- videoStreamIndex = decoder.StreamIndex;
- }
- catch (Exception)
- {
- CLog.Error("Error opening video file (can't find decoder): " + _FileName);
- return false;
+ _FrameDuration = 1f / (float)_Decoder.FramesPerSecond;
}
- if (videoStreamIndex < 0)
- {
- return false;
- }
-
- _Width = decoder.StreamInfo.VideoInfo.FrameWidth;
- _Height = decoder.StreamInfo.VideoInfo.FrameHeight;
-
- if (decoder.StreamInfo.VideoInfo.FramesPerSecond > 0)
- {
- _FrameDuration = 1f / (float)decoder.StreamInfo.VideoInfo.FramesPerSecond;
- }
-
- _Framebuffer.Init(_Width * _Height * 4);
+ _Framebuffer.Init(_Decoder.BufferSize);
_FrameAvailable = false;
return true;
}
@@ -349,16 +338,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
//Just call this if thread is not alive
private void _Free()
{
- if (_Videodecoder != IntPtr.Zero)
- {
- CAcinerella.AcFreeDecoder(_Videodecoder);
- }
-
- if (_Instance != IntPtr.Zero)
- {
- CAcinerella.AcClose(_Instance);
- CAcinerella.AcFree(_Instance);
- }
+ _Decoder?.Dispose();
}
// Skip to a given time (in s)
@@ -372,7 +352,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
try
{
- CAcinerella.AcSeek(_Videodecoder, -1, (Int64)(skipTime * 1000f));
+ _Decoder.Seek(true, (Int64)(skipTime * 1000f));
}
catch (Exception e)
{
@@ -418,7 +398,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
{
try
{
- hasFrameDecoded = CAcinerella.AcGetFrame(_Instance, _Videodecoder);
+ hasFrameDecoded = _Decoder.GetFrame();
}
catch (Exception)
{
@@ -454,7 +434,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
var hasFrameDecoded = false;
try
{
- hasFrameDecoded = CAcinerella.AcSeek(_Videodecoder, 0, (long)videoTime * 1000L);
+ hasFrameDecoded = _Decoder.Seek(false, (long)videoTime * 1000L);
}
catch (Exception)
{
@@ -478,7 +458,7 @@ namespace Vocaluxe.Lib.Video.Acinerella
frameDropCount += frameDropCount / 16;
try
{
- hasFrameDecoded = CAcinerella.AcSkipFrames(_Instance, _Videodecoder, frameDropCount);
+ hasFrameDecoded = _Decoder.SkipFrames(frameDropCount);
}
catch (Exception)
{
@@ -493,22 +473,11 @@ namespace Vocaluxe.Lib.Video.Acinerella
private bool _CopyDecodedFrameToBuffer()
{
bool result;
- SACDecoder decoder;
- try
+ if (_Decoder.Buffer != IntPtr.Zero)
{
- decoder = (SACDecoder)Marshal.PtrToStructure(_Videodecoder, typeof(SACDecoder));
- }
- catch (Exception e)
- {
- CLog.Error(e, "Couldn't copy the frame to the managed environment.");
- return false;
- }
-
- if (decoder.Buffer != IntPtr.Zero)
- {
- _LastDecodedTime = (float)decoder.Timecode;
- result = _Framebuffer.Put(decoder.Buffer, _LastDecodedTime);
+ _LastDecodedTime = (float)_Decoder.Timecode;
+ result = _Framebuffer.Put(_Decoder.Buffer, _LastDecodedTime);
}
else
{
diff --git a/Vocaluxe/Lib/Video/FFmpeg/CFFmpegDecoder.cs b/Vocaluxe/Lib/Video/FFmpeg/CFFmpegDecoder.cs
new file mode 100644
index 00000000..88be34e5
--- /dev/null
+++ b/Vocaluxe/Lib/Video/FFmpeg/CFFmpegDecoder.cs
@@ -0,0 +1,734 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using FFmpeg.AutoGen.Abstractions;
+using FFmpeg.AutoGen.Bindings.DynamicallyLoaded;
+
+namespace Vocaluxe.Lib.Video.FFmpeg
+{
+ enum EFFmpegStreamType
+ {
+ Video,
+ Audio
+ }
+
+ sealed unsafe class CFFmpegDecoder : IDisposable
+ {
+ private const AVSampleFormat _OutputSampleFormat = AVSampleFormat.AV_SAMPLE_FMT_S16;
+
+ private static readonly AVRational _TimeBaseQ = new AVRational { num = 1, den = ffmpeg.AV_TIME_BASE };
+
+ private readonly object _Lock = new object();
+ private readonly EFFmpegStreamType _Type;
+ private AVFormatContext* _FormatContext;
+ private AVCodecContext* _CodecContext;
+ private AVPacket* _Packet;
+ private AVFrame* _Frame;
+ private SwsContext* _SwsContext;
+ private SwrContext* _SwrContext;
+ private int _StreamIndex = -1;
+ private AVPixelFormat _OutputPixelFormat = AVPixelFormat.AV_PIX_FMT_BGRA;
+ private AVColorSpace _ColorSpace = AVColorSpace.AVCOL_SPC_NB;
+ private bool _FullRange;
+ private SwsContext* _ConfiguredSwsContext;
+ private int _SwsColorSpace = -1;
+ private int _SwsSourceRange = -1;
+ private AVRational _TimeBase;
+ private bool _EndOfFile;
+ private int _BufferCapacity;
+ private byte_ptr4 _ImageData;
+ private int4 _ImageLineSize;
+ private AVFrame* _TransferFrame;
+ private AVPixelFormat _HardwarePixelFormat = AVPixelFormat.AV_PIX_FMT_NONE;
+ private AVCodecContext_get_format _GetFormatCallback;
+
+ private static readonly object _HardwareDevicesLock = new object();
+ private static readonly Dictionary _HardwareDevices = new Dictionary();
+ private static readonly HashSet _FailedHardwareDevices = new HashSet();
+
+ public bool HardwareDecoding { get; set; }
+ public bool OutputYuv { get; set; }
+ public int MaxHeight { get; set; }
+ public int DecoderThreads { get; set; }
+ public float[] YuvToRgbRows { get; private set; }
+ public string HardwareDeviceName { get; private set; }
+
+ public float Length { get; private set; }
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public double FramesPerSecond { get; private set; }
+ public int SamplesPerSecond { get; private set; }
+ public int ChannelCount { get; private set; }
+ public double Timecode { get; private set; }
+ public IntPtr Buffer { get; private set; }
+ public int BufferSize { get; private set; }
+
+ static CFFmpegDecoder()
+ {
+ DynamicallyLoadedBindings.LibrariesPath = _GetLibrariesPath();
+ DynamicallyLoadedBindings.Initialize();
+ ffmpeg.av_log_set_level(ffmpeg.AV_LOG_QUIET);
+ }
+
+ private static string _GetLibrariesPath()
+ {
+ var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+#if ARCH_X86
+ var folder = "ffmpeg86";
+#else
+ var folder = "ffmpeg64";
+#endif
+ if (OperatingSystem.IsWindows())
+ {
+ return Path.Combine(baseDirectory, "libs", "unmanaged", folder);
+ }
+
+ var candidates = new[]
+ {
+ Path.Combine(baseDirectory, "libs", "unmanaged", folder),
+ Path.Combine(baseDirectory, "libs", "unmanaged"),
+ baseDirectory,
+ "/opt/homebrew/opt/ffmpeg@8/lib",
+ "/usr/local/opt/ffmpeg@8/lib"
+ };
+ var library = OperatingSystem.IsMacOS() ? "libavutil.60.dylib" : "libavutil.so.60";
+ return candidates.FirstOrDefault(c => File.Exists(Path.Combine(c, library))) ?? "";
+ }
+
+ public CFFmpegDecoder(EFFmpegStreamType type)
+ {
+ _Type = type;
+ }
+
+ public bool Open(string fileName)
+ {
+ lock (_Lock)
+ {
+ AVFormatContext* formatContext = null;
+ if (ffmpeg.avformat_open_input(&formatContext, fileName, null, null) < 0)
+ {
+ return false;
+ }
+
+ _FormatContext = formatContext;
+ if (ffmpeg.avformat_find_stream_info(_FormatContext, null) < 0)
+ {
+ _Free();
+ return false;
+ }
+
+ Length = _FormatContext->duration > 0 ? _FormatContext->duration * 1000 / ffmpeg.AV_TIME_BASE / 1000f : 0f;
+
+ var mediaType = _Type == EFFmpegStreamType.Video ? AVMediaType.AVMEDIA_TYPE_VIDEO : AVMediaType.AVMEDIA_TYPE_AUDIO;
+ for (var i = 0; i < _FormatContext->nb_streams; i++)
+ {
+ if (_FormatContext->streams[i]->codecpar->codec_type == mediaType)
+ {
+ _StreamIndex = i;
+ break;
+ }
+ }
+
+ if (_StreamIndex < 0 || !_OpenCodec())
+ {
+ _Free();
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private bool _OpenCodec()
+ {
+ var stream = _FormatContext->streams[_StreamIndex];
+ _TimeBase = stream->time_base;
+
+ var codec = ffmpeg.avcodec_find_decoder(stream->codecpar->codec_id);
+ if (codec == null)
+ {
+ return false;
+ }
+
+ if (_Type == EFFmpegStreamType.Video && HardwareDecoding && stream->codecpar->codec_id == AVCodecID.AV_CODEC_ID_AV1)
+ {
+ var hardwareCodec = ffmpeg.avcodec_find_decoder_by_name("av1");
+ if (hardwareCodec != null && hardwareCodec != codec && _CreateCodecContext(hardwareCodec, stream) && _HardwarePixelFormat != AVPixelFormat.AV_PIX_FMT_NONE &&
+ _ProbeHardwareFrame(stream))
+ {
+ return _InitOutput(stream);
+ }
+
+ _FreeCodecContext();
+ _Rewind(stream);
+ }
+
+ return _CreateCodecContext(codec, stream) && _InitOutput(stream);
+ }
+
+ private bool _CreateCodecContext(AVCodec* codec, AVStream* stream)
+ {
+ _CodecContext = ffmpeg.avcodec_alloc_context3(codec);
+ if (_CodecContext == null || ffmpeg.avcodec_parameters_to_context(_CodecContext, stream->codecpar) < 0)
+ {
+ return false;
+ }
+
+ if (_Type == EFFmpegStreamType.Video)
+ {
+ _CodecContext->thread_count = Math.Max(0, DecoderThreads);
+ _CodecContext->thread_type = ffmpeg.FF_THREAD_FRAME | ffmpeg.FF_THREAD_SLICE;
+ if (HardwareDecoding)
+ {
+ _InitHardwareDecoding(codec);
+ }
+ }
+
+ if (ffmpeg.avcodec_open2(_CodecContext, codec, null) < 0)
+ {
+ return false;
+ }
+
+ if (_Packet == null)
+ {
+ _Packet = ffmpeg.av_packet_alloc();
+ }
+
+ if (_Frame == null)
+ {
+ _Frame = ffmpeg.av_frame_alloc();
+ }
+
+ return true;
+ }
+
+ private bool _ProbeHardwareFrame(AVStream* stream)
+ {
+ var hardwareFrame = _ReceiveFrame() && _Frame->hw_frames_ctx != null;
+ ffmpeg.av_frame_unref(_Frame);
+ _Rewind(stream);
+ ffmpeg.avcodec_flush_buffers(_CodecContext);
+ return hardwareFrame;
+ }
+
+ private void _Rewind(AVStream* stream)
+ {
+ ffmpeg.av_seek_frame(_FormatContext, _StreamIndex, stream->start_time != ffmpeg.AV_NOPTS_VALUE ? stream->start_time : 0, ffmpeg.AVSEEK_FLAG_BACKWARD);
+ _EndOfFile = false;
+ }
+
+ private void _FreeCodecContext()
+ {
+ if (_CodecContext != null)
+ {
+ var codecContext = _CodecContext;
+ ffmpeg.avcodec_free_context(&codecContext);
+ _CodecContext = null;
+ }
+
+ if (_TransferFrame != null)
+ {
+ var transferFrame = _TransferFrame;
+ ffmpeg.av_frame_free(&transferFrame);
+ _TransferFrame = null;
+ }
+
+ _HardwarePixelFormat = AVPixelFormat.AV_PIX_FMT_NONE;
+ HardwareDeviceName = null;
+ }
+
+ private bool _InitOutput(AVStream* stream)
+ {
+ if (_Type == EFFmpegStreamType.Video)
+ {
+ Width = _CodecContext->width;
+ Height = _CodecContext->height;
+ if (stream->r_frame_rate.den > 0)
+ {
+ FramesPerSecond = ffmpeg.av_q2d(stream->r_frame_rate);
+ }
+
+ if (Width <= 0 || Height <= 0)
+ {
+ return false;
+ }
+
+ if (MaxHeight > 0 && Height > MaxHeight)
+ {
+ Width = Math.Max(1, (int)((long)Width * MaxHeight / Height));
+ Height = MaxHeight;
+ }
+
+ _OutputPixelFormat = OutputYuv ? AVPixelFormat.AV_PIX_FMT_YUV420P : AVPixelFormat.AV_PIX_FMT_BGRA;
+ _EnsureBuffer(ffmpeg.av_image_get_buffer_size(_OutputPixelFormat, Width, Height, 1));
+ BufferSize = ffmpeg.av_image_get_buffer_size(_OutputPixelFormat, Width, Height, 1);
+ ffmpeg.av_image_fill_arrays(ref _ImageData, ref _ImageLineSize, (byte*)Buffer, _OutputPixelFormat, Width, Height, 1);
+ _UpdateColorRows(AVColorSpace.AVCOL_SPC_UNSPECIFIED, false);
+ return true;
+ }
+
+ SamplesPerSecond = _CodecContext->sample_rate;
+ ChannelCount = _CodecContext->ch_layout.nb_channels;
+
+ AVChannelLayout layout;
+ if (_CodecContext->ch_layout.order != AVChannelOrder.AV_CHANNEL_ORDER_UNSPEC)
+ {
+ ffmpeg.av_channel_layout_copy(&layout, &_CodecContext->ch_layout);
+ }
+ else
+ {
+ ffmpeg.av_channel_layout_default(&layout, ChannelCount);
+ }
+
+ SwrContext* swrContext = null;
+ var result = ffmpeg.swr_alloc_set_opts2(&swrContext, &layout, _OutputSampleFormat, SamplesPerSecond, &layout, _CodecContext->sample_fmt, SamplesPerSecond, 0,
+ null);
+ ffmpeg.av_channel_layout_uninit(&layout);
+ _SwrContext = swrContext;
+ return result >= 0 && ffmpeg.swr_init(_SwrContext) >= 0;
+ }
+
+ private int _GetSwsColorSpace(AVColorSpace colorSpace)
+ {
+ switch (colorSpace)
+ {
+ case AVColorSpace.AVCOL_SPC_BT709:
+ return ffmpeg.SWS_CS_ITU709;
+ case AVColorSpace.AVCOL_SPC_BT2020_NCL:
+ case AVColorSpace.AVCOL_SPC_BT2020_CL:
+ return ffmpeg.SWS_CS_BT2020;
+ case AVColorSpace.AVCOL_SPC_UNSPECIFIED:
+ return Height >= 720 ? ffmpeg.SWS_CS_ITU709 : ffmpeg.SWS_CS_ITU601;
+ default:
+ return ffmpeg.SWS_CS_ITU601;
+ }
+ }
+
+ private void _UpdateColorRows(AVColorSpace colorSpace, bool fullRange)
+ {
+ if (colorSpace == _ColorSpace && fullRange == _FullRange && YuvToRgbRows != null)
+ {
+ return;
+ }
+
+ _ColorSpace = colorSpace;
+ _FullRange = fullRange;
+
+ double kr, kb;
+ switch (_GetSwsColorSpace(colorSpace))
+ {
+ case ffmpeg.SWS_CS_ITU709:
+ kr = 0.2126;
+ kb = 0.0722;
+ break;
+ case ffmpeg.SWS_CS_BT2020:
+ kr = 0.2627;
+ kb = 0.0593;
+ break;
+ default:
+ kr = 0.299;
+ kb = 0.114;
+ break;
+ }
+
+ var kg = 1 - kr - kb;
+ var yScale = fullRange ? 1.0 : 255.0 / 219.0;
+ var yOffset = fullRange ? 0.0 : -16.0 / 255.0 * yScale;
+ var cScale = fullRange ? 1.0 : 255.0 / 224.0;
+ const double cCenter = 128.0 / 255.0;
+
+ var rv = 2 * (1 - kr) * cScale;
+ var gu = -2 * kb * (1 - kb) / kg * cScale;
+ var gv = -2 * kr * (1 - kr) / kg * cScale;
+ var bu = 2 * (1 - kb) * cScale;
+
+ YuvToRgbRows = new[]
+ {
+ (float)yScale, 0f, (float)rv, (float)(yOffset - rv * cCenter),
+ (float)yScale, (float)gu, (float)gv, (float)(yOffset - (gu + gv) * cCenter),
+ (float)yScale, (float)bu, 0f, (float)(yOffset - bu * cCenter)
+ };
+ }
+
+ private static AVHWDeviceType[] _GetHardwareDeviceTypes()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ return new[] { AVHWDeviceType.AV_HWDEVICE_TYPE_D3D11VA, AVHWDeviceType.AV_HWDEVICE_TYPE_DXVA2 };
+ }
+
+ if (OperatingSystem.IsMacOS())
+ {
+ return new[] { AVHWDeviceType.AV_HWDEVICE_TYPE_VIDEOTOOLBOX };
+ }
+
+ return new[] { AVHWDeviceType.AV_HWDEVICE_TYPE_VAAPI, AVHWDeviceType.AV_HWDEVICE_TYPE_VDPAU };
+ }
+
+ private static AVPixelFormat _GetHardwarePixelFormat(AVCodec* codec, AVHWDeviceType type)
+ {
+ const int hwConfigMethodHwDeviceContext = 0x01;
+ AVCodecHWConfig* config;
+ for (var i = 0; (config = ffmpeg.avcodec_get_hw_config(codec, i)) != null; i++)
+ {
+ if ((config->methods & hwConfigMethodHwDeviceContext) != 0 && config->device_type == type)
+ {
+ return config->pix_fmt;
+ }
+ }
+
+ return AVPixelFormat.AV_PIX_FMT_NONE;
+ }
+
+ private static AVBufferRef* _GetHardwareDevice(AVHWDeviceType type)
+ {
+ lock (_HardwareDevicesLock)
+ {
+ if (_FailedHardwareDevices.Contains(type))
+ {
+ return null;
+ }
+
+ IntPtr device;
+ if (!_HardwareDevices.TryGetValue(type, out device))
+ {
+ AVBufferRef* deviceContext = null;
+ if (ffmpeg.av_hwdevice_ctx_create(&deviceContext, type, null, null, 0) < 0)
+ {
+ _FailedHardwareDevices.Add(type);
+ return null;
+ }
+
+ device = (IntPtr)deviceContext;
+ _HardwareDevices.Add(type, device);
+ }
+
+ return ffmpeg.av_buffer_ref((AVBufferRef*)device);
+ }
+ }
+
+ private void _InitHardwareDecoding(AVCodec* codec)
+ {
+ foreach (var type in _GetHardwareDeviceTypes())
+ {
+ var pixelFormat = _GetHardwarePixelFormat(codec, type);
+ if (pixelFormat == AVPixelFormat.AV_PIX_FMT_NONE)
+ {
+ continue;
+ }
+
+ var device = _GetHardwareDevice(type);
+ if (device == null)
+ {
+ continue;
+ }
+
+ _HardwarePixelFormat = pixelFormat;
+ _CodecContext->hw_device_ctx = device;
+ _GetFormatCallback = _GetFormat;
+ _CodecContext->get_format = _GetFormatCallback;
+ _TransferFrame = ffmpeg.av_frame_alloc();
+ HardwareDeviceName = ffmpeg.av_hwdevice_get_type_name(type);
+ return;
+ }
+ }
+
+ private AVPixelFormat _GetFormat(AVCodecContext* context, AVPixelFormat* formats)
+ {
+ var fallback = AVPixelFormat.AV_PIX_FMT_NONE;
+ for (var format = formats; *format != AVPixelFormat.AV_PIX_FMT_NONE; format++)
+ {
+ if (*format == _HardwarePixelFormat)
+ {
+ return *format;
+ }
+
+ if (fallback == AVPixelFormat.AV_PIX_FMT_NONE)
+ {
+ var descriptor = ffmpeg.av_pix_fmt_desc_get(*format);
+ if (descriptor != null && (descriptor->flags & ffmpeg.AV_PIX_FMT_FLAG_HWACCEL) == 0)
+ {
+ fallback = *format;
+ }
+ }
+ }
+
+ HardwareDeviceName = null;
+ return fallback;
+ }
+
+ private void _EnsureBuffer(int size)
+ {
+ if (size <= _BufferCapacity)
+ {
+ return;
+ }
+
+ Buffer = Buffer == IntPtr.Zero ? Marshal.AllocHGlobal(size) : Marshal.ReAllocHGlobal(Buffer, (IntPtr)size);
+ _BufferCapacity = size;
+ }
+
+ private bool _ReceiveFrame()
+ {
+ while (true)
+ {
+ var result = ffmpeg.avcodec_receive_frame(_CodecContext, _Frame);
+ if (result >= 0)
+ {
+ _UpdateTimecode();
+ return true;
+ }
+
+ if (result != ffmpeg.AVERROR(ffmpeg.EAGAIN) || _EndOfFile)
+ {
+ return false;
+ }
+
+ while (true)
+ {
+ if (ffmpeg.av_read_frame(_FormatContext, _Packet) < 0)
+ {
+ _EndOfFile = true;
+ ffmpeg.avcodec_send_packet(_CodecContext, null);
+ break;
+ }
+
+ var isOwnStream = _Packet->stream_index == _StreamIndex;
+ if (isOwnStream)
+ {
+ ffmpeg.avcodec_send_packet(_CodecContext, _Packet);
+ }
+
+ ffmpeg.av_packet_unref(_Packet);
+ if (isOwnStream)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ private void _UpdateTimecode()
+ {
+ var timestamp = _Frame->best_effort_timestamp;
+ if (timestamp != ffmpeg.AV_NOPTS_VALUE)
+ {
+ Timecode = timestamp * ffmpeg.av_q2d(_TimeBase);
+ }
+ }
+
+ private bool _ConvertVideoFrame()
+ {
+ var frame = _Frame;
+ if (frame->hw_frames_ctx != null)
+ {
+ ffmpeg.av_frame_unref(_TransferFrame);
+ if (ffmpeg.av_hwframe_transfer_data(_TransferFrame, frame, 0) < 0)
+ {
+ return false;
+ }
+
+ frame = _TransferFrame;
+ }
+
+ var format = (AVPixelFormat)frame->format;
+ if (OutputYuv && (format == AVPixelFormat.AV_PIX_FMT_YUV420P || format == AVPixelFormat.AV_PIX_FMT_YUVJ420P) && frame->width == Width && frame->height == Height)
+ {
+ _UpdateColorRows(_Frame->colorspace, _Frame->color_range == AVColorRange.AVCOL_RANGE_JPEG || format == AVPixelFormat.AV_PIX_FMT_YUVJ420P);
+ var data = new byte_ptr4();
+ var lineSize = new int4();
+ for (uint i = 0; i < 4; i++)
+ {
+ data[i] = frame->data[i];
+ lineSize[i] = frame->linesize[i];
+ }
+
+ return ffmpeg.av_image_copy_to_buffer((byte*)Buffer, BufferSize, data, lineSize, AVPixelFormat.AV_PIX_FMT_YUV420P, Width, Height, 1) >= 0;
+ }
+
+ _UpdateColorRows(_Frame->colorspace, false);
+ _SwsContext = ffmpeg.sws_getCachedContext(_SwsContext, frame->width, frame->height, format, Width, Height, _OutputPixelFormat,
+ (int)SwsFlags.SWS_BICUBIC, null, null, null);
+ if (_SwsContext == null)
+ {
+ return false;
+ }
+
+ if (_OutputPixelFormat == AVPixelFormat.AV_PIX_FMT_BGRA)
+ {
+ var colorSpace = _GetSwsColorSpace(_Frame->colorspace);
+ var sourceRange = _Frame->color_range == AVColorRange.AVCOL_RANGE_JPEG || format == AVPixelFormat.AV_PIX_FMT_YUVJ420P ||
+ format == AVPixelFormat.AV_PIX_FMT_YUVJ422P || format == AVPixelFormat.AV_PIX_FMT_YUVJ444P ? 1 : 0;
+ if (_SwsContext != _ConfiguredSwsContext || colorSpace != _SwsColorSpace || sourceRange != _SwsSourceRange)
+ {
+ var sourceTable = *(int4*)ffmpeg.sws_getCoefficients(colorSpace);
+ var targetTable = *(int4*)ffmpeg.sws_getCoefficients(ffmpeg.SWS_CS_DEFAULT);
+ ffmpeg.sws_setColorspaceDetails(_SwsContext, sourceTable, sourceRange, targetTable, 1, 0, 1 << 16, 1 << 16);
+ _ConfiguredSwsContext = _SwsContext;
+ _SwsColorSpace = colorSpace;
+ _SwsSourceRange = sourceRange;
+ }
+ }
+
+ return ffmpeg.sws_scale(_SwsContext, frame->data.ToArray(), frame->linesize.ToArray(), 0, frame->height, _ImageData.ToArray(), _ImageLineSize.ToArray()) > 0;
+ }
+
+ private bool _ConvertAudioFrame()
+ {
+ var outSamples = ffmpeg.swr_get_out_samples(_SwrContext, _Frame->nb_samples);
+ if (outSamples <= 0)
+ {
+ BufferSize = 0;
+ return true;
+ }
+
+ var bytesPerSample = ChannelCount * sizeof(short);
+ _EnsureBuffer(outSamples * bytesPerSample);
+ var output = (byte*)Buffer;
+ var input = _Frame->extended_data;
+ var converted = ffmpeg.swr_convert(_SwrContext, &output, outSamples, input, _Frame->nb_samples);
+ if (converted < 0)
+ {
+ return false;
+ }
+
+ BufferSize = converted * bytesPerSample;
+ return true;
+ }
+
+ public bool GetFrame()
+ {
+ lock (_Lock)
+ {
+ if (_CodecContext == null || !_ReceiveFrame())
+ {
+ return false;
+ }
+
+ return _Type == EFFmpegStreamType.Video ? _ConvertVideoFrame() : _ConvertAudioFrame();
+ }
+ }
+
+ public bool SkipFrames(int count)
+ {
+ lock (_Lock)
+ {
+ if (_CodecContext == null)
+ {
+ return false;
+ }
+
+ for (var i = 0; i < count; i++)
+ {
+ if (!_ReceiveFrame())
+ {
+ return false;
+ }
+ }
+
+ return _Type != EFFmpegStreamType.Video || _ConvertVideoFrame();
+ }
+ }
+
+ public bool Seek(bool backward, long targetMilliseconds)
+ {
+ lock (_Lock)
+ {
+ if (_CodecContext == null)
+ {
+ return false;
+ }
+
+ Timecode = targetMilliseconds / 1000.0;
+ var position = ffmpeg.av_rescale_q(targetMilliseconds * 1000, _TimeBaseQ, _TimeBase);
+ if (ffmpeg.av_seek_frame(_FormatContext, _StreamIndex, position, backward ? ffmpeg.AVSEEK_FLAG_BACKWARD : 0) < 0)
+ {
+ return false;
+ }
+
+ ffmpeg.avcodec_flush_buffers(_CodecContext);
+ if (_SwrContext != null)
+ {
+ ffmpeg.swr_init(_SwrContext);
+ }
+
+ _EndOfFile = false;
+ return true;
+ }
+ }
+
+ private void _Free()
+ {
+ if (_SwsContext != null)
+ {
+ ffmpeg.sws_freeContext(_SwsContext);
+ _SwsContext = null;
+ }
+
+ if (_SwrContext != null)
+ {
+ var swrContext = _SwrContext;
+ ffmpeg.swr_free(&swrContext);
+ _SwrContext = null;
+ }
+
+ if (_Frame != null)
+ {
+ var frame = _Frame;
+ ffmpeg.av_frame_free(&frame);
+ _Frame = null;
+ }
+
+ if (_Packet != null)
+ {
+ var packet = _Packet;
+ ffmpeg.av_packet_free(&packet);
+ _Packet = null;
+ }
+
+ _FreeCodecContext();
+
+ if (_FormatContext != null)
+ {
+ var formatContext = _FormatContext;
+ ffmpeg.avformat_close_input(&formatContext);
+ _FormatContext = null;
+ }
+
+ if (Buffer != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(Buffer);
+ Buffer = IntPtr.Zero;
+ _BufferCapacity = 0;
+ BufferSize = 0;
+ }
+ }
+
+ public void Dispose()
+ {
+ lock (_Lock)
+ {
+ _Free();
+ }
+ }
+ }
+}
diff --git a/Vocaluxe/Lib/Video/Acinerella/CFramebuffer.cs b/Vocaluxe/Lib/Video/FFmpeg/CFramebuffer.cs
similarity index 98%
rename from Vocaluxe/Lib/Video/Acinerella/CFramebuffer.cs
rename to Vocaluxe/Lib/Video/FFmpeg/CFramebuffer.cs
index 44b6e1ae..7a7ce518 100644
--- a/Vocaluxe/Lib/Video/Acinerella/CFramebuffer.cs
+++ b/Vocaluxe/Lib/Video/FFmpeg/CFramebuffer.cs
@@ -18,7 +18,7 @@
using System;
using System.Runtime.InteropServices;
-namespace Vocaluxe.Lib.Video.Acinerella
+namespace Vocaluxe.Lib.Video.FFmpeg
{
class CFramebuffer
{
diff --git a/Vocaluxe/Lib/Webcam/CAForgeNet.cs b/Vocaluxe/Lib/Webcam/CAForgeNet.cs
index dfcb3cec..00a3ae6b 100644
--- a/Vocaluxe/Lib/Webcam/CAForgeNet.cs
+++ b/Vocaluxe/Lib/Webcam/CAForgeNet.cs
@@ -22,6 +22,7 @@ using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using AForge.Video;
using AForge.Video.DirectShow;
+using SkiaSharp;
using Vocaluxe.Base;
using VocaluxeLib.Draw;
@@ -241,16 +242,14 @@ namespace Vocaluxe.Lib.Webcam
}
}
- public Bitmap GetBitmap()
+ public SKBitmap GetBitmap()
{
lock (_MutexData)
{
if (_Data != null && _Data.Length == _Width * _Height * 4)
{
- var bmp = new Bitmap(_Width, _Height);
- var bitmapdata = bmp.LockBits(new Rectangle(0, 0, _Width, _Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
- Marshal.Copy(_Data, 0, bitmapdata.Scan0, _Data.Length);
- bmp.UnlockBits(bitmapdata);
+ var bmp = new SKBitmap(new SKImageInfo(_Width, _Height, SKColorType.Bgra8888, SKAlphaType.Unpremul));
+ Marshal.Copy(_Data, 0, bmp.GetPixels(), _Data.Length);
return bmp;
}
diff --git a/Vocaluxe/Lib/Webcam/IWebcam.cs b/Vocaluxe/Lib/Webcam/IWebcam.cs
index 289b51f5..ee90cad6 100644
--- a/Vocaluxe/Lib/Webcam/IWebcam.cs
+++ b/Vocaluxe/Lib/Webcam/IWebcam.cs
@@ -16,7 +16,7 @@
#endregion
using System.Collections.Generic;
-using System.Drawing;
+using SkiaSharp;
using VocaluxeLib.Draw;
namespace Vocaluxe.Lib.Webcam
@@ -64,7 +64,7 @@ namespace Vocaluxe.Lib.Webcam
/// Gets the last captured frame as a bitma
///
/// Null if no frame was captured, the bitmap otherwise
- Bitmap GetBitmap();
+ SKBitmap GetBitmap();
SWebcamConfig GetConfig();
SWebcamDevice[] GetDevices();
diff --git a/Vocaluxe/Program.cs b/Vocaluxe/Program.cs
index a8180d5f..0285fd35 100644
--- a/Vocaluxe/Program.cs
+++ b/Vocaluxe/Program.cs
@@ -16,19 +16,24 @@
#endregion
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
-using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
-using System.Windows.Forms;
+using System.Xml.Linq;
using SQLitePCL;
using Vocaluxe.Base;
using Vocaluxe.Base.Fonts;
using Vocaluxe.Base.Web;
using Vocaluxe.Base.ThemeSystem;
+using Vocaluxe.Lib;
using Vocaluxe.Reporting;
using VocaluxeLib.Log;
@@ -46,7 +51,7 @@ namespace Vocaluxe
{
private static CSplashScreen _SplashScreen;
- [STAThread, HandleProcessCorruptedStateExceptions]
+ [STAThread]
// ReSharper disable InconsistentNaming
private static void Main(string[] args)
// ReSharper restore InconsistentNaming
@@ -55,21 +60,18 @@ namespace Vocaluxe
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
#endif
AppDomain.CurrentDomain.AssemblyResolve += _AssemblyResolver;
- COSFunctions.AddEnvironmentPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs\\unmanaged\\"));
-#if ARCH_X86
- COSFunctions.AddEnvironmentPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs\\unmanaged\\x86\\"));
-#endif
-#if ARCH_X64
- COSFunctions.AddEnvironmentPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs\\unmanaged\\x64\\"));
-#endif
+ AssemblyLoadContext.Default.ResolvingUnmanagedDll += _NativeLibraryResolver;
+ _Main(args);
+ }
- // Microsoft.Data.Sqlite (SQLitePCLRaw) native library location
-#if ARCH_X86
- COSFunctions.AddEnvironmentPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs\\managed\\runtimes\\win-x86\\native\\"));
-#endif
-#if ARCH_X64
- COSFunctions.AddEnvironmentPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs\\managed\\runtimes\\win-x64\\native\\"));
-#endif
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void _Main(string[] args)
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ foreach (var path in _NativeLibraryPaths)
+ {
+ COSFunctions.AddEnvironmentPath(path);
+ }
// Make sure SQLitePCL is initialized before any DB usage
Batteries_V2.Init();
@@ -97,7 +99,7 @@ namespace Vocaluxe
private static void _Run(string[] args)
{
- Application.DoEvents();
+ CSdl.DoEvents();
try
{
@@ -118,12 +120,17 @@ namespace Vocaluxe
return;
}
+ if (!CSdl.Init())
+ {
+ throw new CLoadingException("SDL");
+ }
+
CProgrammHelper.Init();
using (CBenchmark.Time("Init Program"))
{
CMain.Init();
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Language
using (CBenchmark.Time("Init Language"))
@@ -134,7 +141,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// load config
using (CBenchmark.Time("Init Config"))
@@ -149,7 +156,7 @@ namespace Vocaluxe
CSettings.CreateFolders();
_SplashScreen = new CSplashScreen();
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Draw
using (CBenchmark.Time("Init Draw"))
@@ -160,7 +167,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Playback
using (CBenchmark.Time("Init Playback"))
@@ -171,7 +178,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Record
using (CBenchmark.Time("Init Record"))
@@ -182,7 +189,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init VideoDecoder
using (CBenchmark.Time("Init Videodecoder"))
@@ -193,7 +200,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Database
using (CBenchmark.Time("Init Database"))
@@ -206,7 +213,7 @@ namespace Vocaluxe
CSongs.StartLoadSongs();
- Application.DoEvents();
+ CSdl.DoEvents();
//Init Webcam
using (CBenchmark.Time("Init Webcam"))
@@ -217,7 +224,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Background Music
using (CBenchmark.Time("Init Background Music"))
@@ -225,7 +232,7 @@ namespace Vocaluxe
CBackgroundMusic.Init();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Profiles
using (CBenchmark.Time("Init Profiles"))
@@ -233,7 +240,7 @@ namespace Vocaluxe
CProfiles.Init();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Fonts
using (CBenchmark.Time("Init Fonts"))
@@ -244,7 +251,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Theme System
using (CBenchmark.Time("Init Theme"))
@@ -260,7 +267,7 @@ namespace Vocaluxe
CThemes.Load();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Load Cover
using (CBenchmark.Time("Init Cover"))
@@ -271,7 +278,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Screens
using (CBenchmark.Time("Init Screens"))
@@ -279,7 +286,7 @@ namespace Vocaluxe
CGraphics.Init();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Server
using (CBenchmark.Time("Init Server"))
@@ -287,7 +294,7 @@ namespace Vocaluxe
CWebServer.Init();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Input
using (CBenchmark.Time("Init Input"))
@@ -296,7 +303,7 @@ namespace Vocaluxe
CController.Connect();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Game
using (CBenchmark.Time("Init Game"))
@@ -306,7 +313,7 @@ namespace Vocaluxe
CConfig.UsePlayers();
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Init Party Modes
using (CBenchmark.Time("Init Party Modes"))
@@ -317,7 +324,7 @@ namespace Vocaluxe
}
}
- Application.DoEvents();
+ CSdl.DoEvents();
//Only reasonable point to call GC.Collect() because initialization may cause lots of garbage
//Rely on GC doing its job afterwards and call Dispose methods where appropriate
GC.Collect();
@@ -335,7 +342,7 @@ namespace Vocaluxe
return;
}
- Application.DoEvents();
+ CSdl.DoEvents();
// Start Main Loop
if (_SplashScreen != null)
@@ -466,6 +473,15 @@ namespace Vocaluxe
CLog.Error(e, "Error during shutdown! (CDraw)");
}
+ try
+ {
+ CSdl.Close();
+ }
+ catch (Exception e)
+ {
+ CLog.Error(e, "Error during shutdown! (CSdl)");
+ }
+
GC.Collect(); // Do a GC run here before we close logs to have finalizers run
try
{
@@ -477,7 +493,6 @@ namespace Vocaluxe
}
#if !DEBUG
- [HandleProcessCorruptedStateExceptions]
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
var e = (Exception)args.ExceptionObject;
@@ -508,11 +523,11 @@ namespace Vocaluxe
string[] probePaths =
{
- Path.Combine(CSettings.ProgramFolder, "libs", "managed", fileName),
- Path.Combine(CSettings.ProgramFolder, "libs", "managed", arch, fileName),
- Path.Combine(CSettings.ProgramFolder, "libs", fileName),
- Path.Combine(CSettings.ProgramFolder, arch, fileName),
- Path.Combine(CSettings.ProgramFolder, fileName)
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", "managed", fileName),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", "managed", arch, fileName),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", fileName),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, arch, fileName),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName)
};
var path = probePaths.FirstOrDefault(File.Exists);
@@ -534,7 +549,7 @@ namespace Vocaluxe
}
catch (Exception e)
{
- CLog.Error("Cannot load assembly " + args.Name + " from " + path + ": " + e + "\r\nOuter Error: " + e1);
+ _LogError("Cannot load assembly " + args.Name + " from " + path + ": " + e + "\r\nOuter Error: " + e1);
}
}
#if LINUX
@@ -545,7 +560,167 @@ namespace Vocaluxe
return assembly;
}
- private static readonly Mutex _Mutex = new Mutex(false, Application.ProductName + "-SingleInstanceMutex");
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void _LogError(string message)
+ {
+ CLog.Error(message);
+ }
+
+ private static string _Arch
+ {
+ get
+ {
+#if ARCH_X86
+ return "x86";
+#else
+ return "x64";
+#endif
+ }
+ }
+
+ private static readonly string[] _NativeLibraryPaths =
+ {
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", "unmanaged"),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", "unmanaged", _Arch),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", "managed"),
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "libs", "managed", _Arch),
+ AppDomain.CurrentDomain.BaseDirectory
+ };
+
+ private static readonly Regex _VersionedLibraryName = new Regex(@"^(?:lib)?(.+?)-(\d+)$");
+
+ private static IntPtr _NativeLibraryResolver(Assembly assembly, string libraryName)
+ {
+ var mapped = _GetMappedLibraryName(assembly, libraryName);
+ var names = mapped != null ? new[] { mapped }.Concat(_GetNativeLibraryNames(libraryName)) : _GetNativeLibraryNames(libraryName);
+ var folders = OperatingSystem.IsMacOS() ? _NativeLibraryPaths.Concat(new[] { "/opt/homebrew/lib", "/usr/local/lib" }) : _NativeLibraryPaths;
+ foreach (var name in names)
+ {
+ foreach (var folder in folders)
+ {
+ var path = Path.Combine(folder, name);
+ IntPtr handle;
+ if (File.Exists(path) && NativeLibrary.TryLoad(path, out handle))
+ {
+ return handle;
+ }
+ }
+
+ IntPtr systemHandle;
+ if (NativeLibrary.TryLoad(name, out systemHandle))
+ {
+ return systemHandle;
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ private static string _GetMappedLibraryName(Assembly assembly, string libraryName)
+ {
+ if (OperatingSystem.IsWindows() || string.IsNullOrEmpty(assembly.Location))
+ {
+ return null;
+ }
+
+ var config = assembly.Location + ".config";
+ if (!File.Exists(config))
+ {
+ return null;
+ }
+
+ try
+ {
+ var os = OperatingSystem.IsMacOS() ? "osx" : "linux";
+ foreach (var map in XDocument.Load(config).Descendants("dllmap"))
+ {
+ var mapOs = (string)map.Attribute("os");
+ if (string.Equals((string)map.Attribute("dll"), libraryName, StringComparison.OrdinalIgnoreCase) &&
+ (mapOs == null || mapOs.Split(',').Any(o => o.Trim() == os)))
+ {
+ return (string)map.Attribute("target");
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+
+ return null;
+ }
+
+ private static IEnumerable _GetNativeLibraryNames(string libraryName)
+ {
+ var baseName = Path.GetFileName(libraryName);
+ foreach (var extension in new[] { ".dll", ".so", ".dylib" })
+ {
+ if (baseName.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
+ {
+ baseName = baseName.Substring(0, baseName.Length - extension.Length);
+ }
+ }
+
+ var soIndex = baseName.IndexOf(".so.", StringComparison.Ordinal);
+ if (soIndex > 0)
+ {
+ baseName = baseName.Substring(0, soIndex);
+ }
+
+ if (baseName.StartsWith("lib", StringComparison.Ordinal))
+ {
+ baseName = baseName.Substring(3);
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ yield return baseName + ".dll";
+ yield return "lib" + baseName + ".dll";
+ yield break;
+ }
+
+ var names = new List { baseName, baseName.ToLowerInvariant() };
+ var versioned = _VersionedLibraryName.Match(baseName);
+ if (versioned.Success)
+ {
+ names.Insert(0, versioned.Groups[1].Value);
+ }
+
+ if (baseName.EndsWith("-libusb", StringComparison.Ordinal))
+ {
+ names.Add(baseName.Substring(0, baseName.Length - "-libusb".Length));
+ }
+
+ foreach (var name in names)
+ {
+ if (OperatingSystem.IsMacOS())
+ {
+ if (versioned.Success && name == versioned.Groups[1].Value)
+ {
+ yield return "lib" + name + "." + versioned.Groups[2].Value + ".dylib";
+ }
+
+ yield return "lib" + name + ".dylib";
+ yield return name + ".dylib";
+ }
+ else
+ {
+ if (versioned.Success && name == versioned.Groups[1].Value)
+ {
+ yield return "lib" + name + ".so." + versioned.Groups[2].Value;
+ }
+
+ yield return "lib" + name + ".so";
+ yield return name + ".so";
+ for (var version = 0; version <= 2; version++)
+ {
+ yield return "lib" + name + ".so." + version;
+ }
+ }
+ }
+ }
+
+ private static readonly Mutex _Mutex = new Mutex(false, CSettings.ProgramName + "-SingleInstanceMutex");
private static bool _EnsureSingleInstance()
{
@@ -553,7 +728,7 @@ namespace Vocaluxe
if (!_Mutex.WaitOne(TimeSpan.FromSeconds(2), false))
{
//TODO: put it into language file
- MessageBox.Show("Another Instance of Vocaluxe is already runnning!");
+ CSdl.ShowMessage(CSettings.ProgramName, "Another Instance of Vocaluxe is already runnning!");
#if WIN
var currentProcess = Process.GetCurrentProcess();
var processes = Process.GetProcessesByName(currentProcess.ProcessName);
diff --git a/Vocaluxe/Reporting/Reporter.Designer.cs b/Vocaluxe/Reporting/Reporter.Designer.cs
deleted file mode 100644
index 4a27bf90..00000000
--- a/Vocaluxe/Reporting/Reporter.Designer.cs
+++ /dev/null
@@ -1,220 +0,0 @@
-namespace Vocaluxe.Reporting
-{
- partial class CReporter
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CReporter));
- this.TitleLabel = new System.Windows.Forms.Label();
- this.MessageText = new System.Windows.Forms.TextBox();
- this.LogBox = new System.Windows.Forms.TextBox();
- this.GistOnlySelect = new System.Windows.Forms.RadioButton();
- this.GistAndIssueSelect = new System.Windows.Forms.RadioButton();
- this.NoUploadSelect = new System.Windows.Forms.RadioButton();
- this.SubmitButton = new System.Windows.Forms.Button();
- this.Url = new System.Windows.Forms.TextBox();
- this.SubmitedTitleLabel = new System.Windows.Forms.Label();
- this.LastErrorTitleLabel = new System.Windows.Forms.Label();
- this.LastErrorBox = new System.Windows.Forms.TextBox();
- this.SuspendLayout();
- //
- // TitleLabel
- //
- this.TitleLabel.AutoSize = true;
- this.TitleLabel.Enabled = false;
- this.TitleLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.TitleLabel.Location = new System.Drawing.Point(9, 8);
- this.TitleLabel.Name = "TitleLabel";
- this.TitleLabel.Size = new System.Drawing.Size(110, 20);
- this.TitleLabel.TabIndex = 0;
- this.TitleLabel.Text = "We are sorry";
- //
- // MessageText
- //
- this.MessageText.BorderStyle = System.Windows.Forms.BorderStyle.None;
- this.MessageText.Enabled = false;
- this.MessageText.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.MessageText.Location = new System.Drawing.Point(13, 31);
- this.MessageText.Multiline = true;
- this.MessageText.Name = "MessageText";
- this.MessageText.ReadOnly = true;
- this.MessageText.Size = new System.Drawing.Size(625, 63);
- this.MessageText.TabIndex = 0;
- this.MessageText.TabStop = false;
- this.MessageText.Text = "that Vocaluxe run into an error and crashed.\r\nTo help us fix the problem please s" +
- "end us a report.\r\nEdit the following log before submitting to remove possibly se" +
- "nsitive information.";
- //
- // LogBox
- //
- this.LogBox.AcceptsReturn = true;
- this.LogBox.AcceptsTab = true;
- this.LogBox.Location = new System.Drawing.Point(13, 182);
- this.LogBox.Multiline = true;
- this.LogBox.Name = "LogBox";
- this.LogBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
- this.LogBox.Size = new System.Drawing.Size(625, 189);
- this.LogBox.TabIndex = 1;
- this.LogBox.WordWrap = false;
- //
- // GistOnlySelect
- //
- this.GistOnlySelect.AutoSize = true;
- this.GistOnlySelect.Checked = true;
- this.GistOnlySelect.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.GistOnlySelect.Location = new System.Drawing.Point(12, 377);
- this.GistOnlySelect.Name = "GistOnlySelect";
- this.GistOnlySelect.Size = new System.Drawing.Size(385, 24);
- this.GistOnlySelect.TabIndex = 2;
- this.GistOnlySelect.TabStop = true;
- this.GistOnlySelect.Text = "Upload and get a link to your report (publicly visible)";
- this.GistOnlySelect.UseVisualStyleBackColor = true;
- //
- // GistAndIssueSelect
- //
- this.GistAndIssueSelect.AutoSize = true;
- this.GistAndIssueSelect.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.GistAndIssueSelect.Location = new System.Drawing.Point(12, 407);
- this.GistAndIssueSelect.Name = "GistAndIssueSelect";
- this.GistAndIssueSelect.Size = new System.Drawing.Size(519, 24);
- this.GistAndIssueSelect.TabIndex = 3;
- this.GistAndIssueSelect.TabStop = true;
- this.GistAndIssueSelect.Text = "Upload and open an issue (publicly visible + requires a github account)";
- this.GistAndIssueSelect.UseVisualStyleBackColor = true;
- //
- // NoUploadSelect
- //
- this.NoUploadSelect.AutoSize = true;
- this.NoUploadSelect.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.NoUploadSelect.Location = new System.Drawing.Point(12, 437);
- this.NoUploadSelect.Name = "NoUploadSelect";
- this.NoUploadSelect.Size = new System.Drawing.Size(493, 24);
- this.NoUploadSelect.TabIndex = 4;
- this.NoUploadSelect.TabStop = true;
- this.NoUploadSelect.Text = "Don\'t upload anything (you can still copy the error message above)";
- this.NoUploadSelect.UseVisualStyleBackColor = true;
- this.NoUploadSelect.CheckedChanged += new System.EventHandler(this.NoUpload_CheckedChanged);
- //
- // SubmitButton
- //
- this.SubmitButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.SubmitButton.Location = new System.Drawing.Point(537, 388);
- this.SubmitButton.Name = "SubmitButton";
- this.SubmitButton.Size = new System.Drawing.Size(101, 73);
- this.SubmitButton.TabIndex = 5;
- this.SubmitButton.Text = "Submit";
- this.SubmitButton.UseVisualStyleBackColor = true;
- this.SubmitButton.Click += new System.EventHandler(this.Submit_Click);
- //
- // Url
- //
- this.Url.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.Url.Location = new System.Drawing.Point(12, 409);
- this.Url.Name = "Url";
- this.Url.ReadOnly = true;
- this.Url.Size = new System.Drawing.Size(519, 22);
- this.Url.TabIndex = 6;
- this.Url.Text = "https://gist.github.com/lukeIam/cc81f7ef3545ef2baf0ca9a28ffe4bd8";
- this.Url.Visible = false;
- //
- // SubmitedTitleLabel
- //
- this.SubmitedTitleLabel.AutoSize = true;
- this.SubmitedTitleLabel.Enabled = false;
- this.SubmitedTitleLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.SubmitedTitleLabel.Location = new System.Drawing.Point(9, 381);
- this.SubmitedTitleLabel.Name = "SubmitedTitleLabel";
- this.SubmitedTitleLabel.Size = new System.Drawing.Size(189, 20);
- this.SubmitedTitleLabel.TabIndex = 8;
- this.SubmitedTitleLabel.Text = "The link to your report:";
- this.SubmitedTitleLabel.Visible = false;
- //
- // LastErrorTitleLabel
- //
- this.LastErrorTitleLabel.AutoSize = true;
- this.LastErrorTitleLabel.Enabled = false;
- this.LastErrorTitleLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.LastErrorTitleLabel.Location = new System.Drawing.Point(9, 97);
- this.LastErrorTitleLabel.Name = "LastErrorTitleLabel";
- this.LastErrorTitleLabel.Size = new System.Drawing.Size(87, 20);
- this.LastErrorTitleLabel.TabIndex = 9;
- this.LastErrorTitleLabel.Text = "Last error";
- //
- // LastErrorBox
- //
- this.LastErrorBox.AcceptsReturn = true;
- this.LastErrorBox.AcceptsTab = true;
- this.LastErrorBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
- this.LastErrorBox.Location = new System.Drawing.Point(12, 120);
- this.LastErrorBox.Multiline = true;
- this.LastErrorBox.Name = "LastErrorBox";
- this.LastErrorBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
- this.LastErrorBox.Size = new System.Drawing.Size(626, 38);
- this.LastErrorBox.TabIndex = 10;
- //
- // CReporter
- //
- this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(650, 467);
- this.Controls.Add(this.LastErrorBox);
- this.Controls.Add(this.LastErrorTitleLabel);
- this.Controls.Add(this.SubmitedTitleLabel);
- this.Controls.Add(this.Url);
- this.Controls.Add(this.SubmitButton);
- this.Controls.Add(this.NoUploadSelect);
- this.Controls.Add(this.GistAndIssueSelect);
- this.Controls.Add(this.GistOnlySelect);
- this.Controls.Add(this.LogBox);
- this.Controls.Add(this.MessageText);
- this.Controls.Add(this.TitleLabel);
- this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.MaximizeBox = false;
- this.MinimizeBox = false;
- this.Name = "CReporter";
- this.Text = "Vocaluxe Issue Reporter";
- this.ResumeLayout(false);
- this.PerformLayout();
-
- }
-
- #endregion
-
- private System.Windows.Forms.Label TitleLabel;
- private System.Windows.Forms.TextBox MessageText;
- private System.Windows.Forms.TextBox LogBox;
- private System.Windows.Forms.RadioButton GistOnlySelect;
- private System.Windows.Forms.RadioButton GistAndIssueSelect;
- private System.Windows.Forms.RadioButton NoUploadSelect;
- private System.Windows.Forms.Button SubmitButton;
- private System.Windows.Forms.TextBox Url;
- private System.Windows.Forms.Label SubmitedTitleLabel;
- private System.Windows.Forms.Label LastErrorTitleLabel;
- private System.Windows.Forms.TextBox LastErrorBox;
- }
-}
\ No newline at end of file
diff --git a/Vocaluxe/Reporting/Reporter.cs b/Vocaluxe/Reporting/Reporter.cs
index 917c5c29..68f85269 100644
--- a/Vocaluxe/Reporting/Reporter.cs
+++ b/Vocaluxe/Reporting/Reporter.cs
@@ -17,30 +17,35 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
-using System.Windows.Forms;
using Newtonsoft.Json;
using Vocaluxe.Base;
+using Vocaluxe.Lib;
using VocaluxeLib.Log;
namespace Vocaluxe.Reporting
{
- public partial class CReporter : Form
+ public class CReporter
{
// Init string with defaults for the case that loading localizations caused the problem
private string _TitleText = "We are sorry";
private string _MessageCrashText =
- "that Vocaluxe run into an error and crashed.\r\nTo help us fix the problem please send us a report.\r\nEdit the following log before submitting to remove possibly sensitive information.";
+ "that Vocaluxe run into an error and crashed.\r\nTo help us fix the problem please send us a report.\r\nOpen the log to check it and remove possibly sensitive information before submitting.";
private string _MessageNoCrashText =
- "that you experienced an error.\r\nTo help us fix the problem please send us a report.\r\nEdit the following log before submitting to remove possibly sensitive information.";
+ "that you experienced an error.\r\nTo help us fix the problem please send us a report.\r\nOpen the log to check it and remove possibly sensitive information before submitting.";
private string _NoUploadText = "Don\'t upload anything (you can still copy the error message above)";
private string _GistAndIssueText = "Upload and open an issue (publicly visible + requires a github account)";
private string _GistOnlyText = "Upload and get a link to your report (publicly visible)";
- private string _SubmitStep0Text = "Submit";
- private string _SubmitStep1Text = "Uploading";
+ private string _OpenLogText = "Open the log to check or edit it";
+ private string _NoUploadButtonText = "Don't upload";
+ private string _GistAndIssueButtonText = "Upload + issue";
+ private string _GistOnlyButtonText = "Upload";
+ private string _OpenLogButtonText = "Open log";
private string _SubmitStep2ExitText = "Exit";
private string _SubmitStep2ContinueText = "Continue";
private string _SubmitedMessageText = "The link to your report:";
@@ -49,10 +54,11 @@ namespace Vocaluxe.Reporting
private string _LastErrorNa = "Not available";
private string _IssueTemplate = "Describe your issue here.\n\n### Steps to reproduce\nTell us how to reproduce this issue.\n\n### Vocaluxe version and logfile\n{0}\n{1}";
- private int _Step = 0;
private readonly bool _Crash;
private readonly string _VocaluxeVersionTag;
private readonly bool _ShowContinue;
+ private string _LastError;
+ private string _LogFile;
private static readonly HttpClient _Client = new HttpClient();
private static readonly Regex _GetGistUrlRegex = new Regex("\"html_url\": *\"([^\"]+)\"");
@@ -72,10 +78,7 @@ namespace Vocaluxe.Reporting
/// The last error message.
private static void _ShowReporter(bool crash, bool showContinue, string vocaluxeVersionTag, string log, string lastError)
{
- using (var reporter = new CReporter(crash, showContinue, vocaluxeVersionTag, log, lastError))
- {
- reporter.ShowDialog();
- }
+ new CReporter(crash, showContinue, vocaluxeVersionTag, log, lastError)._Show();
}
///
@@ -88,7 +91,6 @@ namespace Vocaluxe.Reporting
/// The last error message.
public CReporter(bool crash, bool showContinue, string vocaluxeVersionTag, string log, string lastError)
{
- InitializeComponent();
_Crash = crash;
_ShowContinue = showContinue;
_VocaluxeVersionTag = vocaluxeVersionTag;
@@ -111,8 +113,11 @@ namespace Vocaluxe.Reporting
_NoUploadText = CLanguage.Translate("TR_REPORTER_NO_UPLOAD_TEXT");
_GistAndIssueText = CLanguage.Translate("TR_REPORTER_GIST_AND_ISSUE_TEXT");
_GistOnlyText = CLanguage.Translate("TR_REPORTER_GIST_ONLY_TEXT");
- _SubmitStep0Text = CLanguage.Translate("TR_REPORTER_SUBMIT_STEP0_TEXT");
- _SubmitStep1Text = CLanguage.Translate("TR_REPORTER_SUBMIT_STEP1_TEXT");
+ _OpenLogText = CLanguage.Translate("TR_REPORTER_OPEN_LOG_TEXT");
+ _NoUploadButtonText = CLanguage.Translate("TR_REPORTER_NO_UPLOAD_BUTTON");
+ _GistAndIssueButtonText = CLanguage.Translate("TR_REPORTER_GIST_AND_ISSUE_BUTTON");
+ _GistOnlyButtonText = CLanguage.Translate("TR_REPORTER_GIST_ONLY_BUTTON");
+ _OpenLogButtonText = CLanguage.Translate("TR_REPORTER_OPEN_LOG_BUTTON");
_SubmitStep2ExitText = CLanguage.Translate("TR_REPORTER_SUBMIT_STEP2_EXIT_TEXT");
_SubmitStep2ContinueText = CLanguage.Translate("TR_REPORTER_SUBMIT_STEP2_CONTINUE_TEXT");
_SubmitedMessageText = CLanguage.Translate("TR_REPORTER_SUBMITED_MESSAGE_TEXT");
@@ -131,70 +136,73 @@ namespace Vocaluxe.Reporting
_Client.DefaultRequestHeaders.Add("accept", "application/json");
_Client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)");
- this.TitleLabel.Text = _TitleText;
- this.MessageText.Text = _Crash ? _MessageCrashText : _MessageNoCrashText;
- this.NoUploadSelect.Text = _NoUploadText;
- this.GistAndIssueSelect.Text = _GistAndIssueText;
- this.GistOnlySelect.Text = _GistOnlyText;
- this.SubmitButton.Text = _SubmitStep0Text;
- this.SubmitedTitleLabel.Text = _SubmitedMessageText;
- this.LastErrorTitleLabel.Text = _LastErrorTitleText;
- this.LastErrorBox.Text = string.IsNullOrWhiteSpace(lastError) ? _LastErrorNa : lastError;
- this.LogBox.Text = log.Replace("\n", "\r\n");
- this.LogBox.SelectionStart = 0;
- }
-
- private async void Submit_Click(object sender, EventArgs e)
- {
- switch (_Step)
+ _LastError = string.IsNullOrWhiteSpace(lastError) ? _LastErrorNa : lastError;
+ _LogFile = Path.Combine(CSettings.DataFolder, CSettings.FolderNameLogs, "Report.log");
+ try
{
- case 0:
- if (this.NoUploadSelect.Checked)
- {
- this.Close();
- }
- else
- {
- _Step = 1;
- this.SubmitButton.Text = _SubmitStep1Text;
- this.SubmitButton.Enabled = false;
- this.NoUploadSelect.Visible = false;
- this.GistAndIssueSelect.Visible = false;
- this.GistOnlySelect.Visible = false;
- this.LogBox.Enabled = false;
- this.LastErrorBox.Enabled = false;
-
- if (this.GistOnlySelect.Checked)
- {
- await Task.Run(() => _StartGistUpload(this.LogBox.Text, this.LastErrorBox.Text));
- }
- else if (this.GistAndIssueSelect.Checked)
- {
- await Task.Run(() => _StartIssueUpload(this.LogBox.Text, this.LastErrorBox.Text));
- }
- else
- {
- _Step = 0;
- this.SubmitButton.Text = _SubmitStep0Text;
- this.SubmitButton.Enabled = true;
- this.NoUploadSelect.Visible = true;
- this.GistAndIssueSelect.Visible = true;
- this.GistOnlySelect.Visible = true;
- this.LogBox.Enabled = true;
- this.LastErrorBox.Enabled = false;
- }
- }
-
- break;
- case 2:
- this.Close();
- break;
+ File.WriteAllText(_LogFile, log.Replace("\n", Environment.NewLine));
+ }
+ catch (Exception e)
+ {
+ _LogFile = null;
+ CLog.Error(e, "Couldn't write the report log");
}
}
- private void NoUpload_CheckedChanged(object sender, EventArgs e)
+ private void _Show()
{
- this.SubmitButton.Text = this.NoUploadSelect.Checked ? _ShowContinue ? _SubmitStep2ContinueText : _SubmitStep2ExitText : _SubmitStep0Text;
+ const int gistOnly = 0;
+ const int gistAndIssue = 1;
+ const int openLog = 2;
+ const int noUpload = 3;
+ var options = new List { gistOnly, gistAndIssue };
+ if (_LogFile != null)
+ {
+ options.Add(openLog);
+ }
+
+ options.Add(noUpload);
+ var buttonTexts = new[] { _GistOnlyButtonText, _GistAndIssueButtonText, _OpenLogButtonText, _NoUploadButtonText };
+ var descriptions = new[] { _GistOnlyText, _GistAndIssueText, _OpenLogText, _NoUploadText };
+
+ var message = (_Crash ? _MessageCrashText : _MessageNoCrashText).Replace("\r\n", "\n") + "\n\n" + _LastErrorTitleText + ":\n" + _LastError + "\n";
+ foreach (var option in options)
+ {
+ message += "\n" + buttonTexts[option] + ": " + descriptions[option];
+ }
+
+ while (true)
+ {
+ var selected = CSdl.ShowMessage(_TitleText, message, true, options.Select(option => buttonTexts[option]).ToArray());
+ if (selected < 0 || options[selected] == noUpload)
+ {
+ return;
+ }
+
+ if (options[selected] == openLog)
+ {
+ CSdl.OpenUrl(new Uri(_LogFile).AbsoluteUri);
+ continue;
+ }
+
+ var log = _ReadLog();
+ var url = options[selected] == gistAndIssue ? _StartIssueUpload(log, _LastError) : _UploadLogToGist(log, _LastError).GetAwaiter().GetResult();
+ _UploadFinished(url);
+ return;
+ }
+ }
+
+ private string _ReadLog()
+ {
+ try
+ {
+ return File.ReadAllText(_LogFile);
+ }
+ catch (Exception e)
+ {
+ CLog.Error(e, "Couldn't read the report log");
+ return "";
+ }
}
///
@@ -203,32 +211,13 @@ namespace Vocaluxe.Reporting
/// Url to show, if null or empty an error message is shown.
private void _UploadFinished(string url)
{
- if (this.InvokeRequired)
+ var text = string.IsNullOrWhiteSpace(url) ? _LogUploadErrorText : _SubmitedMessageText + "\n" + url;
+ if (!string.IsNullOrWhiteSpace(url))
{
- this.Invoke(new MethodInvoker(() => { _UploadFinished(url); }));
+ CSdl.SetClipboardText(url);
}
- else
- {
- _Step = 2;
- this.SubmitButton.Text = _ShowContinue ? _SubmitStep2ContinueText : _SubmitStep2ExitText;
- this.SubmitButton.Enabled = true;
- this.SubmitedTitleLabel.Visible = true;
- this.Url.Text = string.IsNullOrWhiteSpace(url) ? _LogUploadErrorText : url;
- this.Url.Visible = true;
- this.Url.SelectAll();
- this.Url.Focus();
- }
- }
- ///
- /// Upload a log to gist and updates the gui.
- ///
- /// The log to upload.
- /// The error message displayed to the user (if available).
- private async void _StartGistUpload(string log, string lastError = "not available")
- {
- // Upload log to github gist and show the link
- _UploadFinished(await _UploadLogToGist(log, lastError));
+ CSdl.ShowMessage(_TitleText, text, false, _ShowContinue ? _SubmitStep2ContinueText : _SubmitStep2ExitText);
}
///
@@ -236,10 +225,10 @@ namespace Vocaluxe.Reporting
///
/// The log to upload.
/// The error message displayed to the user (if available).
- private async void _StartIssueUpload(string log, string lastError = "not available")
+ private string _StartIssueUpload(string log, string lastError = "not available")
{
// Upload log to github gist
- var gistUrl = await _UploadLogToGist(log, lastError);
+ var gistUrl = _UploadLogToGist(log, lastError).GetAwaiter().GetResult();
// Build issue body
var template = string.Format(_IssueTemplate, _VocaluxeVersionTag, gistUrl);
@@ -247,11 +236,10 @@ namespace Vocaluxe.Reporting
// Build url for github issue template
var issueUrl = $"https://github.com/Vocaluxe/Vocaluxe/issues/new?title=Give%20me%20a%20meaningful%20title&body={Uri.EscapeDataString(template)}";
- // Show the link
- _UploadFinished(issueUrl);
-
// Start the browser with the issue template url
- System.Diagnostics.Process.Start(issueUrl);
+ CSdl.OpenUrl(issueUrl);
+
+ return issueUrl;
}
///
diff --git a/Vocaluxe/Reporting/Reporter.resx b/Vocaluxe/Reporting/Reporter.resx
deleted file mode 100644
index c223cf8e..00000000
--- a/Vocaluxe/Reporting/Reporter.resx
+++ /dev/null
@@ -1,1615 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
-
-
- 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
- aIAAh1bHbIZXgAiGfox27HeACIVnw0eIZ4AAh+hnds54gACGjHdXeM+AAHzndjduiAAM58dxR3fsAAjn
- eI+I944ABoyACIAAAAAAgAAAAAAAAP+/AAD4HwAA8AcAAOADAADAAwAAwAEAAMABAACAAQAAgAEAAMAB
- AADAAQAAwAMAAIADAACAAwAAhn8AAN//AAAoAAAAMAAAAGAAAAABAAgAAAAAAAAJAAAAAAAAAAAAAAAB
- AAAAAQAAAAAAAA8PDwAVFRUAHBwcACIiIgAqKioANTU1ADw8PABFKBkAaDceAEIsIABGOzYASDkxAEM/
- PgBJQDsAZkAsAHJFLwBlRjYAekszAHBQPwBCQkIAS0tLAFBFQABSSEMAVFRUAFxVUgBbW1sAY1VOAH5c
- SgBjW1YAa1tTAGZfXABrYl0Af2BQAHBiWwBkZGQAamRiAGtoZgBqamoAcnJyAHl5eQCgTB4Ag0MiAItF
- IgCCSCoAkkkjAJtOJgCXTigAl1AsAIZNMQCNUTIAmFc1AJNXOACbWzsArFUlAKBUKwCzViIAs1glALxc
- JAC7XikAp1w1AL5gKQCqYDkAumk/AMVaHwDIXR4AzF8eANJiHgDVZR8A2mgeAN5pHQDgax4A4GwfAMVb
- IADFXiIAwFslAMZgJADLYiIAzWgnAMNjKgDIZSkAyGgtANJmIQDUbCQA2GkgAN1rIADbbSIA3m8jANlu
- JADXcCcA3HIlANdxKADadCkAxWcxAMNmNADFZzQAymwyAMRqOgDIazkAyG05AMhuPQDPcTMA0XQ0ANR4
- NgDbfjYA1ns5ANh+OgDhbiAA43MjAOZ4JQDhdygA43wqAOh9KACDWEMAi1tDAIRdSgCWYEQAm2NGAJdk
- SgCIa1wAmWtTAJNtWQCsaEQAum1EALNtSgCicVcAr3hcALB5XACOcWIAw21BAMFwRQDIckQAyXRHAMJ5
- UwDNflMAw31ZANyDPQDngS0A64MsAO2KMQDwjTMAuoVqAKmHdwCuiXYAp4p7AN+LRwDhjEQA4Y1IAOiT
- RADtmEcA5ZNKAOiWSgDsnEwA7J5RAO2hVADupVoA7qhdAMKBYADEhGMAwoRlAMOJbADRh2AA0YliANSQ
- awDTkG0A1JBsAMOMcQDFlX4A1JNwANaYeADTm34A2Zx7AO+qYADwq2IAiomJAJWUlACbmpoAvpaCALad
- jwCknpwApqGfALugkgCkoqEAqqWkAKyopgCuqqoAsKuoALCsqgCxrq4Ata+tAKm1qwCtuK8AsbCvALOz
- sQC5trUAvLe1ALO6tAC5uLgAvLu7ALi+uAC/vLsAvr29AMmbhADHn4sA2qOGANqkiADdqYwAyqiXANyw
- mgDgrZEA0biqAMG8ugDBwL8AyMK/ANPEvQDkxLQAwcDAAMTCwgDFxMQAyMLAAMnEwgDNxcEAyMfGAM7H
- xQDFycUAzsnGAMrJyQDMysoAzczMANDJxQDQzs4A1NDOANLR0QDU0tIA1dTUANjU0gDY19YA29raAOLM
- wQDr2tIA4N7eAOHg3wDw5d8A4eHhAOTi4gDl5eUA6OfmAOnp6QDs6+sA7u3tAPHx8QD08vIA9fX1APj3
- 9wD5+fkA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApWJBhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAADNYk1DRUVrSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOvT3p9USEVr
- a2trQbLnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADe6vX3919Ia2tGRkZGRqfYvMMAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4fD3+/v37kJIa0ZGRkZrVIfq5tm80wAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAADr+/39+/v7qlRra0ZrRmtrSF/q6ubmybgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5ff9
- /f39/fv1YWtIa2tra2tIa0LW6urm5tm85gAAAAAAAAAAAAAAAAAAAAAAAADe+/39/f396iMKQWtra0hI
- SEhra1N5yerq5ubZweEAAAAAAAAAAAAAAAAAAAAAAN77/f39/fsnBBQyVG1rbWtrbWtrbWsqBCfm5ubm
- 5rzmAAAAAAAAAAAAAAAAAAAAAPn9/f397QcGGCM4bW1ta21rbW1tbWs4GAUHyerm5tm8AAAAAAAAAAAA
- AAAAAAAA7f79/f3mBBUjIxxTa21tbXBtbWttbXBCIh8VBMDm5ubZvAAAAAAAAAAAAAAAAADe+/39/eYF
- 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
- AAD/AAAAAD8AAP4AAAAAHwAA/gAAAAAfAAD8AAAAAA8AAPwAAAAADwAA+AAAAAAHAAD4AAAAAAcAAPgA
- AAAABwAA+AAAAAAHAADwAAAAAAcAAPAAAAAAAwAA8AAAAAADAADwAAAAAAMAAPAAAAAAAwAA8AAAAAAH
- AAD4AAAAAAcAAPgAAAAABwAA+AAAAAAHAAD4AAAAAAcAAPwAAAAADwAA/AAAAAAPAAD+AAAAAB8AAPwA
- AAAAHwAA+AAAAAA/AADmAAAAAD8AAMCAAAAAPwAAwAAAAAA/AADAAAAAAB8AAMAAAAAAHwAAwAAAAAAf
- AADAAAAAAA8AAMABwAD/7wAA4AP4D///AADgA/////8AAPAH/////wAA/B//////AAD///////8AACgA
- AAAgAAAAQAAAAAEACAAAAAAAAAQAAAAAAAAAAAAAAAEAAAABAAAAAAAAKCgoAC8vLwA/MiwAMTExADU1
- NQA5OTkAPDw7AD09PQBlOSMAXkAxAFxENwBWQjgAfkYnAEFBQQBFRUUAS0dFAElJSQBOTk4AWU1GAFNT
- UwBVVVUAWVlZAF1dXQBlT0QAblBBAGhSRwBvWk8AclZJAHdcTwByXlMAel5QAGNgXgBtY14AfmhdAGFh
- YQBlZWUAaGNgAGlpaQBsamkAbGxsAHlrZABwa2kAcnFxAHV1dQCETCwAi00qAJBRLACGUjgAjVo/AJZX
- MgCdXTkAm18/AKZcLwC7XyYAol02AKZfNgCpXzIAr18wALphJwC9aC8AqWA0ALFiMQC0ZjUAtWg3AL9q
- MAC+bTcAs2Y5ALBoOwDQYh8A1GQfANlnHwDeaR0A4GseAOBsHwDPYSAAzWUlAMppJwDFZyoAymgpAM5p
- KwDRYyEA0mYhANRoIQDVbSUA3W0gANFrKgDacicA3nMlAN51JwDbcygA23UpANx4LQDffC8AxWYwAMhq
- MgDOazIAyWwwANJvMADLdTkA03Q0ANB4OADWfDsA1n4+AOFvIADicSEA5HQjAOFzJQDidSUA5XYlAOZ6
- JwDheioA534qAOl+KQCEXUkAiF9KAJFeQwCCZFUAsnNPALt0TwCnd14At3xbAMVzRgDKdEEAyXVFAMR3
- TQDUfUcA0ntJANF/SgDLf1AA3oE2ANeAPwDZgT4A3YQ9AN+IPwDqgSsA6oIsAOyFLQDtiC8A7YkwAO2O
- OADukDkA7pI9ALGAaADUgEcA2oRBAN+IQQDeikYA0oFMANOFUwDUiFcA1oxbAOOORADkkUUA5JRMAOiX
- SwDrmksA7JtLAOmZTQDsnU0A6pxQAOyeUADtoFIA7aJVAO6kVwDtolgA7qVZAO+mXADvqF0AyodiANaN
- YADPkm4A1ZNsAMaPcgDFk3kA2Zp0ANuffwDvqmAA8KphAPCsYwDwrWQAg4ODAIWFhQCWlZUAoJ+fAKid
- mAC9qJ4AoaGhAKampgCopqUAoq+kAKqpqQCtqqkAr6+vALCtrACrta0AtLGvALOxsAC0srEAuLW0ALq5
- uQC+ubgAxZiBAMmeiQDJoIsA2qCBANWljADbpooA3qmLAMmpmQDfrJAA2q+ZAMCupgDOu7IAzb21AMK+
- vQDFwsEAxcTEAMjFwwDIxsYAysnJAM3KyQDLzMoAzMzLAM7NzQDSzssA0M7OANTPzQDO0M4A0dDPANTR
- zwDd088A0tHQANTS0gDV1NQA2NPSANjW1gDb2NYA2djYANza2gDf3t4A6dvUAOje2QDi4eEA5OPjAObl
- 5QDo5+cA6unpAO3t7QDy7+0A8fHxAPTz8wD29vYA+Pf3APr6+gD///8AAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSf0tHSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOPj
- fEpJSUlSuu0AAAAAAAAAAAAAAAAAAAAAAAAAAOr4+PJSSklJSUnL3MfqAAAAAAAAAAAAAAAAAAAAAOv6
- /fr601VKSkpKaKnq49nHAAAAAAAAAAAAAAAAAADt/v7++LY6aWloaGhpX8fq3NzHAAAAAAAAAAAAAAAA
- 6/7+/rwCE1JpaWlpaWlTAybj6tzHAAAAAAAAAAAAAOP+/v4sCCR0aW5pbm5pbmlyEg7Z3NzHAAAAAAAA
- AAAA+Pr+LA4VFjZubm5ubm5ubjcWEQjc6tnjAAAAAAAAAOP6+scIKyZ1WG6Hboduh4duUCYsFSTq3McA
- AAAAAAAA9vj4CBYkFkOHh4eHU26Hh4dZHRYkBMfq2+sAAAAAANv2+LwIFhYeWYeKh24wWYuHi4c0FhYR
- JOrqyQAAAAAA1/X1FiQmLDiLi4qLUBE7i4uKik4sLCYE6urXAAAAAADj9fUEFhYeWYuLi4suCC2Li4uL
- bhwWFgTJ6twAAAAAAOrc7wImJj2Mjo6OXAwOC1yOjo6MMyQkEbvc6gAAAAAA6sPcAiQfhaGioaE5CAgI
- TqGhoqFlKiQOt8PqAAAAAADc7+4BFj6hoqKhhgoEBgUtoaGioplzFgi/9eoAAAAAANjq7ghymaKipKE+
- EQ4PDxqFoqKiokAmCNz17wAAAAAAwurqJUGipKSikhkWDg4RFUGiqKeikhgI+PXjAAAAAAC/4+qtm6Kk
- qKI6IyYkFiMkMaCiqKegL7b4+N0AAAAAAADZ1maipKiikiImJiYsJiYphaSoqKRm5/j4AAAAAAAAAL3K
- gWagpKAyFhUWFhYWFhZDpKiypKDO+usAAAAAAADOXkVLfXqSZSUmJiwmJiYmLHKasbKxpJD5AAAAAAAA
- zc6wX2lpUH0JFiYkJCMmJiQkJGOktLSxoMwAAAAAAABpaWKueFBufMcOCBEWFRYWFRUIDaSxtLSolQAA
- AAAAf4eHi4dfj3xT1uq8Jg4EBAQIJLzxkbG0tLGaAAAAAABijo6Ojo6CeXfR6urv7+/q8Pj4+PqspKiy
- qKSqAAAAAJCioqKooqKZdrnd6urv6r719fj4+tSrqpBnkmIAAAAA0KGiqKiooqKQAAC/wtnq6u/jyccA
- AAAAAAAAAAAAAAAAkKCoqKikkgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAr2eTkpUAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////g////AD///A
- AP//AAB//gAAP/wAAB/4AAAP+AAAB/AAAAfwAAAD4AAAA+AAAAPgAAAD4AAAA+AAAAPgAAAD4AAAA+AA
- AAPgAAAD8AAAB/AAAAfgAAAPwAAAD8AAAA+AAAAPgAAAB4AAAAeAMAf/wH///+D/////////KAAAABAA
- AAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA/Pz8ART88AEhAPABxSzUAR0dHAEpK
- SgBOTk4AUkhDAFJSUgBYWFgAXl5eAH5bRQBnX1sAc15SAGdhXgBiYmIAZmJhAGVlZQBrZmMAaGhoAHFw
- cAB8e3sAfn19AJ5aLgCGVjUAqGE0AK1vPwDdaR4A4W0fAMVrLADPbywA1m0qAMtxLADacycA1nkvANN0
- NADVfT8A4W8gAOR0IwDkdSQA5nwpAOh8KACAX0sAlmVEAJlqSQCBY1IAompEAKVyTACwcUAAunJHALV8
- UwDPdEAAznhBAMF8TwDSgD4A3oU5AOqCLADtiDAAk4F4ANKGRgDZhkYA24tFANmMSgDqlkcA7ZdEAO2Y
- RgDklE0A7ptKAOudTwDklVAA4ZVWAOSXVQDmmFAA6Z5XAO2iVQDupVgA76dcAO+pXgDZkGcA3ZdlANKa
- dADvqmAA8K1lAIODgwCnoZ8ApqWlAL2upgCwr68Avbu6AL+9vADIn4cA3KiCAMChkADjr40AwbWvAMq6
- swDHyscAycrIAMzKyQDOzcwAzdDNANPR0QDW1dUA3tnXANrZ2QDe2tkA3t3dAObXzwDk4eAA5OTkAOrq
- 6gDu7OwA8O7uAPHx8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAABPAAAAAAAAAAAAAABt
- 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
- qqtjoyYthvnnLgHQEoYoM7YQohhmzOrsD4lieO3aqqeqKsvcKMFAwosxAoJPT2jHX1OHgi8Y7/3q938i
- yVLx9791f0t6emoyMMcvINE0PzHzJ66bmBjn+uuvZ2xsjOzsbJYtW0Zra6uW0HLv3r2UlpbS09PDypUK
- aPjee+9hsVjYvn07LpeL8fFxysvLmZyc5MiRI9TU1NDW1qblxGtqauKdd97hzJkz3HPPPWRkZPDWW2/R
- 1tbGnz8XpGc80t9JubmkrIh1lkmoFptvzr4kSswccbUEbQ48fZFBwiT4uKv2JOfOneHixYtcc801mM1m
- zp07x/LlSttPnDhBVlYWJSubeLEnkvhD0OvJbGxYuOULPNvVEgBSMITz1GmVawB4/A+yOdfxPvX19QiC
- wIEDBygoKGDXrl2MjIwwPT1NRUUFFy9e5MSJE6xZs0YzC1f9Sz788EP6+/u5+eabycrK4tChQ9TUKB6W
- //Iv/0JeXh6VlZUcO3aMvr4+zdU8Pz+fJ574+TwgoNL4xTK/JIW1JYzf78PvD+IPBMSuC2MfhsPSzA//
- +A++DPiBECAuVgBcxXgAyi8sS0M6QVc0ZXd60tNTk5dq6DNfnXqeICjWgMuWLdOi2er1et544w38fj86
- nY7Ozk52797NAw88wMGDB3n++eexWhVjnjfffJP77ruPqqoqnn32Wd555x2CwSCvv/4669at48EHHyQU
- CvGtb32LwUFlKvmb3/yGm266ib/8y7/kL/7iLxj9+CyUfl1rVWBqStEIZMTG6ZsD92kFV6oWYF7NgFps
- ysnCNzKKNIt5BOVknn6lnelBJR1ad3c3DQ0N7Nu3j7feeouTJ0/i9/sRBIEMawnkR65pys7Srj1vW5Ze
- tTAtQgMQckzHML/g6ePVX73En//5n/P444/z/PPPa/EQXnvtNb7+9a9TVlbG008/zfvvv4/f7+eVV16h
- tbWVL3/5y3i9Xv74j/+Y/n5FcB46dIibbrqJr3/96/zP//k/ee655wgGlVh+FotFUy3a7XbN4KqysnJJ
- hj6JmF8N/KGu/yVJEQiBQMgjy6DX64dQQoDN1zXz0lUFAQE5HAr2IMDYuNV9OYY+89XFA4rKCKZM36xW
- awwwEwqFOHr0KBcuXODEiRMa8wMMDw9z5swZTpw4QWdnpwbwBYNBBgYGeP/99+ns7NSYHxQQ8YUXXsDv
- 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
- 9//29vb/9PT0//Py8v/w8PD/05t+/9lpIP/hbiD/4Gwf/+BrHv/fax7/32se/+BsH//gbR//4W8g/8Nm
- NP/V09P/09LS/9HQ0P/NzMz/vr29/6aioe3Fvrtu1dHQCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4rqoNtaunoOvq
- 6vv6+fn++/r6/vn5+f/49/f+9vb2/vT09P/k4uL+xWo6/uJwIf/ibyD+4W4g/uFtH//gbR/+4G0f/uFu
- IP/hbyD+4nAh/spgIf/TxL3+1dTU/9PS0v7Rz8/+zs3N/8bExP6tqqr5tq+smdnV1A4AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALas
- qA22rKit8vLx/f39/f79/f3+/Pz8/vr6+v/5+fn+0tLS/mFhYf9CLCD+zGAg/uNyIv/jcSL+4nAh/uJv
- If/ibyH+4m8h/uJwIf/icSL+43Ii/tRnIf+TbVn+v76+/9XU1P7T0dH+0M/P/87Nzf7Jx8f+sbCv/LOs
- qKPX09INAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAtqyoB7KopKLy8vL9/Pz8//7+/v/+/v7//f39/+/u7v9xcXH/ICAg/0FBQf+OUzT/220i/+R0
- I//jcyP/43Ii/+NyIv/jciL/43Ii/+NyIv/jcyL/5HQj/95wI/+DQyL/ISEh/3BwcP/Pzs7/0tHR/9DP
- z//OzMz/ycjI/7Gvr/y3sK2W1dHPCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAqZ6Zee7t7fv6+vr+/Pz8//39/f7+/v7+0dHR/jc3N/80NDT+VFRU/mdk
- Yv+2VyH+5HYk/uV1JP/kdST+5HQj/uR0JP/kdCP+5HQj/uR0JP/kdST+5XUk/uV2JP+wViT+VlVU/y4u
- Lv49PT3+ubi4/9LR0f7Qzs7+zszM/8jHx/6tq6r5xL26bc7KyAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACpnpk/3drY8ff29v/5+fn/+/v7//z8/P/ExMT/Jycn/0lJ
- Sf9lZWX/Y2Nj/39dTP/SZyL/5ngm/+V4Jf/ldyX/5Xcl/+V3Jf/ldyX/5Xcl/+V3Jf/ldyX/5Xgl/+Z4
- Jv/LYSH/cGFZ/15eXv9ISEj/JCQk/7Szs//S0dH/0M7O/83MzP/FxMT/p6Oh7NTPzDYAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK+loQu9trLK8/Ly/vb29v/49/f++fn5/8rK
- yv4mJib+T09P/lVVVf9jY2P+Z2dn/plOJ//idib+53om/ud5Jv/neSb+53km/ud5Jv/neSb+53km/ud5
- Jv/neSb+53om/ud6Jv/YbST+g1hC/2hoaP5gYGD+Pz8//ykpKf65t7f+0tDQ/8/Ozv7Ny8v+v729/6yl
- orzU0M4LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKKXkmnr6ur88vLy/vT0
- 9P/29vb+6enp/zQ0NP5CQkL+Z2dn/m5ubv9paWn+Y1VO/slhI//ofCj+6Hwo/uh8KP/ofCj+6Hwo/uh8
- KP/ofCj+6Hwo/uh8KP/ofCj+6Hwo/uh8KP/keCf+qWA7/11dXf5qamr+aWlp/0JCQv47Ozv+zczM/9HQ
- 0P7Pzs7+y8rK/7GurvnHwL1aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqJ6aCr+4
- tdjv7u7+8fDw/vPy8v/19PT+ZWVl/zk5Of5jY2P+YWFh/k1NTf9gYGD+m2NG/t1zJv/pfyn+6X8p/ul/
- Kf/pfyn+6X8p/ul/Kf/pfyn+6X8p/ul/Kf/pfyn+6X8p/ul/Kf/pfyn+v1og/2xoZv5jY2P+U1NT/1NT
- U/44ODj+bW1t/9PS0v7R0ND+z83N/8TCwv6qo6DH0s7MCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAnZKNVOfl5fzt7Oz/7+/v//Hx8f+/v7//LCws/0hISP9ZWVn/aGho/2tra/9mX1z/vFsk/+qA
- Kv/qgSv/6oEr/+qBK//qgSv/6oEr/9VtJf/ddCf/6oEr/+qBK//qgSv/6oEr/+qBK//qgCv/1Gsk/2VF
- Nf9lZWX/a2tr/2dnZ/9HR0f/Hh4e/7y7u//T0tL/0dDQ/83MzP+xr673ycPARAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAACRiIQBpZ2ZrOno6P/r6ur+7e3t/u/v7/9RUVH+NjY2/2ZmZv5nZ2f+X19f/kpK
- Sv+Za1P+2XAm/uuDLP/rgyz+64Ms/uuELf/rgyz+6IAs/rppP/+9XSX+64Ms/uuDLf/rgyz+64Ms/uuD
- LP/rgiz+43sp/5tbO/5iYmL+UFBQ/1ZWVv5iYmL+PDw8/15dXf7V1NT+09LS/9HPz/7CwMD+ubKwl8jD
- wQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB7c28VxL685+fm5v/p6Oj+6+vr/tHR0f8hISH+V1dX/1BQ
- UP5ZWVn+ZmZm/m1paP+0WSb+6oIs/uyFLf/shS7+7IUu/uyFLv/shS3+2nMn/nFFL/+XUCz+538r/uyF
- Lv/shS7+7IYu/uyFLv/shS3+64Qt/71eKP5mY2H+aGho/2ZmZv5aWlr+Pz8//yoqKv7My8v+1dTU/9PR
- 0f7Mysr+qaKg29DMygkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwZ2ND3NrZ+uXk5P/n5ub/6enp/4iI
- iP8mJib/YWFh/21tbf9sbGz/Z2dn/4NZRP/XcCf/7IYu/+2HLv/thy//7Ycv/+2HLv/shy7/xV8i/1FI
- RP9mQCz/2HEo/+yHLv/thy//7Ycv/+2HL//thy7/7IYu/9NrJf+OcWL/W1tb/19fX/9qamr/Z2dn/yIi
- Iv+VlJT/19bW/9XU1P/R0ND/tLGw9cjBvjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAByamZy4eDg/uPi
- 4v/l5OT+5+bm/kxMTP9JSUn+ZWVl/1paWv5gYGD+bGlo/rldKv/rhS7+7Ygw/u6JMP/uiTD+7okw/u2I
- MP/ngS3+jkYh/j8/P/9KQDv+xWAi/u2IMP/tiTD+7okw/u6JMf/uiTD+7Ygw/+R+LP6YVzX+aGho/2dn
- Z/5hYWH+VlZW/0NDQ/5eXl7+2djY/9fW1v7U09P+wcC//r62s1gAAAAAAAAAAAAAAAAAAAAAAAAAAF9X
- VAF6cm+Y397e/uHg4P/j4uL+5eXl/iwsLP9NTU3+ampq/2tra/5sbGz+iVxG/thyKP/tiTD+7oox/u+L
- Mf/uizH+7oox/u6JMP/UbSf+Zkc3/kFBQf87Ozv+oFMq/uuGL//tijH+7osx/u+LMv/uizH+7oox/+2I
- MP6/Xib+aGNh/2VlZf5ra2v+a2tr/05OTv46Ojr+29ra/9nY2P7W1dX+yMfH/762s30AAAAAAAAAAAAA
- AAAAAAAAAAAAAF9YVQKOhoOx3dzc/9/e3v/i4eH/3Nvb/xoaGv9XV1f/Z2dn/2BgYP9sZmP/t1kk/+2J
- Mf/vjDP/740z//CNM//vjTP/74wy/+yIMf+xWCb/Pz07/z8/P/89PT3/ckYv/9x2Kv/uizL/74wz//CN
- M//vjTP/74wz/+6LMv/XcSj/f1xJ/2VlZf9kZGT/X19f/1ZWVv8oKCj/2tnZ/9zb2//Z2Nj/zs3N/7au
- q5W6s7ABAAAAAAAAAAAAAAAAAAAAAEdCPwOSjIm+2NfX/q24r/+zurT+zczM/h4eHv9bW1v+a2tr/2dn
- Z/6OW0H+2384/u2ZSP/tnEv+7p5N/u6eTf/tnEv+7ZlI/tt+Nv96SzP+Pz8//kFBQf8+Pj7+S0A6/sln
- Kf/tmEf+7ZtK/u6dTf/unk3+7p1M/+2bSf7okUL+p1w1/2hoaP5qamr+Z2dn/1xcXP4hISH+1NPT/6i1
- qv64vrj+0tHR/7exraK4sa4BAAAAAAAAAAAAAAAAAAAAAEQ/PQORi4i/1tTU/rC2sf/DyMP+ysnJ/h4e
- Hv9WVlb+Z2dn/2tiXf67XSb+7JlK/u2eTv/toFH+7aFS/u2gUf/tnk7+7JpK/r5gKf9IQT3+Pj4+/j8/
- P/86Ojr+PT09/p5PJf/plUf+7J1O/u2gUf/toVL+7qBS/+2eT/7sm0v+yWgs/3BkXv5nZ2f+ZWVl/1hY
- WP4gICD+09LS/7K5sv7Hy8f+1dTU/7mxrqK4sa4BAAAAAAAAAAAAAAAAAAAAAEdCQAOLg4C019bW/9jX
- 1//a2dn/1NPT/xkZGf9dXV3/ampq/41QMP/fhz7/7Z1O/+2gUf/uolP/7qJT/+2gUf/tnU7/34Y+/4dO
- Mf89PT3/PT09/0FBQf9AQED/QUFB/3FFL//bgDr/7Z1O/+2gUv/uolT/7qJU/+2gUv/tnU7/3YQ9/5dk
- Sv9nZ2f/YGBg/1paWv8pKSn/4N/f/+Hg4P/f3t7/1tXV/7avrJUAAAAAAAAAAAAAAAAAAAAAAAAAAFJM
- SQJybGme1dTU/tbV1f/Y19f+29ra/igoKP9GRkb+bl1V/8ZmK/7snE3+7aBR/u2iVP/to1X+7aJU/u2g
- Uf/snE3+w2Qq/kY7Nv8+Pj7+Pz8//j09Pf82Njb+Ojo6/lBFQP/IaS3+7J1N/u2gUv/tolT+7qNV/+2i
- VP7tn1H+7JtM/69XJv5nZWT+Z2dn/0lJSf44ODj+5eXl/+Pi4v7h4OD+19bW/7Cno30AAAAAAAAAAAAA
- AAAAAAAAAAAAAEhCQAFcVVN71NPT/tTT0//W1dX+2djY/kJCQv9QUFD+kkok/+SPRf7tn1H+7qJU/u6k
- Vv/upFb+7aJU/u2fUP/giED+k1c4/kxMTP87Ozv+Ojo6/kJCQv9DQ0P+Q0ND/kdGRv+rYDj+6JVJ/u2g
- Uv/uo1X+7qRX/+6kVv7tolT+7Z5Q/9N2Nf5/YFD+WFhY/0lJSf5fX1/+6Ofn/+Xl5f7j4uL+19bW/qmf
- m1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHQD1PzcvL+9LR0f/U09P/19bW/3d2dv9FKBn/z3Ey/+2f
- UP/uolX/7qRX/++lWP/upFf/7qFU/+2dT//EZy7/VElD/1VVVf9NTU3/R0dH/0BAQP82Njb/QkJC/1hY
- WP+EXUr/2H05/+6fUv/uo1b/76VY/++lWP/upFf/7qFT/+eUSf+SSSL/YmJi/yAgIP+bmpr/6unp/+fn
- 5//l5OT/zsvK9ayini8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/OjggtrGv7NHPz//S0dH+1dPT/rm1
- tP+gTB7+6ZdL/+2hVP7upFf+7qZZ/u6lWf/upFf+7aBT/t2GP/+FTTH+bm5u/mZmZv9RUVH+TU1N/ltb
- W/9iYmL+ZWVl/lZWVv9cVVL+w2Ut/u2fUf/to1X+7qVZ/+6mWv7upVn+7qNW/+2fUf7LbTD+SDkx/yws
- LP7e3t7+7Ovr/+np6f7l5eX+u7Sx3rOrqAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCPjwFhoB9u9DO
- zv/Qz8/+09HR/smbhP/XfDn+7Z9S/+2jV/7upln+7qZa/u6lWf/to1b+7J5R/rxfKv9jW1b+SUlJ/l5e
- Xv9nZ2f+aGho/l5eXv9LS0v+V1dX/mdnZ/9oaGj+iUUj/uaUSf/tolX+7qVZ/+6nXP7up1v+7qVZ/+2i
- Vf7gi0P+gkgq/15eXv7v7+/+7e3t/+vr6/7l5eX+sKejmQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AABEPz0BSUJAZ87MzP3Ozc3/z8XA/8VnMf/rmkz/7aBT/+6jV//uplr/7qZa/+6lWP/uoVT/2H88/3ZH
- L/9paWn/bW1t/2dnZ/9YWFj/V1dX/2lpaf9sbGz/aWlp/1hYWP9XV1f/iGtc/9N4Nv/uolX/7qVZ/++o
- XP/vqF3/7qdc/+6kWP/toFP/uVwm/8vFw//x8fH/7+/v/+3s7P/d29r4q6CcQwAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAODMyGKagnuDNzMz+woJh/th8OP/smUz+7p9S/+6iVv7upFn+7qRZ/u6j
- Vv/qmk/+qlMk/mtoZv9iYmL+VlZW/mVlZf9qamr+a2tr/mVlZf9XV1f+YGBg/mpqav9ra2v+Z2Zl/rNa
- J//tn1P+76VZ/++oXf7vqV/+76le/++nW/7uo1f+2oE+/9ywmv7z8/P+8fHx/+7t7f66sq/Ktq2pBgAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa0o5AlVMSX/MxMH9w4xx/8KBYP/DbkD/z3Ey/+eV
- TP/uolX/7qFV/+6gU//RdDT/cFA//2FhYf9nZ2f/Z2dn/2ZmZv9dXV3/XV1d/2dnZ/9nZ2f/ZmZm/11d
- Xf9dXV3/Z2dn/5ZgRP/gi0X/7qRZ/++oXf/wqmD/8Kpg/++pXv/vplr/7J9T/8hyRP/08vH/8/Ly/+jn
- 5vuonZhbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACvThwHqVAiZqxPHtPFWh/+yFwe/sVb
- IP/CbED+w4Fg/8FvRf7Zfzz+7Z1Q/uSPRv+XTij+bGtr/m1tbf9qamr+YmJi/mtra/9sbGz+bGxs/mxs
- bP9jY2P+aGho/mxsbP9sbGz+bGxs/mxcVP/MbjL+7qNY/+6oXf7vqmD+8Kti/++rYf7vqF7+7qRZ/9R5
- OP7kxLT+9PT0/7uzsMG2raoHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALxUHgO8Ux5XvFMe2Mld
- H/7ZaSD+4W8g/uJvIP/gbiH+0mQg/8FrPf7CeVP+1Xg3/sBhKf9pWVH+YWFh/mVlZf9nZ2f+Y2Nj/mdn
- Z/9iYmL+YmJi/mhoaP9jY2P+ZmZm/mJiYv9iYmL+aGho/mRjY/+hVS3+6ZtR/++nXP7vqmH+8Kxj//Cs
- Y/7vqmH+76dc/+iaUf7Mf1f+39za8audlzkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwlkfD71V
- HtK9VR7Av1YebsBXHkm8VB6Zwlke89hpIf/jcyP/5HQj/95vI//CaDj/w3xY/2g3Hv8+Pj7/Wlpa/2ho
- aP9ra2v/ZmZm/2tra/9oaGj/aGho/2xsbP9nZ2f/ampq/2hoaP9oaGj/bGxs/2dnZ/9+W0n/1308/++m
- W//wqmD/8K1k//GtZf/wrGP/8Kpg/++lWv/PcjX/qXthiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAvlYeldpvJP/kdyb+12wj/sZdIPi7VB6lcTQTUIpLK8nGYCX+33Ik/+Z5Jv7abyT+xIRj/r+8
- u/9ISEj+IiIi/kVFRf9YWFj+Wlpa/mVlZf9lZWX+ZmZm/mdnZ/9fX1/+ZGRk/mVlZf9lZWX+Y2Nj/lBQ
- UP9DPz7+slgl/+6kWf7vqV/+8Kxj//CuZv7wrmX+8Kxj/++oXv7llU3+vVsmpgAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAADBWB8Uxl0g9Ol+Kf/pfin+6X4p/ul/Kf/hdyj+ymIi+5NIIryaeGbcwXBF/tZs
- JP7ofin+x2Em/tHIxP/U09P+oaCg/j4+Pv8aGhr+NjY2/klJSf9OTk7+VFRU/l9fX/9fX1/+XFxc/k1N
- Tf9AQED+MjIy/h4eHv9SUlL+uoVq/9+LR/7vp13+8Kti//CuZf7wrmb+8K1k/++qYf7vpVv+ymou+sRj
- Kh8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAWB9Q1m4m/+uELf/rhS3/7IUt/+yFLv/shS7/7IUu/+B5
- Kv+7WiT3qYFt7sOJbP7OZiT/1Gwl/8qol//T0dH/1dTU/9fV1f+5uLj/eXl5/0RERP8hISH/FhYW/xYW
- Fv8PDw//ExMT/ycnJ/9QUFD/jIuL/9bW1v/08/P/8OXf/8psMv/vpVr/76lf//CsY//wrWX/8K1k//Cq
- Yf/vp13/4Y1I/8BaI40AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAWSBw3Xcr/u2KMv/uizP+7owz/u+M
- NP/vjDT+74wz/u+MM//riTL+zWgn/qxyVPK9lH/5wFsl/saWf//Qz8/+09LS/tXU1P/X1tb+2tnZ/tzb
- 2//f3t7+2NfX/s7Nzf/Q0ND+4ODg/urp6f/s7Oz+7u7u/vDw8P/y8vL+9PT0/9GRcP7jkEr+76Vb/++o
- X/7wqmH+8Kph/++pYP7vplz+7qJX/8ViKe/FYigTAAAAAAAAAAAAAAAAAAAAAAAAAADAWSFq24A5/u2b
- S//tnk/+7aBR/u6iU//tolP+7aJS/u6hUv/toFH+7J5O/9Z7OP6mXDbsnnhl4cefi/3Qz8/+0dDQ/tPS
- 0v/V1NT+2NfX/trZ2f/c29v+3Nzc/rW9tv/Mzsz+5uXl/ujn5//q6ur+7Ozs/u/u7v/w8PD+8/Ly/+va
- 0v7KazH+5ZJL/+qdVP7upFr+76dc/++mW/7vpFn+7qFV/9yEP/7AWyN3AAAAAAAAAAAAAAAAAAAAAAAA
- AADBWyI+0nQz/u2cTv/toFL/7qNW/+6lWP/upVj/7qVY/+6kV//uo1b/7aFT/+2eT//cgzz/ok0i0FlM
- RommoZ/h0c/P/dPS0v/U09P/1tXV/9jX1//a2dn/0tHR/6q2rP/FysX/5OPj/+bl5f/o6Oj/6+rq/+3s
- 7P/v7+//7Ovr/LawrdeATjSIulwojsBbJKzBXCXLw2An6clpLv7QcjT/1no5/9qBPv++WCHivFYfCAAA
- AAAAAAAAAAAAAAAAAADCXSQHwVsj3+ubTf/toVT+7qRY/u6mWv/vp1z+76dc/u+nW//upVn+7qNW/+2f
- Uv7rmUv+wVwk5oRRNQc6MS0aQTs4aoV/fL63srHt0c/P+9nY2P7a2dn+3Nvb/tfW1v/e3d3+4uLi/uXl
- 5f/n5ub+4d/f+sS/veiLhYKyUktIW1lQTA+KZVMBAAAAAAAAAAAAAAAAAAAAAMZkKgnGZConxGEnRsBb
- JGS+WCGDvFQfLAAAAAAAAAAAAAAAAAAAAAAAAAAAwVwjZtV4N//uoFT+7qRY/u+mW//vqF3+76hd/u+n
- Xf/uplv+7qNX/+2fUv7UeDb+vlskaAAAAAAAAAAAREA+AT06OAc7NzUjQDo3U1NNSoBvaWeiiIF/uJGK
- iMSRi4nCioOBtXBqZ51ZU1B4SkRBSktGRBpYVFIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxGEnAb9ZIqTYfzz/7qJW/++l
- Wv/vplz/76dc/++mXP/vpVn/7qJW/9h/PP/AWiOlxmQpAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEtG
- RAFHQ0EDSUVDBE9LSgVQTEoFTUlHBFpVUwJXUk8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMVi
- KAPBXCSEyWgt9+CMR//sn1X+7aFX/uueVP/gjEf+yWgs98FcJIbHZisEAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAxWIpIsJdJYDAWiO4wVwkyb9ZI7XCXSWAxmUqIQAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///+P//wAA///8A///AAD//8AA//8AAP//AAAf/wAA//wAAA//
- AAD/8AAAA/8AAP/gAAAB/wAA/8AAAAD/AAD/gAAAAH8AAP8AAAAAPwAA/wAAAAAfAAD+AAAAAB8AAPwA
- AAAADwAA/AAAAAAPAAD4AAAAAAcAAPgAAAAABwAA8AAAAAADAADwAAAAAAMAAPAAAAAAAwAA8AAAAAAD
- AADgAAAAAAMAAOAAAAAAAQAA4AAAAAABAADgAAAAAAEAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAPAA
- AAAAAwAA8AAAAAADAADwAAAAAAcAAPAAAAAABwAA+AAAAAAHAAD4AAAAAA8AAPAAAAAADwAA4AAAAAAf
- AADAAAAAAD8AAMAAAAAAPwAAgAAAAAAfAACAAAAAAB8AAIAAAAAADwAAgAAAAAAPAACAAAAAAAcAAIAA
- AAAeBwAAwAGAAP//AADAAfgH//8AAOAD/////wAA+A//////AAD///////8AACgAAAAgAAAAQAAAAAEA
- IAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL5VHhC9VB5NvFQebQAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAANbRzwLOxL8cy5yDUslwQJPIXSDPz2Ae/NlnH//MXx7105+DCQAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjBvhXFvrtxyMPBytDOzffKdkb+4Gwf/99qHv/faR3/32oe/9Rk
- H/+0nJHfv7q4j87IxizV0c8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMG5tQfDu7hx1NHQ6ezs7P/v7+//6N7Z/9FjIf/gbB//32oe/99p
- Hf/fax7/3Wof/8meif/Lysr/trOy97u1s53Tz80aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC1q6cbysTCu/Ly8v749/f/9fX1//Ly8v/ar5n/3Wwg/+Ft
- H//gbB//4Gse/+BtH//hbyD/yodi/9TT0//Rz8//xsXF/7CsquDMx8VCAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsaeiIdXRz9f7+/v//Pz8//r6+v/n5+f/hYWF/69f
- MP/jcSL/4nAh/+FvIP/hbyD/4m8h/+NxIv/IajL/uLe3/9TT0//Qz8//y8rK/7KvrvTLxsNTAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK2inRTTzszT+/v7//7+/v/9/f3/oqKi/zEx
- Mf9ZTUb/0mYh/+R0I//jcyL/43Ii/+NyIv/jcyL/5HQj/9RoIf8/Miz/aWho/87MzP/Qz8//zMrK/7Kv
- rvPMx8VBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjmJIBwLi1q/f39//6+vr/+/v7/3Nz
- c/8+Pj7/Z2dn/5FeQ//jdSX/5Xcl/+V2Jf/ldiX/5XYl/+V2Jf/ldyX/4XMl/4RcSP9PT0//QkFB/8XE
- xP/Qzs7/y8nJ/7CsquDQy8kZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKuhnVTu7u389vb2//n4
- +P93d3f/PT09/1VVVf9ZWFj/u18m/+d7J//neif/53on/+d6J//neif/53on/+d6J//neyf/ol02/1lZ
- Wf9JSUn/Pj4+/8zLy//Pzs7/xsTE/7m0sZvRzMoBAAAAAAAAAAAAAAAAAAAAAAAAAACbkIsEycTB0/Hw
- 8P/08/P/r6+v/zo6Ov9ycnL/bGxs/4JkVf/dcyb/6X4p/+l+Kf/pfin/6X4p/+l+Kf/pfin/6X4p/+l+
- Kf/KaCn/bGpp/3BwcP9TU1P/ZmVl/9LR0f/Pzc3/trOz98nEwSoAAAAAAAAAAAAAAAAAAAAAAAAAAJ2T
- j0fp6Oj97u7u/+3s7P8+Pj7/Wlpa/2FhYf9eXl7/s2Y5/+qBK//qgSv/6oIr/+qBK//NZyf/534q/+qC
- K//qgiv/6oEr/951J/93XE//YGBg/19fX/8zMzP/tLOz/9LR0f/KyMj/vbi1jQAAAAAAAAAAAAAAAAAA
- AAAAAAAApqCdm+jn5//r6+v/oaGh/0BAQP9aWlr/XV1d/3JeU//acif/7IQt/+yELv/shS3/43sq/4ZS
- OP/bcyj/7IUt/+yFLv/shC3/6oMs/5tfP/9bW1v/XFxc/0tLS/9lZWX/1dTU/9HQ0P+0r63c0MrIBQAA
- AAAAAAAAAAAAAFlSTwK5tLLa5eTk/+jo6P9dXFz/X19f/25ubv92dnb/pl82/+yGLv/tiC//7Ygv/+2H
- L//KaSf/S0dF/7phJ//thy//7Ygv/+2IL//shy//xWcq/3Nycf90dHT/aWlp/zMzM//W1dX/1dPT/8C+
- vfrEvbomAAAAAAAAAAAAAAAAVU5KFc/My/Xi4eH/5eTk/zQ0NP9dXV3/W1tb/29aT//bdSn/7oox/+6L
- Mf/uijH/7Igw/4tNKv86Ojr/hUwt/+yIMP/uijH/74sy/+6KMf/geiv/clZJ/11dXf9ZWVn/NDQ0/7q5
- uf/Y19f/y8rK/7y1sk4AAAAAAAAAAAAAAABVTUop19XV+87Qzv/b2tr/KSkp/2xsbP9lZWX/qWA0/+2O
- OP/ukTr/75I6/+6QOf/ceC3/VkI4/0FBQf9cRDf/33wv/+6QOf/vkjr/75E6/+2OOP+dXTn/ZGRk/2Rk
- ZP9ISEj/oJ+f/8vMyv/S0dH/uLGuZwAAAAAAAAAAAAAAAElCPy/U09P9q7Wt/8zMy/8vLy//YmJi/3pe
- UP/dhDz/7Z5O/+6gUf/tn1D/7JtL/6lfMv89PT3/Ozs7/zw8O/+/ajD/7Z1N/+2gUf/uoFH/7Z1N/9F4
- OP9wa2n/Y2Nj/0RERP+WlZX/q7at/9XU1P+5s7BtAAAAAAAAAAAAAAAAS0RBJs/NzfrY19f/2NfX/ygo
- KP9eXV3/sWIx/+ydTv/toVL/7qJU/+2fUP/fiD//XkAx/zc3N/84ODj/Nzc3/4RMLP/rmkv/7aFS/+6i
- VP/toFH/5ZFF/4hfSv9ZWVn/PDw8/6qpqf/h4OD/2djY/7WtqmAAAAAAAAAAAAAAAAA/OjcQwLy78tXU
- 1P/Z2Nj/PT09/4VeSv/jjkT/7aFT/+6jVv/uolX/7Z5Q/7RmNf9HR0f/QkJC/0ZGRv9GRkb/aFJH/92F
- Pv/toVP/7qNW/+6jVf/tn1D/tWg3/21tbf85OTn/0M/P/+Tj4//a2tr+rqWhQQAAAAAAAAAAAAAAADUx
- LwKgm5nP0tHR/9bU1P9oY2D/vWgv/+2gUv/upFf/7qVY/+6iVP/fiEH/blBB/1ZWVv9CQkL/QUFB/0dH
- R/9UVFT/vm03/+2gUv/upFj/76VY/+6iVf/dhT//ZU9E/zw8PP/r6ur/5+fn/9LPzvaqoJsYAAAAAAAA
- AAAAAAAAAAAAAHFsao3Qz8//09HR/8aPcv/ol0v/7qNX/++mWv/upVn/7aBT/6ZcL/9hYWH/aWlp/2Bg
- YP9eXl7/X19f/2FhYf+NWj//6ptP/+6lWP/vp1v/7qVZ/+2fUv+QUSz/g4OD/+3t7f/q6en/wry5yAAA
- AAAAAAAAAAAAAAAAAAAAAAAASUNBN8jGxvvNvbX/1nw7/+2fUf/upFj/7qZa/+6jV//agz//fmhd/2tr
- a/9qamr/ampq/3Jycv9wcHD/a2tr/3lrZP/YgD3/7qRY/++oXf/vqF3/7qRY/9B5OP/d08//8PDw/+vr
- 6/+1rKhsAAAAAAAAAAAAAAAAAAAAAAAAAABUOy8Ci4WDu8WYgf/Lf1D/1n49/+ucUf/uolb/65xQ/5ZX
- Mv9bW1v/VlZW/1xcXP9XV1f/Wlpa/1lZWf9XV1f/XFxc/7BoO//uo1f/76le//CqYP/vqF3/6pxR/9qm
- i//z8vL/1tLQ6KyinhEAAAAAAAAAAAAAAAAAAAAAulIdBq5QH4LBWh/r0GIf/85kJP/Gc0b/xXRH/+OO
- Rf/Odzr/bWNe/2lpaf9sbGz/c3Nz/2xsbP9oaGj/aWlp/2xsbP9zc3P/hF5L/+WVTf/vqF7/8Kti/++r
- Yf/uplv/1IBH//Lv7f61rKhtAAAAAAAAAAAAAAAAAAAAAL9WHga9VR6OvVUehcBXHpHKXh/p3m4h/+Ny
- Iv/OaSv/xHdN/2U5I/9YWFj/aGho/2BgYP9iYmL/YGBg/2hoaP9mZmb/YGBg/2JiYv9jYF7/yXQ5/++n
- Xf/wrGP/8K1k/++qYP/ml0//uYRpxLGVhwYAAAAAAAAAAAAAAAAAAAAAwFgfeOJ2Jv/fcyX/z2Ui7pxH
- GpWQUjLI0Wsq/uZ5J//KdEH/s7Gw/0dHR/83Nzf/Tk5O/1lZWf9XV1f/Xl5e/1xcXP9WVlb/VFRU/z4+
- Pv9+Rif/7KJY//CrYv/wrmb/8K1k/++nXf/LbTLVuWIyAQAAAAAAAAAAAAAAAAAAAADMZCPV64Ms/+uD
- LP/shC3/6IAs/8dmJ/SteV/wyXVE/tVtJf/Ou7L/1dTU/6inp/9oaGj/RERE/zExMf8xMTH/MDAw/z4+
- Pv9lZGT/pqam/+nb1P/Zg0L/76lf//CtZP/wrWX/76lg/+GQSv/CXiVLAAAAAAAAAAAAAAAAAAAAANJt
- KvTtkDr/7pI9/+6TPv/ukz3/7pI9/96BNv61d1X1unFL+cmpmf/S0dH/1dTU/9nY2P/c29v/3Nvb/9DQ
- z//f3t7/6unp/+3t7f/w8PD/8/Pz/9WTbP/uolf/76he//CqYP/vqF7/76NY/8lnLL8AAAAAAAAAAAAA
- AAAAAAAAzW0u3O2dTv/tolT/7qRX/+6kV//uo1b/7aFT/+SRRv+mXDPbjH53wMvJyPvT0tL/1tXV/9nY
- 2P/X1tb/oq+k/+Pj4//n5ub/6urq/+7t7f/v7u79tJ+V1bpiLbDNcDPN0XY469d+Pv7dhkL/03Y2/r1W
- IDQAAAAAAAAAAAAAAADCXSSG6ZhM/+6jV//up1v/76hc/+6nW//upFf/7Z5R/8pqLdtxRi4FQz48OW1o
- Zo+gnJrRw7++89PR0frZ19f93Nra+9LQz/a5tbPdiIOBoGNdW0xgU00HAAAAAAAAAAAAAAAAwl4mC8Jd
- JSm/WSJIvFUfMgAAAAAAAAAAAAAAAMFcJA7KaS3W7J5T/++mW//vp1z/76Zc/+6jV//XfTv5w2AmQgAA
- AAAAAAAAAAAAADc0MgM8NzYSRT89KU5IRTNMRkMtSURBGEZBQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMJfJhXFYyml1n08+t6KRv/ag0H/ymsvzcRh
- KD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBXSUPwVwkL8Fd
- JRwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+P///wB///wAD//wAAf/4A
- AD/8AAAf+AAAD/AAAAfwAAAD4AAAA+AAAAPgAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAeAA
- AAPgAAAD4AAAA8AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAA4AAAcOAOA//wH////H///8oAAAAEAAAACAA
- AAABACAAAAAAAEAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAko6MB6x5
- XjnDWiB3yFserKN7ZwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACuqKUe08/Nm9zX
- 1e/XbCn+32oe/9xpHv/Cr6fbxcC/cK+sqwYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACxqKRF7evr7vPz
- 8//AoZD/4W8h/+FtH//hbyD/yJ+H/8/Ozv++u7rFxsLBFQAAAAAAAAAAAAAAAAAAAACakIsw8O7u9Nra
- 2v9eXl7/qGE0/+R1JP/kdCP/5HUk/55aLv9ycXH/zMvL/767usSwrasGAAAAAAAAAABJQj8B1dHPyOTk
- 5P9YWFj/Z19b/9pzJ//ofCj/6Hwo/+h8KP/Pbyz/X19f/3FwcP/OzMz/wr68bwAAAAAAAAAAc2xoOOrq
- 6v6Dg4P/XV1d/5dlRf/rgyz/6YEs/8VrLP/rgyz/6H8r/3NeUv9OTk7/sK+v/8PAv9qMiokBAAAAAI2I
- hXnl5OT/U1NT/2tmY//WeS//7okw/8txLP9xSzX/7Ygw/+2JMP+iakT/ZWVl/359ff/Ozcz9o56cHQAA
- AACSjoyUyMvI/0pKSv+Zakn/7ZdE/+6XRP+GVjX/RT88/96FOf/umUb/0oA+/2dlZf9wcHD/x8rH/5uW
- kzUAAAAAhoKAidfW1v9SSEP/24tF/+6iVP/bi0X/SEA8/z8/P/+tbz//7aJU/+udT/+BY1L/fHt7/97d
- 3f6Si4goAAAAAFxYVljT0dH/tXxT/+6jV//uo1b/lWVE/1hYWP9RUVH/gF9L/+2hVP/upVj/sHFA/6al
- pf/Z19bvbGVhBgAAAAA7KyIOuaig7dmGRv/tolb/0oZG/2dhXv9iYmL/ZWVl/2ZiYf/ZjEr/76le/+aY
- UP/m18//yMPAmQAAAABaKA4BuVMeZ8dcH9nVbiv/znhB/35bRf9nZ2f/aGho/2hoaP9oaGj/pXJM/++q
- YP/vqmD/2qN78JKEfRwAAAAAr1EcU+Z8Kf/QbCXgtWc469N0NP6noZ//ZWVl/0pKSv9HR0f/U1NT/5OB
- eP/pnlf/8K1l/+OTTvSRRx4TAAAAALtdJHTtmEb/7ptK/+qWR/6+dUbyvbCq7tbV1f/a2dn/zdDN/+rq
- 6v/w8PD+zI1h4N+OSu3klE3+xmMpfAAAAACdSRwl45FJ9O+nXP/upVn/1Hk4xVQ1JQ9dWVdZhoKAipOQ
- jpaHgoF8VVFPOzkpIQEAAAAAejoWDYo/Fx4AAAAAAAAAAK9UIS7OcDOOymouepdHGw8AAAAAAAAAAAAA
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwfAADwBwAA4AMAAMABAACAAQAAgAAAAIAA
- AACAAAAAgAAAAIAAAACAAQAAAAEAAAABAAAAAQAAAAkAAIf/AAA=
-
-
-
\ No newline at end of file
diff --git a/Vocaluxe/Screens/CPopupScreenPlayerControl.cs b/Vocaluxe/Screens/CPopupScreenPlayerControl.cs
index c99213d1..ab71d8f5 100644
--- a/Vocaluxe/Screens/CPopupScreenPlayerControl.cs
+++ b/Vocaluxe/Screens/CPopupScreenPlayerControl.cs
@@ -16,7 +16,6 @@
#endregion
using System;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CPopupScreenServerQR.cs b/Vocaluxe/Screens/CPopupScreenServerQR.cs
index 7bc1da1b..4a1eccfc 100644
--- a/Vocaluxe/Screens/CPopupScreenServerQR.cs
+++ b/Vocaluxe/Screens/CPopupScreenServerQR.cs
@@ -15,8 +15,8 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
using QRCoder;
+using SkiaSharp;
using Vocaluxe.Base;
using Vocaluxe.Base.Web;
using VocaluxeLib;
@@ -100,7 +100,10 @@ namespace Vocaluxe.Screens
CDraw.RemoveTexture(ref _QRServerAddress);
_QRAddress = CWebServer.GetServerAddress();
var qrcode = qr.CreateQrCode(_QRAddress, QRCodeGenerator.ECCLevel.H);
- _QRServerAddress = CDraw.AddTexture(qrcode.GetGraphic(20));
+ using (var bitmap = SKBitmap.Decode(new PngByteQRCode(qrcode).GetGraphic(20)))
+ {
+ _QRServerAddress = CDraw.AddTexture(bitmap);
+ }
}
}
}
\ No newline at end of file
diff --git a/Vocaluxe/Screens/CPopupScreenVolumeControl.cs b/Vocaluxe/Screens/CPopupScreenVolumeControl.cs
index c2751361..3949b046 100644
--- a/Vocaluxe/Screens/CPopupScreenVolumeControl.cs
+++ b/Vocaluxe/Screens/CPopupScreenVolumeControl.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenCredits.cs b/Vocaluxe/Screens/CScreenCredits.cs
index 47c88f08..45a670f7 100644
--- a/Vocaluxe/Screens/CScreenCredits.cs
+++ b/Vocaluxe/Screens/CScreenCredits.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
diff --git a/Vocaluxe/Screens/CScreenHighscore.cs b/Vocaluxe/Screens/CScreenHighscore.cs
index aa312a99..72028bd9 100644
--- a/Vocaluxe/Screens/CScreenHighscore.cs
+++ b/Vocaluxe/Screens/CScreenHighscore.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Windows.Forms;
using Vocaluxe.Base;
using Vocaluxe.Base.Web;
using VocaluxeLib;
diff --git a/Vocaluxe/Screens/CScreenJukebox.cs b/Vocaluxe/Screens/CScreenJukebox.cs
index 9f3e55ef..2af2b53b 100644
--- a/Vocaluxe/Screens/CScreenJukebox.cs
+++ b/Vocaluxe/Screens/CScreenJukebox.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenJukeboxPlayer.cs b/Vocaluxe/Screens/CScreenJukeboxPlayer.cs
index 15fd6b60..2e92f503 100644
--- a/Vocaluxe/Screens/CScreenJukeboxPlayer.cs
+++ b/Vocaluxe/Screens/CScreenJukeboxPlayer.cs
@@ -20,7 +20,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Draw;
diff --git a/Vocaluxe/Screens/CScreenLoad.cs b/Vocaluxe/Screens/CScreenLoad.cs
index 562beefa..a06455b2 100644
--- a/Vocaluxe/Screens/CScreenLoad.cs
+++ b/Vocaluxe/Screens/CScreenLoad.cs
@@ -16,7 +16,6 @@
#endregion
using System;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Log;
diff --git a/Vocaluxe/Screens/CScreenMain.cs b/Vocaluxe/Screens/CScreenMain.cs
index 3eca2386..12905bd1 100644
--- a/Vocaluxe/Screens/CScreenMain.cs
+++ b/Vocaluxe/Screens/CScreenMain.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenNames.cs b/Vocaluxe/Screens/CScreenNames.cs
index d67bb5d0..885d1e2d 100644
--- a/Vocaluxe/Screens/CScreenNames.cs
+++ b/Vocaluxe/Screens/CScreenNames.cs
@@ -17,7 +17,6 @@
using System;
using System.Collections.Generic;
-using System.Windows.Forms;
using Vocaluxe.Base;
using Vocaluxe.Base.Web;
using VocaluxeLib;
diff --git a/Vocaluxe/Screens/CScreenOptionsBase.cs b/Vocaluxe/Screens/CScreenOptionsBase.cs
index 075bef14..5d6a98da 100644
--- a/Vocaluxe/Screens/CScreenOptionsBase.cs
+++ b/Vocaluxe/Screens/CScreenOptionsBase.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenOptionsGame.cs b/Vocaluxe/Screens/CScreenOptionsGame.cs
index 8984281d..9e7950ff 100644
--- a/Vocaluxe/Screens/CScreenOptionsGame.cs
+++ b/Vocaluxe/Screens/CScreenOptionsGame.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenOptionsGraphics.cs b/Vocaluxe/Screens/CScreenOptionsGraphics.cs
index 23750927..0dae110c 100644
--- a/Vocaluxe/Screens/CScreenOptionsGraphics.cs
+++ b/Vocaluxe/Screens/CScreenOptionsGraphics.cs
@@ -17,7 +17,6 @@
using System;
using System.IO;
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenOptionsLyrics.cs b/Vocaluxe/Screens/CScreenOptionsLyrics.cs
index d000dabf..ce53627f 100644
--- a/Vocaluxe/Screens/CScreenOptionsLyrics.cs
+++ b/Vocaluxe/Screens/CScreenOptionsLyrics.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenOptionsRecord.cs b/Vocaluxe/Screens/CScreenOptionsRecord.cs
index 86295231..f30c5ed5 100644
--- a/Vocaluxe/Screens/CScreenOptionsRecord.cs
+++ b/Vocaluxe/Screens/CScreenOptionsRecord.cs
@@ -17,7 +17,6 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Windows.Forms;
using Vocaluxe.Base;
using Vocaluxe.Lib.Sound.Record;
using VocaluxeLib;
diff --git a/Vocaluxe/Screens/CScreenOptionsServer.cs b/Vocaluxe/Screens/CScreenOptionsServer.cs
index 78812365..7a83c1f4 100644
--- a/Vocaluxe/Screens/CScreenOptionsServer.cs
+++ b/Vocaluxe/Screens/CScreenOptionsServer.cs
@@ -15,7 +15,6 @@
// along with Vocaluxe. If not, see .
#endregion
-using System.Windows.Forms;
using Vocaluxe.Base;
using VocaluxeLib;
using VocaluxeLib.Menu;
diff --git a/Vocaluxe/Screens/CScreenOptionsSongFolders.cs b/Vocaluxe/Screens/CScreenOptionsSongFolders.cs
index e69d6805..09451135 100644
--- a/Vocaluxe/Screens/CScreenOptionsSongFolders.cs
+++ b/Vocaluxe/Screens/CScreenOptionsSongFolders.cs
@@ -19,8 +19,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Windows.Forms;
using Vocaluxe.Base;
+using Vocaluxe.Lib;
using VocaluxeLib;
using VocaluxeLib.Menu;
@@ -358,18 +358,7 @@ namespace Vocaluxe.Screens
private static string _OpenSongFolderDialog()
{
- using (var dialog = new FolderBrowserDialog())
- {
- dialog.Description = CLanguage.Translate("TR_SCREENO_SONGFOLDER");
- dialog.ShowNewFolderButton = true;
- var owner = Application.OpenForms.Cast
/// Full path to image file
/// Bitmap or null on error
- public static Bitmap LoadBitmap(string filePath)
+ public static SKBitmap LoadBitmap(string filePath)
{
if (!File.Exists(filePath))
{
@@ -359,12 +360,17 @@ namespace VocaluxeLib
return null;
}
- Bitmap bmp;
+ SKBitmap bmp;
try
{
- bmp = new Bitmap(filePath);
+ bmp = SKBitmap.Decode(filePath);
}
catch (Exception)
+ {
+ bmp = null;
+ }
+
+ if (bmp == null)
{
CLog.Error("Error loading bitmap: " + filePath);
return null;
diff --git a/VocaluxeLib/Enums.cs b/VocaluxeLib/Enums.cs
index 3f976347..4145ed63 100644
--- a/VocaluxeLib/Enums.cs
+++ b/VocaluxeLib/Enums.cs
@@ -182,6 +182,17 @@ namespace VocaluxeLib
GstreamerSharp
}
+ public enum EVideoDownscaleResolution
+ {
+ TR_CONFIG_SCALING_360p = 360,
+ TR_CONFIG_SCALING_480p = 480,
+ TR_CONFIG_SCALING_720p = 720,
+ TR_CONFIG_SCALING_1080p = 1080,
+ TR_CONFIG_SCALING_1440p = 1440,
+ TR_CONFIG_SCALING_2160p = 2160,
+ TR_CONFIG_SCALING_DISABLED = -1
+ }
+
public enum EWebcamLib
{
AForgeNet
diff --git a/VocaluxeLib/Extensions.cs b/VocaluxeLib/Extensions.cs
index 239a581b..3abbbe5e 100644
--- a/VocaluxeLib/Extensions.cs
+++ b/VocaluxeLib/Extensions.cs
@@ -19,10 +19,10 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Drawing.Drawing2D;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
+using SkiaSharp;
namespace VocaluxeLib
{
@@ -217,14 +217,14 @@ namespace VocaluxeLib
return list;
}
- public static Size GetSize(this Bitmap bmp)
+ public static Size GetSize(this SKBitmap bmp)
{
return new Size(bmp.Width, bmp.Height);
}
- public static Rectangle GetRect(this Bitmap bmp)
+ public static Rectangle GetRect(this SKBitmap bmp)
{
- return Rectangle.Round(new Rectangle(0, 0, bmp.Width, bmp.Height));
+ return new Rectangle(0, 0, bmp.Width, bmp.Height);
}
///
@@ -233,19 +233,19 @@ namespace VocaluxeLib
///
///
/// Resized bitmap or null on error
- public static Bitmap Resize(this Bitmap bmp, Size newSize)
+ public static SKBitmap Resize(this SKBitmap bmp, Size newSize)
{
- Bitmap result = null;
+ SKBitmap result = null;
try
{
//Create a new Bitmap with the new sizes
- result = new Bitmap(newSize.Width, newSize.Height);
+ result = new SKBitmap(newSize.Width, newSize.Height, SKColorType.Bgra8888, SKAlphaType.Premul);
//Scale the texture
- using (var g = Graphics.FromImage(result))
+ using (var canvas = new SKCanvas(result))
+ using (var image = SKImage.FromBitmap(bmp))
{
- g.InterpolationMode = InterpolationMode.HighQualityBicubic;
- g.SmoothingMode = SmoothingMode.HighQuality;
- g.DrawImage(bmp, result.GetRect());
+ canvas.Clear(SKColors.Transparent);
+ canvas.DrawImage(image, new SKRect(0, 0, newSize.Width, newSize.Height), new SKSamplingOptions(SKCubicResampler.Mitchell));
}
}
catch (Exception)
@@ -259,5 +259,27 @@ namespace VocaluxeLib
return result;
}
+
+ public static SKBitmap ToBgra(this SKBitmap bmp)
+ {
+ if (bmp.ColorType == SKColorType.Bgra8888 && bmp.AlphaType != SKAlphaType.Premul && bmp.RowBytes == bmp.Width * 4)
+ {
+ return bmp;
+ }
+
+ var result = new SKBitmap(new SKImageInfo(bmp.Width, bmp.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul));
+ bmp.CopyPixelsTo(result);
+ return result;
+ }
+
+ public static void CopyPixelsTo(this SKBitmap bmp, SKBitmap target)
+ {
+ using (var pixmap = bmp.PeekPixels())
+ {
+ pixmap.ReadPixels(target.Info.WithSize(bmp.Width, bmp.Height), target.GetPixels(), target.RowBytes, 0, 0);
+ }
+
+ target.NotifyPixelsChanged();
+ }
}
-}
\ No newline at end of file
+}
diff --git a/VocaluxeLib/Keys.cs b/VocaluxeLib/Keys.cs
new file mode 100644
index 00000000..850ea208
--- /dev/null
+++ b/VocaluxeLib/Keys.cs
@@ -0,0 +1,161 @@
+#region license
+// This file is part of Vocaluxe.
+//
+// Vocaluxe is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Vocaluxe is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Vocaluxe. If not, see .
+#endregion
+
+using System;
+
+namespace VocaluxeLib
+{
+ [Flags]
+ public enum Keys
+ {
+ None = 0,
+ Back = 8,
+ Tab = 9,
+ Clear = 12,
+ Enter = 13,
+ Return = 13,
+ ShiftKey = 16,
+ ControlKey = 17,
+ Menu = 18,
+ Pause = 19,
+ CapsLock = 20,
+ Escape = 27,
+ Space = 32,
+ PageUp = 33,
+ PageDown = 34,
+ End = 35,
+ Home = 36,
+ Left = 37,
+ Up = 38,
+ Right = 39,
+ Down = 40,
+ PrintScreen = 44,
+ Insert = 45,
+ Delete = 46,
+ D0 = 48,
+ D1 = 49,
+ D2 = 50,
+ D3 = 51,
+ D4 = 52,
+ D5 = 53,
+ D6 = 54,
+ D7 = 55,
+ D8 = 56,
+ D9 = 57,
+ A = 65,
+ B = 66,
+ C = 67,
+ D = 68,
+ E = 69,
+ F = 70,
+ G = 71,
+ H = 72,
+ I = 73,
+ J = 74,
+ K = 75,
+ L = 76,
+ M = 77,
+ N = 78,
+ O = 79,
+ P = 80,
+ Q = 81,
+ R = 82,
+ S = 83,
+ T = 84,
+ U = 85,
+ V = 86,
+ W = 87,
+ X = 88,
+ Y = 89,
+ Z = 90,
+ LWin = 91,
+ RWin = 92,
+ Apps = 93,
+ NumPad0 = 96,
+ NumPad1 = 97,
+ NumPad2 = 98,
+ NumPad3 = 99,
+ NumPad4 = 100,
+ NumPad5 = 101,
+ NumPad6 = 102,
+ NumPad7 = 103,
+ NumPad8 = 104,
+ NumPad9 = 105,
+ Multiply = 106,
+ Add = 107,
+ Separator = 108,
+ Subtract = 109,
+ Decimal = 110,
+ Divide = 111,
+ F1 = 112,
+ F2 = 113,
+ F3 = 114,
+ F4 = 115,
+ F5 = 116,
+ F6 = 117,
+ F7 = 118,
+ F8 = 119,
+ F9 = 120,
+ F10 = 121,
+ F11 = 122,
+ F12 = 123,
+ F13 = 124,
+ F14 = 125,
+ F15 = 126,
+ F16 = 127,
+ F17 = 128,
+ F18 = 129,
+ F19 = 130,
+ F20 = 131,
+ F21 = 132,
+ F22 = 133,
+ F23 = 134,
+ F24 = 135,
+ NumLock = 144,
+ Scroll = 145,
+ LShiftKey = 160,
+ RShiftKey = 161,
+ LControlKey = 162,
+ RControlKey = 163,
+ LMenu = 164,
+ RMenu = 165,
+ VolumeMute = 173,
+ VolumeDown = 174,
+ VolumeUp = 175,
+ MediaNextTrack = 176,
+ MediaPreviousTrack = 177,
+ MediaStop = 178,
+ MediaPlayPause = 179,
+ OemSemicolon = 186,
+ Oemplus = 187,
+ Oemcomma = 188,
+ OemMinus = 189,
+ OemPeriod = 190,
+ OemQuestion = 191,
+ Oemtilde = 192,
+ OemOpenBrackets = 219,
+ OemPipe = 220,
+ OemCloseBrackets = 221,
+ OemQuotes = 222,
+ OemBackslash = 226,
+ KeyCode = 0x0000FFFF,
+ Shift = 0x00010000,
+ Control = 0x00020000,
+ Alt = 0x00040000,
+ Modifiers = unchecked((int)0xFFFF0000)
+ }
+}
diff --git a/VocaluxeLib/Menu/CMenu.cs b/VocaluxeLib/Menu/CMenu.cs
index 6f570231..5847ab10 100644
--- a/VocaluxeLib/Menu/CMenu.cs
+++ b/VocaluxeLib/Menu/CMenu.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Windows.Forms;
using System.Xml.Serialization;
using VocaluxeLib.Draw;
using VocaluxeLib.Log;
diff --git a/VocaluxeLib/Menu/CMenuPartyNameSelection.cs b/VocaluxeLib/Menu/CMenuPartyNameSelection.cs
index 9b0f7642..6e179094 100644
--- a/VocaluxeLib/Menu/CMenuPartyNameSelection.cs
+++ b/VocaluxeLib/Menu/CMenuPartyNameSelection.cs
@@ -17,7 +17,6 @@
using System;
using System.Collections.Generic;
-using System.Windows.Forms;
using VocaluxeLib.Profile;
namespace VocaluxeLib.Menu
diff --git a/VocaluxeLib/Menu/CMenuPartySongSelection.cs b/VocaluxeLib/Menu/CMenuPartySongSelection.cs
index f5df07c6..8c1481fc 100644
--- a/VocaluxeLib/Menu/CMenuPartySongSelection.cs
+++ b/VocaluxeLib/Menu/CMenuPartySongSelection.cs
@@ -17,7 +17,6 @@
using System;
using System.Collections.Generic;
-using System.Windows.Forms;
namespace VocaluxeLib.Menu
{
diff --git a/VocaluxeLib/Menu/CNameSelection.cs b/VocaluxeLib/Menu/CNameSelection.cs
index 36d1f121..d74e5d6e 100644
--- a/VocaluxeLib/Menu/CNameSelection.cs
+++ b/VocaluxeLib/Menu/CNameSelection.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Windows.Forms;
using System.Xml.Serialization;
using VocaluxeLib.Draw;
diff --git a/VocaluxeLib/Menu/CObjectInteractions.cs b/VocaluxeLib/Menu/CObjectInteractions.cs
index 2fb7c948..5fd36fc6 100644
--- a/VocaluxeLib/Menu/CObjectInteractions.cs
+++ b/VocaluxeLib/Menu/CObjectInteractions.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using System.Windows.Forms;
using VocaluxeLib.Menu.SingNotes;
using VocaluxeLib.Menu.SongMenu;
diff --git a/VocaluxeLib/Menu/CPlaylist.cs b/VocaluxeLib/Menu/CPlaylist.cs
index 9890f5fa..5d3e1ec5 100644
--- a/VocaluxeLib/Menu/CPlaylist.cs
+++ b/VocaluxeLib/Menu/CPlaylist.cs
@@ -17,7 +17,6 @@
using System;
using System.Collections.Generic;
-using System.Windows.Forms;
using System.Xml.Serialization;
using VocaluxeLib.Songs;
diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuList.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuList.cs
index 2a60cb51..a110bc9a 100644
--- a/VocaluxeLib/Menu/SongMenu/CSongMenuList.cs
+++ b/VocaluxeLib/Menu/SongMenu/CSongMenuList.cs
@@ -19,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Windows.Forms;
using VocaluxeLib.Draw;
using VocaluxeLib.PartyModes;
using VocaluxeLib.Songs;
diff --git a/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs b/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs
index 7e02182f..5eaf3e59 100644
--- a/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs
+++ b/VocaluxeLib/Menu/SongMenu/CSongMenuTileBoard.cs
@@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Windows.Forms;
using VocaluxeLib.Draw;
using VocaluxeLib.PartyModes;
using VocaluxeLib.Songs;
diff --git a/VocaluxeLib/Structs.cs b/VocaluxeLib/Structs.cs
index 815967a2..e7e6e823 100644
--- a/VocaluxeLib/Structs.cs
+++ b/VocaluxeLib/Structs.cs
@@ -21,7 +21,6 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Drawing;
-using System.Windows.Forms;
using System.Xml.Serialization;
using VocaluxeLib.Songs;
using VocaluxeLib.Xml;
diff --git a/VocaluxeLib/VocaluxeLib.csproj b/VocaluxeLib/VocaluxeLib.csproj
index eb9f4923..0b159e24 100644
--- a/VocaluxeLib/VocaluxeLib.csproj
+++ b/VocaluxeLib/VocaluxeLib.csproj
@@ -1,211 +1,27 @@
-
-
+
- DebugWin
- AnyCPU
- 8.0.30703
- 2.0
- {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}
Library
- Properties
VocaluxeLib
VocaluxeLib
- v4.8.1
- 512
-
-
-
-
-
-
- AnyCPU
- true
- full
- false
- ..\Output\libs\managed
- TRACE;DEBUG;WIN
- prompt
- 4
- Off
- ..\CheckingRules.ruleset
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- ..\Output\libs\managed
- prompt
- 4
- false
- WIN
- Off
- false
- true
- 9.0
-
-
- AnyCPU
- true
- full
- false
- ..\Output\libs\managed
- TRACE;DEBUG;LINUX
- prompt
- 4
- Off
- false
- 9.0
-
-
- AnyCPU
- pdbonly
- true
- ..\Output\libs\managed
- prompt
- 4
- false
- LINUX
- Off
- false
- true
- 9.0
-
-
- ..\Output\libs\managed
- WIN
- true
- AnyCPU
- false
- Off
- prompt
- ManagedMinimumRules.ruleset
- false
- pdbonly
- true
- 9.0
+ ..\Output\libs\managed\
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
True
True
CLogFunctions.tt
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
TextTemplatingFileGenerator
CLogFunctions.cs
-
+
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/VocaluxeWeb/Hosting/CCertificateProvider.cs b/VocaluxeWeb/Hosting/CCertificateProvider.cs
index 0be67176..9aba8b34 100644
--- a/VocaluxeWeb/Hosting/CCertificateProvider.cs
+++ b/VocaluxeWeb/Hosting/CCertificateProvider.cs
@@ -37,7 +37,7 @@ namespace VocaluxeWeb.Hosting
{
try
{
- var existing = new X509Certificate2(File.ReadAllBytes(path), (string)null, X509KeyStorageFlags.UserKeySet);
+ var existing = X509CertificateLoader.LoadPkcs12(File.ReadAllBytes(path), null, X509KeyStorageFlags.UserKeySet);
if (existing.NotAfter > DateTime.Now.AddDays(7) && _Covers(existing, hostName, addresses))
{
return existing;
@@ -57,7 +57,7 @@ namespace VocaluxeWeb.Hosting
}
File.WriteAllBytes(path, pfx);
- return new X509Certificate2(pfx, (string)null, X509KeyStorageFlags.UserKeySet);
+ return X509CertificateLoader.LoadPkcs12(pfx, null, X509KeyStorageFlags.UserKeySet);
}
private static bool _Covers(X509Certificate2 cert, string hostName, IEnumerable addresses)
diff --git a/VocaluxeWeb/Hosting/CStartup.cs b/VocaluxeWeb/Hosting/CStartup.cs
index 7c8432e6..42547488 100644
--- a/VocaluxeWeb/Hosting/CStartup.cs
+++ b/VocaluxeWeb/Hosting/CStartup.cs
@@ -20,12 +20,11 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Newtonsoft.Json.Converters;
using VocaluxeWeb.Api;
using VocaluxeWeb.Dispatching;
@@ -58,13 +57,12 @@ namespace VocaluxeWeb.Hosting
compression.Providers.Add();
});
- // The entry assembly is Vocaluxe.exe, so MVC would not find our controllers on its own
+ // The entry assembly is Vocaluxe, so MVC would not find our controllers on its own
services.AddMvcCore(mvc => mvc.Filters.Add(new CExceptionFilter(options.OnError)))
.AddApplicationPart(typeof(CStartup).Assembly)
.AddApiExplorer()
.AddDataAnnotations()
- .AddJsonFormatters(json => json.Converters.Add(new StringEnumConverter()))
- .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
+ .AddNewtonsoftJson(json => json.SerializerSettings.Converters.Add(new StringEnumConverter()));
services.AddSwaggerGen(swagger =>
{
@@ -89,7 +87,8 @@ namespace VocaluxeWeb.Hosting
app.UseSwagger();
app.UseSwaggerUI(ui => ui.SwaggerEndpoint("/swagger/v1/swagger.json", "Vocaluxe API v1"));
- app.UseMvc();
+ app.UseRouting();
+ app.UseEndpoints(endpoints => endpoints.MapControllers());
if (!string.IsNullOrEmpty(options.WebRoot) && Directory.Exists(options.WebRoot))
{
diff --git a/VocaluxeWeb/Hosting/CWebHost.cs b/VocaluxeWeb/Hosting/CWebHost.cs
index 13fad8ea..9d0e09fb 100644
--- a/VocaluxeWeb/Hosting/CWebHost.cs
+++ b/VocaluxeWeb/Hosting/CWebHost.cs
@@ -21,6 +21,7 @@ using System.Net;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.Extensions.Hosting;
using VocaluxeWeb.Dispatching;
using VocaluxeWeb.Remote;
using VocaluxeWeb.Services;
@@ -32,7 +33,7 @@ namespace VocaluxeWeb.Hosting
{
private readonly IVocaluxeRemote _Remote;
private readonly CWebServerOptions _Options;
- private IWebHost _Host;
+ private IHost _Host;
private CLiveMonitor _Monitor;
private CQueueService _Queue;
private bool _QueuePlaylistReady;
@@ -69,11 +70,12 @@ namespace VocaluxeWeb.Hosting
var lanAddresses = CNetworkAddress.GetLanAddresses();
var certificate = _Options.UseHttps ? CCertificateProvider.GetOrCreate(_Options.CertificatePath, hostName, lanAddresses) : null;
- var host = new WebHostBuilder()
- .UseKestrel(kestrel => _ConfigureKestrel(kestrel, certificate))
+ var host = new HostBuilder()
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
- .ConfigureServices(services => CStartup.ConfigureServices(services, this, _Remote, Dispatcher, Settings, _Options))
- .Configure(app => CStartup.Configure(app, _Options))
+ .ConfigureWebHost(web => web
+ .UseKestrel(kestrel => _ConfigureKestrel(kestrel, certificate))
+ .ConfigureServices(services => CStartup.ConfigureServices(services, this, _Remote, Dispatcher, Settings, _Options))
+ .Configure(app => CStartup.Configure(app, _Options)))
.Build();
try
diff --git a/VocaluxeWeb/Services/CImageService.cs b/VocaluxeWeb/Services/CImageService.cs
index e9ba332b..ee535819 100644
--- a/VocaluxeWeb/Services/CImageService.cs
+++ b/VocaluxeWeb/Services/CImageService.cs
@@ -16,22 +16,19 @@
#endregion
using System;
-using System.Drawing;
-using System.Drawing.Drawing2D;
-using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
+using SkiaSharp;
namespace VocaluxeWeb.Services
{
/// Scaled JPEG versions of covers and avatars, cached on disk.
- /// Uses System.Drawing, which is fine on .NET Framework; replace it (e.g. with SkiaSharp) when moving to .NET 10 / Linux.
public sealed class CImageService
{
private static readonly int[] _Sizes = { 96, 200, 400 };
- private const long _JpegQuality = 82L;
+ private const int _JpegQuality = 82;
private const int _MaxAvatarSize = 512;
private readonly string _CacheFolder;
@@ -87,19 +84,16 @@ namespace VocaluxeWeb.Services
public static byte[] CreateAvatar(byte[] uploaded)
{
using (var input = new MemoryStream(uploaded))
- using (var source = Image.FromStream(input))
+ using (var source = _Decode(input, true))
+ using (var square = _Resize(source, Math.Min(_MaxAvatarSize, Math.Min(source.Width, source.Height)), true))
+ using (var output = new MemoryStream())
{
- _ApplyExifOrientation(source);
- using (var square = _Resize(source, Math.Min(_MaxAvatarSize, Math.Min(source.Width, source.Height)), true))
- using (var output = new MemoryStream())
- {
- _SaveJpeg(square, output);
- return output.ToArray();
- }
+ _SaveJpeg(square, output);
+ return output.ToArray();
}
}
- private static Image _Load(string path)
+ private static SKBitmap _Load(string path)
{
//Read into memory so the file is not locked; the game may have it open at the same time
using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
@@ -107,19 +101,38 @@ namespace VocaluxeWeb.Services
var buffer = new MemoryStream();
file.CopyTo(buffer);
buffer.Position = 0;
- return Image.FromStream(buffer);
+ return _Decode(buffer, false);
+ }
+ }
+
+ private static SKBitmap _Decode(Stream stream, bool applyOrientation)
+ {
+ using (var codec = SKCodec.Create(stream))
+ {
+ if (codec == null)
+ {
+ throw new ArgumentException("Unsupported image format");
+ }
+
+ var bitmap = SKBitmap.Decode(codec);
+ if (bitmap == null)
+ {
+ throw new ArgumentException("Invalid image");
+ }
+
+ return applyOrientation ? _ApplyExifOrientation(bitmap, codec.EncodedOrigin) : bitmap;
}
}
/// Fits the image into size x size, or crops it to a square of that size.
- private static Bitmap _Resize(Image source, int size, bool crop)
+ private static SKBitmap _Resize(SKBitmap source, int size, bool crop)
{
int width, height;
- var srcRect = new Rectangle(0, 0, source.Width, source.Height);
+ var srcRect = new SKRect(0, 0, source.Width, source.Height);
if (crop)
{
var side = Math.Min(source.Width, source.Height);
- srcRect = new Rectangle((source.Width - side) / 2, (source.Height - side) / 2, side, side);
+ srcRect = SKRect.Create((source.Width - side) / 2, (source.Height - side) / 2, side, side);
width = height = size;
}
else
@@ -129,20 +142,19 @@ namespace VocaluxeWeb.Services
height = Math.Max(1, (int)Math.Round(source.Height * scale));
}
- var result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
- using (var g = Graphics.FromImage(result))
+ var result = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Opaque);
+ using (var canvas = new SKCanvas(result))
+ using (var image = SKImage.FromBitmap(source))
+ using (var paint = new SKPaint { IsAntialias = true })
{
- g.Clear(Color.White);
- g.InterpolationMode = InterpolationMode.HighQualityBicubic;
- g.PixelOffsetMode = PixelOffsetMode.HighQuality;
- g.CompositingQuality = CompositingQuality.HighQuality;
- g.DrawImage(source, new Rectangle(0, 0, width, height), srcRect, GraphicsUnit.Pixel);
+ canvas.Clear(SKColors.White);
+ canvas.DrawImage(image, srcRect, new SKRect(0, 0, width, height), new SKSamplingOptions(SKCubicResampler.Mitchell), paint);
}
return result;
}
- private static void _SaveJpeg(Image image, string path)
+ private static void _SaveJpeg(SKBitmap image, string path)
{
using (var file = File.Create(path))
{
@@ -150,40 +162,43 @@ namespace VocaluxeWeb.Services
}
}
- private static void _SaveJpeg(Image image, Stream stream)
+ private static void _SaveJpeg(SKBitmap image, Stream stream)
{
- var codec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
- using (var parameters = new EncoderParameters(1))
- {
- parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, _JpegQuality);
- image.Save(stream, codec, parameters);
- }
+ image.Encode(stream, SKEncodedImageFormat.Jpeg, _JpegQuality);
}
/// Photos from phones are often stored rotated with an EXIF hint.
- private static void _ApplyExifOrientation(Image image)
+ private static SKBitmap _ApplyExifOrientation(SKBitmap image, SKEncodedOrigin origin)
{
- const int orientationId = 0x0112;
- if (!image.PropertyIdList.Contains(orientationId))
+ float degrees;
+ switch (origin)
{
- return;
+ case SKEncodedOrigin.BottomRight:
+ degrees = 180;
+ break;
+ case SKEncodedOrigin.RightTop:
+ degrees = 90;
+ break;
+ case SKEncodedOrigin.LeftBottom:
+ degrees = 270;
+ break;
+ default:
+ return image;
}
- var rotate = RotateFlipType.RotateNoneFlipNone;
- switch (image.GetPropertyItem(orientationId).Value[0])
+ var swap = degrees != 180;
+ var rotated = new SKBitmap(swap ? image.Height : image.Width, swap ? image.Width : image.Height, image.ColorType, image.AlphaType);
+ using (var canvas = new SKCanvas(rotated))
+ using (var source = SKImage.FromBitmap(image))
{
- case 3:
- rotate = RotateFlipType.Rotate180FlipNone;
- break;
- case 6:
- rotate = RotateFlipType.Rotate90FlipNone;
- break;
- case 8:
- rotate = RotateFlipType.Rotate270FlipNone;
- break;
+ canvas.Translate(rotated.Width / 2f, rotated.Height / 2f);
+ canvas.RotateDegrees(degrees);
+ canvas.Translate(-image.Width / 2f, -image.Height / 2f);
+ canvas.DrawImage(source, 0, 0, new SKSamplingOptions(SKFilterMode.Linear));
}
- image.RotateFlip(rotate);
+ image.Dispose();
+ return rotated;
}
private static string _Hash(string text)
diff --git a/VocaluxeWeb/Services/CPinHasher.cs b/VocaluxeWeb/Services/CPinHasher.cs
index 6615942b..aa779b00 100644
--- a/VocaluxeWeb/Services/CPinHasher.cs
+++ b/VocaluxeWeb/Services/CPinHasher.cs
@@ -58,10 +58,7 @@ namespace VocaluxeWeb.Services
private static byte[] _Pbkdf2(string pin, byte[] salt)
{
- using (var kdf = new Rfc2898DeriveBytes(pin, salt, _Iterations, HashAlgorithmName.SHA256))
- {
- return kdf.GetBytes(_HashSize);
- }
+ return Rfc2898DeriveBytes.Pbkdf2(pin, salt, _Iterations, HashAlgorithmName.SHA256, _HashSize);
}
private static byte[] _LegacySha256(string pin, byte[] salt)
diff --git a/VocaluxeWeb/VocaluxeWeb.csproj b/VocaluxeWeb/VocaluxeWeb.csproj
index 15354651..35355824 100644
--- a/VocaluxeWeb/VocaluxeWeb.csproj
+++ b/VocaluxeWeb/VocaluxeWeb.csproj
@@ -1,42 +1,25 @@
- net481
- 9.0
VocaluxeWeb
VocaluxeWeb
- DebugWin;ReleaseWin;InstallerWin;DebugLinux;ReleaseLinux
AnyCPU
true
$(NoWarn);CS1591
$(MSBuildThisFileDirectory)..\WebApp\
-
- true
- pdbonly
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+