diff --git a/.github/workflows/build_android.yml b/.github/workflows/build_android.yml index b0d4633a8..772e3f67c 100644 --- a/.github/workflows/build_android.yml +++ b/.github/workflows/build_android.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Setup .NET Workload run: | dotnet workload install android @@ -33,4 +33,4 @@ jobs: git lfs pull - name: Build run: | - .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame + .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=ARM64 -platform=Android -configuration=Release -buildtargets=FlaxGame diff --git a/.github/workflows/build_ios.yml b/.github/workflows/build_ios.yml index 2aec46320..5a0d285fe 100644 --- a/.github/workflows/build_ios.yml +++ b/.github/workflows/build_ios.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Setup .NET Workload run: | dotnet workload install ios @@ -33,4 +33,4 @@ jobs: git lfs pull - name: Build run: | - ./Development/Scripts/Mac/CallBuildTool.sh -build -log -dotnet=7 -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame + ./Development/Scripts/Mac/CallBuildTool.sh -build -log -dotnet=8 -arch=ARM64 -platform=iOS -configuration=Release -buildtargets=FlaxGame diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 56accba84..b3348c288 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -36,7 +36,7 @@ jobs: git lfs pull - name: Build run: | - ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor + ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxEditor # Game game-linux: @@ -53,7 +53,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -64,4 +64,4 @@ jobs: git lfs pull - name: Build run: | - ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame + ./Development/Scripts/Linux/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Linux -configuration=Release -buildtargets=FlaxGame diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml index 54bdb77b5..139bf2416 100644 --- a/.github/workflows/build_mac.yml +++ b/.github/workflows/build_mac.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -30,7 +30,7 @@ jobs: git lfs pull - name: Build run: | - ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor + ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Mac -configuration=Development -buildtargets=FlaxEditor # Game game-mac: @@ -44,7 +44,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -55,4 +55,4 @@ jobs: git lfs pull - name: Build run: | - ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame + ./Development/Scripts/Mac/CallBuildTool.sh -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Mac -configuration=Release -buildtargets=FlaxGame diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml index b6131fb53..85f4e0c79 100644 --- a/.github/workflows/build_windows.yml +++ b/.github/workflows/build_windows.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -30,7 +30,7 @@ jobs: git lfs pull - name: Build run: | - .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor + .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxEditor # Game game-windows: @@ -44,7 +44,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -55,4 +55,4 @@ jobs: git lfs pull - name: Build run: | - .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=7 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame + .\Development\Scripts\Windows\CallBuildTool.bat -build -log -printSDKs -dotnet=8 -arch=x64 -platform=Windows -configuration=Release -buildtargets=FlaxGame diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index a5d8bc043..70f275eac 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -59,7 +59,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -95,7 +95,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -129,7 +129,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -159,7 +159,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -187,7 +187,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a524bdca2..e36642cdf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -34,21 +34,21 @@ jobs: sudo apt-get install libx11-dev libxcursor-dev libxinerama-dev build-essential gettext libtool libtool-bin libpulse-dev libasound2-dev libjack-dev portaudio19-dev - name: Build run: | - ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=7 - ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget + ./GenerateProjectFiles.sh -vs2022 -log -verbose -printSDKs -dotnet=8 + ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=8 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo dotnet msbuild Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo - name: Test run: | ${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests - dotnet test -f net7.0 Binaries/Tests/Flax.Build.Tests.dll + dotnet test -f net8.0 Binaries/Tests/Flax.Build.Tests.dll cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.dll Binaries/Tests cp Binaries/Editor/Linux/Development/FlaxEngine.CSharp.runtimeconfig.json Binaries/Tests cp Binaries/Editor/Linux/Development/Newtonsoft.Json.dll Binaries/Tests - dotnet test -f net7.0 Binaries/Tests/FlaxEngine.CSharp.dll + dotnet test -f net8.0 Binaries/Tests/FlaxEngine.CSharp.dll - name: Test UseLargeWorlds run: | - ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=7 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true + ./Development/Scripts/Linux/CallBuildTool.sh -build -log -dotnet=8 -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true ${GITHUB_WORKSPACE}/Binaries/Editor/Linux/Development/FlaxTests # Tests on Windows @@ -61,7 +61,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Print .NET info run: | dotnet --info @@ -72,14 +72,14 @@ jobs: git lfs pull - name: Build run: | - .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=7 - .\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=7 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget + .\GenerateProjectFiles.bat -vs2022 -log -verbose -printSDKs -dotnet=8 + .\Development\Scripts\Windows\CallBuildTool.bat -build -log -dotnet=8 -arch=x64 -platform=Windows -configuration=Development -buildtargets=FlaxTestsTarget dotnet msbuild Source\Tools\Flax.Build.Tests\Flax.Build.Tests.csproj /m /t:Restore,Build /p:Configuration=Debug /p:Platform=AnyCPU /nologo - name: Test run: | .\Binaries\Editor\Win64\Development\FlaxTests.exe - dotnet test -f net7.0 Binaries\Tests\Flax.Build.Tests.dll + dotnet test -f net8.0 Binaries\Tests\Flax.Build.Tests.dll xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.dll Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\FlaxEngine.CSharp.runtimeconfig.json Binaries\Tests xcopy /y Binaries\Editor\Win64\Development\Newtonsoft.Json.dll Binaries\Tests - dotnet test -f net7.0 Binaries\Tests\FlaxEngine.CSharp.dll + dotnet test -f net8.0 Binaries\Tests\FlaxEngine.CSharp.dll diff --git a/Content/Shaders/DebugDraw.flax b/Content/Shaders/DebugDraw.flax index 284c55f64..455cf8c20 100644 --- a/Content/Shaders/DebugDraw.flax +++ b/Content/Shaders/DebugDraw.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10fefac1db81c41041c95970dc13694a17dfbca09819d6f7344b5d45f5e8c0df -size 2060 +oid sha256:7f8833a76cdda54b6c1de3b98f7993b835d17a5ac60b0bf11a9ee9faa42cc177 +size 2108 diff --git a/Content/Shaders/TAA.flax b/Content/Shaders/TAA.flax index c8e96e9ed..e719850ac 100644 --- a/Content/Shaders/TAA.flax +++ b/Content/Shaders/TAA.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:707c2630916ad6f0d2b604e6cda290fdc24e027a1ee6e435f62e6efb393e8ada -size 3265 +oid sha256:97f24b13b752313dae0eda0540748554fa98ffbb6c05e0fca95ee5f4b37f3a03 +size 4285 diff --git a/Flax.flaxproj b/Flax.flaxproj index 7153e51d5..f7806caac 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -2,9 +2,9 @@ "Name": "Flax", "Version": { "Major": 1, - "Minor": 7, - "Revision": 2, - "Build": 6408 + "Minor": 8, + "Revision": 0, + "Build": 6510 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.", diff --git a/PackageAll.bat b/PackageAll.bat index c6b319a57..25046e1b8 100644 --- a/PackageAll.bat +++ b/PackageAll.bat @@ -7,7 +7,7 @@ pushd echo Performing the full package... rem Run the build tool. -call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* +call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -deployPlatforms -dotnet=8 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* if errorlevel 1 goto BuildToolFailed popd diff --git a/PackageEditor.bat b/PackageEditor.bat index d9d87c1a8..4428d4ec6 100644 --- a/PackageEditor.bat +++ b/PackageEditor.bat @@ -7,7 +7,7 @@ pushd echo Building and packaging Flax Editor... rem Run the build tool. -call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* +call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployEditor -dotnet=8 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* if errorlevel 1 goto BuildToolFailed popd diff --git a/PackageEditor.command b/PackageEditor.command index a748d79ff..a9db070d3 100755 --- a/PackageEditor.command +++ b/PackageEditor.command @@ -9,4 +9,4 @@ echo Building and packaging Flax Editor... cd "`dirname "$0"`" # Run Flax.Build (also pass the arguments) -bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" +bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployEditor --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" diff --git a/PackageEditor.sh b/PackageEditor.sh index cd985affd..1719f3766 100755 --- a/PackageEditor.sh +++ b/PackageEditor.sh @@ -9,4 +9,4 @@ echo Building and packaging Flax Editor... cd "`dirname "$0"`" # Run Flax.Build (also pass the arguments) -bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" +bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployEditor --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" diff --git a/PackagePlatforms.bat b/PackagePlatforms.bat index efa4fd74f..274185e6c 100644 --- a/PackagePlatforms.bat +++ b/PackagePlatforms.bat @@ -7,7 +7,7 @@ pushd echo Building and packaging platforms data... rem Run the build tool. -call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -dotnet=7 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* +call "Development\Scripts\Windows\CallBuildTool.bat" -deploy -deployPlatforms -dotnet=8 -verbose -log -logFile="Cache\Intermediate\PackageLog.txt" %* if errorlevel 1 goto BuildToolFailed popd diff --git a/PackagePlatforms.command b/PackagePlatforms.command index abdc30a7f..ae1cc0446 100755 --- a/PackagePlatforms.command +++ b/PackagePlatforms.command @@ -9,4 +9,4 @@ echo Building and packaging platforms data... cd "`dirname "$0"`" # Run Flax.Build (also pass the arguments) -bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" +bash ./Development/Scripts/Mac/CallBuildTool.sh --deploy --deployPlatforms --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" diff --git a/PackagePlatforms.sh b/PackagePlatforms.sh index adf6c8f43..5dfaec3e4 100755 --- a/PackagePlatforms.sh +++ b/PackagePlatforms.sh @@ -9,4 +9,4 @@ echo Building and packaging platforms data... cd "`dirname "$0"`" # Run Flax.Build (also pass the arguments) -bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --dotnet=7 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" +bash ./Development/Scripts/Linux/CallBuildTool.sh --deploy --deployPlatforms --dotnet=8 --verbose --log --logFile="Cache/Intermediate/PackageLog.txt" "$@" diff --git a/README.md b/README.md index d6688bd03..1b0d06bf6 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Follow the instructions below to compile and run the engine from source. * Install Visual Studio 2022 or newer * Install Windows 8.1 SDK or newer (via Visual Studio Installer) * Install Microsoft Visual C++ 2015 v140 toolset or newer (via Visual Studio Installer) -* Install .NET 7 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 8 SDK for **Windows x64** (via Visual Studio Installer or [from web](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)) * Install Git with LFS * Clone repo (with LFS) * Run **GenerateProjectFiles.bat** @@ -44,8 +44,8 @@ Follow the instructions below to compile and run the engine from source. ## Linux * Install Visual Studio Code -* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) - * Ubuntu: `sudo apt install dotnet-sdk-7.0` +* Install .NET 8 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)) + * Ubuntu: `sudo apt install dotnet-sdk-8.0` * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Ubuntu: `sudo apt install vulkan-sdk` * Arch: `sudo pacman -S spirv-tools vulkan-headers vulkan-tools vulkan-validation-layers` @@ -67,7 +67,7 @@ Follow the instructions below to compile and run the engine from source. ## Mac * Install XCode -* Install .NET 7 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)) +* Install .NET 8 SDK ([https://dotnet.microsoft.com/en-us/download/dotnet/8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)) * Install Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)) * Clone repo (with LFS) * Run `GenerateProjectFiles.command` @@ -80,9 +80,9 @@ Follow the instructions below to compile and run the engine from source. Restart PC - ensure DotNet is added to PATH for command line tools execution. -* `Microsoft.NET.TargetFrameworkInference.targets(141,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 7.0. Either target .NET 5.0 or lower, or use a version of the .NET SDK that supports .NET 7.0` +* `Microsoft.NET.TargetFrameworkInference.targets(141,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 8.0. Either target .NET 5.0 or lower, or use a version of the .NET SDK that supports .NET 8.0` -Use Visual Studio 2022, older versions are not supported by .NET SDK 7. +Use Visual Studio 2022, older versions are not supported by .NET SDK 8. * `Building for Windows without Vulkan rendering backend (Vulkan SDK is missing)` diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 105df76d4..fee36bbb0 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -646,7 +646,9 @@ namespace FlaxEditor.Content.GUI _rubberBandRectangle = new Rectangle(_mousePressLocation, 0, 0); _isRubberBandSpanning = true; StartMouseCapture(); + return true; } + return AutoFocus && Focus(this); } diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 92583f1aa..1d2e6a0bc 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -213,7 +213,8 @@ namespace FlaxEditor.Content if (_attributes == null) { var data = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.AttributeMetaTypeID); - _attributes = Surface.SurfaceMeta.GetAttributes(data); + var dataOld = _type.Asset.GetMethodMetaData(_index, Surface.SurfaceMeta.OldAttributeMetaTypeID); + _attributes = Surface.SurfaceMeta.GetAttributes(data, dataOld); } return _attributes; } @@ -290,13 +291,11 @@ namespace FlaxEditor.Content _methods = Utils.GetEmptyArray(); // Cache Visual Script attributes - var attributesData = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID); - if (attributesData != null && attributesData.Length != 0) { - _attributes = Surface.SurfaceMeta.GetAttributes(attributesData); + var data = _asset.GetMetaData(Surface.SurfaceMeta.AttributeMetaTypeID); + var dataOld = _asset.GetMetaData(Surface.SurfaceMeta.OldAttributeMetaTypeID); + _attributes = Surface.SurfaceMeta.GetAttributes(data, dataOld); } - else - _attributes = Utils.GetEmptyArray(); } private void OnAssetReloading(Asset asset) diff --git a/Source/Editor/Content/Proxy/PrefabProxy.cs b/Source/Editor/Content/Proxy/PrefabProxy.cs index ee3674daa..18860995e 100644 --- a/Source/Editor/Content/Proxy/PrefabProxy.cs +++ b/Source/Editor/Content/Proxy/PrefabProxy.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; +using System.IO; using FlaxEditor.Content.Thumbnails; using FlaxEditor.Viewport.Previews; using FlaxEditor.Windows; @@ -194,4 +195,64 @@ namespace FlaxEditor.Content base.Dispose(); } } + + /// + /// Content proxy for quick UI Control prefab creation as widget. + /// + [ContentContextMenu("New/Widget")] + internal sealed class WidgetProxy : AssetProxy + { + /// + public override string Name => "UI Widget"; + + /// + public override bool IsProxyFor(ContentItem item) + { + return false; + } + + /// + public override string FileExtension => PrefabProxy.Extension; + + /// + public override EditorWindow Open(Editor editor, ContentItem item) + { + return null; + } + + /// + public override Color AccentColor => Color.Transparent; + + /// + public override string TypeName => PrefabProxy.AssetTypename; + + /// + public override AssetItem ConstructItem(string path, string typeName, ref Guid id) + { + return null; + } + + /// + public override bool CanCreate(ContentFolder targetLocation) + { + return targetLocation.CanHaveAssets; + } + + /// + public override void Create(string outputPath, object arg) + { + // Create prefab with UI Control + var actor = new UIControl + { + Name = Path.GetFileNameWithoutExtension(outputPath), + StaticFlags = StaticFlags.None, + }; + actor.Control = new Button + { + Text = "Button", + }; + PrefabManager.CreatePrefab(actor, outputPath, false); + Object.Destroy(actor, 20.0f); + } + } } diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index 84ad74a7f..779b472a0 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -14,7 +14,7 @@ class PlatformTools; #if OFFICIAL_BUILD // Use the fixed .NET SDK version in packaged builds for compatibility (FlaxGame is precompiled with it) -#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=7") +#define GAME_BUILD_DOTNET_VER TEXT("-dotnet=8") #else #define GAME_BUILD_DOTNET_VER TEXT("") #endif diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp index 88f993f20..d55ad01e3 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.cpp @@ -22,6 +22,11 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform); namespace { + struct AndroidPlatformCache + { + AndroidPlatformSettings::TextureQuality TexturesQuality; + }; + void DeployIcon(const CookingData& data, const TextureData& iconData, const Char* subDir, int32 iconSize, int32 adaptiveIconSize) { const String mipmapPath = data.OriginalOutputPath / TEXT("app/src/main/res") / subDir; @@ -30,6 +35,24 @@ namespace FileSystem::CreateDirectory(mipmapPath); EditorUtilities::ExportApplicationImage(iconData, iconSize, iconSize, PixelFormat::B8G8R8A8_UNorm, iconPath); } + + PixelFormat GetQualityTextureFormat(bool sRGB, PixelFormat format) + { + const auto platformSettings = AndroidPlatformSettings::Get(); + switch (platformSettings->TexturesQuality) + { + case AndroidPlatformSettings::TextureQuality::Uncompressed: + return PixelFormatExtensions::FindUncompressedFormat(format); + case AndroidPlatformSettings::TextureQuality::ASTC_High: + return sRGB ? PixelFormat::ASTC_4x4_UNorm_sRGB : PixelFormat::ASTC_4x4_UNorm; + case AndroidPlatformSettings::TextureQuality::ASTC_Medium: + return sRGB ? PixelFormat::ASTC_6x6_UNorm_sRGB : PixelFormat::ASTC_6x6_UNorm; + case AndroidPlatformSettings::TextureQuality::ASTC_Low: + return sRGB ? PixelFormat::ASTC_8x8_UNorm_sRGB : PixelFormat::ASTC_8x8_UNorm; + default: + return format; + } + } } const Char* AndroidPlatformTools::GetDisplayName() const @@ -54,62 +77,67 @@ ArchitectureType AndroidPlatformTools::GetArchitecture() const PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) { - // TODO: add ETC compression support for Android - // TODO: add ASTC compression support for Android - - // BC formats are not widely supported on Android - if (PixelFormatExtensions::IsCompressedBC(format)) - { - switch (format) - { - case PixelFormat::BC1_Typeless: - case PixelFormat::BC2_Typeless: - case PixelFormat::BC3_Typeless: - return PixelFormat::R8G8B8A8_Typeless; - case PixelFormat::BC1_UNorm: - case PixelFormat::BC2_UNorm: - case PixelFormat::BC3_UNorm: - return PixelFormat::R8G8B8A8_UNorm; - case PixelFormat::BC1_UNorm_sRGB: - case PixelFormat::BC2_UNorm_sRGB: - case PixelFormat::BC3_UNorm_sRGB: - return PixelFormat::R8G8B8A8_UNorm_sRGB; - case PixelFormat::BC4_Typeless: - return PixelFormat::R8_Typeless; - case PixelFormat::BC4_UNorm: - return PixelFormat::R8_UNorm; - case PixelFormat::BC4_SNorm: - return PixelFormat::R8_SNorm; - case PixelFormat::BC5_Typeless: - return PixelFormat::R16G16_Typeless; - case PixelFormat::BC5_UNorm: - return PixelFormat::R16G16_UNorm; - case PixelFormat::BC5_SNorm: - return PixelFormat::R16G16_SNorm; - case PixelFormat::BC7_Typeless: - case PixelFormat::BC6H_Typeless: - return PixelFormat::R16G16B16A16_Typeless; - case PixelFormat::BC7_UNorm: - case PixelFormat::BC6H_Uf16: - case PixelFormat::BC6H_Sf16: - return PixelFormat::R16G16B16A16_Float; - case PixelFormat::BC7_UNorm_sRGB: - return PixelFormat::R16G16B16A16_UNorm; - default: - return format; - } - } - switch (format) { - // Not all Android devices support R11G11B10 textures (eg. M6 Note) case PixelFormat::R11G11B10_Float: + // Not all Android devices support R11G11B10 textures (eg. M6 Note) return PixelFormat::R16G16B16A16_UNorm; + case PixelFormat::BC1_Typeless: + case PixelFormat::BC2_Typeless: + case PixelFormat::BC3_Typeless: + case PixelFormat::BC4_Typeless: + case PixelFormat::BC5_Typeless: + case PixelFormat::BC1_UNorm: + case PixelFormat::BC2_UNorm: + case PixelFormat::BC3_UNorm: + case PixelFormat::BC4_UNorm: + case PixelFormat::BC5_UNorm: + return GetQualityTextureFormat(false, format); + case PixelFormat::BC1_UNorm_sRGB: + case PixelFormat::BC2_UNorm_sRGB: + case PixelFormat::BC3_UNorm_sRGB: + case PixelFormat::BC7_UNorm_sRGB: + return GetQualityTextureFormat(true, format); + case PixelFormat::BC4_SNorm: + return PixelFormat::R8_SNorm; + case PixelFormat::BC5_SNorm: + return PixelFormat::R16G16_SNorm; + case PixelFormat::BC6H_Typeless: + case PixelFormat::BC6H_Uf16: + case PixelFormat::BC6H_Sf16: + case PixelFormat::BC7_Typeless: + case PixelFormat::BC7_UNorm: + return PixelFormat::R16G16B16A16_Float; // TODO: ASTC HDR default: return format; } } +void AndroidPlatformTools::LoadCache(CookingData& data, IBuildCache* cache, const Span& bytes) +{ + const auto platformSettings = AndroidPlatformSettings::Get(); + bool invalidTextures = true; + if (bytes.Length() == sizeof(AndroidPlatformCache)) + { + auto* platformCache = (AndroidPlatformCache*)bytes.Get(); + invalidTextures = platformCache->TexturesQuality != platformSettings->TexturesQuality; + } + if (invalidTextures) + { + LOG(Info, "{0} option has been modified.", TEXT("TexturesQuality")); + cache->InvalidateCacheTextures(); + } +} + +Array AndroidPlatformTools::SaveCache(CookingData& data, IBuildCache* cache) +{ + const auto platformSettings = AndroidPlatformSettings::Get(); + AndroidPlatformCache platformCache; + platformCache.TexturesQuality = platformSettings->TexturesQuality; + Array result; + result.Add((const byte*)&platformCache, sizeof(platformCache)); + return result; +} void AndroidPlatformTools::OnBuildStarted(CookingData& data) { // Adjust the cooking output folder to be located inside the Gradle assets directory @@ -328,7 +356,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data) // Copy result package const String apk = data.OriginalOutputPath / (distributionPackage ? TEXT("app/build/outputs/apk/release/app-release-unsigned.apk") : TEXT("app/build/outputs/apk/debug/app-debug.apk")); - const String outputApk = data.OriginalOutputPath / gameSettings->ProductName + TEXT(".apk"); + const String outputApk = data.OriginalOutputPath / EditorUtilities::GetOutputName() + TEXT(".apk"); if (FileSystem::CopyFile(outputApk, apk)) { LOG(Error, "Failed to copy package from {0} to {1}", apk, outputApk); diff --git a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h index 09a4d6b2f..501ba3f13 100644 --- a/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h +++ b/Source/Editor/Cooker/Platform/Android/AndroidPlatformTools.h @@ -30,6 +30,8 @@ public: PlatformType GetPlatform() const override; ArchitectureType GetArchitecture() const override; PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override; + void LoadCache(CookingData& data, IBuildCache* cache, const Span& bytes) override; + Array SaveCache(CookingData& data, IBuildCache* cache) override; void OnBuildStarted(CookingData& data) override; bool OnPostProcess(CookingData& data) override; }; diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 99d59f6b3..9d3280f4b 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -494,11 +494,11 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) { const auto platformSettings = WindowsPlatformSettings::Get(); - // Apply executable icon Array files; FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly); if (files.HasItems()) { + // Apply executable icon TextureData iconData; if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData)) { @@ -508,11 +508,31 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) return true; } } + + // Rename app + const String newName = EditorUtilities::GetOutputName(); + if (newName != StringUtils::GetFileNameWithoutExtension(files[0])) + { + if (FileSystem::MoveFile(data.NativeCodeOutputPath / newName + TEXT(".exe"), files[0], true)) + { + data.Error(TEXT("Failed to change output executable name.")); + return true; + } + } } return false; } +void WindowsPlatformTools::OnBuildStarted(CookingData& data) +{ + // Remove old executable + Array files; + FileSystem::DirectoryGetFiles(files, data.NativeCodeOutputPath, TEXT("*.exe"), DirectorySearchOption::TopDirectoryOnly); + for (auto& file : files) + FileSystem::DeleteFile(file); +} + void WindowsPlatformTools::OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) { // Pick the first executable file diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h index ad30c93eb..4cb206953 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.h @@ -31,6 +31,7 @@ public: ArchitectureType GetArchitecture() const override; bool UseSystemDotnet() const override; bool OnDeployBinaries(CookingData& data) override; + void OnBuildStarted(CookingData& data) override; void OnRun(CookingData& data, String& executableFile, String& commandLineFormat, String& workingDir) override; }; diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp index 2dfd779b0..a54676a64 100644 --- a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.cpp @@ -24,6 +24,11 @@ IMPLEMENT_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform); namespace { + struct iOSPlatformCache + { + iOSPlatformSettings::TextureQuality TexturesQuality; + }; + String GetAppName() { const auto gameSettings = GameSettings::Get(); @@ -60,6 +65,24 @@ namespace result = result.TrimTrailing(); return result; } + + PixelFormat GetQualityTextureFormat(bool sRGB, PixelFormat format) + { + const auto platformSettings = iOSPlatformSettings::Get(); + switch (platformSettings->TexturesQuality) + { + case iOSPlatformSettings::TextureQuality::Uncompressed: + return PixelFormatExtensions::FindUncompressedFormat(format); + case iOSPlatformSettings::TextureQuality::ASTC_High: + return sRGB ? PixelFormat::ASTC_4x4_UNorm_sRGB : PixelFormat::ASTC_4x4_UNorm; + case iOSPlatformSettings::TextureQuality::ASTC_Medium: + return sRGB ? PixelFormat::ASTC_6x6_UNorm_sRGB : PixelFormat::ASTC_6x6_UNorm; + case iOSPlatformSettings::TextureQuality::ASTC_Low: + return sRGB ? PixelFormat::ASTC_8x8_UNorm_sRGB : PixelFormat::ASTC_8x8_UNorm; + default: + return format; + } + } } const Char* iOSPlatformTools::GetDisplayName() const @@ -89,51 +112,37 @@ DotNetAOTModes iOSPlatformTools::UseAOT() const PixelFormat iOSPlatformTools::GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) { - // TODO: add ETC compression support for iOS - // TODO: add ASTC compression support for iOS - - if (PixelFormatExtensions::IsCompressedBC(format)) + switch (format) { - switch (format) - { - case PixelFormat::BC1_Typeless: - case PixelFormat::BC2_Typeless: - case PixelFormat::BC3_Typeless: - return PixelFormat::R8G8B8A8_Typeless; - case PixelFormat::BC1_UNorm: - case PixelFormat::BC2_UNorm: - case PixelFormat::BC3_UNorm: - return PixelFormat::R8G8B8A8_UNorm; - case PixelFormat::BC1_UNorm_sRGB: - case PixelFormat::BC2_UNorm_sRGB: - case PixelFormat::BC3_UNorm_sRGB: - return PixelFormat::R8G8B8A8_UNorm_sRGB; - case PixelFormat::BC4_Typeless: - return PixelFormat::R8_Typeless; - case PixelFormat::BC4_UNorm: - return PixelFormat::R8_UNorm; - case PixelFormat::BC4_SNorm: - return PixelFormat::R8_SNorm; - case PixelFormat::BC5_Typeless: - return PixelFormat::R16G16_Typeless; - case PixelFormat::BC5_UNorm: - return PixelFormat::R16G16_UNorm; - case PixelFormat::BC5_SNorm: - return PixelFormat::R16G16_SNorm; - case PixelFormat::BC7_Typeless: - case PixelFormat::BC6H_Typeless: - return PixelFormat::R16G16B16A16_Typeless; - case PixelFormat::BC7_UNorm: - case PixelFormat::BC6H_Uf16: - case PixelFormat::BC6H_Sf16: - return PixelFormat::R16G16B16A16_Float; - case PixelFormat::BC7_UNorm_sRGB: - return PixelFormat::R16G16B16A16_UNorm; - default: - return format; - } + case PixelFormat::BC1_Typeless: + case PixelFormat::BC2_Typeless: + case PixelFormat::BC3_Typeless: + case PixelFormat::BC4_Typeless: + case PixelFormat::BC5_Typeless: + case PixelFormat::BC1_UNorm: + case PixelFormat::BC2_UNorm: + case PixelFormat::BC3_UNorm: + case PixelFormat::BC4_UNorm: + case PixelFormat::BC5_UNorm: + return GetQualityTextureFormat(false, format); + case PixelFormat::BC1_UNorm_sRGB: + case PixelFormat::BC2_UNorm_sRGB: + case PixelFormat::BC3_UNorm_sRGB: + case PixelFormat::BC7_UNorm_sRGB: + return GetQualityTextureFormat(true, format); + case PixelFormat::BC4_SNorm: + return PixelFormat::R8_SNorm; + case PixelFormat::BC5_SNorm: + return PixelFormat::R16G16_SNorm; + case PixelFormat::BC6H_Typeless: + case PixelFormat::BC6H_Uf16: + case PixelFormat::BC6H_Sf16: + case PixelFormat::BC7_Typeless: + case PixelFormat::BC7_UNorm: + return PixelFormat::R16G16B16A16_Float; // TODO: ASTC HDR + default: + return format; } - return format; } @@ -143,6 +152,32 @@ bool iOSPlatformTools::IsNativeCodeFile(CookingData& data, const String& file) return extension.IsEmpty() || extension == TEXT("dylib"); } +void iOSPlatformTools::LoadCache(CookingData& data, IBuildCache* cache, const Span& bytes) +{ + const auto platformSettings = iOSPlatformSettings::Get(); + bool invalidTextures = true; + if (bytes.Length() == sizeof(iOSPlatformCache)) + { + auto* platformCache = (iOSPlatformCache*)bytes.Get(); + invalidTextures = platformCache->TexturesQuality != platformSettings->TexturesQuality; + } + if (invalidTextures) + { + LOG(Info, "{0} option has been modified.", TEXT("TexturesQuality")); + cache->InvalidateCacheTextures(); + } +} + +Array iOSPlatformTools::SaveCache(CookingData& data, IBuildCache* cache) +{ + const auto platformSettings = iOSPlatformSettings::Get(); + iOSPlatformCache platformCache; + platformCache.TexturesQuality = platformSettings->TexturesQuality; + Array result; + result.Add((const byte*)&platformCache, sizeof(platformCache)); + return result; +} + void iOSPlatformTools::OnBuildStarted(CookingData& data) { // Adjust the cooking output folders for packaging app @@ -167,13 +202,25 @@ bool iOSPlatformTools::OnPostProcess(CookingData& data) if (EditorUtilities::FormatAppPackageName(appIdentifier)) return true; - // Copy fresh Gradle project template + // Copy fresh XCode project template if (FileSystem::CopyDirectory(data.OriginalOutputPath, platformDataPath / TEXT("Project"), true)) { LOG(Error, "Failed to deploy XCode project to {0} from {1}", data.OriginalOutputPath, platformDataPath); return true; } + // Fix MoltenVK lib (copied from VulkanSDK xcframework) + FileSystem::MoveFile(data.DataOutputPath / TEXT("libMoltenVK.dylib"), data.DataOutputPath / TEXT("MoltenVK"), true); + { + // Fix rpath to point into dynamic library (rather than framework location) + CreateProcessSettings procSettings; + procSettings.HiddenWindow = true; + procSettings.WorkingDirectory = data.DataOutputPath; + procSettings.FileName = TEXT("/usr/bin/install_name_tool"); + procSettings.Arguments = TEXT("-id \"@rpath/libMoltenVK.dylib\" libMoltenVK.dylib"); + Platform::CreateProcess(procSettings); + } + // Format project template files Dictionary configReplaceMap; configReplaceMap[TEXT("${AppName}")] = appName; diff --git a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h index a3abf4d35..153daebbe 100644 --- a/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h +++ b/Source/Editor/Cooker/Platform/iOS/iOSPlatformTools.h @@ -19,6 +19,8 @@ public: ArchitectureType GetArchitecture() const override; DotNetAOTModes UseAOT() const override; PixelFormat GetTextureFormat(CookingData& data, TextureBase* texture, PixelFormat format) override; + void LoadCache(CookingData& data, IBuildCache* cache, const Span& bytes) override; + Array SaveCache(CookingData& data, IBuildCache* cache) override; bool IsNativeCodeFile(CookingData& data, const String& file) override; void OnBuildStarted(CookingData& data) override; bool OnPostProcess(CookingData& data) override; diff --git a/Source/Editor/Cooker/PlatformTools.h b/Source/Editor/Cooker/PlatformTools.h index cff6fd273..078307d74 100644 --- a/Source/Editor/Cooker/PlatformTools.h +++ b/Source/Editor/Cooker/PlatformTools.h @@ -8,6 +8,37 @@ class TextureBase; +/// +/// The game cooker cache interface. +/// +class FLAXENGINE_API IBuildCache +{ +public: + /// + /// Removes all cached entries for assets that contain a given asset type. This forces rebuild for them. + /// + virtual void InvalidateCachePerType(const StringView& typeName) = 0; + + /// + /// Removes all cached entries for assets that contain a given asset type. This forces rebuild for them. + /// + template + FORCE_INLINE void InvalidateCachePerType() + { + InvalidateCachePerType(T::TypeName); + } + + /// + /// Removes all cached entries for assets that contain a shader. This forces rebuild for them. + /// + void InvalidateCacheShaders(); + + /// + /// Removes all cached entries for assets that contain a texture. This forces rebuild for them. + /// + void InvalidateCacheTextures(); +}; + /// /// The platform support tools base interface. /// @@ -76,6 +107,27 @@ public: virtual bool IsNativeCodeFile(CookingData& data, const String& file); public: + /// + /// Loads the build cache. Allows to invalidate any cached asset types based on the build settings for incremental builds (eg. invalidate textures/shaders). + /// + /// The cooking data. + /// The build cache interface. + /// The loaded cache data. Can be empty when starting a fresh build. + virtual void LoadCache(CookingData& data, IBuildCache* cache, const Span& bytes) + { + } + + /// + /// Saves the build cache. Allows to store any build settings to be used for cache invalidation on incremental builds. + /// + /// The cooking data. + /// The build cache interface. + /// Data to cache, will be restored during next incremental build. + virtual Array SaveCache(CookingData& data, IBuildCache* cache) + { + return Array(); + } + /// /// Called when game building starts. /// diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index d6421f04e..2558181aa 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -31,10 +31,12 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Materials/MaterialShader.h" +#include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" #include "Engine/Engine/Base/GameBase.h" #include "Engine/Engine/Globals.h" #include "Engine/Tools/TextureTool/TextureTool.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" #if PLATFORM_TOOLS_WINDOWS #include "Engine/Platform/Windows/WindowsPlatformSettings.h" @@ -49,6 +51,20 @@ Dictionary CookAssetsStep::AssetProcessors; +void IBuildCache::InvalidateCacheShaders() +{ + InvalidateCachePerType(); + InvalidateCachePerType(); + InvalidateCachePerType(); +} + +void IBuildCache::InvalidateCacheTextures() +{ + InvalidateCachePerType(); + InvalidateCachePerType(); + InvalidateCachePerType(); +} + bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies) { AssetInfo assetInfo; @@ -113,15 +129,13 @@ void CookAssetsStep::CacheData::InvalidateCachePerType(const StringView& typeNam void CookAssetsStep::CacheData::Load(CookingData& data) { + PROFILE_CPU(); HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD); CacheFolder = data.CacheDirectory / TEXT("Cooked"); Entries.Clear(); if (!FileSystem::DirectoryExists(CacheFolder)) - { FileSystem::CreateDirectory(CacheFolder); - } - if (!FileSystem::FileExists(HeaderFilePath)) { LOG(Warning, "Missing incremental build cooking assets cache."); @@ -143,9 +157,7 @@ void CookAssetsStep::CacheData::Load(CookingData& data) return; LOG(Info, "Loading incremental build cooking cache (entries count: {0})", entriesCount); - file->ReadBytes(&Settings, sizeof(Settings)); - Entries.EnsureCapacity(Math::RoundUpToPowerOf2(static_cast(entriesCount * 3.0f))); Array> fileDependencies; @@ -179,6 +191,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data) e.FileDependencies = fileDependencies; } + Array platformCache; + file->Read(platformCache); + int32 checkChar; file->ReadInt32(&checkChar); if (checkChar != 13) @@ -187,6 +202,9 @@ void CookAssetsStep::CacheData::Load(CookingData& data) Entries.Clear(); } + // Per-platform custom data loading (eg. to invalidate textures/shaders options) + data.Tools->LoadCache(data, this, ToSpan(platformCache)); + const auto buildSettings = BuildSettings::Get(); const auto gameSettings = GameSettings::Get(); @@ -200,12 +218,12 @@ void CookAssetsStep::CacheData::Load(CookingData& data) if (MATERIAL_GRAPH_VERSION != Settings.Global.MaterialGraphVersion) { LOG(Info, "{0} option has been modified.", TEXT("MaterialGraphVersion")); - InvalidateCachePerType(Material::TypeName); + InvalidateCachePerType(); } if (PARTICLE_GPU_GRAPH_VERSION != Settings.Global.ParticleGraphVersion) { LOG(Info, "{0} option has been modified.", TEXT("ParticleGraphVersion")); - InvalidateCachePerType(ParticleEmitter::TypeName); + InvalidateCachePerType(); } if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize) { @@ -262,24 +280,24 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #endif if (invalidateShaders) { - InvalidateCachePerType(Shader::TypeName); - InvalidateCachePerType(Material::TypeName); - InvalidateCachePerType(ParticleEmitter::TypeName); + InvalidateCachePerType(); + InvalidateCachePerType(); + InvalidateCachePerType(); } // Invalidate textures if streaming settings gets modified if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid())) { - InvalidateCachePerType(Texture::TypeName); - InvalidateCachePerType(CubeTexture::TypeName); - InvalidateCachePerType(SpriteAtlas::TypeName); + InvalidateCachePerType(); + InvalidateCachePerType(); + InvalidateCachePerType(); } } -void CookAssetsStep::CacheData::Save() +void CookAssetsStep::CacheData::Save(CookingData& data) { + PROFILE_CPU(); LOG(Info, "Saving incremental build cooking cache (entries count: {0})", Entries.Count()); - auto file = FileWriteStream::Open(HeaderFilePath); if (file == nullptr) return; @@ -302,6 +320,7 @@ void CookAssetsStep::CacheData::Save() file->Write(f.Second); } } + file->Write(data.Tools->SaveCache(data, this)); file->WriteInt32(13); } @@ -624,7 +643,8 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data) const auto asset = static_cast(data.Asset); const auto& assetHeader = asset->StreamingTexture()->GetHeader(); const auto format = asset->Format(); - const auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format); + auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format); + CHECK_RETURN(!PixelFormatExtensions::IsTypeless(targetFormat), true); const auto streamingSettings = StreamingSettings::Get(); int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS; if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count()) @@ -634,6 +654,11 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data) group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax); } + // If texture is smaller than the block size of the target format (eg. 4x4 texture using ASTC_6x6) then fallback to uncompressed + int32 blockSize = PixelFormatExtensions::ComputeBlockSize(targetFormat); + if (assetHeader->Width < blockSize || assetHeader->Height < blockSize || (blockSize != 1 && mipLevelsMax < 4)) + targetFormat = PixelFormatExtensions::FindUncompressedFormat(format); + // Faster path if don't need to modify texture for the target platform if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax) { @@ -961,6 +986,7 @@ public: const int32 count = addedEntries.Count(); if (count == 0) return false; + PROFILE_CPU(); // Get assets init data and load all chunks Array assetsData; @@ -1143,7 +1169,7 @@ bool CookAssetsStep::Perform(CookingData& data) // Cook asset if (Process(data, cache, assetRef.Get())) { - cache.Save(); + cache.Save(data); return true; } data.Stats.CookedAssets++; @@ -1151,12 +1177,12 @@ bool CookAssetsStep::Perform(CookingData& data) // Auto save build cache after every few cooked assets (reduces next build time if cooking fails later) if (data.Stats.CookedAssets % 50 == 0) { - cache.Save(); + cache.Save(data); } } // Save build cache header - cache.Save(); + cache.Save(data); // Create build game header { @@ -1173,7 +1199,6 @@ bool CookAssetsStep::Perform(CookingData& data) } stream->WriteInt32(('x' + 'D') * 131); // think about it as '131 times xD' - stream->WriteInt32(FLAXENGINE_VERSION_BUILD); Array bytes; diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.h b/Source/Editor/Cooker/Steps/CookAssetsStep.h index ce144b905..57ae0abd7 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.h +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.h @@ -3,6 +3,7 @@ #pragma once #include "Editor/Cooker/GameCooker.h" +#include "Editor/Cooker/PlatformTools.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Collections/Dictionary.h" @@ -56,7 +57,7 @@ public: /// /// Assets cooking cache data (incremental building feature). /// - struct FLAXENGINE_API CacheData + struct FLAXENGINE_API CacheData : public IBuildCache { /// /// The cache header file path. @@ -136,16 +137,6 @@ public: /// The added entry reference. CacheEntry& CreateEntry(const Asset* asset, String& cachedFilePath); - /// - /// Removes all cached entries for assets that contain a shader. This forces rebuild for them. - /// - void InvalidateShaders(); - - /// - /// Removes all cached entries for assets that contain a texture. This forces rebuild for them. - /// - void InvalidateCachePerType(const StringView& typeName); - /// /// Loads the cache for the given cooking data. /// @@ -155,7 +146,11 @@ public: /// /// Saves this cache (header file). /// - void Save(); + /// The data. + void Save(CookingData& data); + + using IBuildCache::InvalidateCachePerType; + void InvalidateCachePerType(const StringView& typeName) override; }; struct FLAXENGINE_API AssetCookData diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index c59938135..e9e41a8ea 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -116,7 +116,7 @@ bool DeployDataStep::Perform(CookingData& data) for (String& version : versions) { version = String(StringUtils::GetFileName(version)); - if (!version.StartsWith(TEXT("7.")) && !version.StartsWith(TEXT("8."))) // .NET 7 or .NET 8 + if (!version.StartsWith(TEXT("8."))) // Check for major part of 8.0 version.Clear(); } Sorting::QuickSort(versions); @@ -204,14 +204,14 @@ bool DeployDataStep::Perform(CookingData& data) { // AOT runtime files inside Engine Platform folder packFolder /= TEXT("Dotnet"); - dstDotnetLibs /= TEXT("lib/net7.0"); - srcDotnetLibs = packFolder / TEXT("lib/net7.0"); + dstDotnetLibs /= TEXT("lib/net8.0"); + srcDotnetLibs = packFolder / TEXT("lib/net8.0"); } else { // Runtime files inside Dotnet SDK folder but placed for AOT - dstDotnetLibs /= TEXT("lib/net7.0"); - srcDotnetLibs /= TEXT("../lib/net7.0"); + dstDotnetLibs /= TEXT("lib/net8.0"); + srcDotnetLibs /= TEXT("../lib/net8.0"); } } else @@ -219,16 +219,18 @@ bool DeployDataStep::Perform(CookingData& data) if (srcDotnetFromEngine) { // Runtime files inside Engine Platform folder - dstDotnetLibs /= TEXT("lib/net7.0"); - srcDotnetLibs /= TEXT("lib/net7.0"); + dstDotnetLibs /= TEXT("lib/net8.0"); + srcDotnetLibs /= TEXT("lib/net8.0"); } else { // Runtime files inside Dotnet SDK folder dstDotnetLibs /= TEXT("shared/Microsoft.NETCore.App"); - srcDotnetLibs /= TEXT("../lib/net7.0"); + srcDotnetLibs /= TEXT("../lib/net8.0"); } } + LOG(Info, "Copying .NET files from {} to {}", packFolder, dstDotnet); + LOG(Info, "Copying .NET files from {} to {}", srcDotnetLibs, dstDotnetLibs); FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.txt")); FileSystem::CopyFile(dstDotnet / TEXT("LICENSE.TXT"), packFolder / TEXT("LICENSE.TXT")); FileSystem::CopyFile(dstDotnet / TEXT("THIRD-PARTY-NOTICES.TXT"), packFolder / TEXT("ThirdPartyNotices.txt")); diff --git a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp index cead25b46..e46109336 100644 --- a/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp +++ b/Source/Editor/Cooker/Steps/PrecompileAssembliesStep.cpp @@ -35,7 +35,8 @@ void PrecompileAssembliesStep::OnBuildStarted(CookingData& data) if (cachedData != aotModeCacheValue) { LOG(Info, "AOT cache invalidation"); - FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); + FileSystem::DeleteDirectory(data.ManagedCodeOutputPath); // Remove AOT cache + FileSystem::DeleteDirectory(data.DataOutputPath / TEXT("Dotnet")); // Remove deployed Dotnet libs (be sure to remove any leftovers from previous build) } } if (!FileSystem::DirectoryExists(data.ManagedCodeOutputPath)) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index ef6302e8e..48ada9150 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -385,6 +385,15 @@ namespace FlaxEditor.CustomEditors LinkedLabel = label; } + internal bool CanEditValue + { + get + { + var readOnly = Values.Info.GetAttribute(); + return readOnly == null; + } + } + /// /// If true, the value reverting to default/reference will be handled via iteration over children editors, instead of for a whole object at once. /// @@ -413,7 +422,7 @@ namespace FlaxEditor.CustomEditors { if (!Values.IsDefaultValueModified) return false; - return true; + return CanEditValue; } } @@ -422,7 +431,7 @@ namespace FlaxEditor.CustomEditors /// public void RevertToDefaultValue() { - if (!Values.HasDefaultValue) + if (!Values.HasDefaultValue || !CanEditValue) return; RevertDiffToDefault(); } @@ -471,7 +480,7 @@ namespace FlaxEditor.CustomEditors } else { - if (Values.IsReferenceValueModified) + if (CanRevertReferenceValue) SetValueToReference(); } } @@ -485,7 +494,7 @@ namespace FlaxEditor.CustomEditors { if (!Values.IsReferenceValueModified) return false; - return true; + return CanEditValue; } } @@ -494,7 +503,7 @@ namespace FlaxEditor.CustomEditors /// public void RevertToReferenceValue() { - if (!Values.HasReferenceValue) + if (!Values.HasReferenceValue || !CanEditValue) return; RevertDiffToReference(); } diff --git a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp index d4526afee..692428a03 100644 --- a/Source/Editor/CustomEditors/CustomEditorsUtil.cpp +++ b/Source/Editor/CustomEditors/CustomEditorsUtil.cpp @@ -4,6 +4,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Engine/EngineService.h" #include "Engine/Scripting/Scripting.h" @@ -81,7 +82,7 @@ bool CustomEditorsUtilService::Init() void OnAssemblyLoaded(MAssembly* assembly) { - const auto startTime = DateTime::NowUTC(); + Stopwatch stopwatch; // Prepare FlaxEngine auto engineAssembly = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; @@ -162,8 +163,8 @@ void OnAssemblyLoaded(MAssembly* assembly) } } - const auto endTime = DateTime::NowUTC(); - LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), (int32)(endTime - startTime).GetTotalMilliseconds()); + stopwatch.Stop(); + LOG(Info, "Assembly \'{0}\' scanned for custom editors in {1} ms", assembly->ToString(), stopwatch.GetMilliseconds()); } void OnAssemblyUnloading(MAssembly* assembly) diff --git a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs index cd0b534e0..27be8e538 100644 --- a/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/UIControlEditor.cs @@ -589,25 +589,27 @@ namespace FlaxEditor.CustomEditors.Dedicated LayoutElementsContainer yEl; LayoutElementsContainer hEl; LayoutElementsContainer vEl; + Color axisColorX = ActorTransformEditor.AxisColorX; + Color axisColorY = ActorTransformEditor.AxisColorY; if (xEq) { - xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values)); - vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values)); + xEl = UniformPanelCapsuleForObjectWithText(horUp, "X: ", xItem.GetValues(Values), axisColorX); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Width: ", widthItem.GetValues(Values), axisColorX); } else { - xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values)); - vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values)); + xEl = UniformPanelCapsuleForObjectWithText(horUp, "Left: ", leftItem.GetValues(Values), axisColorX); + vEl = UniformPanelCapsuleForObjectWithText(horDown, "Right: ", rightItem.GetValues(Values), axisColorX); } if (yEq) { - yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values)); - hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values)); + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Y: ", yItem.GetValues(Values), axisColorY); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Height: ", heightItem.GetValues(Values), axisColorY); } else { - yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values)); - hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values)); + yEl = UniformPanelCapsuleForObjectWithText(horUp, "Top: ", topItem.GetValues(Values), axisColorY); + hEl = UniformPanelCapsuleForObjectWithText(horDown, "Bottom: ", bottomItem.GetValues(Values), axisColorY); } xEl.Control.AnchorMin = new Float2(0, xEl.Control.AnchorMin.Y); xEl.Control.AnchorMax = new Float2(0.5f, xEl.Control.AnchorMax.Y); @@ -624,28 +626,34 @@ namespace FlaxEditor.CustomEditors.Dedicated private VerticalPanelElement VerticalPanelWithoutMargin(LayoutElementsContainer cont) { - var horUp = cont.VerticalPanel(); - horUp.Panel.Margin = Margin.Zero; - return horUp; + var panel = cont.VerticalPanel(); + panel.Panel.Margin = Margin.Zero; + return panel; } private CustomElementsContainer UniformGridTwoByOne(LayoutElementsContainer cont) { - var horUp = cont.CustomContainer(); - horUp.CustomControl.SlotsHorizontally = 2; - horUp.CustomControl.SlotsVertically = 1; - horUp.CustomControl.SlotPadding = Margin.Zero; - horUp.CustomControl.ClipChildren = false; - return horUp; + var grid = cont.CustomContainer(); + grid.CustomControl.SlotsHorizontally = 2; + grid.CustomControl.SlotsVertically = 1; + grid.CustomControl.SlotPadding = Margin.Zero; + grid.CustomControl.ClipChildren = false; + return grid; } - private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values) + private CustomElementsContainer UniformPanelCapsuleForObjectWithText(LayoutElementsContainer el, string text, ValueContainer values, Color borderColor) { - CustomElementsContainer hor = UniformGridTwoByOne(el); - hor.CustomControl.SlotPadding = new Margin(5, 5, 0, 0); - LabelElement lab = hor.Label(text); - hor.Object(values); - return hor; + var grid = UniformGridTwoByOne(el); + grid.CustomControl.SlotPadding = new Margin(5, 5, 1, 1); + var label = grid.Label(text); + var editor = grid.Object(values); + if (editor is FloatEditor floatEditor && floatEditor.Element is FloatValueElement floatEditorElement) + { + var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; + floatEditorElement.ValueBox.BorderColor = Color.Lerp(borderColor, back, ActorTransformEditor.AxisGreyOutFactor); + floatEditorElement.ValueBox.BorderSelectedColor = borderColor; + } + return grid; } private bool _cachedXEq; diff --git a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs index 29a9f45e8..3794bffc8 100644 --- a/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs +++ b/Source/Editor/CustomEditors/Editors/ActorTransformEditor.cs @@ -26,6 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors /// public static Color AxisColorZ = new Color(0.0f, 0.0235294f, 1.0f, 1.0f); + /// + /// The axes colors grey out scale when input field is not focused. + /// + public static float AxisGreyOutFactor = 0.6f; + /// /// Custom editor for actor position property. /// @@ -39,13 +44,15 @@ namespace FlaxEditor.CustomEditors.Editors // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - var grayOutFactor = 0.6f; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor); + XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); + XElement.ValueBox.Category = Utils.ValueCategory.Distance; + YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); + YElement.ValueBox.Category = Utils.ValueCategory.Distance; + ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + ZElement.ValueBox.Category = Utils.ValueCategory.Distance; } } @@ -62,13 +69,15 @@ namespace FlaxEditor.CustomEditors.Editors // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; - var grayOutFactor = 0.6f; - XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, grayOutFactor); + XElement.ValueBox.BorderColor = Color.Lerp(AxisColorX, back, AxisGreyOutFactor); XElement.ValueBox.BorderSelectedColor = AxisColorX; - YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, grayOutFactor); + XElement.ValueBox.Category = Utils.ValueCategory.Angle; + YElement.ValueBox.BorderColor = Color.Lerp(AxisColorY, back, AxisGreyOutFactor); YElement.ValueBox.BorderSelectedColor = AxisColorY; - ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, grayOutFactor); + YElement.ValueBox.Category = Utils.ValueCategory.Angle; + ZElement.ValueBox.BorderColor = Color.Lerp(AxisColorZ, back, AxisGreyOutFactor); ZElement.ValueBox.BorderSelectedColor = AxisColorZ; + ZElement.ValueBox.Category = Utils.ValueCategory.Angle; } } @@ -102,14 +111,17 @@ namespace FlaxEditor.CustomEditors.Editors SetLinkStyle(); var textSize = FlaxEngine.GUI.Style.Current.FontMedium.MeasureText(LinkedLabel.Text.Value); _linkButton.LocalX += textSize.X + 10; - LinkedLabel.SetupContextMenu += (label, menu, editor) => + if (LinkedLabel != null) { - menu.AddSeparator(); - if (LinkValues) - menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling"); - else - menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling"); - }; + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + if (LinkValues) + menu.AddButton("Unlink", ToggleLink).LinkTooltip("Unlinks scale components from uniform scaling"); + else + menu.AddButton("Link", ToggleLink).LinkTooltip("Links scale components for uniform scaling"); + }; + } // Override colors var back = FlaxEngine.GUI.Style.Current.TextBoxBackground; diff --git a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs index 6e3048eb9..906e6fff0 100644 --- a/Source/Editor/CustomEditors/Editors/DoubleEditor.cs +++ b/Source/Editor/CustomEditors/Editors/DoubleEditor.cs @@ -21,32 +21,31 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - _element = null; - - // Try get limit attribute for value min/max range setting and slider speed + var doubleValue = layout.DoubleValue(); + doubleValue.ValueBox.ValueChanged += OnValueChanged; + doubleValue.ValueBox.SlidingEnd += ClearToken; + _element = doubleValue; var attributes = Values.GetAttributes(); if (attributes != null) { - var limit = attributes.FirstOrDefault(x => x is LimitAttribute); - if (limit != null) + var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + doubleValue.SetLimits(limit); + var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None; + if (valueCategory != Utils.ValueCategory.None) { - // Use double value editor with limit - var doubleValue = layout.DoubleValue(); - doubleValue.SetLimits((LimitAttribute)limit); - doubleValue.ValueBox.ValueChanged += OnValueChanged; - doubleValue.ValueBox.SlidingEnd += ClearToken; - _element = doubleValue; - return; + doubleValue.SetCategory(valueCategory); + if (LinkedLabel != null) + { + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => { doubleValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); }); + mb.AutoCheck = true; + mb.Checked = doubleValue.ValueBox.Category != Utils.ValueCategory.None; + }; + } } } - if (_element == null) - { - // Use double value editor - var doubleValue = layout.DoubleValue(); - doubleValue.ValueBox.ValueChanged += OnValueChanged; - doubleValue.ValueBox.SlidingEnd += ClearToken; - _element = doubleValue; - } } private void OnValueChanged() diff --git a/Source/Editor/CustomEditors/Editors/FloatEditor.cs b/Source/Editor/CustomEditors/Editors/FloatEditor.cs index 83d2c3f21..85f8bb5a3 100644 --- a/Source/Editor/CustomEditors/Editors/FloatEditor.cs +++ b/Source/Editor/CustomEditors/Editors/FloatEditor.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using FlaxEditor.CustomEditors.Elements; using FlaxEngine; +using Utils = FlaxEngine.Utils; namespace FlaxEditor.CustomEditors.Editors { @@ -27,41 +28,42 @@ namespace FlaxEditor.CustomEditors.Editors public override void Initialize(LayoutElementsContainer layout) { _element = null; - - // Try get limit attribute for value min/max range setting and slider speed var attributes = Values.GetAttributes(); + var range = (RangeAttribute)attributes?.FirstOrDefault(x => x is RangeAttribute); + if (range != null) + { + // Use slider + var slider = layout.Slider(); + slider.Slider.SetLimits(range); + slider.Slider.ValueChanged += OnValueChanged; + slider.Slider.SlidingEnd += ClearToken; + _element = slider; + return; + } + + var floatValue = layout.FloatValue(); + floatValue.ValueBox.ValueChanged += OnValueChanged; + floatValue.ValueBox.SlidingEnd += ClearToken; + _element = floatValue; if (attributes != null) { - var range = attributes.FirstOrDefault(x => x is RangeAttribute); - if (range != null) + var limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + floatValue.SetLimits(limit); + var valueCategory = ((ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute))?.Category ?? Utils.ValueCategory.None; + if (valueCategory != Utils.ValueCategory.None) { - // Use slider - var slider = layout.Slider(); - slider.SetLimits((RangeAttribute)range); - slider.Slider.ValueChanged += OnValueChanged; - slider.Slider.SlidingEnd += ClearToken; - _element = slider; - return; + floatValue.SetCategory(valueCategory); + if (LinkedLabel != null) + { + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => { floatValue.SetCategory(bt.Checked ? valueCategory : Utils.ValueCategory.None); }); + mb.AutoCheck = true; + mb.Checked = floatValue.ValueBox.Category != Utils.ValueCategory.None; + }; + } } - var limit = attributes.FirstOrDefault(x => x is LimitAttribute); - if (limit != null) - { - // Use float value editor with limit - var floatValue = layout.FloatValue(); - floatValue.SetLimits((LimitAttribute)limit); - floatValue.ValueBox.ValueChanged += OnValueChanged; - floatValue.ValueBox.SlidingEnd += ClearToken; - _element = floatValue; - return; - } - } - if (_element == null) - { - // Use float value editor - var floatValue = layout.FloatValue(); - floatValue.ValueBox.ValueChanged += OnValueChanged; - floatValue.ValueBox.SlidingEnd += ClearToken; - _element = floatValue; } } diff --git a/Source/Editor/CustomEditors/Editors/InputEditor.cs b/Source/Editor/CustomEditors/Editors/InputEditor.cs index 3521f35a5..5a4905206 100644 --- a/Source/Editor/CustomEditors/Editors/InputEditor.cs +++ b/Source/Editor/CustomEditors/Editors/InputEditor.cs @@ -22,7 +22,8 @@ namespace FlaxEditor.CustomEditors.Editors /// public override void Initialize(LayoutElementsContainer layout) { - LinkedLabel.SetupContextMenu += OnSetupContextMenu; + if (LinkedLabel != null) + LinkedLabel.SetupContextMenu += OnSetupContextMenu; var comboBoxElement = layout.ComboBox(); _comboBox = comboBoxElement.ComboBox; var names = new List(); diff --git a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs index 453090d42..96f9d0646 100644 --- a/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/QuaternionEditor.cs @@ -45,23 +45,29 @@ namespace FlaxEditor.CustomEditors.Editors gridControl.SlotsVertically = 1; XElement = grid.FloatValue(); + XElement.ValueBox.Category = Utils.ValueCategory.Angle; XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.FloatValue(); + YElement.ValueBox.Category = Utils.ValueCategory.Angle; YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.FloatValue(); + ZElement.ValueBox.Category = Utils.ValueCategory.Angle; ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; - LinkedLabel.SetupContextMenu += (label, menu, editor) => + if (LinkedLabel != null) { - menu.AddSeparator(); - var value = ((Quaternion)Values[0]).EulerAngles; - menu.AddButton("Copy Euler", () => { Clipboard.Text = JsonSerializer.Serialize(value); }).TooltipText = "Copy the Euler Angles in Degrees"; - }; + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var value = ((Quaternion)Values[0]).EulerAngles; + menu.AddButton("Copy Euler", () => { Clipboard.Text = JsonSerializer.Serialize(value); }).TooltipText = "Copy the Euler Angles in Degrees"; + }; + } } private void OnValueChanged() diff --git a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs index da0969002..719fbf05d 100644 --- a/Source/Editor/CustomEditors/Editors/Vector3Editor.cs +++ b/Source/Editor/CustomEditors/Editors/Vector3Editor.cs @@ -70,25 +70,48 @@ namespace FlaxEditor.CustomEditors.Editors LimitAttribute limit = null; var attributes = Values.GetAttributes(); + var category = Utils.ValueCategory.None; if (attributes != null) { limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute); + if (categoryAttribute != null) + category = categoryAttribute.Category; } XElement = grid.FloatValue(); XElement.SetLimits(limit); + XElement.SetCategory(category); XElement.ValueBox.ValueChanged += OnXValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.FloatValue(); YElement.SetLimits(limit); + YElement.SetCategory(category); YElement.ValueBox.ValueChanged += OnYValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.FloatValue(); ZElement.SetLimits(limit); + ZElement.SetCategory(category); ZElement.ValueBox.ValueChanged += OnZValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; + + if (LinkedLabel != null) + { + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => + { + XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + }); + mb.AutoCheck = true; + mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None; + }; + } } private void OnXValueChanged() @@ -248,26 +271,49 @@ namespace FlaxEditor.CustomEditors.Editors gridControl.SlotsVertically = 1; LimitAttribute limit = null; + Utils.ValueCategory category = Utils.ValueCategory.None; var attributes = Values.GetAttributes(); if (attributes != null) { limit = (LimitAttribute)attributes.FirstOrDefault(x => x is LimitAttribute); + var categoryAttribute = (ValueCategoryAttribute)attributes.FirstOrDefault(x => x is ValueCategoryAttribute); + if (categoryAttribute != null) + category = categoryAttribute.Category; } XElement = grid.DoubleValue(); XElement.SetLimits(limit); + XElement.SetCategory(category); XElement.ValueBox.ValueChanged += OnValueChanged; XElement.ValueBox.SlidingEnd += ClearToken; YElement = grid.DoubleValue(); YElement.SetLimits(limit); + YElement.SetCategory(category); YElement.ValueBox.ValueChanged += OnValueChanged; YElement.ValueBox.SlidingEnd += ClearToken; ZElement = grid.DoubleValue(); ZElement.SetLimits(limit); + ZElement.SetCategory(category); ZElement.ValueBox.ValueChanged += OnValueChanged; ZElement.ValueBox.SlidingEnd += ClearToken; + + if (LinkedLabel != null) + { + LinkedLabel.SetupContextMenu += (label, menu, editor) => + { + menu.AddSeparator(); + var mb = menu.AddButton("Show formatted", bt => + { + XElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + YElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + ZElement.SetCategory(bt.Checked ? category : Utils.ValueCategory.None); + }); + mb.AutoCheck = true; + mb.Checked = XElement.ValueBox.Category != Utils.ValueCategory.None; + }; + } } private void OnValueChanged() diff --git a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs index f426df605..cf16a1194 100644 --- a/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs +++ b/Source/Editor/CustomEditors/Elements/DoubleValueElement.cs @@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements } } + /// + /// Sets the editor value category. + /// + /// The category. + public void SetCategory(Utils.ValueCategory category) + { + ValueBox.Category = category; + } + /// /// Sets the editor limits from member . /// diff --git a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs index 43490da7c..aabdb79e4 100644 --- a/Source/Editor/CustomEditors/Elements/FloatValueElement.cs +++ b/Source/Editor/CustomEditors/Elements/FloatValueElement.cs @@ -51,6 +51,15 @@ namespace FlaxEditor.CustomEditors.Elements } } + /// + /// Sets the editor value category. + /// + /// The category. + public void SetCategory(Utils.ValueCategory category) + { + ValueBox.Category = category; + } + /// /// Sets the editor limits from member . /// diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 1e70df4a3..643601b09 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -315,9 +315,9 @@ namespace FlaxEditor.CustomEditors internal LabelElement Header(HeaderAttribute header) { var element = Header(header.Text); - if (header.FontSize != -1) + if (header.FontSize > 0) element.Label.Font = new FontReference(element.Label.Font.Font, header.FontSize); - if (header.Color != 0) + if (header.Color > 0) element.Label.TextColor = Color.FromRGBA(header.Color); return element; } diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index aa9f014ba..4da379fac 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -407,12 +407,26 @@ int32 Editor::LoadProduct() { Array projectFiles; FileSystem::DirectoryGetFiles(projectFiles, projectPath, TEXT("*.flaxproj"), DirectorySearchOption::TopDirectoryOnly); - if (projectFiles.Count() == 1) + if (projectFiles.Count() > 1) { - // Skip creating new project if it already exists - LOG(Info, "Skip creatinng new project because it already exists"); + Platform::Fatal(TEXT("Too many project files.")); + return -2; + } + else if (projectFiles.Count() == 1) + { + LOG(Info, "Skip creating new project because it already exists"); CommandLine::Options.NewProject.Reset(); } + else + { + Array files; + FileSystem::DirectoryGetFiles(files, projectPath, TEXT("*"), DirectorySearchOption::TopDirectoryOnly); + if (files.Count() > 0) + { + Platform::Fatal(String::Format(TEXT("Target project folder '{0}' is not empty."), projectPath)); + return -1; + } + } } if (CommandLine::Options.NewProject) { diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 4cee6d971..d0076068e 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -290,7 +290,6 @@ namespace FlaxEditor StateMachine = new EditorStateMachine(this); Undo = new EditorUndo(this); - UIControl.FallbackParentGetDelegate += OnUIControlFallbackParentGet; if (newProject) InitProject(); @@ -355,27 +354,6 @@ namespace FlaxEditor StateMachine.LoadingState.StartInitEnding(skipCompile); } - private ContainerControl OnUIControlFallbackParentGet(UIControl control) - { - // Check if prefab root control is this UIControl - var loadingPreview = Viewport.Previews.PrefabPreview.LoadingPreview; - var activePreviews = Viewport.Previews.PrefabPreview.ActivePreviews; - if (activePreviews != null) - { - foreach (var preview in activePreviews) - { - if (preview == loadingPreview || - (preview.Instance != null && (preview.Instance == control || preview.Instance.HasActorInHierarchy(control)))) - { - // Link it to the prefab preview to see it in the editor - preview.customControlLinked = control; - return preview; - } - } - } - return null; - } - internal void RegisterModule(EditorModule module) { Log("Register Editor module " + module); diff --git a/Source/Editor/GUI/CurveEditor.cs b/Source/Editor/GUI/CurveEditor.cs index 141b3aa60..bc96cf3bc 100644 --- a/Source/Editor/GUI/CurveEditor.cs +++ b/Source/Editor/GUI/CurveEditor.cs @@ -431,7 +431,6 @@ namespace FlaxEditor.GUI /// protected CurveEditor() { - _tickStrengths = new float[TickSteps.Length]; Accessor.GetDefaultValue(out DefaultValue); var style = Style.Current; @@ -780,75 +779,31 @@ namespace FlaxEditor.GUI return _mainPanel.PointToParent(point); } - private void DrawAxis(Float2 axis, ref Rectangle viewRect, float min, float max, float pixelRange) + private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange) { - int minDistanceBetweenTicks = 20; - int maxDistanceBetweenTicks = 60; - var range = max - min; - - // Find the strength for each modulo number tick marker - int smallestTick = 0; - int biggestTick = TickSteps.Length - 1; - for (int i = TickSteps.Length - 1; i >= 0; i--) + Utilities.Utils.DrawCurveTicks((float tick, float strength) => { - // Calculate how far apart these modulo tick steps are spaced - float tickSpacing = TickSteps[i] * pixelRange / range; + var p = PointFromKeyframes(axis * tick, ref viewRect); - // Calculate the strength of the tick markers based on the spacing - _tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); + // Draw line + var lineRect = new Rectangle + ( + viewRect.Location + (p - 0.5f) * axis, + Float2.Lerp(viewRect.Size, Float2.One, axis) + ); + Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength)); - // Beyond threshold the ticks don't get any bigger or fatter - if (_tickStrengths[i] >= 1) - biggestTick = i; - - // Do not show small tick markers - if (tickSpacing <= minDistanceBetweenTicks) - { - smallestTick = i; - break; - } - } - - // Draw all tick levels - int tickLevels = biggestTick - smallestTick + 1; - for (int level = 0; level < tickLevels; level++) - { - float strength = _tickStrengths[smallestTick + level]; - if (strength <= Mathf.Epsilon) - continue; - - // Draw all ticks - int l = Mathf.Clamp(smallestTick + level, 0, TickSteps.Length - 1); - int startTick = Mathf.FloorToInt(min / TickSteps[l]); - int endTick = Mathf.CeilToInt(max / TickSteps[l]); - for (int i = startTick; i <= endTick; i++) - { - if (l < biggestTick && (i % Mathf.RoundToInt(TickSteps[l + 1] / TickSteps[l]) == 0)) - continue; - - var tick = i * TickSteps[l]; - var p = PointFromKeyframes(axis * tick, ref viewRect); - - // Draw line - var lineRect = new Rectangle - ( - viewRect.Location + (p - 0.5f) * axis, - Float2.Lerp(viewRect.Size, Float2.One, axis) - ); - Render2D.FillRectangle(lineRect, _linesColor.AlphaMultiplied(strength)); - - // Draw label - string label = tick.ToString(CultureInfo.InvariantCulture); - var labelRect = new Rectangle - ( - viewRect.X + 4.0f + (p.X * axis.X), - viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X), - 50, - LabelsSize - ); - Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); - } - } + // Draw label + string label = tick.ToString(CultureInfo.InvariantCulture); + var labelRect = new Rectangle + ( + viewRect.X + 4.0f + (p.X * axis.X), + viewRect.Y - LabelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X), + 50, + LabelsSize + ); + Render2D.DrawText(_labelsFont, label, labelRect, _labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + }, TickSteps, ref _tickStrengths, min, max, pixelRange); } /// @@ -890,9 +845,9 @@ namespace FlaxEditor.GUI Render2D.PushClip(ref viewRect); if ((ShowAxes & UseMode.Vertical) == UseMode.Vertical) - DrawAxis(Float2.UnitX, ref viewRect, min.X, max.X, pixelRange.X); + DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X); if ((ShowAxes & UseMode.Horizontal) == UseMode.Horizontal) - DrawAxis(Float2.UnitY, ref viewRect, min.Y, max.Y, pixelRange.Y); + DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y); Render2D.PopClip(); } diff --git a/Source/Editor/GUI/Input/DoubleValueBox.cs b/Source/Editor/GUI/Input/DoubleValueBox.cs index e4a8c6d3f..86acc1203 100644 --- a/Source/Editor/GUI/Input/DoubleValueBox.cs +++ b/Source/Editor/GUI/Input/DoubleValueBox.cs @@ -3,6 +3,7 @@ using System; using FlaxEditor.Utilities; using FlaxEngine; +using Utils = FlaxEngine.Utils; namespace FlaxEditor.GUI.Input { @@ -13,6 +14,8 @@ namespace FlaxEditor.GUI.Input [HideInEditor] public class DoubleValueBox : ValueBox { + private Utils.ValueCategory _category = Utils.ValueCategory.None; + /// public override double Value { @@ -129,10 +132,25 @@ namespace FlaxEditor.GUI.Input Value = Value; } + /// + /// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance. + /// + public Utils.ValueCategory Category + { + get => _category; + set + { + if (_category == value) + return; + _category = value; + UpdateText(); + } + } + /// protected sealed override void UpdateText() { - SetText(Utilities.Utils.FormatFloat(_value)); + SetText(Utilities.Utils.FormatFloat(_value, Category)); } /// diff --git a/Source/Editor/GUI/Input/FloatValueBox.cs b/Source/Editor/GUI/Input/FloatValueBox.cs index 5fc90f260..f22ecd89c 100644 --- a/Source/Editor/GUI/Input/FloatValueBox.cs +++ b/Source/Editor/GUI/Input/FloatValueBox.cs @@ -1,9 +1,9 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Globalization; using FlaxEditor.Utilities; using FlaxEngine; +using Utils = FlaxEngine.Utils; namespace FlaxEditor.GUI.Input { @@ -14,6 +14,8 @@ namespace FlaxEditor.GUI.Input [HideInEditor] public class FloatValueBox : ValueBox { + private Utils.ValueCategory _category = Utils.ValueCategory.None; + /// public override float Value { @@ -137,10 +139,25 @@ namespace FlaxEditor.GUI.Input Value = Value; } + /// + /// Gets or sets the category of the value. This can be none for just a number or a more specific one like a distance. + /// + public Utils.ValueCategory Category + { + get => _category; + set + { + if (_category == value) + return; + _category = value; + UpdateText(); + } + } + /// protected sealed override void UpdateText() { - SetText(Utilities.Utils.FormatFloat(_value)); + SetText(Utilities.Utils.FormatFloat(_value, Category)); } /// diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 4ec634a0b..7bda8f4c0 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -28,7 +28,6 @@ namespace FlaxEditor.GUI.Timeline.GUI { _timeline = timeline; _tickSteps = Utilities.Utils.CurveTickSteps; - _tickStrengths = new float[_tickSteps.Length]; } private void UpdateSelectionRectangle() @@ -173,55 +172,20 @@ namespace FlaxEditor.GUI.Timeline.GUI var rightFrame = Mathf.Ceil((right - Timeline.StartOffset) / zoom) * _timeline.FramesPerSecond; var min = leftFrame; var max = rightFrame; - int smallestTick = 0; - int biggestTick = _tickSteps.Length - 1; - for (int i = _tickSteps.Length - 1; i >= 0; i--) - { - // Calculate how far apart these modulo tick steps are spaced - float tickSpacing = _tickSteps[i] * _timeline.Zoom; - - // Calculate the strength of the tick markers based on the spacing - _tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); - - // Beyond threshold the ticks don't get any bigger or fatter - if (_tickStrengths[i] >= 1) - biggestTick = i; - - // Do not show small tick markers - if (tickSpacing <= minDistanceBetweenTicks) - { - smallestTick = i; - break; - } - } - int tickLevels = biggestTick - smallestTick + 1; // Draw vertical lines for time axis - for (int level = 0; level < tickLevels; level++) + var pixelsInRange = _timeline.Zoom; + var pixelRange = pixelsInRange * (max - min); + var tickRange = Utilities.Utils.DrawCurveTicks((float tick, float strength) => { - float strength = _tickStrengths[smallestTick + level]; - if (strength <= Mathf.Epsilon) - continue; - - // Draw all ticks - int l = Mathf.Clamp(smallestTick + level, 0, _tickSteps.Length - 1); - var lStep = _tickSteps[l]; - var lNextStep = _tickSteps[l + 1]; - int startTick = Mathf.FloorToInt(min / lStep); - int endTick = Mathf.CeilToInt(max / lStep); - Color lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); - for (int i = startTick; i <= endTick; i++) - { - if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) - continue; - var tick = i * lStep; - var time = tick / _timeline.FramesPerSecond; - var x = time * zoom + Timeline.StartOffset; - - // Draw line - Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor); - } - } + var time = tick / _timeline.FramesPerSecond; + var x = time * zoom + Timeline.StartOffset; + var lineColor = style.ForegroundDisabled.RGBMultiplied(0.7f).AlphaMultiplied(strength); + Render2D.FillRectangle(new Rectangle(x - 0.5f, 0, 1.0f, height), lineColor); + }, _tickSteps, ref _tickStrengths, min, max, pixelRange, minDistanceBetweenTicks, maxDistanceBetweenTicks); + var smallestTick = tickRange.X; + var biggestTick = tickRange.Y; + var tickLevels = biggestTick - smallestTick + 1; // Draw selection rectangle if (_isSelecting) diff --git a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs index 5eb289e0a..797a0a044 100644 --- a/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs +++ b/Source/Editor/GUI/Timeline/GUI/KeyframesEditor.cs @@ -904,7 +904,7 @@ namespace FlaxEditor.GUI var k = new Keyframe { Time = keyframesPos.X, - Value = DefaultValue, + Value = Utilities.Utils.CloneValue(DefaultValue), }; OnEditingStart(); AddKeyframe(k); diff --git a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs index 1bada224a..779eb7ddf 100644 --- a/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs +++ b/Source/Editor/GUI/Timeline/GUI/TimelineEdge.cs @@ -1,5 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using System; +using System.Globalization; using FlaxEditor.GUI.Timeline.Undo; using FlaxEngine; using FlaxEngine.GUI; @@ -44,6 +46,25 @@ namespace FlaxEditor.GUI.Timeline.GUI var thickness = 2.0f; var borderColor = _isMoving ? moveColor : (IsMouseOver && _canEdit ? Color.Yellow : style.BorderNormal); Render2D.FillRectangle(new Rectangle((Width - thickness) * 0.5f, timeAxisHeaderOffset, thickness, Height - timeAxisHeaderOffset), borderColor); + if (_canEdit && _isMoving) + { + // TODO: handle start + string labelText; + switch (_timeline.TimeShowMode) + { + case Timeline.TimeShowModes.Frames: + labelText = _timeline.DurationFrames.ToString("###0", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Seconds: + labelText = _timeline.Duration.ToString("###0.##'s'", CultureInfo.InvariantCulture); + break; + case Timeline.TimeShowModes.Time: + labelText = TimeSpan.FromSeconds(_timeline.DurationFrames / _timeline.FramesPerSecond).ToString("g"); + break; + default: throw new ArgumentOutOfRangeException(); + } + Render2D.DrawText(style.FontSmall, labelText, style.Foreground, new Float2((Width - thickness) * 0.5f + 4, timeAxisHeaderOffset)); + } } /// @@ -90,13 +111,26 @@ namespace FlaxEditor.GUI.Timeline.GUI { _timeline.MarkAsEdited(); } + Cursor = CursorType.SizeWE; + } + else if (IsMouseOver && _canEdit) + { + Cursor = CursorType.SizeWE; } else { + Cursor = CursorType.Default; base.OnMouseMove(location); } } + /// + public override void OnMouseLeave() + { + Cursor = CursorType.Default; + base.OnMouseLeave(); + } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { @@ -127,6 +161,7 @@ namespace FlaxEditor.GUI.Timeline.GUI { EndMoving(); } + Cursor = CursorType.Default; base.OnLostFocus(); } diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index f86730b99..81fe950b9 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -1141,17 +1141,19 @@ namespace FlaxEditor.GUI.Timeline { foreach (var e in _playbackNavigation) { - e.Enabled = false; - e.Visible = false; + e.Enabled = true; + e.Visible = true; } } if (_playbackStop != null) { - _playbackStop.Visible = false; + _playbackStop.Visible = true; + _playbackStop.Enabled = false; } if (_playbackPlay != null) { - _playbackPlay.Visible = false; + _playbackPlay.Visible = true; + _playbackPlay.Enabled = false; } if (_positionHandle != null) { diff --git a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs index 95b5dc57f..795674289 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ActorTrack.cs @@ -204,7 +204,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks b.TooltipText = Utilities.Utils.GetTooltip(actorNode.Actor); } } - menu.AddButton("Select...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track"; + menu.AddButton("Retarget...", OnClickedSelect).TooltipText = "Opens actor picker dialog to select the target actor for this track"; } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs index dbff7267a..ed1a53981 100644 --- a/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/KeyframesPropertyTrack.cs @@ -7,6 +7,8 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; using FlaxEditor.GUI.Timeline.Undo; +using FlaxEditor.Scripting; +using FlaxEditor.Utilities; using FlaxEngine; using FlaxEngine.GUI; using FlaxEngine.Utilities; @@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks throw new Exception("Invalid track data."); var keyframes = new KeyframesEditor.Keyframe[keyframesCount]; - var dataBuffer = new byte[e.ValueSize]; var propertyType = TypeUtils.GetManagedType(e.MemberTypeName); if (propertyType == null) { @@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks return; } - GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned); - for (int i = 0; i < keyframesCount; i++) + if (e.ValueSize != 0) { - var time = stream.ReadSingle(); - stream.Read(dataBuffer, 0, e.ValueSize); - var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType); - - keyframes[i] = new KeyframesEditor.Keyframe + // POD value type - use raw memory + var dataBuffer = new byte[e.ValueSize]; + GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned); + for (int i = 0; i < keyframesCount; i++) { - Time = time, - Value = value, - }; + var time = stream.ReadSingle(); + stream.Read(dataBuffer, 0, e.ValueSize); + var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType); + + keyframes[i] = new KeyframesEditor.Keyframe + { + Time = time, + Value = value, + }; + } + handle.Free(); + } + else + { + // Generic value - use Json storage (as UTF-8) + for (int i = 0; i < keyframesCount; i++) + { + var time = stream.ReadSingle(); + var len = stream.ReadInt32(); + var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null; + keyframes[i] = new KeyframesEditor.Keyframe + { + Time = time, + Value = value, + }; + } } - handle.Free(); e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType); e.Keyframes.SetKeyframes(keyframes); @@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks stream.Write(propertyTypeNameData); stream.Write('\0'); - var dataBuffer = new byte[e.ValueSize]; - IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize); - for (int i = 0; i < keyframes.Count; i++) + if (e.ValueSize != 0) { - var keyframe = keyframes[i]; - Marshal.StructureToPtr(keyframe.Value, ptr, true); - Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize); - stream.Write(keyframe.Time); - stream.Write(dataBuffer); + // POD value type - use raw memory + var dataBuffer = new byte[e.ValueSize]; + IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize); + for (int i = 0; i < keyframes.Count; i++) + { + var keyframe = keyframes[i]; + Marshal.StructureToPtr(keyframe.Value, ptr, true); + Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize); + stream.Write(keyframe.Time); + stream.Write(dataBuffer); + } + Marshal.FreeHGlobal(ptr); + } + else + { + // Generic value - use Json storage (as UTF-8) + for (int i = 0; i < keyframes.Count; i++) + { + var keyframe = keyframes[i]; + stream.Write(keyframe.Time); + var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null; + var len = json?.Length ?? 0; + stream.Write(len); + if (len > 0) + stream.Write(Encoding.UTF8.GetBytes(json)); + } } - Marshal.FreeHGlobal(ptr); } private byte[] _keyframesEditingStartData; @@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks private void OnKeyframesEditingEnd() { var after = EditTrackAction.CaptureData(this); - if (!Utils.ArraysEqual(_keyframesEditingStartData, after)) + if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after)) Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after)); _keyframesEditingStartData = null; } @@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks /// The default value. protected virtual object GetDefaultValue(Type propertyType) { - return Activator.CreateInstance(propertyType); + var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType)); + if (value == null) + value = Activator.CreateInstance(propertyType); + return value; } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs index eccd9ab06..f6fb4b4b2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ObjectTrack.cs @@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks { typeof(Guid), KeyframesPropertyTrack.GetArchetype() }, { typeof(DateTime), KeyframesPropertyTrack.GetArchetype() }, { typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() }, + { typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() }, { typeof(string), StringPropertyTrack.GetArchetype() }, }; } diff --git a/Source/Editor/Gizmo/GizmosCollection.cs b/Source/Editor/Gizmo/GizmosCollection.cs index b77d22e84..0f69c0756 100644 --- a/Source/Editor/Gizmo/GizmosCollection.cs +++ b/Source/Editor/Gizmo/GizmosCollection.cs @@ -161,5 +161,20 @@ namespace FlaxEditor.Gizmo } throw new ArgumentException("Not added mode to activate."); } + + /// + /// Gets the gizmo of a given type or returns null if not added. + /// + /// Type of the gizmo. + /// Found gizmo or null. + public T Get() where T : GizmoBase + { + foreach (var e in this) + { + if (e is T asT) + return asT; + } + return null; + } } } diff --git a/Source/Editor/Gizmo/GridGizmo.cs b/Source/Editor/Gizmo/GridGizmo.cs index 8638e1b54..74a16449e 100644 --- a/Source/Editor/Gizmo/GridGizmo.cs +++ b/Source/Editor/Gizmo/GridGizmo.cs @@ -46,7 +46,8 @@ namespace FlaxEditor.Gizmo var plane = new Plane(Vector3.Zero, Vector3.UnitY); var dst = CollisionsHelper.DistancePlanePoint(ref plane, ref viewPos); - float space = Editor.Instance.Options.Options.Viewport.ViewportGridScale, size; + var options = Editor.Instance.Options.Options; + float space = options.Viewport.ViewportGridScale, size; if (dst <= 500.0f) { size = 8000; @@ -62,8 +63,12 @@ namespace FlaxEditor.Gizmo size = 100000; } - Color color = Color.Gray * 0.7f; + float bigLineIntensity = 0.8f; + Color bigColor = Color.Gray * bigLineIntensity; + Color color = bigColor * 0.8f; int count = (int)(size / space); + int midLine = count / 2; + int bigLinesMod = count / 8; Vector3 start = new Vector3(0, 0, size * -0.5f); Vector3 end = new Vector3(0, 0, size * 0.5f); @@ -71,7 +76,12 @@ namespace FlaxEditor.Gizmo for (int i = 0; i <= count; i++) { start.X = end.X = i * space + start.Z; - DebugDraw.DrawLine(start, end, color); + Color lineColor = color; + if (i == midLine) + lineColor = Color.Blue * bigLineIntensity; + else if (i % bigLinesMod == 0) + lineColor = bigColor; + DebugDraw.DrawLine(start, end, lineColor); } start = new Vector3(size * -0.5f, 0, 0); @@ -80,7 +90,12 @@ namespace FlaxEditor.Gizmo for (int i = 0; i <= count; i++) { start.Z = end.Z = i * space + start.X; - DebugDraw.DrawLine(start, end, color); + Color lineColor = color; + if (i == midLine) + lineColor = Color.Red * bigLineIntensity; + else if (i % bigLinesMod == 0) + lineColor = bigColor; + DebugDraw.DrawLine(start, end, lineColor); } DebugDraw.Draw(ref renderContext, input.View(), null, true); diff --git a/Source/Editor/Gizmo/IGizmoOwner.cs b/Source/Editor/Gizmo/IGizmoOwner.cs index 7237d724b..0a5c520a7 100644 --- a/Source/Editor/Gizmo/IGizmoOwner.cs +++ b/Source/Editor/Gizmo/IGizmoOwner.cs @@ -117,5 +117,10 @@ namespace FlaxEditor.Gizmo /// /// The new actor to spawn. void Spawn(Actor actor); + + /// + /// Opens the context menu at the current mouse location (using current selection). + /// + void OpenContextMenu(); } } diff --git a/Source/Editor/Gizmo/TransformGizmo.cs b/Source/Editor/Gizmo/TransformGizmo.cs index 64f969990..cd5e16e92 100644 --- a/Source/Editor/Gizmo/TransformGizmo.cs +++ b/Source/Editor/Gizmo/TransformGizmo.cs @@ -42,6 +42,11 @@ namespace FlaxEditor.Gizmo /// public Action Duplicate; + /// + /// Gets the array of selected objects. + /// + public List Selection => _selection; + /// /// Gets the array of selected parent objects (as actors). /// diff --git a/Source/Editor/Gizmo/UIEditorGizmo.cs b/Source/Editor/Gizmo/UIEditorGizmo.cs new file mode 100644 index 000000000..43461b0a6 --- /dev/null +++ b/Source/Editor/Gizmo/UIEditorGizmo.cs @@ -0,0 +1,891 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; +using FlaxEditor.SceneGraph.Actors; +using FlaxEditor.Viewport.Cameras; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor +{ + /// + /// UI editor camera. + /// + [HideInEditor] + internal sealed class UIEditorCamera : ViewportCamera + { + public UIEditorRoot UIEditor; + + public void ShowActors(IEnumerable actors) + { + var root = UIEditor.UIRoot; + if (root == null) + return; + + // Calculate bounds of all selected objects + var areaRect = Rectangle.Empty; + foreach (var actor in actors) + { + Rectangle bounds; + if (actor is UIControl uiControl && uiControl.HasControl && uiControl.IsActive) + { + var control = uiControl.Control; + bounds = control.EditorBounds; + + var ul = control.PointToParent(root, bounds.UpperLeft); + var ur = control.PointToParent(root, bounds.UpperRight); + var bl = control.PointToParent(root, bounds.BottomLeft); + var br = control.PointToParent(root, bounds.BottomRight); + + var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br)); + var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br)); + bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); + } + else if (actor is UICanvas uiCanvas && uiCanvas.IsActive && uiCanvas.GUI.Parent == root) + { + bounds = uiCanvas.GUI.Bounds; + } + else + continue; + + if (areaRect == Rectangle.Empty) + areaRect = bounds; + else + areaRect = Rectangle.Union(areaRect, bounds); + } + if (areaRect == Rectangle.Empty) + return; + + // Add margin + areaRect = areaRect.MakeExpanded(100.0f); + + // Show bounds + UIEditor.ViewScale = (UIEditor.Size / areaRect.Size).MinValue * 0.95f; + UIEditor.ViewCenterPosition = areaRect.Center; + } + + public override void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation) + { + ShowActors(gizmos.Get().Selection, ref orientation); + } + + public override void ShowActor(Actor actor) + { + ShowActors(new[] { actor }); + } + + public override void ShowActors(List selection, ref Quaternion orientation) + { + ShowActors(selection.ConvertAll(x => (Actor)x.EditableObject)); + } + + public override void UpdateView(float dt, ref Vector3 moveDelta, ref Float2 mouseDelta, out bool centerMouse) + { + centerMouse = false; + } + } + + /// + /// Root control for UI Controls presentation in the game/prefab viewport. + /// + [HideInEditor] + internal class UIEditorRoot : InputsPassThrough + { + /// + /// View for the UI structure to be linked in for camera zoom and panning operations. + /// + private sealed class View : ContainerControl + { + public View(UIEditorRoot parent) + { + AutoFocus = false; + ClipChildren = false; + CullChildren = false; + Pivot = Float2.Zero; + Size = new Float2(1920, 1080); + Parent = parent; + } + + public override bool RayCast(ref Float2 location, out Control hit) + { + // Ignore self + return RayCastChildren(ref location, out hit); + } + + public override bool IntersectsContent(ref Float2 locationParent, out Float2 location) + { + location = PointFromParent(ref locationParent); + return true; + } + + public override void DrawSelf() + { + var uiRoot = (UIEditorRoot)Parent; + if (!uiRoot.EnableBackground) + return; + + // Draw canvas area + var bounds = new Rectangle(Float2.Zero, Size); + Render2D.FillRectangle(bounds, new Color(0, 0, 0, 0.2f)); + } + } + + /// + /// Cached placement of the widget used to size/edit control + /// + private struct Widget + { + public UIControl UIControl; + public Rectangle Bounds; + public Float2 ResizeAxis; + public CursorType Cursor; + } + + private bool _mouseMovesControl, _mouseMovesView, _mouseMovesWidget; + private Float2 _mouseMovesPos, _moveSnapDelta; + private float _mouseMoveSum; + private UndoMultiBlock _undoBlock; + private View _view; + private float[] _gridTickSteps = Utilities.Utils.CurveTickSteps, _gridTickStrengths; + private List _widgets; + private Widget _activeWidget; + + /// + /// True if enable displaying UI editing background and grid elements. + /// + public virtual bool EnableBackground => false; + + /// + /// True if enable selecting controls with mouse button. + /// + public virtual bool EnableSelecting => false; + + /// + /// True if enable panning and zooming the view. + /// + public bool EnableCamera => _view != null && EnableBackground; + + /// + /// Transform gizmo to use sync with (selection, snapping, transformation settings). + /// + public virtual TransformGizmo TransformGizmo => null; + + /// + /// The root control for controls to be linked in. + /// + public readonly ContainerControl UIRoot; + + internal Float2 ViewPosition + { + get => _view.Location / -ViewScale; + set => _view.Location = value * -ViewScale; + } + + internal Float2 ViewCenterPosition + { + get => (_view.Location - Size * 0.5f) / -ViewScale; + set => _view.Location = Size * 0.5f + value * -ViewScale; + } + + internal float ViewScale + { + get => _view?.Scale.X ?? 1; + set + { + if (_view == null) + return; + value = Mathf.Clamp(value, 0.1f, 4.0f); + _view.Scale = new Float2(value); + } + } + + public UIEditorRoot(bool enableCamera = false) + { + AnchorPreset = AnchorPresets.StretchAll; + Offsets = Margin.Zero; + AutoFocus = false; + UIRoot = this; + CullChildren = false; + ClipChildren = true; + if (enableCamera) + { + _view = new View(this); + UIRoot = _view; + } + } + + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (base.OnMouseDown(location, button)) + return true; + + var transformGizmo = TransformGizmo; + var owner = transformGizmo?.Owner; + if (_widgets != null && _widgets.Count != 0 && button == MouseButton.Left) + { + foreach (var widget in _widgets) + { + if (widget.Bounds.Contains(ref location)) + { + // Initialize widget movement + _activeWidget = widget; + _mouseMovesWidget = true; + _mouseMovesPos = location; + Cursor = widget.Cursor; + StartUndo(); + Focus(); + StartMouseCapture(); + return true; + } + } + } + if (EnableSelecting && owner != null && !_mouseMovesControl && button == MouseButton.Left) + { + // Raycast the control under the mouse + var mousePos = PointFromWindow(RootWindow.MousePosition); + if (RayCastControl(ref mousePos, out var hitControl)) + { + var uiControlNode = FindUIControlNode(hitControl); + if (uiControlNode != null) + { + // Select node (with additive mode) + var selection = new List(); + if (Root.GetKey(KeyboardKeys.Control)) + { + // Add/remove from selection + selection.AddRange(transformGizmo.Selection); + if (transformGizmo.Selection.Contains(uiControlNode)) + selection.Remove(uiControlNode); + else + selection.Add(uiControlNode); + } + else + { + // Select + selection.Add(uiControlNode); + } + owner.Select(selection); + + // Initialize control movement + _mouseMovesControl = true; + _mouseMovesPos = location; + _mouseMoveSum = 0.0f; + _moveSnapDelta = Float2.Zero; + Focus(); + StartMouseCapture(); + return true; + } + } + // Allow deselecting if user clicks on nothing + else + { + owner.Select(null); + } + } + if (EnableCamera && (button == MouseButton.Right || button == MouseButton.Middle)) + { + // Initialize surface movement + _mouseMovesView = true; + _mouseMovesPos = location; + _mouseMoveSum = 0.0f; + Focus(); + StartMouseCapture(); + return true; + } + + return Focus(this); + } + + public override void OnMouseMove(Float2 location) + { + base.OnMouseMove(location); + + // Change cursor if mouse is over active control widget + bool cursorChanged = false; + if (_widgets != null && _widgets.Count != 0 && !_mouseMovesControl && !_mouseMovesWidget && !_mouseMovesView) + { + foreach (var widget in _widgets) + { + if (widget.Bounds.Contains(ref location)) + { + Cursor = widget.Cursor; + cursorChanged = true; + } + else if (Cursor != CursorType.Default && !cursorChanged) + { + Cursor = CursorType.Default; + } + } + } + + var transformGizmo = TransformGizmo; + if (_mouseMovesControl && transformGizmo != null) + { + // Calculate transform delta + var delta = location - _mouseMovesPos; + if (transformGizmo.TranslationSnapEnable || transformGizmo.Owner.UseSnapping) + { + _moveSnapDelta += delta; + delta = Float2.SnapToGrid(_moveSnapDelta, new Float2(transformGizmo.TranslationSnapValue * ViewScale)); + _moveSnapDelta -= delta; + } + + // Move selected controls + if (delta.LengthSquared > 0.0f) + { + StartUndo(); + var moved = false; + var moveLocation = _mouseMovesPos + delta; + var selection = transformGizmo.Selection; + for (var i = 0; i < selection.Count; i++) + { + if (IsValidControl(selection[i], out var uiControl)) + { + // Move control (handle any control transformations by moving in editor's local-space) + var control = uiControl.Control; + var localLocation = control.LocalLocation; + var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); + control.LocalLocation = localLocation + uiControlDelta; + + // Don't move if layout doesn't allow it + if (control.Parent != null) + control.Parent.PerformLayout(); + else + control.PerformLayout(); + + // Check if control was moved (parent container could block it) + if (localLocation != control.LocalLocation) + moved = true; + } + } + _mouseMovesPos = location; + _mouseMoveSum += delta.Length; + if (moved) + Cursor = CursorType.SizeAll; + } + } + if (_mouseMovesWidget && _activeWidget.UIControl) + { + // Calculate transform delta + var resizeAxisAbs = _activeWidget.ResizeAxis.Absolute; + var resizeAxisPos = Float2.Clamp(_activeWidget.ResizeAxis, Float2.Zero, Float2.One); + var resizeAxisNeg = Float2.Clamp(-_activeWidget.ResizeAxis, Float2.Zero, Float2.One); + var delta = location - _mouseMovesPos; + // TODO: scale/size snapping? + delta *= resizeAxisAbs; + + // Resize control via widget + var moveLocation = _mouseMovesPos + delta; + var control = _activeWidget.UIControl.Control; + var uiControlDelta = GetControlDelta(control, ref _mouseMovesPos, ref moveLocation); + control.LocalLocation += uiControlDelta * resizeAxisNeg; + control.Size += uiControlDelta * resizeAxisPos - uiControlDelta * resizeAxisNeg; + + // Don't move if layout doesn't allow it + if (control.Parent != null) + control.Parent.PerformLayout(); + else + control.PerformLayout(); + + _mouseMovesPos = location; + } + if (_mouseMovesView) + { + // Move view + var delta = location - _mouseMovesPos; + if (delta.LengthSquared > 4.0f) + { + _mouseMovesPos = location; + _mouseMoveSum += delta.Length; + _view.Location += delta; + Cursor = CursorType.SizeAll; + } + } + } + + public override bool OnMouseUp(Float2 location, MouseButton button) + { + EndMovingControls(); + EndMovingWidget(); + if (_mouseMovesView) + { + EndMovingView(); + if (button == MouseButton.Right && _mouseMoveSum < 2.0f) + TransformGizmo.Owner.OpenContextMenu(); + } + + return base.OnMouseUp(location, button); + } + + public override void OnMouseLeave() + { + EndMovingControls(); + EndMovingView(); + EndMovingWidget(); + + base.OnMouseLeave(); + } + + public override void OnLostFocus() + { + EndMovingControls(); + EndMovingView(); + EndMovingWidget(); + + base.OnLostFocus(); + } + + public override bool OnMouseWheel(Float2 location, float delta) + { + if (base.OnMouseWheel(location, delta)) + return true; + + if (EnableCamera && !_mouseMovesControl) + { + // Zoom view + var nextViewScale = ViewScale + delta * 0.1f; + if (delta > 0 && !_mouseMovesControl) + { + // Scale towards mouse when zooming in + var nextCenterPosition = ViewPosition + location / ViewScale; + ViewScale = nextViewScale; + ViewPosition = nextCenterPosition - (location / ViewScale); + } + else + { + // Scale while keeping center position when zooming out or when dragging view + var viewCenter = ViewCenterPosition; + ViewScale = nextViewScale; + ViewCenterPosition = viewCenter; + } + + return true; + } + + return false; + } + + public override void Draw() + { + if (EnableBackground && _view != null) + { + // Draw background + Surface.VisjectSurface.DrawBackgroundDefault(Editor.Instance.UI.VisjectSurfaceBackground, Width, Height); + + // Draw grid + var viewRect = GetClientArea(); + var upperLeft = _view.PointFromParent(viewRect.Location); + var bottomRight = _view.PointFromParent(viewRect.Size); + var min = Float2.Min(upperLeft, bottomRight); + var max = Float2.Max(upperLeft, bottomRight); + var pixelRange = (max - min) * ViewScale; + Render2D.PushClip(ref viewRect); + DrawAxis(Float2.UnitX, viewRect, min.X, max.X, pixelRange.X); + DrawAxis(Float2.UnitY, viewRect, min.Y, max.Y, pixelRange.Y); + Render2D.PopClip(); + } + + base.Draw(); + + if (!_mouseMovesWidget) + { + // Clear widgets to collect them during drawing + _widgets?.Clear(); + } + + bool drawAnySelectedControl = false; + var transformGizmo = TransformGizmo; + var mousePos = PointFromWindow(RootWindow.MousePosition); + if (transformGizmo != null) + { + // Selected UI controls outline + var selection = transformGizmo.Selection; + for (var i = 0; i < selection.Count; i++) + { + if (IsValidControl(selection[i], out var controlActor)) + { + DrawControl(controlActor, controlActor.Control, true, ref mousePos, ref drawAnySelectedControl, EnableSelecting); + } + } + } + if (EnableSelecting && !_mouseMovesControl && !_mouseMovesWidget && IsMouseOver) + { + // Highlight control under mouse for easier selecting (except if already selected) + if (RayCastControl(ref mousePos, out var hitControl) && + (transformGizmo == null || !transformGizmo.Selection.Any(x => x.EditableObject is UIControl controlActor && controlActor.Control == hitControl))) + { + DrawControl(null, hitControl, false, ref mousePos, ref drawAnySelectedControl); + } + } + if (drawAnySelectedControl) + Render2D.PopTransform(); + + if (EnableBackground) + { + // Draw border + if (ContainsFocus) + { + Render2D.DrawRectangle(new Rectangle(1, 1, Width - 2, Height - 2), Editor.IsPlayMode ? Color.OrangeRed : Style.Current.BackgroundSelected); + } + } + } + + public override void OnDestroy() + { + if (IsDisposing) + return; + EndMovingControls(); + EndMovingView(); + EndMovingWidget(); + + base.OnDestroy(); + } + + private Float2 GetControlDelta(Control control, ref Float2 start, ref Float2 end) + { + var pointOrigin = control.Parent ?? control; + var startPos = pointOrigin.PointFromParent(this, start); + var endPos = pointOrigin.PointFromParent(this, end); + return endPos - startPos; + } + + private void DrawAxis(Float2 axis, Rectangle viewRect, float min, float max, float pixelRange) + { + var style = Style.Current; + var linesColor = style.ForegroundDisabled.RGBMultiplied(0.5f); + var labelsColor = style.ForegroundDisabled; + var labelsSize = 10.0f; + Utilities.Utils.DrawCurveTicks((float tick, float strength) => + { + var p = _view.PointToParent(axis * tick); + + // Draw line + var lineRect = new Rectangle + ( + viewRect.Location + (p - 0.5f) * axis, + Float2.Lerp(viewRect.Size, Float2.One, axis) + ); + Render2D.FillRectangle(lineRect, linesColor.AlphaMultiplied(strength)); + + // Draw label + string label = tick.ToString(System.Globalization.CultureInfo.InvariantCulture); + var labelRect = new Rectangle + ( + viewRect.X + 4.0f + (p.X * axis.X), + viewRect.Y - labelsSize + (p.Y * axis.Y) + (viewRect.Size.Y * axis.X), + 50, + labelsSize + ); + Render2D.DrawText(style.FontSmall, label, labelRect, labelsColor.AlphaMultiplied(strength), TextAlignment.Near, TextAlignment.Center, TextWrapping.NoWrap, 1.0f, 0.7f); + }, _gridTickSteps, ref _gridTickStrengths, min, max, pixelRange); + } + + private void DrawControl(UIControl uiControl, Control control, bool selection, ref Float2 mousePos, ref bool drawAnySelectedControl, bool withWidgets = false) + { + if (!drawAnySelectedControl) + { + drawAnySelectedControl = true; + Render2D.PushTransform(ref _cachedTransform); + } + var options = Editor.Instance.Options.Options.Visual; + + // Draw bounds + var bounds = control.EditorBounds; + var ul = control.PointToParent(this, bounds.UpperLeft); + var ur = control.PointToParent(this, bounds.UpperRight); + var bl = control.PointToParent(this, bounds.BottomLeft); + var br = control.PointToParent(this, bounds.BottomRight); + var color = selection ? options.SelectionOutlineColor0 : Style.Current.SelectionBorder; +#if false + // AABB + var min = Float2.Min(Float2.Min(ul, ur), Float2.Min(bl, br)); + var max = Float2.Max(Float2.Max(ul, ur), Float2.Max(bl, br)); + bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); + Render2D.DrawRectangle(bounds, color, options.UISelectionOutlineSize); +#else + // OBB + Render2D.DrawLine(ul, ur, color, options.UISelectionOutlineSize); + Render2D.DrawLine(ur, br, color, options.UISelectionOutlineSize); + Render2D.DrawLine(br, bl, color, options.UISelectionOutlineSize); + Render2D.DrawLine(bl, ul, color, options.UISelectionOutlineSize); +#endif + if (withWidgets) + { + // Draw sizing widgets + if (_widgets == null) + _widgets = new List(); + var widgetSize = 8.0f; + var viewScale = ViewScale; + if (viewScale < 0.7f) + widgetSize *= viewScale; + var controlSize = control.Size.Absolute.MinValue / 50.0f; + if (controlSize < 1.0f) + widgetSize *= Mathf.Clamp(controlSize + 0.1f, 0.1f, 1.0f); + var cornerSize = new Float2(widgetSize); + DrawControlWidget(uiControl, ref ul, ref mousePos, ref cornerSize, new Float2(-1, -1), CursorType.SizeNWSE); + DrawControlWidget(uiControl, ref ur, ref mousePos, ref cornerSize, new Float2(1, -1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref bl, ref mousePos, ref cornerSize, new Float2(-1, 1), CursorType.SizeNESW); + DrawControlWidget(uiControl, ref br, ref mousePos, ref cornerSize, new Float2(1, 1), CursorType.SizeNWSE); + var edgeSizeV = new Float2(widgetSize * 2, widgetSize); + var edgeSizeH = new Float2(edgeSizeV.Y, edgeSizeV.X); + Float2.Lerp(ref ul, ref bl, 0.5f, out var el); + Float2.Lerp(ref ur, ref br, 0.5f, out var er); + Float2.Lerp(ref ul, ref ur, 0.5f, out var eu); + Float2.Lerp(ref bl, ref br, 0.5f, out var eb); + DrawControlWidget(uiControl, ref el, ref mousePos, ref edgeSizeH, new Float2(-1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref er, ref mousePos, ref edgeSizeH, new Float2(1, 0), CursorType.SizeWE); + DrawControlWidget(uiControl, ref eu, ref mousePos, ref edgeSizeV, new Float2(0, -1), CursorType.SizeNS); + DrawControlWidget(uiControl, ref eb, ref mousePos, ref edgeSizeV, new Float2(0, 1), CursorType.SizeNS); + + // TODO: draw anchors + } + } + + private void DrawControlWidget(UIControl uiControl, ref Float2 pos, ref Float2 mousePos, ref Float2 size, Float2 resizeAxis, CursorType cursor) + { + var style = Style.Current; + var rect = new Rectangle(pos - size * 0.5f, size); + if (rect.Contains(ref mousePos)) + { + Render2D.FillRectangle(rect, style.Foreground); + } + else + { + Render2D.FillRectangle(rect, style.ForegroundGrey); + Render2D.DrawRectangle(rect, style.Foreground); + } + if (!_mouseMovesWidget && uiControl != null) + { + // Collect widget + _widgets.Add(new Widget + { + UIControl = uiControl, + Bounds = rect, + ResizeAxis = resizeAxis, + Cursor = cursor, + }); + } + } + + private bool IsValidControl(SceneGraphNode node, out UIControl uiControl) + { + uiControl = null; + if (node.EditableObject is UIControl controlActor) + uiControl = controlActor; + return uiControl != null && + uiControl.Control != null && + uiControl.Control.VisibleInHierarchy && + uiControl.Control.RootWindow != null; + } + + private bool RayCastControl(ref Float2 location, out Control hit) + { +#if false + // Raycast only controls with content (eg. skips transparent panels) + return RayCastChildren(ref location, out hit); +#else + // Find any control under mouse (hierarchical) + hit = GetChildAtRecursive(location); + if (hit is View || hit is CanvasContainer) + hit = null; + return hit != null; +#endif + } + + private UIControlNode FindUIControlNode(Control control) + { + return FindUIControlNode(TransformGizmo.Owner.SceneGraphRoot, control); + } + + private UIControlNode FindUIControlNode(SceneGraphNode node, Control control) + { + var result = node as UIControlNode; + if (result != null && ((UIControl)result.Actor).Control == control) + return result; + foreach (var e in node.ChildNodes) + { + result = FindUIControlNode(e, control); + if (result != null) + return result; + } + return null; + } + + private void StartUndo() + { + var undo = TransformGizmo?.Owner?.Undo; + if (undo == null || _undoBlock != null) + return; + _undoBlock = new UndoMultiBlock(undo, TransformGizmo.Selection.ConvertAll(x => x.EditableObject), "Edit control"); + } + + private void EndUndo() + { + if (_undoBlock == null) + return; + _undoBlock.Dispose(); + _undoBlock = null; + } + + private void EndMovingControls() + { + if (!_mouseMovesControl) + return; + _mouseMovesControl = false; + EndMouseCapture(); + Cursor = CursorType.Default; + EndUndo(); + } + + private void EndMovingView() + { + if (!_mouseMovesView) + return; + _mouseMovesView = false; + EndMouseCapture(); + Cursor = CursorType.Default; + } + + private void EndMovingWidget() + { + if (!_mouseMovesWidget) + return; + _mouseMovesWidget = false; + _activeWidget = new Widget(); + EndMouseCapture(); + Cursor = CursorType.Default; + EndUndo(); + } + } + + /// + /// Control that can optionally disable inputs to the children. + /// + [HideInEditor] + internal class InputsPassThrough : ContainerControl + { + private bool _isMouseOver; + + /// + /// True if enable input events passing to the UI. + /// + public virtual bool EnableInputs => true; + + public override bool RayCast(ref Float2 location, out Control hit) + { + return RayCastChildren(ref location, out hit); + } + + public override bool ContainsPoint(ref Float2 location, bool precise = false) + { + if (precise) + return false; + return base.ContainsPoint(ref location, precise); + } + + public override bool OnCharInput(char c) + { + if (!EnableInputs) + return false; + return base.OnCharInput(c); + } + + public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) + { + if (!EnableInputs) + return DragDropEffect.None; + return base.OnDragDrop(ref location, data); + } + + public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) + { + if (!EnableInputs) + return DragDropEffect.None; + return base.OnDragEnter(ref location, data); + } + + public override void OnDragLeave() + { + if (!EnableInputs) + return; + base.OnDragLeave(); + } + + public override DragDropEffect OnDragMove(ref Float2 location, DragData data) + { + if (!EnableInputs) + return DragDropEffect.None; + return base.OnDragMove(ref location, data); + } + + public override bool OnKeyDown(KeyboardKeys key) + { + if (!EnableInputs) + return false; + return base.OnKeyDown(key); + } + + public override void OnKeyUp(KeyboardKeys key) + { + if (!EnableInputs) + return; + base.OnKeyUp(key); + } + + public override bool OnMouseDoubleClick(Float2 location, MouseButton button) + { + if (!EnableInputs) + return false; + return base.OnMouseDoubleClick(location, button); + } + + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (!EnableInputs) + return false; + return base.OnMouseDown(location, button); + } + + public override bool IsMouseOver => _isMouseOver; + + public override void OnMouseEnter(Float2 location) + { + _isMouseOver = true; + if (!EnableInputs) + return; + base.OnMouseEnter(location); + } + + public override void OnMouseLeave() + { + _isMouseOver = false; + if (!EnableInputs) + return; + base.OnMouseLeave(); + } + + public override void OnMouseMove(Float2 location) + { + if (!EnableInputs) + return; + base.OnMouseMove(location); + } + + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (!EnableInputs) + return false; + return base.OnMouseUp(location, button); + } + + public override bool OnMouseWheel(Float2 location, float delta) + { + if (!EnableInputs) + return false; + return base.OnMouseWheel(location, delta); + } + } +} diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index 43c805bf4..b2867c2df 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -263,22 +263,22 @@ DEFINE_INTERNAL_CALL(MString*) EditorInternal_GetShaderAssetSourceCode(BinaryAss INTERNAL_CALL_CHECK_RETURN(obj, nullptr); if (obj->WaitForLoaded()) DebugLog::ThrowNullReference(); - auto lock = obj->Storage->Lock(); - if (obj->LoadChunk(SHADER_FILE_CHUNK_SOURCE)) return nullptr; + // Decrypt source code BytesContainer data; obj->GetChunkData(SHADER_FILE_CHUNK_SOURCE, data); + auto source = data.Get(); + auto sourceLength = data.Length(); + Encryption::DecryptBytes(data.Get(), data.Length()); + source[sourceLength - 1] = 0; - Encryption::DecryptBytes((byte*)data.Get(), data.Length()); - + // Get source and encrypt it back const StringAnsiView srcData((const char*)data.Get(), data.Length()); - const String source(srcData); - const auto str = MUtils::ToString(source); - - Encryption::EncryptBytes((byte*)data.Get(), data.Length()); + const auto str = MUtils::ToString(srcData); + Encryption::EncryptBytes(data.Get(), data.Length()); return str; } diff --git a/Source/Editor/Modules/ContentDatabaseModule.cs b/Source/Editor/Modules/ContentDatabaseModule.cs index fd830db86..0e4e96d30 100644 --- a/Source/Editor/Modules/ContentDatabaseModule.cs +++ b/Source/Editor/Modules/ContentDatabaseModule.cs @@ -1106,6 +1106,7 @@ namespace FlaxEditor.Modules Proxy.Add(new VisualScriptProxy()); Proxy.Add(new BehaviorTreeProxy()); Proxy.Add(new LocalizedStringTableProxy()); + Proxy.Add(new WidgetProxy()); Proxy.Add(new FileProxy()); Proxy.Add(new SpawnableJsonAssetProxy()); diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index df9009cc8..13a920fa3 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -101,7 +101,10 @@ namespace FlaxEditor.Modules public void Select(List selection, bool additive = false) { if (selection == null) - throw new ArgumentNullException(); + { + Deselect(); + return; + } // Prevent from selecting null nodes selection.RemoveAll(x => x == null); diff --git a/Source/Editor/Modules/SceneModule.cs b/Source/Editor/Modules/SceneModule.cs index 302d1e02a..9aae0b72f 100644 --- a/Source/Editor/Modules/SceneModule.cs +++ b/Source/Editor/Modules/SceneModule.cs @@ -6,6 +6,7 @@ using System.IO; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; +using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Modules @@ -454,6 +455,41 @@ namespace FlaxEditor.Modules Profiler.EndEvent(); } + private Dictionary _uiRootSizes; + + internal void OnSaveStart(ContainerControl uiRoot) + { + // Force viewport UI to have fixed size during scene/prefabs saving to result in stable data (less mess in version control diffs) + if (_uiRootSizes == null) + _uiRootSizes = new Dictionary(); + _uiRootSizes[uiRoot] = uiRoot.Size; + uiRoot.Size = new Float2(1920, 1080); + } + + internal void OnSaveEnd(ContainerControl uiRoot) + { + // Restore cached size of the UI root container + if (_uiRootSizes != null && _uiRootSizes.Remove(uiRoot, out var size)) + { + uiRoot.Size = size; + } + } + + private void OnSceneSaving(Scene scene, Guid sceneId) + { + OnSaveStart(RootControl.GameRoot); + } + + private void OnSceneSaved(Scene scene, Guid sceneId) + { + OnSaveEnd(RootControl.GameRoot); + } + + private void OnSceneSaveError(Scene scene, Guid sceneId) + { + OnSaveEnd(RootControl.GameRoot); + } + private void OnSceneLoaded(Scene scene, Guid sceneId) { var startTime = DateTime.UtcNow; @@ -659,6 +695,9 @@ namespace FlaxEditor.Modules Root = new ScenesRootNode(); // Bind events + Level.SceneSaving += OnSceneSaving; + Level.SceneSaved += OnSceneSaved; + Level.SceneSaveError += OnSceneSaveError; Level.SceneLoaded += OnSceneLoaded; Level.SceneUnloading += OnSceneUnloading; Level.ActorSpawned += OnActorSpawned; @@ -673,6 +712,9 @@ namespace FlaxEditor.Modules public override void OnExit() { // Unbind events + Level.SceneSaving -= OnSceneSaving; + Level.SceneSaved -= OnSceneSaved; + Level.SceneSaveError -= OnSceneSaveError; Level.SceneLoaded -= OnSceneLoaded; Level.SceneUnloading -= OnSceneUnloading; Level.ActorSpawned -= OnActorSpawned; diff --git a/Source/Editor/Options/InputBinding.cs b/Source/Editor/Options/InputBinding.cs index 47147eaa8..59954dec5 100644 --- a/Source/Editor/Options/InputBinding.cs +++ b/Source/Editor/Options/InputBinding.cs @@ -201,8 +201,8 @@ namespace FlaxEditor.Options /// True if input has been processed, otherwise false. public bool Process(Control control) { - var root = control.Root; - return root.GetKey(Key) && ProcessModifiers(control); + var root = control?.Root; + return root != null && root.GetKey(Key) && ProcessModifiers(control); } /// diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 05557f3c0..4cf1fe050 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using FlaxEditor.GUI.Docking; +using FlaxEditor.Utilities; using FlaxEngine; namespace FlaxEditor.Options @@ -116,6 +117,27 @@ namespace FlaxEditor.Options BorderlessWindow, } + /// + /// Options for formatting numerical values. + /// + public enum ValueFormattingType + { + /// + /// No formatting. + /// + None, + + /// + /// Format using the base SI unit. + /// + BaseUnit, + + /// + /// Format using a unit that matches the value best. + /// + AutoUnit, + } + /// /// Gets or sets the Editor User Interface scale. Applied to all UI elements, windows and text. Can be used to scale the interface up on a bigger display. Editor restart required. /// @@ -174,6 +196,20 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(280), Tooltip("Editor content window orientation.")] public FlaxEngine.GUI.Orientation ContentWindowOrientation { get; set; } = FlaxEngine.GUI.Orientation.Horizontal; + /// + /// Gets or sets the formatting option for numeric values in the editor. + /// + [DefaultValue(ValueFormattingType.None)] + [EditorDisplay("Interface"), EditorOrder(300)] + public ValueFormattingType ValueFormatting { get; set; } + + /// + /// Gets or sets the option to put a space between numbers and units for unit formatting. + /// + [DefaultValue(false)] + [EditorDisplay("Interface"), EditorOrder(310)] + public bool SeparateValueAndUnit { get; set; } + /// /// Gets or sets the timestamps prefix mode for output log messages. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 2dceb3400..debc0a663 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -200,6 +200,27 @@ namespace FlaxEditor.Options EditorAssets.Cache.OnEditorOptionsChanged(Options); + // Units formatting options + bool useUnitsFormatting = Options.Interface.ValueFormatting != InterfaceOptions.ValueFormattingType.None; + bool automaticUnitsFormatting = Options.Interface.ValueFormatting == InterfaceOptions.ValueFormattingType.AutoUnit; + bool separateValueAndUnit = Options.Interface.SeparateValueAndUnit; + if (useUnitsFormatting != Utilities.Units.UseUnitsFormatting || + automaticUnitsFormatting != Utilities.Units.AutomaticUnitsFormatting || + separateValueAndUnit != Utilities.Units.SeparateValueAndUnit) + { + Utilities.Units.UseUnitsFormatting = useUnitsFormatting; + Utilities.Units.AutomaticUnitsFormatting = automaticUnitsFormatting; + Utilities.Units.SeparateValueAndUnit = separateValueAndUnit; + + // Refresh UI in property panels + Editor.Windows.PropertiesWin?.Presenter.BuildLayoutOnUpdate(); + foreach (var window in Editor.Windows.Windows) + { + if (window is Windows.Assets.PrefabWindow prefabWindow) + prefabWindow.Presenter.BuildLayoutOnUpdate(); + } + } + // Send event OptionsChanged?.Invoke(Options); } diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index c57119e00..0b999d6b1 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -308,11 +308,14 @@ namespace FlaxEditor.SceneGraph.Actors var selection = Editor.Instance.SceneEditing.Selection; if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) { - if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) + var mouse = Input.Mouse; + var keyboard = Input.Keyboard; + + if (keyboard.GetKey(KeyboardKeys.Shift)) EditSplineWithSnap(selectedPoint); - var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero; - var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right); + var canAddSplinePoint = mouse.PositionDelta == Float2.Zero && mouse.Position != Float2.Zero; + var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && mouse.GetButtonDown(MouseButton.Right); if (requestAddSplinePoint && canAddSplinePoint) AddSplinePoint(selectedPoint); } diff --git a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs index 60be5bef9..65bea6c32 100644 --- a/Source/Editor/SceneGraph/Actors/StaticModelNode.cs +++ b/Source/Editor/SceneGraph/Actors/StaticModelNode.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using FlaxEditor.Content; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Windows; +using FlaxEditor.Windows.Assets; using FlaxEngine; namespace FlaxEditor.SceneGraph.Actors @@ -84,76 +85,109 @@ namespace FlaxEditor.SceneGraph.Actors { base.OnContextMenu(contextMenu, window); - contextMenu.AddButton("Add collider", OnAddMeshCollider).Enabled = ((StaticModel)Actor).Model != null; + contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null; } - private void OnAddMeshCollider() + private void OnAddMeshCollider(EditorWindow window) { - var model = ((StaticModel)Actor).Model; - if (!model) - return; - - // Special case for in-built Editor models that can use analytical collision - var modelPath = model.Path; - if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal)) + // Allow collider to be added to evey static model selection + SceneGraphNode[] selection = Array.Empty(); + if (window is SceneTreeWindow) { - var actor = new BoxCollider - { - StaticFlags = Actor.StaticFlags, - Transform = Actor.Transform, - }; - Root.Spawn(actor, Actor); - return; + selection = Editor.Instance.SceneEditing.Selection.ToArray(); } - if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal)) + else if (window is PrefabWindow prefabWindow) { - var actor = new SphereCollider - { - StaticFlags = Actor.StaticFlags, - Transform = Actor.Transform, - }; - Root.Spawn(actor, Actor); - return; - } - if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal)) - { - var actor = new BoxCollider - { - StaticFlags = Actor.StaticFlags, - Transform = Actor.Transform, - Size = new Float3(100.0f, 100.0f, 1.0f), - }; - Root.Spawn(actor, Actor); - return; - } - if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal)) - { - var actor = new CapsuleCollider - { - StaticFlags = Actor.StaticFlags, - Transform = Actor.Transform, - Radius = 25.0f, - Height = 50.0f, - }; - Editor.Instance.SceneEditing.Spawn(actor, Actor); - actor.LocalPosition = new Vector3(0, 50.0f, 0); - actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f); - return; + selection = prefabWindow.Selection.ToArray(); } - // Create collision data (or reuse) and add collision actor - Action created = collisionData => + var createdNodes = new List(); + foreach (var node in selection) { - var actor = new MeshCollider + if (node is not StaticModelNode staticModelNode) + continue; + + var model = ((StaticModel)staticModelNode.Actor).Model; + if (!model) + continue; + + // Special case for in-built Editor models that can use analytical collision + var modelPath = model.Path; + if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal)) { - StaticFlags = Actor.StaticFlags, - Transform = Actor.Transform, - CollisionData = collisionData, + var actor = new BoxCollider + { + StaticFlags = staticModelNode.Actor.StaticFlags, + Transform = staticModelNode.Actor.Transform, + }; + staticModelNode.Root.Spawn(actor, staticModelNode.Actor); + createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); + continue; + } + if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal)) + { + var actor = new SphereCollider + { + StaticFlags = staticModelNode.Actor.StaticFlags, + Transform = staticModelNode.Actor.Transform, + }; + staticModelNode.Root.Spawn(actor, staticModelNode.Actor); + createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); + continue; + } + if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal)) + { + var actor = new BoxCollider + { + StaticFlags = staticModelNode.Actor.StaticFlags, + Transform = staticModelNode.Actor.Transform, + Size = new Float3(100.0f, 100.0f, 1.0f), + }; + staticModelNode.Root.Spawn(actor, staticModelNode.Actor); + createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); + continue; + } + if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal)) + { + var actor = new CapsuleCollider + { + StaticFlags = staticModelNode.Actor.StaticFlags, + Transform = staticModelNode.Actor.Transform, + Radius = 25.0f, + Height = 50.0f, + }; + Editor.Instance.SceneEditing.Spawn(actor, staticModelNode.Actor); + actor.LocalPosition = new Vector3(0, 50.0f, 0); + actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f); + createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); + continue; + } + + // Create collision data (or reuse) and add collision actor + Action created = collisionData => + { + var actor = new MeshCollider + { + StaticFlags = staticModelNode.Actor.StaticFlags, + Transform = staticModelNode.Actor.Transform, + CollisionData = collisionData, + }; + staticModelNode.Root.Spawn(actor, staticModelNode.Actor); + createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor)); }; - Root.Spawn(actor, Actor); - }; - var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy(); - collisionDataProxy.CreateCollisionDataFromModel(model, created); + var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy(); + collisionDataProxy.CreateCollisionDataFromModel(model, created, selection.Length == 1); + } + + // Select all created nodes + if (window is SceneTreeWindow) + { + Editor.Instance.SceneEditing.Select(createdNodes); + } + else if (window is PrefabWindow pWindow) + { + pWindow.Select(createdNodes); + } } } } diff --git a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs index 2747e1023..1178f54de 100644 --- a/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs +++ b/Source/Editor/Surface/Archetypes/Animation.MultiBlend.cs @@ -617,8 +617,9 @@ namespace FlaxEditor.Surface.Archetypes public override void SetLocation(int index, Float2 location) { var dataA = (Float4)_node.Values[4 + index * 2]; + var ranges = (Float4)_node.Values[0]; - dataA.X = location.X; + dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); _node.Values[4 + index * 2] = dataA; _node.Surface.MarkAsEdited(); @@ -750,9 +751,10 @@ namespace FlaxEditor.Surface.Archetypes public override void SetLocation(int index, Float2 location) { var dataA = (Float4)_node.Values[4 + index * 2]; + var ranges = (Float4)_node.Values[0]; - dataA.X = location.X; - dataA.Y = location.Y; + dataA.X = Mathf.Clamp(location.X, ranges.X, ranges.Y); + dataA.Y = Mathf.Clamp(location.Y, ranges.Z, ranges.W); _node.Values[4 + index * 2] = dataA; _node.Surface.MarkAsEdited(); diff --git a/Source/Editor/Surface/AttributesEditor.cs b/Source/Editor/Surface/AttributesEditor.cs index 1da73e7c2..dda4c184c 100644 --- a/Source/Editor/Surface/AttributesEditor.cs +++ b/Source/Editor/Surface/AttributesEditor.cs @@ -117,17 +117,9 @@ namespace FlaxEditor.Surface editor.Panel.Tag = attributeType; _presenter = editor; - using (var stream = new MemoryStream()) - { - // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) - using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); + // Cache 'previous' state to check if attributes were edited after operation + _oldData = SurfaceMeta.GetAttributesData(attributes); - var formatter = new BinaryFormatter(); -#pragma warning disable SYSLIB0011 - formatter.Serialize(stream, attributes); -#pragma warning restore SYSLIB0011 - _oldData = stream.ToArray(); - } editor.Select(new Proxy { Value = attributes, @@ -145,20 +137,11 @@ namespace FlaxEditor.Surface return; } } - using (var stream = new MemoryStream()) - { - // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) - using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); - var formatter = new BinaryFormatter(); -#pragma warning disable SYSLIB0011 - formatter.Serialize(stream, newValue); -#pragma warning restore SYSLIB0011 - var newData = stream.ToArray(); - if (!_oldData.SequenceEqual(newData)) - { - Edited?.Invoke(newValue); - } + var newData = SurfaceMeta.GetAttributesData(newValue); + if (!_oldData.SequenceEqual(newData)) + { + Edited?.Invoke(newValue); } Hide(); diff --git a/Source/Editor/Surface/SurfaceMeta.cs b/Source/Editor/Surface/SurfaceMeta.cs index 2c1b674d4..89a55c3e2 100644 --- a/Source/Editor/Surface/SurfaceMeta.cs +++ b/Source/Editor/Surface/SurfaceMeta.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.Loader; using System.Runtime.Serialization.Formatters.Binary; +using System.Text; using FlaxEngine; namespace FlaxEditor.Surface @@ -39,28 +39,48 @@ namespace FlaxEditor.Surface public readonly List Entries = new List(); /// - /// The attribute meta type identifier. + /// The attribute meta type identifier. Uses byte[] as storage for Attribute[] serialized with BinaryFormatter (deprecated in .NET 5). + /// [Deprecated on 8.12.2023, expires on 8.12.2025] /// - public const int AttributeMetaTypeID = 12; + public const int OldAttributeMetaTypeID = 12; + + /// + /// The attribute meta type identifier. Uses byte[] as storage for Attribute[] serialized with JsonSerializer. + /// + public const int AttributeMetaTypeID = 13; /// /// Gets the attributes collection from the data. /// - /// The graph metadata. + /// The graph metadata serialized with JsonSerializer. + /// The graph metadata serialized with BinaryFormatter. /// The attributes collection. - public static Attribute[] GetAttributes(byte[] data) + public static Attribute[] GetAttributes(byte[] data, byte[] oldData) { if (data != null && data.Length != 0) { - using (var stream = new MemoryStream(data)) + try + { + var json = Encoding.Unicode.GetString(data); + return FlaxEngine.Json.JsonSerializer.Deserialize(json); + } + catch (Exception ex) + { + Editor.LogError("Failed to deserialize Visject attributes array."); + Editor.LogWarning(ex); + } + } + if (oldData != null && oldData.Length != 0) + { + // [Deprecated on 8.12.2023, expires on 8.12.2025] + using (var stream = new MemoryStream(oldData)) { try { // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); - - var formatter = new BinaryFormatter(); #pragma warning disable SYSLIB0011 + var formatter = new BinaryFormatter(); return (Attribute[])formatter.Deserialize(stream); #pragma warning restore SYSLIB0011 } @@ -74,6 +94,21 @@ namespace FlaxEditor.Surface return Utils.GetEmptyArray(); } + /// + /// Serializes surface attributes into byte[] data using the current format. + /// + /// The input attributes. + /// The result array with bytes. Can be empty but not null. + internal static byte[] GetAttributesData(Attribute[] attributes) + { + if (attributes != null && attributes.Length != 0) + { + var json = FlaxEngine.Json.JsonSerializer.Serialize(attributes); + return Encoding.Unicode.GetBytes(json); + } + return Utils.GetEmptyArray(); + } + /// /// Determines whether the specified attribute was defined for this member. /// @@ -93,7 +128,8 @@ namespace FlaxEditor.Surface public static Attribute[] GetAttributes(GraphParameter parameter) { var data = parameter.GetMetaData(AttributeMetaTypeID); - return GetAttributes(data); + var dataOld = parameter.GetMetaData(OldAttributeMetaTypeID); + return GetAttributes(data, dataOld); } /// @@ -102,12 +138,7 @@ namespace FlaxEditor.Surface /// The attributes collection. public Attribute[] GetAttributes() { - for (int i = 0; i < Entries.Count; i++) - { - if (Entries[i].TypeID == AttributeMetaTypeID) - return GetAttributes(Entries[i].Data); - } - return Utils.GetEmptyArray(); + return GetAttributes(GetEntry(AttributeMetaTypeID).Data, GetEntry(OldAttributeMetaTypeID).Data); } /// @@ -119,25 +150,12 @@ namespace FlaxEditor.Surface if (attributes == null || attributes.Length == 0) { RemoveEntry(AttributeMetaTypeID); + RemoveEntry(OldAttributeMetaTypeID); } else { - for (int i = 0; i < attributes.Length; i++) - { - if (attributes[i] == null) - throw new NullReferenceException("One of the Visject attributes is null."); - } - using (var stream = new MemoryStream()) - { - // Ensure we are in the correct load context (https://github.com/dotnet/runtime/issues/42041) - using var ctx = AssemblyLoadContext.EnterContextualReflection(typeof(Editor).Assembly); - - var formatter = new BinaryFormatter(); -#pragma warning disable SYSLIB0011 - formatter.Serialize(stream, attributes); -#pragma warning restore SYSLIB0011 - AddEntry(AttributeMetaTypeID, stream.ToArray()); - } + AddEntry(AttributeMetaTypeID, GetAttributesData(attributes)); + RemoveEntry(OldAttributeMetaTypeID); } } @@ -180,11 +198,11 @@ namespace FlaxEditor.Surface /// True if cannot save data public void Save(BinaryWriter stream) { - stream.Write(Entries.Count); - - for (int i = 0; i < Entries.Count; i++) + var entries = Entries; + stream.Write(entries.Count); + for (int i = 0; i < entries.Count; i++) { - Entry e = Entries[i]; + Entry e = entries[i]; stream.Write(e.TypeID); stream.Write((long)0); @@ -214,14 +232,12 @@ namespace FlaxEditor.Surface /// Entry public Entry GetEntry(int typeID) { - for (int i = 0; i < Entries.Count; i++) + var entries = Entries; + for (int i = 0; i < entries.Count; i++) { - if (Entries[i].TypeID == typeID) - { - return Entries[i]; - } + if (entries[i].TypeID == typeID) + return entries[i]; } - return new Entry(); } diff --git a/Source/Editor/Surface/VisjectSurface.Draw.cs b/Source/Editor/Surface/VisjectSurface.Draw.cs index 13da1fb97..59a56e3f3 100644 --- a/Source/Editor/Surface/VisjectSurface.Draw.cs +++ b/Source/Editor/Surface/VisjectSurface.Draw.cs @@ -64,7 +64,11 @@ namespace FlaxEditor.Surface /// protected virtual void DrawBackground() { - var background = Style.Background; + DrawBackgroundDefault(Style.Background, Width, Height); + } + + internal static void DrawBackgroundDefault(Texture background, float width, float height) + { if (background && background.ResidentMipLevels > 0) { var bSize = background.Size; @@ -77,8 +81,8 @@ namespace FlaxEditor.Surface if (pos.Y > 0) pos.Y -= bh; - int maxI = Mathf.CeilToInt(Width / bw + 1.0f); - int maxJ = Mathf.CeilToInt(Height / bh + 1.0f); + int maxI = Mathf.CeilToInt(width / bw + 1.0f); + int maxJ = Mathf.CeilToInt(height / bh + 1.0f); for (int i = 0; i < maxI; i++) { diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index da0723622..0fc614af5 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -10,6 +10,7 @@ #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Core/Config/GameSettings.h" +#include "Engine/Core/Config/BuildSettings.h" #include "Engine/Content/Content.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/Assets/Texture.h" @@ -17,7 +18,18 @@ #if PLATFORM_MAC #include "Engine/Platform/Apple/ApplePlatformSettings.h" #endif -#include + +String EditorUtilities::GetOutputName() +{ + const auto gameSettings = GameSettings::Get(); + const auto buildSettings = BuildSettings::Get(); + String outputName = buildSettings->OutputName; + outputName.Replace(TEXT("${PROJECT_NAME}"), *gameSettings->ProductName, StringSearchCase::IgnoreCase); + outputName.Replace(TEXT("${COMPANY_NAME}"), *gameSettings->CompanyName, StringSearchCase::IgnoreCase); + if (outputName.IsEmpty()) + outputName = TEXT("FlaxGame"); + return outputName; +} bool EditorUtilities::FormatAppPackageName(String& packageName) { diff --git a/Source/Editor/Utilities/EditorUtilities.h b/Source/Editor/Utilities/EditorUtilities.h index b0cbff071..2357f995a 100644 --- a/Source/Editor/Utilities/EditorUtilities.h +++ b/Source/Editor/Utilities/EditorUtilities.h @@ -22,6 +22,7 @@ public: SplashScreen, }; + static String GetOutputName(); static bool FormatAppPackageName(String& packageName); static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon); static bool GetTexture(const Guid& textureId, TextureData& textureData); diff --git a/Source/Editor/Utilities/ShuntingYardParser.cs b/Source/Editor/Utilities/ShuntingYardParser.cs index aa435416f..5ba8e969c 100644 --- a/Source/Editor/Utilities/ShuntingYardParser.cs +++ b/Source/Editor/Utilities/ShuntingYardParser.cs @@ -121,6 +121,37 @@ namespace FlaxEditor.Utilities ["e"] = Math.E, ["infinity"] = double.MaxValue, ["-infinity"] = -double.MaxValue, + ["m"] = Units.Meters2Units, + ["cm"] = Units.Meters2Units / 100, + ["km"] = Units.Meters2Units * 1000, + ["s"] = 1, + ["ms"] = 0.001, + ["min"] = 60, + ["h"] = 3600, + ["cm²"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100), + ["cm³"] = (Units.Meters2Units / 100) * (Units.Meters2Units / 100) * (Units.Meters2Units / 100), + ["dm²"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10), + ["dm³"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10), + ["l"] = (Units.Meters2Units / 10) * (Units.Meters2Units / 10) * (Units.Meters2Units / 10), + ["m²"] = Units.Meters2Units * Units.Meters2Units, + ["m³"] = Units.Meters2Units * Units.Meters2Units * Units.Meters2Units, + ["kg"] = 1, + ["g"] = 0.001, + ["n"] = Units.Meters2Units + }; + + /// + /// List known units which cannot be handled as a variable easily because they contain operator symbols (mostly a forward slash). The value is the factor to calculate game units. + /// + private static readonly IDictionary UnitSymbols = new Dictionary + { + ["cm/s"] = Units.Meters2Units / 100, + ["cm/s²"] = Units.Meters2Units / 100, + ["m/s"] = Units.Meters2Units, + ["m/s²"] = Units.Meters2Units, + ["km/h"] = 1 / 3.6 * Units.Meters2Units, + // Nm is here because these values are compared case-sensitive, and we don't want to confuse nanometers and Newtonmeters + ["Nm"] = Units.Meters2Units * Units.Meters2Units, }; /// @@ -156,7 +187,7 @@ namespace FlaxEditor.Utilities if (Operators.ContainsKey(str)) return TokenType.Operator; - if (char.IsLetter(c)) + if (char.IsLetter(c) || c == '²' || c == '³') return TokenType.Variable; throw new ParsingException("wrong character"); @@ -170,7 +201,24 @@ namespace FlaxEditor.Utilities public static IEnumerable Tokenize(string text) { // Prepare text - text = text.Replace(',', '.'); + text = text.Replace(',', '.').Replace("°", ""); + foreach (var kv in UnitSymbols) + { + int idx; + do + { + idx = text.IndexOf(kv.Key, StringComparison.InvariantCulture); + if (idx > 0) + { + if (DetermineType(text[idx - 1]) != TokenType.Number) + throw new ParsingException($"unit found without a number: {kv.Key} at {idx} in {text}"); + if (Mathf.Abs(kv.Value - 1) < Mathf.Epsilon) + text = text.Remove(idx, kv.Key.Length); + else + text = text.Replace(kv.Key, "*" + kv.Value); + } + } while (idx > 0); + } // Necessary to correctly parse negative numbers var previous = TokenType.WhiteSpace; @@ -240,6 +288,11 @@ namespace FlaxEditor.Utilities } else if (type == TokenType.Variable) { + if (previous == TokenType.Number) + { + previous = TokenType.Operator; + yield return new Token(TokenType.Operator, "*"); + } // Continue till the end of the variable while (i + 1 < text.Length && DetermineType(text[i + 1]) == TokenType.Variable) { @@ -335,7 +388,7 @@ namespace FlaxEditor.Utilities } else { - throw new ParsingException("unknown variable"); + throw new ParsingException($"unknown variable : {token.Value}"); } } else @@ -372,6 +425,15 @@ namespace FlaxEditor.Utilities } } + // if stack has more than one item we're not finished with evaluating + // we assume the remaining values are all factors to be multiplied + if (stack.Count > 1) + { + var v1 = stack.Pop(); + while (stack.Count > 0) + v1 *= stack.Pop(); + return v1; + } return stack.Pop(); } diff --git a/Source/Editor/Utilities/Units.cs b/Source/Editor/Utilities/Units.cs new file mode 100644 index 000000000..64977c18b --- /dev/null +++ b/Source/Editor/Utilities/Units.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +namespace FlaxEditor.Utilities; + +/// +/// Units display utilities for Editor. +/// +public class Units +{ + /// + /// Factor of units per meter. + /// + public static readonly float Meters2Units = 100f; + + /// + /// False to always show game units without any postfix. + /// + public static bool UseUnitsFormatting = true; + + /// + /// Add a space between numbers and units for readability. + /// + public static bool SeparateValueAndUnit = true; + + /// + /// If set to true, the distance unit is chosen on the magnitude, otherwise it's meters. + /// + public static bool AutomaticUnitsFormatting = true; + + /// + /// Return the unit according to user settings. + /// + /// The unit name. + /// The formatted text. + public static string Unit(string unit) + { + if (SeparateValueAndUnit) + return $" {unit}"; + return unit; + } +} diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index b403c1489..1b76de47e 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -243,6 +243,63 @@ namespace FlaxEditor.Utilities 500000, 1000000, 5000000, 10000000, 100000000 }; + internal delegate void DrawCurveTick(float tick, float strength); + + internal static Int2 DrawCurveTicks(DrawCurveTick drawTick, float[] tickSteps, ref float[] tickStrengths, float min, float max, float pixelRange, float minDistanceBetweenTicks = 20, float maxDistanceBetweenTicks = 60) + { + if (tickStrengths == null || tickStrengths.Length != tickSteps.Length) + tickStrengths = new float[tickSteps.Length]; + + // Find the strength for each modulo number tick marker + var pixelsInRange = pixelRange / (max - min); + var smallestTick = 0; + var biggestTick = tickSteps.Length - 1; + for (int i = tickSteps.Length - 1; i >= 0; i--) + { + // Calculate how far apart these modulo tick steps are spaced + float tickSpacing = tickSteps[i] * pixelsInRange; + + // Calculate the strength of the tick markers based on the spacing + tickStrengths[i] = Mathf.Saturate((tickSpacing - minDistanceBetweenTicks) / (maxDistanceBetweenTicks - minDistanceBetweenTicks)); + + // Beyond threshold the ticks don't get any bigger or fatter + if (tickStrengths[i] >= 1) + biggestTick = i; + + // Do not show small tick markers + if (tickSpacing <= minDistanceBetweenTicks) + { + smallestTick = i; + break; + } + } + var tickLevels = biggestTick - smallestTick + 1; + + // Draw all tick levels + for (int level = 0; level < tickLevels; level++) + { + float strength = tickStrengths[smallestTick + level]; + if (strength <= Mathf.Epsilon) + continue; + + // Draw all ticks + int l = Mathf.Clamp(smallestTick + level, 0, tickSteps.Length - 1); + var lStep = tickSteps[l]; + var lNextStep = tickSteps[l + 1]; + int startTick = Mathf.FloorToInt(min / lStep); + int endTick = Mathf.CeilToInt(max / lStep); + for (int i = startTick; i <= endTick; i++) + { + if (l < biggestTick && (i % Mathf.RoundToInt(lNextStep / lStep) == 0)) + continue; + var tick = i * lStep; + drawTick(tick, strength); + } + } + + return new Int2(smallestTick, biggestTick); + } + /// /// Determines whether the specified path string contains any invalid character. /// @@ -1187,6 +1244,71 @@ namespace FlaxEditor.Utilities return StringUtils.GetPathWithoutExtension(path); } + private static string InternalFormat(double value, string format, FlaxEngine.Utils.ValueCategory category) + { + switch (category) + { + case FlaxEngine.Utils.ValueCategory.Distance: + if (!Units.AutomaticUnitsFormatting) + return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m"); + var absValue = Mathf.Abs(value); + // in case a unit != cm this would be (value / Meters2Units * 100) + if (absValue < Units.Meters2Units) + return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("cm"); + if (absValue < Units.Meters2Units * 1000) + return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m"); + return (value / 1000 / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("km"); + case FlaxEngine.Utils.ValueCategory.Angle: return value.ToString(format, CultureInfo.InvariantCulture) + "°"; + case FlaxEngine.Utils.ValueCategory.Time: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("s"); + // some fonts have a symbol for that: "\u33A7" + case FlaxEngine.Utils.ValueCategory.Speed: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s"); + case FlaxEngine.Utils.ValueCategory.Acceleration: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m/s²"); + case FlaxEngine.Utils.ValueCategory.Area: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m²"); + case FlaxEngine.Utils.ValueCategory.Volume: return (value / Units.Meters2Units / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("m³"); + case FlaxEngine.Utils.ValueCategory.Mass: return value.ToString(format, CultureInfo.InvariantCulture) + Units.Unit("kg"); + case FlaxEngine.Utils.ValueCategory.Force: return (value / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("N"); + case FlaxEngine.Utils.ValueCategory.Torque: return (value / Units.Meters2Units / Units.Meters2Units).ToString(format, CultureInfo.InvariantCulture) + Units.Unit("Nm"); + case FlaxEngine.Utils.ValueCategory.None: + default: return FormatFloat(value); + } + } + + /// + /// Format a float value either as-is, with a distance unit or with a degree sign. + /// + /// The value to format. + /// The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign. + /// The formatted string. + public static string FormatFloat(float value, FlaxEngine.Utils.ValueCategory category) + { + if (float.IsPositiveInfinity(value) || value == float.MaxValue) + return "Infinity"; + if (float.IsNegativeInfinity(value) || value == float.MinValue) + return "-Infinity"; + if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None) + return FormatFloat(value); + const string format = "G7"; + return InternalFormat(value, format, category); + } + + /// + /// Format a double value either as-is, with a distance unit or with a degree sign + /// + /// The value to format. + /// The value type: none means just a number, distance will format in cm/m/km, angle with an appended degree sign. + /// The formatted string. + public static string FormatFloat(double value, FlaxEngine.Utils.ValueCategory category) + { + if (double.IsPositiveInfinity(value) || value == double.MaxValue) + return "Infinity"; + if (double.IsNegativeInfinity(value) || value == double.MinValue) + return "-Infinity"; + if (!Units.UseUnitsFormatting || category == FlaxEngine.Utils.ValueCategory.None) + return FormatFloat(value); + const string format = "G15"; + return InternalFormat(value, format, category); + } + /// /// Formats the floating point value (double precision) into the readable text representation. /// @@ -1198,7 +1320,7 @@ namespace FlaxEditor.Utilities return "Infinity"; if (float.IsNegativeInfinity(value) || value == float.MinValue) return "-Infinity"; - string str = value.ToString("r", CultureInfo.InvariantCulture); + string str = value.ToString("R", CultureInfo.InvariantCulture); return FormatFloat(str, value < 0); } @@ -1213,7 +1335,7 @@ namespace FlaxEditor.Utilities return "Infinity"; if (double.IsNegativeInfinity(value) || value == double.MinValue) return "-Infinity"; - string str = value.ToString("r", CultureInfo.InvariantCulture); + string str = value.ToString("R", CultureInfo.InvariantCulture); return FormatFloat(str, value < 0); } diff --git a/Source/Editor/Utilities/ViewportIconsRenderer.cpp b/Source/Editor/Utilities/ViewportIconsRenderer.cpp index d3cd2c940..797b4edea 100644 --- a/Source/Editor/Utilities/ViewportIconsRenderer.cpp +++ b/Source/Editor/Utilities/ViewportIconsRenderer.cpp @@ -187,6 +187,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor* actor, Mesh::DrawInfo& draw) { + if (!actor || !actor->IsActiveInHierarchy()) + return; auto& view = renderContext.View; const BoundingFrustum frustum = view.Frustum; Matrix m1, m2, world; @@ -208,8 +210,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor draw.DrawState = &drawState; draw.Deformation = nullptr; - // Support custom icons through types, but not onces that were added through actors, - // since they cant register while in prefab view anyway + // Support custom icons through types, but not ones that were added through actors, since they cant register while in prefab view anyway if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture)) { // Use custom texture diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index 83fc49cd6..45770b007 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -6,9 +6,7 @@ using Real = System.Double; using Real = System.Single; #endif -using System.Collections.Generic; using FlaxEditor.Gizmo; -using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Viewport.Cameras @@ -85,86 +83,8 @@ namespace FlaxEditor.Viewport.Cameras _moveStartTime = Time.UnscaledGameTime; } - /// - /// Moves the viewport to visualize the actor. - /// - /// The actor to preview. - public void ShowActor(Actor actor) - { - Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); - ShowSphere(ref sphere); - } - - /// - /// Moves the viewport to visualize selected actors. - /// - /// The actors to show. - /// The used orientation. - public void ShowActor(Actor actor, ref Quaternion orientation) - { - Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); - ShowSphere(ref sphere, ref orientation); - } - - /// - /// Moves the viewport to visualize selected actors. - /// - /// The actors to show. - public void ShowActors(List selection) - { - if (selection.Count == 0) - return; - - BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < selection.Count; i++) - { - selection[i].GetEditorSphere(out var sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } - - if (mergesSphere == BoundingSphere.Empty) - return; - ShowSphere(ref mergesSphere); - } - - /// - /// Moves the viewport to visualize selected actors. - /// - /// The actors to show. - /// The used orientation. - public void ShowActors(List selection, ref Quaternion orientation) - { - if (selection.Count == 0) - return; - - BoundingSphere mergesSphere = BoundingSphere.Empty; - for (int i = 0; i < selection.Count; i++) - { - selection[i].GetEditorSphere(out var sphere); - BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); - } - - if (mergesSphere == BoundingSphere.Empty) - return; - ShowSphere(ref mergesSphere, ref orientation); - } - - /// - /// Moves the camera to visualize given world area defined by the sphere. - /// - /// The sphere. - public void ShowSphere(ref BoundingSphere sphere) - { - var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); - ShowSphere(ref sphere, ref q); - } - - /// - /// Moves the camera to visualize given world area defined by the sphere. - /// - /// The sphere. - /// The camera orientation. - public void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) + /// + public override void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) { Vector3 position; if (Viewport.UseOrthographicProjection) diff --git a/Source/Editor/Viewport/Cameras/ViewportCamera.cs b/Source/Editor/Viewport/Cameras/ViewportCamera.cs index 5e25a29bb..f8019a481 100644 --- a/Source/Editor/Viewport/Cameras/ViewportCamera.cs +++ b/Source/Editor/Viewport/Cameras/ViewportCamera.cs @@ -6,6 +6,9 @@ using Real = System.Double; using Real = System.Single; #endif +using System.Collections.Generic; +using FlaxEditor.Gizmo; +using FlaxEditor.SceneGraph; using FlaxEngine; namespace FlaxEditor.Viewport.Cameras @@ -33,6 +36,90 @@ namespace FlaxEditor.Viewport.Cameras /// public virtual bool UseMovementSpeed => true; + /// + /// Focuses the viewport on the current selection of the gizmo. + /// + /// The gizmo collection (from viewport). + /// The target view orientation. + public virtual void FocusSelection(GizmosCollection gizmos, ref Quaternion orientation) + { + var transformGizmo = gizmos.Get(); + if (transformGizmo == null || transformGizmo.SelectedParents.Count == 0) + return; + if (gizmos.Active != null) + { + var gizmoBounds = gizmos.Active.FocusBounds; + if (gizmoBounds != BoundingSphere.Empty) + { + ShowSphere(ref gizmoBounds, ref orientation); + return; + } + } + ShowActors(transformGizmo.SelectedParents, ref orientation); + } + + /// + /// Moves the viewport to visualize the actor. + /// + /// The actor to preview. + public virtual void ShowActor(Actor actor) + { + Editor.GetActorEditorSphere(actor, out BoundingSphere sphere); + ShowSphere(ref sphere); + } + + /// + /// Moves the viewport to visualize selected actors. + /// + /// The actors to show. + public void ShowActors(List selection) + { + var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); + ShowActors(selection, ref q); + } + + /// + /// Moves the viewport to visualize selected actors. + /// + /// The actors to show. + /// The used orientation. + public virtual void ShowActors(List selection, ref Quaternion orientation) + { + if (selection.Count == 0) + return; + + BoundingSphere mergesSphere = BoundingSphere.Empty; + for (int i = 0; i < selection.Count; i++) + { + selection[i].GetEditorSphere(out var sphere); + BoundingSphere.Merge(ref mergesSphere, ref sphere, out mergesSphere); + } + + if (mergesSphere == BoundingSphere.Empty) + return; + ShowSphere(ref mergesSphere, ref orientation); + } + + /// + /// Moves the camera to visualize given world area defined by the sphere. + /// + /// The sphere. + public void ShowSphere(ref BoundingSphere sphere) + { + var q = new Quaternion(0.424461186f, -0.0940724313f, 0.0443938486f, 0.899451137f); + ShowSphere(ref sphere, ref q); + } + + /// + /// Moves the camera to visualize given world area defined by the sphere. + /// + /// The sphere. + /// The camera orientation. + public virtual void ShowSphere(ref BoundingSphere sphere, ref Quaternion orientation) + { + SetArcBallView(orientation, sphere.Center, sphere.Radius); + } + /// /// Sets view orientation and position to match the arc ball camera style view for the given target object bounds. /// diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index 8f038ce17..9fb3d962e 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -1,9 +1,12 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +using System; using System.Collections.Generic; using FlaxEditor.Gizmo; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Viewport.Cameras; +using FlaxEditor.Viewport.Widgets; using FlaxEngine; using FlaxEngine.GUI; @@ -41,6 +44,7 @@ namespace FlaxEditor.Viewport Gizmos[i].Update(deltaTime); } } + /// public EditorViewport Viewport => this; @@ -66,19 +70,19 @@ namespace FlaxEditor.Viewport public bool IsControlDown => _input.IsControlDown; /// - public bool SnapToGround => Editor.Instance.Options.Options.Input.SnapToGround.Process(Root); + public bool SnapToGround => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToGround.Process(Root); /// - public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); + public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); /// public Float2 MouseDelta => _mouseDelta * 1000; /// - public bool UseSnapping => Root.GetKey(KeyboardKeys.Control); + public bool UseSnapping => Root?.GetKey(KeyboardKeys.Control) ?? false; /// - public bool UseDuplicate => Root.GetKey(KeyboardKeys.Shift); + public bool UseDuplicate => Root?.GetKey(KeyboardKeys.Shift) ?? false; /// public Undo Undo { get; } @@ -92,6 +96,9 @@ namespace FlaxEditor.Viewport /// public abstract void Spawn(Actor actor); + /// + public abstract void OpenContextMenu(); + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; @@ -121,5 +128,284 @@ namespace FlaxEditor.Viewport base.OnDestroy(); } + + internal static void AddGizmoViewportWidgets(EditorViewport viewport, TransformGizmo transformGizmo, bool useProjectCache = false) + { + var editor = Editor.Instance; + var inputOptions = editor.Options.Options.Input; + + if (useProjectCache) + { + // Initialize snapping enabled from cached values + if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) + transformGizmo.TranslationSnapEnable = bool.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState)) + transformGizmo.RotationSnapEnabled = bool.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState)) + transformGizmo.ScaleSnapEnabled = bool.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState)) + transformGizmo.TranslationSnapValue = float.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState)) + transformGizmo.RotationSnapValue = float.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState)) + transformGizmo.ScaleSnapValue = float.Parse(cachedState); + if (editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space)) + transformGizmo.ActiveTransformSpace = space; + } + + // Transform space widget + var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) + { + Checked = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, + TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", + Parent = transformSpaceWidget + }; + transformSpaceToggle.Toggled += _ => + { + transformGizmo.ToggleTransformSpace(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); + }; + transformSpaceWidget.Parent = viewport; + + // Scale snapping widget + var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) + { + Checked = transformGizmo.ScaleSnapEnabled, + TooltipText = "Enable scale snapping", + Parent = scaleSnappingWidget + }; + enableScaleSnapping.Toggled += _ => + { + transformGizmo.ScaleSnapEnabled = !transformGizmo.ScaleSnapEnabled; + if (useProjectCache) + editor.ProjectCache.SetCustomData("ScaleSnapState", transformGizmo.ScaleSnapEnabled.ToString()); + }; + var scaleSnappingCM = new ContextMenu(); + var scaleSnapping = new ViewportWidgetButton(transformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) + { + TooltipText = "Scale snapping values" + }; + for (int i = 0; i < ScaleSnapValues.Length; i++) + { + var v = ScaleSnapValues[i]; + var button = scaleSnappingCM.AddButton(v.ToString()); + button.Tag = v; + } + scaleSnappingCM.ButtonClicked += button => + { + var v = (float)button.Tag; + transformGizmo.ScaleSnapValue = v; + scaleSnapping.Text = v.ToString(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("ScaleSnapValue", transformGizmo.ScaleSnapValue.ToString("N")); + }; + scaleSnappingCM.VisibleChanged += control => + { + if (control.Visible == false) + return; + var ccm = (ContextMenu)control; + foreach (var e in ccm.Items) + { + if (e is ContextMenuButton b) + { + var v = (float)b.Tag; + b.Icon = Mathf.Abs(transformGizmo.ScaleSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + } + } + }; + scaleSnapping.Parent = scaleSnappingWidget; + scaleSnappingWidget.Parent = viewport; + + // Rotation snapping widget + var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true) + { + Checked = transformGizmo.RotationSnapEnabled, + TooltipText = "Enable rotation snapping", + Parent = rotateSnappingWidget + }; + enableRotateSnapping.Toggled += _ => + { + transformGizmo.RotationSnapEnabled = !transformGizmo.RotationSnapEnabled; + if (useProjectCache) + editor.ProjectCache.SetCustomData("RotationSnapState", transformGizmo.RotationSnapEnabled.ToString()); + }; + var rotateSnappingCM = new ContextMenu(); + var rotateSnapping = new ViewportWidgetButton(transformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) + { + TooltipText = "Rotation snapping values" + }; + for (int i = 0; i < RotateSnapValues.Length; i++) + { + var v = RotateSnapValues[i]; + var button = rotateSnappingCM.AddButton(v.ToString()); + button.Tag = v; + } + rotateSnappingCM.ButtonClicked += button => + { + var v = (float)button.Tag; + transformGizmo.RotationSnapValue = v; + rotateSnapping.Text = v.ToString(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("RotationSnapValue", transformGizmo.RotationSnapValue.ToString("N")); + }; + rotateSnappingCM.VisibleChanged += control => + { + if (control.Visible == false) + return; + var ccm = (ContextMenu)control; + foreach (var e in ccm.Items) + { + if (e is ContextMenuButton b) + { + var v = (float)b.Tag; + b.Icon = Mathf.Abs(transformGizmo.RotationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + } + } + }; + rotateSnapping.Parent = rotateSnappingWidget; + rotateSnappingWidget.Parent = viewport; + + // Translation snapping widget + var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true) + { + Checked = transformGizmo.TranslationSnapEnable, + TooltipText = "Enable position snapping", + Parent = translateSnappingWidget + }; + enableTranslateSnapping.Toggled += _ => + { + transformGizmo.TranslationSnapEnable = !transformGizmo.TranslationSnapEnable; + if (useProjectCache) + editor.ProjectCache.SetCustomData("TranslateSnapState", transformGizmo.TranslationSnapEnable.ToString()); + }; + var translateSnappingCM = new ContextMenu(); + var translateSnapping = new ViewportWidgetButton(transformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) + { + TooltipText = "Position snapping values" + }; + if (transformGizmo.TranslationSnapValue < 0.0f) + translateSnapping.Text = "Bounding Box"; + for (int i = 0; i < TranslateSnapValues.Length; i++) + { + var v = TranslateSnapValues[i]; + var button = translateSnappingCM.AddButton(v.ToString()); + button.Tag = v; + } + var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); + buttonBB.Tag = -1.0f; + translateSnappingCM.ButtonClicked += button => + { + var v = (float)button.Tag; + transformGizmo.TranslationSnapValue = v; + if (v < 0.0f) + translateSnapping.Text = "Bounding Box"; + else + translateSnapping.Text = v.ToString(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TranslateSnapValue", transformGizmo.TranslationSnapValue.ToString("N")); + }; + translateSnappingCM.VisibleChanged += control => + { + if (control.Visible == false) + return; + var ccm = (ContextMenu)control; + foreach (var e in ccm.Items) + { + if (e is ContextMenuButton b) + { + var v = (float)b.Tag; + b.Icon = Mathf.Abs(transformGizmo.TranslationSnapValue - v) < 0.001f ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; + } + } + }; + translateSnapping.Parent = translateSnappingWidget; + translateSnappingWidget.Parent = viewport; + + // Gizmo mode widget + var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) + { + Tag = TransformGizmoBase.Mode.Translate, + TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", + Checked = true, + Parent = gizmoMode + }; + gizmoModeTranslate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate; + var gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) + { + Tag = TransformGizmoBase.Mode.Rotate, + TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", + Parent = gizmoMode + }; + gizmoModeRotate.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate; + var gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) + { + Tag = TransformGizmoBase.Mode.Scale, + TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", + Parent = gizmoMode + }; + gizmoModeScale.Toggled += _ => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale; + gizmoMode.Parent = viewport; + transformGizmo.ModeChanged += () => + { + var mode = transformGizmo.ActiveMode; + gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; + gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; + gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; + }; + + // Setup input actions + viewport.InputActions.Add(options => options.TranslateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); + viewport.InputActions.Add(options => options.RotateMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); + viewport.InputActions.Add(options => options.ScaleMode, () => transformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); + viewport.InputActions.Add(options => options.ToggleTransformSpace, () => + { + transformGizmo.ToggleTransformSpace(); + if (useProjectCache) + editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); + transformSpaceToggle.Checked = !transformSpaceToggle.Checked; + }); + } + + internal static readonly float[] TranslateSnapValues = + { + 0.1f, + 0.5f, + 1.0f, + 5.0f, + 10.0f, + 100.0f, + 1000.0f, + }; + + internal static readonly float[] RotateSnapValues = + { + 1.0f, + 5.0f, + 10.0f, + 15.0f, + 30.0f, + 45.0f, + 60.0f, + 90.0f, + }; + + internal static readonly float[] ScaleSnapValues = + { + 0.05f, + 0.1f, + 0.25f, + 0.5f, + 1.0f, + 2.0f, + 4.0f, + 6.0f, + 8.0f, + }; } } diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index fd7a83164..5bcc2c2b9 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -10,7 +10,6 @@ using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Widgets; using FlaxEngine; using FlaxEngine.GUI; -using Newtonsoft.Json; using JsonSerializer = FlaxEngine.Json.JsonSerializer; namespace FlaxEditor.Viewport @@ -154,6 +153,7 @@ namespace FlaxEditor.Viewport // Input + internal bool _disableInputUpdate; private bool _isControllingMouse, _isViewportControllingMouse, _wasVirtualMouseRightDown, _isVirtualMouseRightDown; private int _deltaFilteringStep; private Float2 _startPos; @@ -704,9 +704,9 @@ namespace FlaxEditor.Viewport // Camera Viewpoints { var cameraView = cameraCM.AddChildMenu("Viewpoints").ContextMenu; - for (int i = 0; i < EditorViewportCameraViewpointValues.Length; i++) + for (int i = 0; i < CameraViewpointValues.Length; i++) { - var co = EditorViewportCameraViewpointValues[i]; + var co = CameraViewpointValues[i]; var button = cameraView.AddButton(co.Name); button.Tag = co.Orientation; } @@ -899,9 +899,9 @@ namespace FlaxEditor.Viewport viewFlags.AddButton("Reset flags", () => Task.ViewFlags = ViewFlags.DefaultEditor).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddButton("Disable flags", () => Task.ViewFlags = ViewFlags.None).Icon = Editor.Instance.Icons.Rotate32; viewFlags.AddSeparator(); - for (int i = 0; i < EditorViewportViewFlagsValues.Length; i++) + for (int i = 0; i < ViewFlagsValues.Length; i++) { - var v = EditorViewportViewFlagsValues[i]; + var v = ViewFlagsValues[i]; var button = viewFlags.AddButton(v.Name); button.CloseMenuOnClick = false; button.Tag = v.Mode; @@ -933,9 +933,9 @@ namespace FlaxEditor.Viewport } }); debugView.AddSeparator(); - for (int i = 0; i < EditorViewportViewModeValues.Length; i++) + for (int i = 0; i < ViewModeValues.Length; i++) { - ref var v = ref EditorViewportViewModeValues[i]; + ref var v = ref ViewModeValues[i]; if (v.Options != null) { var childMenu = debugView.AddChildMenu(v.Name).ContextMenu; @@ -989,12 +989,12 @@ namespace FlaxEditor.Viewport #endregion View mode widget } - InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); - InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); - InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); - InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); - InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); - InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(EditorViewportCameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); + InputActions.Add(options => options.ViewpointTop, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Top").Orientation))); + InputActions.Add(options => options.ViewpointBottom, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Bottom").Orientation))); + InputActions.Add(options => options.ViewpointFront, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Front").Orientation))); + InputActions.Add(options => options.ViewpointBack, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Back").Orientation))); + InputActions.Add(options => options.ViewpointRight, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Right").Orientation))); + InputActions.Add(options => options.ViewpointLeft, () => OrientViewport(Quaternion.Euler(CameraViewpointValues.First(vp => vp.Name == "Left").Orientation))); InputActions.Add(options => options.CameraToggleRotation, () => _isVirtualMouseRightDown = !_isVirtualMouseRightDown); InputActions.Add(options => options.CameraIncreaseMoveSpeed, () => AdjustCameraMoveSpeed(1)); InputActions.Add(options => options.CameraDecreaseMoveSpeed, () => AdjustCameraMoveSpeed(-1)); @@ -1497,6 +1497,9 @@ namespace FlaxEditor.Viewport { base.Update(deltaTime); + if (_disableInputUpdate) + return; + // Update camera bool useMovementSpeed = false; if (_camera != null) @@ -1535,7 +1538,7 @@ namespace FlaxEditor.Viewport } bool useMouse = IsControllingMouse || (Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height)); _prevInput = _input; - var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl)); + var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl) && !(c is UIEditorRoot)); if (canUseInput && ContainsFocus && hit == null) _input.Gather(win.Window, useMouse); else @@ -1868,7 +1871,7 @@ namespace FlaxEditor.Viewport } } - private readonly CameraViewpoint[] EditorViewportCameraViewpointValues = + private readonly CameraViewpoint[] CameraViewpointValues = { new CameraViewpoint("Front", new Float3(0, 180, 0)), new CameraViewpoint("Back", new Float3(0, 0, 0)), @@ -1899,7 +1902,7 @@ namespace FlaxEditor.Viewport } } - private static readonly ViewModeOptions[] EditorViewportViewModeValues = + private static readonly ViewModeOptions[] ViewModeValues = { new ViewModeOptions(ViewMode.Default, "Default"), new ViewModeOptions(ViewMode.Unlit, "Unlit"), @@ -1971,7 +1974,7 @@ namespace FlaxEditor.Viewport } } - private static readonly ViewFlagOptions[] EditorViewportViewFlagsValues = + private static readonly ViewFlagOptions[] ViewFlagsValues = { new ViewFlagOptions(ViewFlags.AntiAliasing, "Anti Aliasing"), new ViewFlagOptions(ViewFlags.Shadows, "Shadows"), @@ -2006,16 +2009,13 @@ namespace FlaxEditor.Viewport { if (cm.Visible == false) return; - var ccm = (ContextMenu)cm; foreach (var e in ccm.Items) { if (e is ContextMenuButton b && b.Tag != null) { var v = (ViewFlags)b.Tag; - b.Icon = (Task.View.Flags & v) != 0 - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; + b.Icon = (Task.View.Flags & v) != 0 ? Style.Current.CheckBoxTick : SpriteHandle.Invalid; } } } @@ -2024,7 +2024,6 @@ namespace FlaxEditor.Viewport { if (cm.Visible == false) return; - var ccm = (ContextMenu)cm; var layersMask = Task.ViewLayersMask; foreach (var e in ccm.Items) diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs index 541bbcfba..7d08213fa 100644 --- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs +++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs @@ -2,15 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; -using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Modes; -using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; @@ -28,13 +25,6 @@ namespace FlaxEditor.Viewport private readonly ContextMenuButton _showGridButton; private readonly ContextMenuButton _showNavigationButton; - private readonly ViewportWidgetButton _gizmoModeTranslate; - private readonly ViewportWidgetButton _gizmoModeRotate; - private readonly ViewportWidgetButton _gizmoModeScale; - - private readonly ViewportWidgetButton _translateSnapping; - private readonly ViewportWidgetButton _rotateSnapping; - private readonly ViewportWidgetButton _scaleSnapping; private SelectionOutline _customSelectionOutline; @@ -196,7 +186,6 @@ namespace FlaxEditor.Viewport { _editor = editor; DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem); - var inputOptions = editor.Options.Options.Input; // Prepare rendering task Task.ActorsSource = ActorsSources.Scenes; @@ -222,8 +211,7 @@ namespace FlaxEditor.Viewport // Add transformation gizmo TransformGizmo = new TransformGizmo(this); TransformGizmo.ApplyTransformation += ApplyTransform; - TransformGizmo.ModeChanged += OnGizmoModeChanged; - TransformGizmo.Duplicate += Editor.Instance.SceneEditing.Duplicate; + TransformGizmo.Duplicate += _editor.SceneEditing.Duplicate; Gizmos.Active = TransformGizmo; // Add grid @@ -232,144 +220,8 @@ namespace FlaxEditor.Viewport editor.SceneEditing.SelectionChanged += OnSelectionChanged; - // Initialize snapping enabled from cached values - if (_editor.ProjectCache.TryGetCustomData("TranslateSnapState", out var cachedState)) - TransformGizmo.TranslationSnapEnable = bool.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedState)) - TransformGizmo.RotationSnapEnabled = bool.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("ScaleSnapState", out cachedState)) - TransformGizmo.ScaleSnapEnabled = bool.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("TranslateSnapValue", out cachedState)) - TransformGizmo.TranslationSnapValue = float.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("RotationSnapValue", out cachedState)) - TransformGizmo.RotationSnapValue = float.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("ScaleSnapValue", out cachedState)) - TransformGizmo.ScaleSnapValue = float.Parse(cachedState); - if (_editor.ProjectCache.TryGetCustomData("TransformSpaceState", out cachedState) && Enum.TryParse(cachedState, out TransformGizmoBase.TransformSpace space)) - TransformGizmo.ActiveTransformSpace = space; - - // Transform space widget - var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var transformSpaceToggle = new ViewportWidgetButton(string.Empty, editor.Icons.Globe32, null, true) - { - Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", - Parent = transformSpaceWidget - }; - transformSpaceToggle.Toggled += OnTransformSpaceToggle; - transformSpaceWidget.Parent = this; - - // Scale snapping widget - var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableScaleSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.ScaleSnap32, null, true) - { - Checked = TransformGizmo.ScaleSnapEnabled, - TooltipText = "Enable scale snapping", - Parent = scaleSnappingWidget - }; - enableScaleSnapping.Toggled += OnScaleSnappingToggle; - - var scaleSnappingCM = new ContextMenu(); - _scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) - { - TooltipText = "Scale snapping values" - }; - - for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++) - { - var v = EditorViewportScaleSnapValues[i]; - var button = scaleSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick; - scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide; - _scaleSnapping.Parent = scaleSnappingWidget; - scaleSnappingWidget.Parent = this; - - // Rotation snapping widget - var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableRotateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.RotateSnap32, null, true) - { - Checked = TransformGizmo.RotationSnapEnabled, - TooltipText = "Enable rotation snapping", - Parent = rotateSnappingWidget - }; - enableRotateSnapping.Toggled += OnRotateSnappingToggle; - - var rotateSnappingCM = new ContextMenu(); - _rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) - { - TooltipText = "Rotation snapping values" - }; - - for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++) - { - var v = EditorViewportRotateSnapValues[i]; - var button = rotateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick; - rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide; - _rotateSnapping.Parent = rotateSnappingWidget; - rotateSnappingWidget.Parent = this; - - // Translation snapping widget - var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, editor.Icons.Grid32, null, true) - { - Checked = TransformGizmo.TranslationSnapEnable, - TooltipText = "Enable position snapping", - Parent = translateSnappingWidget - }; - enableTranslateSnapping.Toggled += OnTranslateSnappingToggle; - - var translateSnappingCM = new ContextMenu(); - _translateSnapping = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) - { - TooltipText = "Position snapping values" - }; - if (TransformGizmo.TranslationSnapValue < 0.0f) - _translateSnapping.Text = "Bounding Box"; - - for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++) - { - var v = EditorViewportTranslateSnapValues[i]; - var button = translateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - var buttonBB = translateSnappingCM.AddButton("Bounding Box").LinkTooltip("Snaps the selection based on it's bounding volume"); - buttonBB.Tag = -1.0f; - - translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; - translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; - _translateSnapping.Parent = translateSnappingWidget; - translateSnappingWidget.Parent = this; - - // Gizmo mode widget - var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, editor.Icons.Translate32, null, true) - { - Tag = TransformGizmoBase.Mode.Translate, - TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", - Checked = true, - Parent = gizmoMode - }; - _gizmoModeTranslate.Toggled += OnGizmoModeToggle; - _gizmoModeRotate = new ViewportWidgetButton(string.Empty, editor.Icons.Rotate32, null, true) - { - Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", - Parent = gizmoMode - }; - _gizmoModeRotate.Toggled += OnGizmoModeToggle; - _gizmoModeScale = new ViewportWidgetButton(string.Empty, editor.Icons.Scale32, null, true) - { - Tag = TransformGizmoBase.Mode.Scale, - TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", - Parent = gizmoMode - }; - _gizmoModeScale.Toggled += OnGizmoModeToggle; - gizmoMode.Parent = this; + // Gizmo widgets + AddGizmoViewportWidgets(this, TransformGizmo); // Show grid widget _showGridButton = ViewWidgetShowMenu.AddButton("Grid", () => Grid.Enabled = !Grid.Enabled); @@ -400,14 +252,6 @@ namespace FlaxEditor.Viewport } // Setup input actions - InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); - InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); - InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.ToggleTransformSpace, () => - { - OnTransformSpaceToggle(transformSpaceToggle); - transformSpaceToggle.Checked = !transformSpaceToggle.Checked; - }); InputActions.Add(options => options.LockFocusSelection, LockFocusSelection); InputActions.Add(options => options.FocusSelection, FocusSelection); InputActions.Add(options => options.RotateSelection, RotateSelection); @@ -486,7 +330,7 @@ namespace FlaxEditor.Viewport }; // Spawn - Editor.Instance.SceneEditing.Spawn(actor, parent); + _editor.SceneEditing.Spawn(actor, parent); } private void OnBegin(RenderTask task, GPUContext context) @@ -552,7 +396,7 @@ namespace FlaxEditor.Viewport var task = renderContext.Task; // Render editor primitives, gizmo and debug shapes in debug view modes - // Note: can use Output buffer as both input and output because EditorPrimitives is using a intermediate buffers + // Note: can use Output buffer as both input and output because EditorPrimitives is using an intermediate buffer if (EditorPrimitives && EditorPrimitives.CanRender()) { EditorPrimitives.Render(context, ref renderContext, task.Output, task.Output); @@ -581,161 +425,6 @@ namespace FlaxEditor.Viewport } } - private void OnGizmoModeToggle(ViewportWidgetButton button) - { - TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag; - } - - private void OnTranslateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable; - _editor.ProjectCache.SetCustomData("TranslateSnapState", TransformGizmo.TranslationSnapEnable.ToString()); - } - - private void OnRotateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled; - _editor.ProjectCache.SetCustomData("RotationSnapState", TransformGizmo.RotationSnapEnabled.ToString()); - } - - private void OnScaleSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled; - _editor.ProjectCache.SetCustomData("ScaleSnapState", TransformGizmo.ScaleSnapEnabled.ToString()); - } - - private void OnTransformSpaceToggle(ViewportWidgetButton button) - { - TransformGizmo.ToggleTransformSpace(); - _editor.ProjectCache.SetCustomData("TransformSpaceState", TransformGizmo.ActiveTransformSpace.ToString()); - } - - private void OnGizmoModeChanged() - { - // Update all viewport widgets status - var mode = TransformGizmo.ActiveMode; - _gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; - _gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; - _gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; - } - - private static readonly float[] EditorViewportScaleSnapValues = - { - 0.05f, - 0.1f, - 0.25f, - 0.5f, - 1.0f, - 2.0f, - 4.0f, - 6.0f, - 8.0f, - }; - - private void OnWidgetScaleSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.ScaleSnapValue = v; - _scaleSnapping.Text = v.ToString(); - _editor.ProjectCache.SetCustomData("ScaleSnapValue", TransformGizmo.ScaleSnapValue.ToString("N")); - } - - private void OnWidgetScaleSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportRotateSnapValues = - { - 1.0f, - 5.0f, - 10.0f, - 15.0f, - 30.0f, - 45.0f, - 60.0f, - 90.0f, - }; - - private void OnWidgetRotateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.RotationSnapValue = v; - _rotateSnapping.Text = v.ToString(); - _editor.ProjectCache.SetCustomData("RotationSnapValue", TransformGizmo.RotationSnapValue.ToString("N")); - } - - private void OnWidgetRotateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportTranslateSnapValues = - { - 0.1f, - 0.5f, - 1.0f, - 5.0f, - 10.0f, - 100.0f, - 1000.0f, - }; - - private void OnWidgetTranslateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.TranslationSnapValue = v; - if (v < 0.0f) - _translateSnapping.Text = "Bounding Box"; - else - _translateSnapping.Text = v.ToString(); - _editor.ProjectCache.SetCustomData("TranslateSnapValue", TransformGizmo.TranslationSnapValue.ToString("N")); - } - - private void OnWidgetTranslateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - private void OnSelectionChanged() { var selection = _editor.SceneEditing.Selection; @@ -761,7 +450,7 @@ namespace FlaxEditor.Viewport Vector3 gizmoPosition = TransformGizmo.Position; // Rotate selected objects - bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + bool isPlayMode = _editor.StateMachine.IsPlayMode; TransformGizmo.StartTransforming(); for (int i = 0; i < selection.Count; i++) { @@ -819,14 +508,7 @@ namespace FlaxEditor.Viewport /// The target view orientation. public void FocusSelection(ref Quaternion orientation) { - if (TransformGizmo.SelectedParents.Count == 0) - return; - - var gizmoBounds = Gizmos.Active.FocusBounds; - if (gizmoBounds != BoundingSphere.Empty) - ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); - else - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + ViewportCamera.FocusSelection(Gizmos, ref orientation); } /// @@ -843,7 +525,7 @@ namespace FlaxEditor.Viewport Vector3 gizmoPosition = TransformGizmo.Position; // Transform selected objects - bool isPlayMode = Editor.Instance.StateMachine.IsPlayMode; + bool isPlayMode = _editor.StateMachine.IsPlayMode; for (int i = 0; i < selection.Count; i++) { var obj = selection[i]; @@ -985,7 +667,14 @@ namespace FlaxEditor.Viewport { var parent = actor.Parent ?? Level.GetScene(0); actor.Name = Utilities.Utils.IncrementNameNumber(actor.Name, x => parent.GetChild(x) == null); - Editor.Instance.SceneEditing.Spawn(actor); + _editor.SceneEditing.Spawn(actor); + } + + /// + public override void OpenContextMenu() + { + var mouse = PointFromWindow(Root.MousePosition); + _editor.Windows.SceneWin.ShowContextMenu(this, mouse); } /// diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs index 97be5911a..32473c5ea 100644 --- a/Source/Editor/Viewport/PrefabWindowViewport.cs +++ b/Source/Editor/Viewport/PrefabWindowViewport.cs @@ -5,12 +5,10 @@ using System.Collections.Generic; using System.Linq; using FlaxEditor.Content; using FlaxEditor.Gizmo; -using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; using FlaxEditor.Scripting; using FlaxEditor.Viewport.Cameras; using FlaxEditor.Viewport.Previews; -using FlaxEditor.Viewport.Widgets; using FlaxEditor.Windows.Assets; using FlaxEngine; using FlaxEngine.GUI; @@ -30,7 +28,6 @@ namespace FlaxEditor.Viewport { public PrefabWindowViewport Viewport; - /// public override bool CanRender() { return (Task.View.Flags & ViewFlags.EditorSprites) == ViewFlags.EditorSprites && Enabled; @@ -42,19 +39,34 @@ namespace FlaxEditor.Viewport } } + [HideInEditor] + private sealed class PrefabUIEditorRoot : UIEditorRoot + { + private readonly PrefabWindowViewport _viewport; + private bool UI => _viewport._hasUILinkedCached; + + public PrefabUIEditorRoot(PrefabWindowViewport viewport) + : base(true) + { + _viewport = viewport; + Parent = viewport; + } + + public override bool EnableInputs => !UI; + public override bool EnableSelecting => UI; + public override bool EnableBackground => UI; + public override TransformGizmo TransformGizmo => _viewport.TransformGizmo; + } + private readonly PrefabWindow _window; private UpdateDelegate _update; - private readonly ViewportWidgetButton _gizmoModeTranslate; - private readonly ViewportWidgetButton _gizmoModeRotate; - private readonly ViewportWidgetButton _gizmoModeScale; - - private ViewportWidgetButton _translateSnappng; - private ViewportWidgetButton _rotateSnapping; - private ViewportWidgetButton _scaleSnapping; - private readonly ViewportDebugDrawData _debugDrawData = new ViewportDebugDrawData(32); private PrefabSpritesRenderer _spritesRenderer; + private IntPtr _tempDebugDrawContext; + + private bool _hasUILinkedCached; + private PrefabUIEditorRoot _uiRoot; /// /// Drag and drop handlers @@ -107,144 +119,74 @@ namespace FlaxEditor.Viewport // Add transformation gizmo TransformGizmo = new TransformGizmo(this); TransformGizmo.ApplyTransformation += ApplyTransform; - TransformGizmo.ModeChanged += OnGizmoModeChanged; TransformGizmo.Duplicate += _window.Duplicate; Gizmos.Active = TransformGizmo; - // Transform space widget - var transformSpaceWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var transformSpaceToggle = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Globe32, null, true) - { - Checked = TransformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World, - TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", - Parent = transformSpaceWidget - }; - transformSpaceToggle.Toggled += OnTransformSpaceToggle; - transformSpaceWidget.Parent = this; + // Use custom root for UI controls + _uiRoot = new PrefabUIEditorRoot(this); + _uiRoot.IndexInParent = 0; // Move viewport down below other widgets in the viewport + _uiParentLink = _uiRoot.UIRoot; - // Scale snapping widget - var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableScaleSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.ScaleSnap32, null, true) - { - Checked = TransformGizmo.ScaleSnapEnabled, - TooltipText = "Enable scale snapping", - Parent = scaleSnappingWidget - }; - enableScaleSnapping.Toggled += OnScaleSnappingToggle; - - var scaleSnappingCM = new ContextMenu(); - _scaleSnapping = new ViewportWidgetButton(TransformGizmo.ScaleSnapValue.ToString(), SpriteHandle.Invalid, scaleSnappingCM) - { - TooltipText = "Scale snapping values" - }; - - for (int i = 0; i < EditorViewportScaleSnapValues.Length; i++) - { - var v = EditorViewportScaleSnapValues[i]; - var button = scaleSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - scaleSnappingCM.ButtonClicked += OnWidgetScaleSnapClick; - scaleSnappingCM.VisibleChanged += OnWidgetScaleSnapShowHide; - _scaleSnapping.Parent = scaleSnappingWidget; - scaleSnappingWidget.Parent = this; - - // Rotation snapping widget - var rotateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableRotateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.RotateSnap32, null, true) - { - Checked = TransformGizmo.RotationSnapEnabled, - TooltipText = "Enable rotation snapping", - Parent = rotateSnappingWidget - }; - enableRotateSnapping.Toggled += OnRotateSnappingToggle; - - var rotateSnappingCM = new ContextMenu(); - _rotateSnapping = new ViewportWidgetButton(TransformGizmo.RotationSnapValue.ToString(), SpriteHandle.Invalid, rotateSnappingCM) - { - TooltipText = "Rotation snapping values" - }; - - for (int i = 0; i < EditorViewportRotateSnapValues.Length; i++) - { - var v = EditorViewportRotateSnapValues[i]; - var button = rotateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - rotateSnappingCM.ButtonClicked += OnWidgetRotateSnapClick; - rotateSnappingCM.VisibleChanged += OnWidgetRotateSnapShowHide; - _rotateSnapping.Parent = rotateSnappingWidget; - rotateSnappingWidget.Parent = this; - - // Translation snapping widget - var translateSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - var enableTranslateSnapping = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Grid32, null, true) - { - Checked = TransformGizmo.TranslationSnapEnable, - TooltipText = "Enable position snapping", - Parent = translateSnappingWidget - }; - enableTranslateSnapping.Toggled += OnTranslateSnappingToggle; - - var translateSnappingCM = new ContextMenu(); - _translateSnappng = new ViewportWidgetButton(TransformGizmo.TranslationSnapValue.ToString(), SpriteHandle.Invalid, translateSnappingCM) - { - TooltipText = "Position snapping values" - }; - - for (int i = 0; i < EditorViewportTranslateSnapValues.Length; i++) - { - var v = EditorViewportTranslateSnapValues[i]; - var button = translateSnappingCM.AddButton(v.ToString()); - button.Tag = v; - } - translateSnappingCM.ButtonClicked += OnWidgetTranslateSnapClick; - translateSnappingCM.VisibleChanged += OnWidgetTranslateSnapShowHide; - _translateSnappng.Parent = translateSnappingWidget; - translateSnappingWidget.Parent = this; - - // Gizmo mode widget - var gizmoMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); - _gizmoModeTranslate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Translate32, null, true) - { - Tag = TransformGizmoBase.Mode.Translate, - TooltipText = $"Translate gizmo mode ({inputOptions.TranslateMode})", - Checked = true, - Parent = gizmoMode - }; - _gizmoModeTranslate.Toggled += OnGizmoModeToggle; - _gizmoModeRotate = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Rotate32, null, true) - { - Tag = TransformGizmoBase.Mode.Rotate, - TooltipText = $"Rotate gizmo mode ({inputOptions.RotateMode})", - Parent = gizmoMode - }; - _gizmoModeRotate.Toggled += OnGizmoModeToggle; - _gizmoModeScale = new ViewportWidgetButton(string.Empty, window.Editor.Icons.Scale32, null, true) - { - Tag = TransformGizmoBase.Mode.Scale, - TooltipText = $"Scale gizmo mode ({inputOptions.ScaleMode})", - Parent = gizmoMode - }; - _gizmoModeScale.Toggled += OnGizmoModeToggle; - gizmoMode.Parent = this; + EditorGizmoViewport.AddGizmoViewportWidgets(this, TransformGizmo); // Setup input actions - InputActions.Add(options => options.TranslateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate); - InputActions.Add(options => options.RotateMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); - InputActions.Add(options => options.ScaleMode, () => TransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); - InputActions.Add(options => options.ToggleTransformSpace, () => - { - OnTransformSpaceToggle(transformSpaceToggle); - transformSpaceToggle.Checked = !transformSpaceToggle.Checked; - }); InputActions.Add(options => options.FocusSelection, ShowSelectedActors); SetUpdate(ref _update, OnUpdate); } + /// + /// Updates the viewport's gizmos, especially to toggle between 3D and UI editing modes. + /// + internal void UpdateGizmoMode() + { + // Skip if gizmo mode was unmodified + if (_hasUILinked == _hasUILinkedCached) + return; + _hasUILinkedCached = _hasUILinked; + + if (_hasUILinked) + { + // UI widget + Gizmos.Active = null; + ViewportCamera = new UIEditorCamera { UIEditor = _uiRoot }; + + // Hide 3D visuals + ShowEditorPrimitives = false; + ShowDefaultSceneActors = false; + ShowDebugDraw = false; + + // Show whole UI on startup + var canvas = (CanvasRootControl)_uiParentLink.Children.FirstOrDefault(x => x is CanvasRootControl); + if (canvas != null) + ViewportCamera.ShowActor(canvas.Canvas); + else if (Instance is UIControl) + ViewportCamera.ShowActor(Instance); + } + else + { + // Generic prefab + Gizmos.Active = TransformGizmo; + ViewportCamera = new FPSCamera(); + } + + // Update default components usage + bool defaultFeatures = !_hasUILinked; + _disableInputUpdate = _hasUILinked; + _spritesRenderer.Enabled = defaultFeatures; + SelectionOutline.Enabled = defaultFeatures; + _showDefaultSceneButton.Visible = defaultFeatures; + _cameraWidget.Visible = defaultFeatures; + _cameraButton.Visible = defaultFeatures; + _orthographicModeButton.Visible = defaultFeatures; + Task.Enabled = defaultFeatures; + UseAutomaticTaskManagement = defaultFeatures; + TintColor = defaultFeatures ? Color.White : Color.Transparent; + } + private void OnUpdate(float deltaTime) { + UpdateGizmoMode(); for (int i = 0; i < Gizmos.Count; i++) { Gizmos[i].Update(deltaTime); @@ -259,11 +201,19 @@ namespace FlaxEditor.Viewport var selectedParents = TransformGizmo.SelectedParents; if (selectedParents.Count > 0) { + // Use temporary Debug Draw context to pull any debug shapes drawing in Scene Graph Nodes - those are used in OnDebugDraw down below + if (_tempDebugDrawContext == IntPtr.Zero) + _tempDebugDrawContext = DebugDraw.AllocateContext(); + DebugDraw.SetContext(_tempDebugDrawContext); + DebugDraw.UpdateContext(_tempDebugDrawContext, 1.0f); + for (int i = 0; i < selectedParents.Count; i++) { if (selectedParents[i].IsActiveInHierarchy) selectedParents[i].OnDebugDraw(_debugDrawData); } + + DebugDraw.SetContext(IntPtr.Zero); } } @@ -307,7 +257,7 @@ namespace FlaxEditor.Viewport public void ShowSelectedActors() { var orient = ViewOrientation; - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orient); + ViewportCamera.ShowActors(TransformGizmo.SelectedParents, ref orient); } /// @@ -338,7 +288,7 @@ namespace FlaxEditor.Viewport public bool SnapToGround => false; /// - public bool SnapToVertex => Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); + public bool SnapToVertex => ContainsFocus && Editor.Instance.Options.Options.Input.SnapToVertex.Process(Root); /// public Float2 MouseDelta => _mouseDelta * 1000; @@ -367,6 +317,13 @@ namespace FlaxEditor.Viewport _window.Spawn(actor); } + /// + public void OpenContextMenu() + { + var mouse = PointFromWindow(Root.MousePosition); + _window.ShowContextMenu(this, ref mouse); + } + /// protected override bool IsControllingMouse => Gizmos.Active?.IsControllingMouse ?? false; @@ -386,151 +343,6 @@ namespace FlaxEditor.Viewport root.UpdateCallbacksToRemove.Add(_update); } - private void OnGizmoModeToggle(ViewportWidgetButton button) - { - TransformGizmo.ActiveMode = (TransformGizmoBase.Mode)(int)button.Tag; - } - - private void OnTranslateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.TranslationSnapEnable = !TransformGizmo.TranslationSnapEnable; - } - - private void OnRotateSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.RotationSnapEnabled = !TransformGizmo.RotationSnapEnabled; - } - - private void OnScaleSnappingToggle(ViewportWidgetButton button) - { - TransformGizmo.ScaleSnapEnabled = !TransformGizmo.ScaleSnapEnabled; - } - - private void OnTransformSpaceToggle(ViewportWidgetButton button) - { - TransformGizmo.ToggleTransformSpace(); - } - - private void OnGizmoModeChanged() - { - // Update all viewport widgets status - var mode = TransformGizmo.ActiveMode; - _gizmoModeTranslate.Checked = mode == TransformGizmoBase.Mode.Translate; - _gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; - _gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; - } - - private static readonly float[] EditorViewportScaleSnapValues = - { - 0.05f, - 0.1f, - 0.25f, - 0.5f, - 1.0f, - 2.0f, - 4.0f, - 6.0f, - 8.0f, - }; - - private void OnWidgetScaleSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.ScaleSnapValue = v; - _scaleSnapping.Text = v.ToString(); - } - - private void OnWidgetScaleSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.ScaleSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportRotateSnapValues = - { - 1.0f, - 5.0f, - 10.0f, - 15.0f, - 30.0f, - 45.0f, - 60.0f, - 90.0f, - }; - - private void OnWidgetRotateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.RotationSnapValue = v; - _rotateSnapping.Text = v.ToString(); - } - - private void OnWidgetRotateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.RotationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - - private static readonly float[] EditorViewportTranslateSnapValues = - { - 0.1f, - 0.5f, - 1.0f, - 5.0f, - 10.0f, - 100.0f, - 1000.0f, - }; - - private void OnWidgetTranslateSnapClick(ContextMenuButton button) - { - var v = (float)button.Tag; - TransformGizmo.TranslationSnapValue = v; - _translateSnappng.Text = v.ToString(); - } - - private void OnWidgetTranslateSnapShowHide(Control control) - { - if (control.Visible == false) - return; - - var ccm = (ContextMenu)control; - foreach (var e in ccm.Items) - { - if (e is ContextMenuButton b) - { - var v = (float)b.Tag; - b.Icon = Mathf.Abs(TransformGizmo.TranslationSnapValue - v) < 0.001f - ? Style.Current.CheckBoxTick - : SpriteHandle.Invalid; - } - } - } - private void OnSelectionChanged() { Gizmos.ForEach(x => x.OnSelectionChanged(_window.Selection)); @@ -585,23 +397,6 @@ namespace FlaxEditor.Viewport } } - /// - public override void Draw() - { - base.Draw(); - - // Selected UI controls outline - for (var i = 0; i < _window.Selection.Count; i++) - { - if (_window.Selection[i]?.EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) - { - var control = controlActor.Control; - var bounds = Rectangle.FromPoints(control.PointToParent(this, Float2.Zero), control.PointToParent(this, control.Size)); - Render2D.DrawRectangle(bounds, Editor.Instance.Options.Options.Visual.SelectionOutlineColor0, Editor.Instance.Options.Options.Visual.UISelectionOutlineSize); - } - } - } - /// protected override void OnLeftMouseButtonUp() { @@ -744,14 +539,7 @@ namespace FlaxEditor.Viewport /// The target view orientation. public void FocusSelection(ref Quaternion orientation) { - if (TransformGizmo.SelectedParents.Count == 0) - return; - - var gizmoBounds = Gizmos.Active.FocusBounds; - if (gizmoBounds != BoundingSphere.Empty) - ((FPSCamera)ViewportCamera).ShowSphere(ref gizmoBounds, ref orientation); - else - ((FPSCamera)ViewportCamera).ShowActors(TransformGizmo.SelectedParents, ref orientation); + ViewportCamera.FocusSelection(Gizmos, ref orientation); } /// @@ -776,6 +564,13 @@ namespace FlaxEditor.Viewport /// public override void OnDestroy() { + if (IsDisposing) + return; + if (_tempDebugDrawContext != IntPtr.Zero) + { + DebugDraw.FreeContext(_tempDebugDrawContext); + _tempDebugDrawContext = IntPtr.Zero; + } FlaxEngine.Object.Destroy(ref SelectionOutline); FlaxEngine.Object.Destroy(ref _spritesRenderer); @@ -799,6 +594,15 @@ namespace FlaxEditor.Viewport { base.OnDebugDraw(context, ref renderContext); + // Collect selected objects debug shapes again when DebugDraw is active with a custom context + _debugDrawData.Clear(); + var selectedParents = TransformGizmo.SelectedParents; + for (int i = 0; i < selectedParents.Count; i++) + { + if (selectedParents[i].IsActiveInHierarchy) + selectedParents[i].OnDebugDraw(_debugDrawData); + } + unsafe { fixed (IntPtr* actors = _debugDrawData.ActorsPtrs) diff --git a/Source/Editor/Viewport/Previews/AssetPreview.cs b/Source/Editor/Viewport/Previews/AssetPreview.cs index 8c88fa85d..c460a24de 100644 --- a/Source/Editor/Viewport/Previews/AssetPreview.cs +++ b/Source/Editor/Viewport/Previews/AssetPreview.cs @@ -21,7 +21,7 @@ namespace FlaxEditor.Viewport.Previews /// public abstract class AssetPreview : EditorViewport, IEditorPrimitivesOwner { - private ContextMenuButton _showDefaultSceneButton; + internal ContextMenuButton _showDefaultSceneButton; private IntPtr _debugDrawContext; private bool _debugDrawEnable; private bool _editorPrimitivesEnable; @@ -239,6 +239,8 @@ namespace FlaxEditor.Viewport.Previews /// public override void OnDestroy() { + if (IsDisposing) + return; Object.Destroy(ref PreviewLight); Object.Destroy(ref EnvProbe); Object.Destroy(ref Sky); diff --git a/Source/Editor/Viewport/Previews/PrefabPreview.cs b/Source/Editor/Viewport/Previews/PrefabPreview.cs index ff235bd04..16ec8f132 100644 --- a/Source/Editor/Viewport/Previews/PrefabPreview.cs +++ b/Source/Editor/Viewport/Previews/PrefabPreview.cs @@ -1,8 +1,8 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Collections.Generic; using FlaxEngine; +using FlaxEngine.GUI; using Object = FlaxEngine.Object; namespace FlaxEditor.Viewport.Previews @@ -13,19 +13,11 @@ namespace FlaxEditor.Viewport.Previews /// public class PrefabPreview : AssetPreview { - /// - /// The currently spawned prefab instance owner. Used to link some actors such as UIControl to preview scene and view. - /// - internal static PrefabPreview LoadingPreview; - - /// - /// The list of active prefab previews. Used to link some actors such as UIControl to preview scene and view. - /// - internal static List ActivePreviews; - private Prefab _prefab; private Actor _instance; - internal UIControl customControlLinked; + private UIControl _uiControlLinked; + internal bool _hasUILinked; + internal ContainerControl _uiParentLink; /// /// Gets or sets the prefab asset to preview. @@ -54,13 +46,10 @@ namespace FlaxEditor.Viewport.Previews _prefab.WaitForLoaded(); // Spawn prefab - LoadingPreview = this; var instance = PrefabManager.SpawnPrefab(_prefab, null); - LoadingPreview = null; if (instance == null) { _prefab = null; - ActivePreviews.Remove(this); throw new Exception("Failed to spawn a prefab for the preview."); } @@ -84,11 +73,11 @@ namespace FlaxEditor.Viewport.Previews if (_instance) { // Unlink UI control - if (customControlLinked) + if (_uiControlLinked) { - if (customControlLinked.Control?.Parent == this) - customControlLinked.Control.Parent = null; - customControlLinked = null; + if (_uiControlLinked.Control?.Parent == _uiParentLink) + _uiControlLinked.Control.Parent = null; + _uiControlLinked = null; } // Remove for the preview @@ -96,27 +85,51 @@ namespace FlaxEditor.Viewport.Previews } _instance = value; + _hasUILinked = false; if (_instance) { // Add to the preview Task.AddCustomActor(_instance); - - // Link UI canvases to the preview - LinkCanvas(_instance); + UpdateLinkage(); } } } + private void UpdateLinkage() + { + // Clear flag + _hasUILinked = false; + + // Link UI canvases to the preview (eg. after canvas added to the prefab) + LinkCanvas(_instance); + + // Link UI control to the preview + if (_uiControlLinked == null && + _instance is UIControl uiControl && + uiControl.Control != null && + uiControl.Control.Parent == null) + { + uiControl.Control.Parent = _uiParentLink; + _uiControlLinked = uiControl; + _hasUILinked = true; + } + else if (_uiControlLinked != null) + _hasUILinked = true; + } + private void LinkCanvas(Actor actor) { if (actor is UICanvas uiCanvas) - uiCanvas.EditorOverride(Task, this); + { + uiCanvas.EditorOverride(Task, _uiParentLink); + if (uiCanvas.GUI.Parent == _uiParentLink) + _hasUILinked = true; + } + var children = actor.ChildrenCount; for (int i = 0; i < children; i++) - { LinkCanvas(actor.GetChild(i)); - } } /// @@ -126,9 +139,8 @@ namespace FlaxEditor.Viewport.Previews public PrefabPreview(bool useWidgets) : base(useWidgets) { - if (ActivePreviews == null) - ActivePreviews = new List(); - ActivePreviews.Add(this); + // Link to itself by default + _uiParentLink = this; } /// @@ -138,15 +150,13 @@ namespace FlaxEditor.Viewport.Previews if (_instance != null) { - // Link UI canvases to the preview (eg. after canvas added to the prefab) - LinkCanvas(_instance); + UpdateLinkage(); } } /// public override void OnDestroy() { - ActivePreviews.Remove(this); Prefab = null; base.OnDestroy(); diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 72fcce828..86a127647 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -146,7 +146,7 @@ namespace FlaxEditor.Viewport var gridPlane = new Plane(Vector3.Zero, Vector3.Up); var flags = SceneGraphNode.RayCastData.FlagTypes.SkipColliders | SceneGraphNode.RayCastData.FlagTypes.SkipEditorPrimitives; hit = _owner.SceneGraphRoot.RayCast(ref ray, ref view, out var closest, out var normal, flags); - var girdGizmo = (GridGizmo)_owner.Gizmos.FirstOrDefault(x => x is GridGizmo); + var girdGizmo = _owner.Gizmos.Get(); if (hit != null) { // Use hit location @@ -180,7 +180,7 @@ namespace FlaxEditor.Viewport var location = hitLocation + new Vector3(0, bottomToCenter, 0); // Apply grid snapping if enabled - var transformGizmo = (TransformGizmo)_owner.Gizmos.FirstOrDefault(x => x is TransformGizmo); + var transformGizmo = _owner.Gizmos.Get(); if (transformGizmo != null && (_owner.UseSnapping || transformGizmo.TranslationSnapEnable)) { float snapValue = transformGizmo.TranslationSnapValue; diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index ae87826b0..2411daf86 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -186,6 +186,10 @@ namespace FlaxEditor.Windows.Assets base.Initialize(layout); + // Ignore import settings GUI if the type is not animation. This removes the import UI if the animation asset was not created using an import. + if (proxy.ImportSettings.Settings.Type != FlaxEngine.Tools.ModelTool.ModelType.Animation) + return; + // Import Settings { var group = layout.Group("Import Settings"); diff --git a/Source/Editor/Windows/Assets/MaterialWindow.cs b/Source/Editor/Windows/Assets/MaterialWindow.cs index e87368e3c..ef37fbe67 100644 --- a/Source/Editor/Windows/Assets/MaterialWindow.cs +++ b/Source/Editor/Windows/Assets/MaterialWindow.cs @@ -76,37 +76,37 @@ namespace FlaxEditor.Windows.Assets // Transparency - [EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")] + [EditorOrder(200), DefaultValue(MaterialTransparentLightingMode.Surface), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Transparent material lighting mode.")] public MaterialTransparentLightingMode TransparentLightingMode; - [EditorOrder(205), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")] + [EditorOrder(205), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables reflections when rendering material.")] public bool EnableReflections; [VisibleIf(nameof(EnableReflections))] - [EditorOrder(210), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")] + [EditorOrder(210), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables Screen Space Reflections when rendering material.")] public bool EnableScreenSpaceReflections; - [EditorOrder(210), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")] + [EditorOrder(210), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables fog effects when rendering material.")] public bool EnableFog; - [EditorOrder(220), DefaultValue(true), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")] + [EditorOrder(220), DefaultValue(true), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables distortion effect when rendering.")] public bool EnableDistortion; - [EditorOrder(224), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")] + [EditorOrder(224), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables sampling Global Illumination in material (eg. light probes or volumetric lightmap).")] public bool EnableGlobalIllumination; - [EditorOrder(225), DefaultValue(false), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")] + [EditorOrder(225), DefaultValue(false), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Enables refraction offset based on the difference between the per-pixel normal and the per-vertex normal. Useful for large water-like surfaces.")] public bool PixelNormalOffsetRefraction; - [EditorOrder(230), DefaultValue(0.12f), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)] + [EditorOrder(230), DefaultValue(0.12f), VisibleIf(nameof(IsStandard)), EditorDisplay("Transparency"), Tooltip("Controls opacity values clipping point."), Limit(0.0f, 1.0f, 0.01f)] public float OpacityThreshold; // Tessellation - [EditorOrder(300), DefaultValue(TessellationMethod.None), EditorDisplay("Tessellation"), Tooltip("Mesh tessellation method.")] + [EditorOrder(300), DefaultValue(TessellationMethod.None), VisibleIf(nameof(IsStandard)), EditorDisplay("Tessellation"), Tooltip("Mesh tessellation method.")] public TessellationMethod TessellationMode; - [EditorOrder(310), DefaultValue(15), EditorDisplay("Tessellation"), Tooltip("Maximum triangle tessellation factor."), Limit(1, 60, 0.01f)] + [EditorOrder(310), DefaultValue(15), VisibleIf(nameof(IsStandard)), EditorDisplay("Tessellation"), Tooltip("Maximum triangle tessellation factor."), Limit(1, 60, 0.01f)] public int MaxTessellationFactor; // Misc @@ -120,10 +120,10 @@ namespace FlaxEditor.Windows.Assets [EditorOrder(420), DefaultValue(0.3f), EditorDisplay("Misc"), Tooltip("Controls mask values clipping point."), Limit(0.0f, 1.0f, 0.01f)] public float MaskThreshold; - [EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")] + [EditorOrder(430), DefaultValue(MaterialDecalBlendingMode.Translucent), VisibleIf(nameof(IsDecal)), EditorDisplay("Misc"), Tooltip("The decal material blending mode.")] public MaterialDecalBlendingMode DecalBlendingMode; - [EditorOrder(440), DefaultValue(MaterialPostFxLocation.AfterPostProcessingPass), EditorDisplay("Misc"), Tooltip("The post fx material rendering location.")] + [EditorOrder(440), DefaultValue(MaterialPostFxLocation.AfterPostProcessingPass), VisibleIf(nameof(IsPostProcess)), EditorDisplay("Misc"), Tooltip("The post fx material rendering location.")] public MaterialPostFxLocation PostFxLocation; // Parameters @@ -140,6 +140,12 @@ namespace FlaxEditor.Windows.Assets set => throw new Exception("No setter."); } + // Visibility conditionals + + private bool IsPostProcess => Domain == MaterialDomain.PostProcess; + private bool IsDecal => Domain == MaterialDomain.Decal; + private bool IsStandard => Domain == MaterialDomain.Surface || Domain == MaterialDomain.Terrain || Domain == MaterialDomain.Particle || Domain == MaterialDomain.Deformable; + /// /// Gathers parameters from the specified material. /// diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs index d7efb1260..21e037fbc 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Hierarchy.cs @@ -360,10 +360,9 @@ namespace FlaxEditor.Windows.Assets /// /// The parent control. /// The location (within a given control). - private void ShowContextMenu(Control parent, ref Float2 location) + internal void ShowContextMenu(Control parent, ref Float2 location) { var contextMenu = CreateContextMenu(); - contextMenu.Show(parent, location); } diff --git a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs index cb4c7fd0f..be20c176c 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.Selection.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; -using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tree; using FlaxEditor.SceneGraph; using FlaxEditor.SceneGraph.GUI; -using FlaxEditor.Viewport.Cameras; using FlaxEngine; namespace FlaxEditor.Windows.Assets @@ -64,8 +62,11 @@ namespace FlaxEditor.Windows.Assets private void OnSelectionUndo(SceneGraphNode[] toSelect) { Selection.Clear(); - Selection.AddRange(toSelect); - + foreach (var e in toSelect) + { + if (e != null) + Selection.Add(e); + } OnSelectionChanges(); } @@ -118,11 +119,13 @@ namespace FlaxEditor.Windows.Assets /// The nodes. public void Select(List nodes) { + nodes?.RemoveAll(x => x == null); if (nodes == null || nodes.Count == 0) { Deselect(); return; } + if (Utils.ArraysEqual(Selection, nodes)) return; diff --git a/Source/Editor/Windows/Assets/PrefabWindow.cs b/Source/Editor/Windows/Assets/PrefabWindow.cs index 5aa1486ee..b788821cf 100644 --- a/Source/Editor/Windows/Assets/PrefabWindow.cs +++ b/Source/Editor/Windows/Assets/PrefabWindow.cs @@ -132,7 +132,7 @@ namespace FlaxEditor.Windows.Assets IsScrollable = false, Offsets = new Margin(0, 0, 0, 18 + 6), }; - _searchBox = new SearchBox() + _searchBox = new SearchBox { AnchorPreset = AnchorPresets.HorizontalStretchMiddle, Parent = headerPanel, @@ -140,7 +140,8 @@ namespace FlaxEditor.Windows.Assets }; _searchBox.TextChanged += OnSearchBoxTextChanged; - _treePanel = new Panel() + // Prefab structure tree + _treePanel = new Panel { AnchorPreset = AnchorPresets.StretchAll, Offsets = new Margin(0.0f, 0.0f, headerPanel.Bottom, 0.0f), @@ -148,8 +149,6 @@ namespace FlaxEditor.Windows.Assets IsScrollable = true, Parent = sceneTreePanel, }; - - // Prefab structure tree Graph = new LocalSceneGraph(new CustomRootNode(this)); Graph.Root.TreeNode.Expand(true); _tree = new PrefabTree @@ -316,11 +315,7 @@ namespace FlaxEditor.Windows.Assets return; // Restore - _viewport.Prefab = _asset; - Graph.MainActor = _viewport.Instance; - Selection.Clear(); - Select(Graph.Main); - Graph.Root.TreeNode.Expand(true); + OnPrefabOpened(); _undo.Clear(); ClearEditedFlag(); } @@ -346,6 +341,16 @@ namespace FlaxEditor.Windows.Assets } } + private void OnPrefabOpened() + { + _viewport.Prefab = _asset; + _viewport.UpdateGizmoMode(); + Graph.MainActor = _viewport.Instance; + Selection.Clear(); + Select(Graph.Main); + Graph.Root.TreeNode.Expand(true); + } + /// public override void Save() { @@ -355,6 +360,8 @@ namespace FlaxEditor.Windows.Assets try { + Editor.Scene.OnSaveStart(_viewport._uiParentLink); + // Simply update changes Editor.Prefabs.ApplyAll(_viewport.Instance); @@ -371,6 +378,10 @@ namespace FlaxEditor.Windows.Assets throw; } + finally + { + Editor.Scene.OnSaveEnd(_viewport._uiParentLink); + } } /// @@ -411,13 +422,8 @@ namespace FlaxEditor.Windows.Assets return; } - _viewport.Prefab = _asset; - Graph.MainActor = _viewport.Instance; + OnPrefabOpened(); _focusCamera = true; - Selection.Clear(); - Select(Graph.Main); - Graph.Root.TreeNode.Expand(true); - _undo.Clear(); ClearEditedFlag(); @@ -462,11 +468,7 @@ namespace FlaxEditor.Windows.Assets _viewport.Prefab = null; if (_asset.IsLoaded) { - _viewport.Prefab = _asset; - Graph.MainActor = _viewport.Instance; - Selection.Clear(); - Select(Graph.Main); - Graph.Root.TreeNode.ExpandAll(true); + OnPrefabOpened(); } } finally @@ -478,7 +480,6 @@ namespace FlaxEditor.Windows.Assets if (_focusCamera && _viewport.Task.FrameCount > 1) { _focusCamera = false; - Editor.GetActorEditorSphere(_viewport.Instance, out BoundingSphere bounds); _viewport.ViewPosition = bounds.Center - _viewport.ViewDirection * (bounds.Radius * 1.2f); } diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index c96aa8e59..3e29b979f 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -281,6 +281,9 @@ namespace FlaxEditor.Windows _view.OnDelete += Delete; _view.OnDuplicate += Duplicate; _view.OnPaste += Paste; + + _view.InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus()); + InputActions.Add(options => options.Search, () => _itemsSearchBox.Focus()); } private ContextMenu OnViewDropdownPopupCreate(ComboBox comboBox) diff --git a/Source/Editor/Windows/EditorOptionsWindow.cs b/Source/Editor/Windows/EditorOptionsWindow.cs index 31dea22e3..e9c4385b5 100644 --- a/Source/Editor/Windows/EditorOptionsWindow.cs +++ b/Source/Editor/Windows/EditorOptionsWindow.cs @@ -23,6 +23,7 @@ namespace FlaxEditor.Windows private Tabs _tabs; private EditorOptions _options; private ToolStripButton _saveButton; + private readonly Undo _undo; private readonly List _customTabs = new List(); /// @@ -33,6 +34,12 @@ namespace FlaxEditor.Windows : base(editor, true, ScrollBars.None) { Title = "Editor Options"; + + // Undo + _undo = new Undo(); + _undo.UndoDone += OnUndoRedo; + _undo.RedoDone += OnUndoRedo; + _undo.ActionDone += OnUndoRedo; var toolstrip = new ToolStrip { @@ -58,9 +65,19 @@ namespace FlaxEditor.Windows CreateTab("Visual", () => _options.Visual); CreateTab("Source Code", () => _options.SourceCode); CreateTab("Theme", () => _options.Theme); + + // Setup input actions + InputActions.Add(options => options.Undo, _undo.PerformUndo); + InputActions.Add(options => options.Redo, _undo.PerformRedo); + InputActions.Add(options => options.Save, SaveData); _tabs.SelectedTabIndex = 0; } + + private void OnUndoRedo(IUndoAction action) + { + MarkAsEdited(); + } private Tab CreateTab(string name, Func getValue) { @@ -73,7 +90,7 @@ namespace FlaxEditor.Windows Parent = tab }; - var settings = new CustomEditorPresenter(null); + var settings = new CustomEditorPresenter(_undo); settings.Panel.Parent = panel; settings.Panel.Tag = getValue; settings.Modified += MarkAsEdited; diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 919abcb5f..30bcd131a 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -38,6 +38,7 @@ namespace FlaxEditor.Windows protected EditorWindow(Editor editor, bool hideOnClose, ScrollBars scrollBars) : base(editor.UI.MasterPanel, hideOnClose, scrollBars) { + AutoFocus = true; Editor = editor; InputActions.Add(options => options.ContentFinder, () => diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 88c42d99a..fe0c79084 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Xml; +using FlaxEditor.Gizmo; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.GUI.Input; using FlaxEditor.Options; @@ -194,133 +195,14 @@ namespace FlaxEditor.Windows public bool Active; } - private class GameRoot : ContainerControl + /// + /// Root control for game UI preview in Editor. Supports basic UI editing via . + /// + private class GameRoot : UIEditorRoot { - public bool EnableEvents => !Time.GamePaused; - - public override bool RayCast(ref Float2 location, out Control hit) - { - return RayCastChildren(ref location, out hit); - } - - public override bool ContainsPoint(ref Float2 location, bool precise = false) - { - if (precise) - return false; - return base.ContainsPoint(ref location, precise); - } - - public override bool OnCharInput(char c) - { - if (!EnableEvents) - return false; - - return base.OnCharInput(c); - } - - public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) - { - if (!EnableEvents) - return DragDropEffect.None; - - return base.OnDragDrop(ref location, data); - } - - public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) - { - if (!EnableEvents) - return DragDropEffect.None; - - return base.OnDragEnter(ref location, data); - } - - public override void OnDragLeave() - { - if (!EnableEvents) - return; - - base.OnDragLeave(); - } - - public override DragDropEffect OnDragMove(ref Float2 location, DragData data) - { - if (!EnableEvents) - return DragDropEffect.None; - - return base.OnDragMove(ref location, data); - } - - public override bool OnKeyDown(KeyboardKeys key) - { - if (!EnableEvents) - return false; - - return base.OnKeyDown(key); - } - - public override void OnKeyUp(KeyboardKeys key) - { - if (!EnableEvents) - return; - - base.OnKeyUp(key); - } - - public override bool OnMouseDoubleClick(Float2 location, MouseButton button) - { - if (!EnableEvents) - return false; - - return base.OnMouseDoubleClick(location, button); - } - - public override bool OnMouseDown(Float2 location, MouseButton button) - { - if (!EnableEvents) - return false; - - return base.OnMouseDown(location, button); - } - - public override void OnMouseEnter(Float2 location) - { - if (!EnableEvents) - return; - - base.OnMouseEnter(location); - } - - public override void OnMouseLeave() - { - if (!EnableEvents) - return; - - base.OnMouseLeave(); - } - - public override void OnMouseMove(Float2 location) - { - if (!EnableEvents) - return; - - base.OnMouseMove(location); - } - - public override bool OnMouseUp(Float2 location, MouseButton button) - { - if (!EnableEvents) - return false; - - return base.OnMouseUp(location, button); - } - - public override bool OnMouseWheel(Float2 location, float delta) - { - if (!EnableEvents) - return false; - - return base.OnMouseWheel(location, delta); - } + public override bool EnableInputs => !Time.GamePaused && Editor.IsPlayMode; + public override bool EnableSelecting => !Editor.IsPlayMode || Time.GamePaused; + public override TransformGizmo TransformGizmo => Editor.Instance.MainTransformGizmo; } /// @@ -348,13 +230,9 @@ namespace FlaxEditor.Windows // Override the game GUI root _guiRoot = new GameRoot { - AnchorPreset = AnchorPresets.StretchAll, - Offsets = Margin.Zero, - //Visible = false, - AutoFocus = false, Parent = _viewport }; - RootControl.GameRoot = _guiRoot; + RootControl.GameRoot = _guiRoot.UIRoot; SizeChanged += control => { ResizeViewport(); }; @@ -382,6 +260,56 @@ namespace FlaxEditor.Windows Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); }); + InputActions.Add(options => options.Save, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SaveAll(); + }); + InputActions.Add(options => options.Undo, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.PerformUndo(); + Focus(); + }); + InputActions.Add(options => options.Redo, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.PerformRedo(); + Focus(); + }); + InputActions.Add(options => options.Cut, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Cut(); + }); + InputActions.Add(options => options.Copy, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Copy(); + }); + InputActions.Add(options => options.Paste, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Paste(); + }); + InputActions.Add(options => options.Duplicate, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Duplicate(); + }); + InputActions.Add(options => options.Delete, () => + { + if (Editor.IsPlayMode) + return; + Editor.Instance.SceneEditing.Delete(); + }); } private void ChangeViewportRatio(ViewportScaleOptions v) @@ -916,35 +844,6 @@ namespace FlaxEditor.Windows Render2D.DrawText(style.FontLarge, "No camera", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } - // Selected UI controls outline - bool drawAnySelectedControl = false; - // TODO: optimize this (eg. cache list of selected UIControl's when selection gets changed) - var selection = Editor.SceneEditing.Selection; - for (var i = 0; i < selection.Count; i++) - { - if (selection[i].EditableObject is UIControl controlActor && controlActor && controlActor.Control != null) - { - if (!drawAnySelectedControl) - { - drawAnySelectedControl = true; - Render2D.PushTransform(ref _viewport._cachedTransform); - } - var options = Editor.Options.Options.Visual; - var control = controlActor.Control; - var bounds = control.EditorBounds; - var p1 = control.PointToParent(_viewport, bounds.UpperLeft); - var p2 = control.PointToParent(_viewport, bounds.UpperRight); - var p3 = control.PointToParent(_viewport, bounds.BottomLeft); - var p4 = control.PointToParent(_viewport, bounds.BottomRight); - var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4)); - var max = Float2.Max(Float2.Max(p1, p2), Float2.Max(p3, p4)); - bounds = new Rectangle(min, Float2.Max(max - min, Float2.Zero)); - Render2D.DrawRectangle(bounds, options.SelectionOutlineColor0, options.UISelectionOutlineSize); - } - } - if (drawAnySelectedControl) - Render2D.PopTransform(); - // Play mode hints and overlay if (Editor.StateMachine.IsPlayMode) { diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs index 87d18246a..cd76412c8 100644 --- a/Source/Editor/Windows/OutputLogWindow.cs +++ b/Source/Editor/Windows/OutputLogWindow.cs @@ -65,6 +65,11 @@ namespace FlaxEditor.Windows /// public OutputLogWindow Window; + /// + /// The input actions collection to processed during user input. + /// + public InputActionsContainer InputActions = new InputActionsContainer(); + /// /// The default text style. /// @@ -80,6 +85,14 @@ namespace FlaxEditor.Windows /// public TextBlockStyle ErrorStyle; + /// + public override bool OnKeyDown(KeyboardKeys key) + { + if (InputActions.Process(Editor.Instance, this, key)) + return true; + return base.OnKeyDown(key); + } + /// protected override void OnParseTextBlocks() { @@ -201,6 +214,9 @@ namespace FlaxEditor.Windows // Setup editor options Editor.Options.OptionsChanged += OnEditorOptionsChanged; OnEditorOptionsChanged(Editor.Options.Options); + + _output.InputActions.Add(options => options.Search, () => _searchBox.Focus()); + InputActions.Add(options => options.Search, () => _searchBox.Focus()); GameCooker.Event += OnGameCookerEvent; ScriptsBuilder.CompilationFailed += OnScriptsCompilationFailed; diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 4ff4752ac..f4a49195f 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Collections.Generic; using System.Linq; using FlaxEditor.GUI.ContextMenu; using FlaxEditor.SceneGraph; @@ -258,10 +257,9 @@ namespace FlaxEditor.Windows /// /// The parent control. /// The location (within a given control). - private void ShowContextMenu(Control parent, Float2 location) + internal void ShowContextMenu(Control parent, Float2 location) { var contextMenu = CreateContextMenu(); - contextMenu.Show(parent, location); } diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index f47cd4243..a7e4e0e44 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -93,6 +93,7 @@ namespace FlaxEditor.Windows InputActions.Add(options => options.RotateMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Rotate); InputActions.Add(options => options.ScaleMode, () => Editor.MainTransformGizmo.ActiveMode = TransformGizmoBase.Mode.Scale); InputActions.Add(options => options.FocusSelection, () => Editor.Windows.EditWin.Viewport.FocusSelection()); + InputActions.Add(options => options.LockFocusSelection, () => Editor.Windows.EditWin.Viewport.LockFocusSelection()); InputActions.Add(options => options.Rename, Rename); } @@ -221,7 +222,6 @@ namespace FlaxEditor.Windows { if (!Editor.StateMachine.CurrentState.CanEditScene) return; - ShowContextMenu(node, location); } diff --git a/Source/Engine/AI/BehaviorTreeNodes.cpp b/Source/Engine/AI/BehaviorTreeNodes.cpp index c6dbd461f..02080da7d 100644 --- a/Source/Engine/AI/BehaviorTreeNodes.cpp +++ b/Source/Engine/AI/BehaviorTreeNodes.cpp @@ -474,8 +474,8 @@ BehaviorUpdateResult BehaviorTreeMoveToNode::Update(const BehaviorUpdateContext& state->NavAgentRadius = navMesh->Properties.Agent.Radius; // Place start and end on navmesh - navMesh->ProjectPoint(state->Path.First(), state->Path.First()); - navMesh->ProjectPoint(state->Path.Last(), state->Path.Last()); + navMesh->FindClosestPoint(state->Path.First(), state->Path.First()); + navMesh->FindClosestPoint(state->Path.Last(), state->Path.Last()); // Calculate offset between path and the agent (aka feet offset) state->AgentOffset = state->Path.First() - agentLocation; diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp index 20924b5cc..5d75b676c 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.cpp @@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load() track.TrackStateIndex = TrackStatesCount++; trackRuntime->PropertyName = stream.Move(trackData->PropertyNameLength + 1); trackRuntime->PropertyTypeName = stream.Move(trackData->PropertyTypeNameLength + 1); - const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize); + int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize); + if (trackData->ValueSize == 0) + { + // When using json data (from non-POD types) read the sum of all keyframes data + const int32 keyframesDataStart = stream.GetPosition(); + for (int32 j = 0; j < trackData->KeyframesCount; j++) + { + stream.Move(); // Time + int32 jsonLen; + stream.ReadInt32(&jsonLen); + stream.Move(jsonLen); + } + const int32 keyframesDataEnd = stream.GetPosition(); + stream.SetPosition(keyframesDataStart); + keyframesDataSize = keyframesDataEnd - keyframesDataStart; + } trackRuntime->ValueSize = trackData->ValueSize; trackRuntime->KeyframesCount = trackData->KeyframesCount; trackRuntime->Keyframes = stream.Move(keyframesDataSize); + trackRuntime->KeyframesSize = keyframesDataSize; needsParent = true; break; } @@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load() trackRuntime->PropertyName = stream.Move(trackData->PropertyNameLength + 1); trackRuntime->PropertyTypeName = stream.Move(trackData->PropertyTypeNameLength + 1); const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3); + ASSERT(trackData->ValueSize > 0); trackRuntime->ValueSize = trackData->ValueSize; trackRuntime->KeyframesCount = trackData->KeyframesCount; trackRuntime->Keyframes = stream.Move(keyframesDataSize); @@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load() trackRuntime->PropertyName = stream.Move(trackData->PropertyNameLength + 1); trackRuntime->PropertyTypeName = stream.Move(trackData->PropertyTypeNameLength + 1); trackRuntime->ValueSize = trackData->ValueSize; + ASSERT(trackData->ValueSize > 0); trackRuntime->KeyframesCount = trackData->KeyframesCount; const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime)); const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount); diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h index bd1091214..f6beeb416 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h @@ -289,6 +289,7 @@ public: /// The keyframes array (items count is KeyframesCount). Each keyframe is represented by pair of time (of type float) and the value data (of size ValueSize). /// void* Keyframes; + int32 KeyframesSize; }; }; diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp index 982428057..ce23b92a4 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimationPlayer.cpp @@ -7,6 +7,7 @@ #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Level/Actors/Camera.h" #include "Engine/Serialization/Serialization.h" +#include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Audio/AudioClip.h" #include "Engine/Audio/AudioSource.h" #include "Engine/Graphics/RenderTask.h" @@ -19,6 +20,7 @@ #include "Engine/Scripting/ManagedCLR/MField.h" #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" +#include "Engine/Scripting/Internal/ManagedSerialization.h" // This could be Update, LateUpdate or FixedUpdate #define UPDATE_POINT Update @@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO case SceneAnimation::Track::Types::KeyframesProperty: case SceneAnimation::Track::Types::ObjectReferenceProperty: { - const auto trackDataKeyframes = track.GetRuntimeData(); - const int32 count = trackDataKeyframes->KeyframesCount; + const auto trackRuntime = track.GetRuntimeData(); + const int32 count = trackRuntime->KeyframesCount; if (count == 0) return false; - // Find the keyframe at time - int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize; -#define GET_KEY_TIME(idx) *(float*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (idx)) - const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1)); - int32 start = 0; - int32 searchLength = count; - while (searchLength > 0) + // If size is 0 then track uses Json storage for keyframes data (variable memory length of keyframes), otherwise it's optimized simple data with O(1) access + if (trackRuntime->ValueSize != 0) { - const int32 half = searchLength >> 1; - int32 mid = start + half; - if (keyTime < GET_KEY_TIME(mid)) + // Find the keyframe at time (binary search) + int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize; +#define GET_KEY_TIME(idx) *(float*)((byte*)trackRuntime->Keyframes + keyframeSize * (idx)) + const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1)); + int32 start = 0; + int32 searchLength = count; + while (searchLength > 0) { - searchLength = half; + const int32 half = searchLength >> 1; + int32 mid = start + half; + if (keyTime < GET_KEY_TIME(mid)) + { + searchLength = half; + } + else + { + start = mid + 1; + searchLength -= half + 1; + } + } + int32 leftKey = Math::Max(0, start - 1); +#undef GET_KEY_TIME + + // Return the value + void* value = (void*)((byte*)trackRuntime->Keyframes + keyframeSize * (leftKey) + sizeof(float)); + if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty) + { + // Object ref track uses Guid for object Id storage + Guid id = *(Guid*)value; + _objectsMapping.TryGet(id, id); + auto obj = Scripting::FindObject(id); + value = obj ? obj->GetOrCreateManagedInstance() : nullptr; + *(void**)target = value; } else { - start = mid + 1; - searchLength -= half + 1; + // POD memory + Platform::MemoryCopy(target, value, trackRuntime->ValueSize); } } - int32 leftKey = Math::Max(0, start - 1); -#undef GET_KEY_TIME - - // Return the value - void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float)); - if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty) - { - Guid id = *(Guid*)value; - _objectsMapping.TryGet(id, id); - auto obj = Scripting::FindObject(id); - value = obj ? obj->GetOrCreateManagedInstance() : nullptr; - *(void**)target = value; - } else { - Platform::MemoryCopy(target, value, trackDataKeyframes->ValueSize); + // Clear pointer + *(void**)target = nullptr; + + // Find the keyframe at time (linear search) + MemoryReadStream stream((byte*)trackRuntime->Keyframes, trackRuntime->KeyframesSize); + int32 prevKeyPos = sizeof(float); + int32 jsonLen; + for (int32 key = 0; key < count; key++) + { + float keyTime; + stream.ReadFloat(&keyTime); + if (keyTime > time) + break; + prevKeyPos = stream.GetPosition(); + stream.ReadInt32(&jsonLen); + stream.Move(jsonLen); + } + + // Read json text + stream.SetPosition(prevKeyPos); + stream.ReadInt32(&jsonLen); + const StringAnsiView json((const char*)stream.GetPositionHandle(), jsonLen); + + // Create empty value of the keyframe type + const auto trackData = track.GetData(); + const StringAnsiView propertyTypeName(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength); + MClass* klass = Scripting::FindClass(propertyTypeName); + if (!klass) + return false; + MObject* obj = MCore::Object::New(klass); + if (!obj) + return false; + if (!klass->IsValueType()) + MCore::Object::Init(obj); + + // Deserialize value from json + ManagedSerialization::Deserialize(json, obj); + + // Set value + *(void**)target = obj; } break; } @@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO } case SceneAnimation::Track::Types::StringProperty: { - const auto trackDataKeyframes = track.GetRuntimeData(); - const int32 count = trackDataKeyframes->KeyframesCount; + const auto trackRuntime = track.GetRuntimeData(); + const int32 count = trackRuntime->KeyframesCount; if (count == 0) return false; - const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime)); - const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount); - const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount); + const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime)); + const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount); + const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount); // Find the keyframe at time #define GET_KEY_TIME(idx) keyframesTimes[idx] @@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO auto& childTrack = anim->Tracks[childTrackIndex]; if (childTrack.Disabled || childTrack.ParentIndex != trackIndex) continue; - const auto childTrackRuntimeData = childTrack.GetRuntimeData(); + const auto childTrackRuntime = childTrack.GetRuntimeData(); auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex]; // Cache field @@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO if (!type) continue; MClass* mclass = MCore::Type::GetClass(type); - childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName); + childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName); if (!childTrackState.Field) continue; } @@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3 if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value)) { // Set the value - if (MCore::Type::IsPointer(valueType)) + auto valueTypes = MCore::Type::GetType(valueType); + if (valueTypes == MTypes::Object || MCore::Type::IsPointer(valueType)) value = (void*)*(intptr*)value; if (state.Property) { diff --git a/Source/Engine/Audio/AudioBackendTools.h b/Source/Engine/Audio/AudioBackendTools.h new file mode 100644 index 000000000..2630dca6f --- /dev/null +++ b/Source/Engine/Audio/AudioBackendTools.h @@ -0,0 +1,171 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Math/Transform.h" + +/// +/// The helper class for that handles active audio backend operations. +/// +class AudioBackendTools +{ +public: + struct Settings + { + float Volume = 1.0f; + float DopplerFactor = 1.0f; + }; + + struct Listener + { + Vector3 Velocity; + Vector3 Position; + Quaternion Orientation; + }; + + struct Source + { + bool Is3D; + float Volume; + float Pitch; + float Pan; + float MinDistance; + float Attenuation; + float DopplerFactor; + Vector3 Velocity; + Vector3 Position; + Quaternion Orientation; + }; + + enum Channels + { + FrontLeft = 0, + FrontRight = 1, + FontCenter = 2, + BackLeft = 3, + BackRight = 4, + SideLeft = 5, + SideRight = 6, + MaxChannels + }; + + struct SoundMix + { + float Pitch; + float Volume; + float Channels[MaxChannels]; + + void VolumeIntoChannels() + { + for (float& c : Channels) + c *= Volume; + Volume = 1.0f; + } + }; + + static SoundMix CalculateSoundMix(const Settings& settings, const Listener& listener, const Source& source, int32 channelCount = 2) + { + ASSERT_LOW_LAYER(channelCount <= MaxChannels); + SoundMix mix; + mix.Pitch = source.Pitch; + mix.Volume = source.Volume * settings.Volume; + Platform::MemoryClear(mix.Channels, sizeof(mix.Channels)); + if (source.Is3D) + { + const Transform listenerTransform(listener.Position, listener.Orientation); + float distance = (float)Vector3::Distance(listener.Position, source.Position); + float gain = 1; + + // Calculate attenuation (OpenAL formula for mode: AL_INVERSE_DISTANCE_CLAMPED) + // [https://www.openal.org/documentation/openal-1.1-specification.pdf] + distance = Math::Clamp(distance, source.MinDistance, MAX_float); + const float dst = source.MinDistance + source.Attenuation * (distance - source.MinDistance); + if (dst > 0) + gain = source.MinDistance / dst; + mix.Volume *= Math::Saturate(gain); + + // Calculate panning + // Ramy Sadek and Chris Kyriakakis, 2004, "A Novel Multichannel Panning Method for Standard and Arbitrary Loudspeaker Configurations" + // [https://www.researchgate.net/publication/235080603_A_Novel_Multichannel_Panning_Method_for_Standard_and_Arbitrary_Loudspeaker_Configurations] + static const Float3 ChannelDirections[MaxChannels] = + { + Float3(-1.0, 0.0, -1.0).GetNormalized(), + Float3(1.0, 0.0, -1.0).GetNormalized(), + Float3(0.0, 0.0, -1.0).GetNormalized(), + Float3(-1.0, 0.0, 1.0).GetNormalized(), + Float3(1.0, 0.0, 1.0).GetNormalized(), + Float3(-1.0, 0.0, 0.0).GetNormalized(), + Float3(1.0, 0.0, 0.0).GetNormalized(), + }; + const Float3 sourceInListenerSpace = (Float3)listenerTransform.WorldToLocal(source.Position); + const Float3 sourceToListener = Float3::Normalize(sourceInListenerSpace); + float sqGainsSum = 0.0f; + for (int32 i = 0; i < channelCount; i++) + { + float othersSum = 0.0f; + for (int32 j = 0; j < channelCount; j++) + othersSum += (1.0f + Float3::Dot(ChannelDirections[i], ChannelDirections[j])) * 0.5f; + const float sqGain = Math::Square(0.5f * Math::Pow(1.0f + Float3::Dot(ChannelDirections[i], sourceToListener), 2.0f) / othersSum); + sqGainsSum += sqGain; + mix.Channels[i] = sqGain; + } + for (int32 i = 0; i < channelCount; i++) + mix.Channels[i] = Math::Sqrt(mix.Channels[i] / sqGainsSum); + + // Calculate doppler + const Float3 velocityInListenerSpace = (Float3)listenerTransform.WorldToLocalVector(source.Velocity - listener.Velocity); + const float velocity = velocityInListenerSpace.Length(); + const float dopplerFactor = settings.DopplerFactor * source.DopplerFactor; + if (dopplerFactor > 0.0f && velocity > 0.0f) + { + constexpr float speedOfSound = 343.3f * 100.0f * 100.0f; // in air, in Flax units + const float approachingFactor = Float3::Dot(sourceToListener, velocityInListenerSpace.GetNormalized()); + const float dopplerPitch = speedOfSound / (speedOfSound + velocity * approachingFactor); + mix.Pitch *= Math::Clamp(dopplerPitch, 0.1f, 10.0f); + } + } + else + { + const float panLeft = Math::Min(1.0f - source.Pan, 1.0f); + const float panRight = Math::Min(1.0f + source.Pan, 1.0f); + switch (channelCount) + { + case 1: + mix.Channels[0] = 1.0f; + break; + case 2: + default: // TODO: handle other channel configuration (eg. 7.1 or 5.1) + mix.Channels[FrontLeft] = panLeft; + mix.Channels[FrontRight] = panRight; + break; + } + } + return mix; + } + + static void MapChannels(int32 sourceChannels, int32 outputChannels, float channels[MaxChannels], float* outputMatrix) + { + Platform::MemoryClear(outputMatrix, sizeof(float) * sourceChannels * outputChannels); + switch (outputChannels) + { + case 1: + outputMatrix[0] = channels[FrontLeft]; + break; + case 2: + default: // TODO: implement multi-channel support (eg. 5.1, 7.1) + if (sourceChannels == 1) + { + outputMatrix[0] = channels[FrontLeft]; + outputMatrix[1] = channels[FrontRight]; + } + else if (sourceChannels == 2) + { + outputMatrix[0] = channels[FrontLeft]; + outputMatrix[1] = 0.0f; + outputMatrix[2] = 0.0f; + outputMatrix[3] = channels[FrontRight]; + } + break; + } + } +}; diff --git a/Source/Engine/Audio/AudioSettings.h b/Source/Engine/Audio/AudioSettings.h index f4f901b97..edff00390 100644 --- a/Source/Engine/Audio/AudioSettings.h +++ b/Source/Engine/Audio/AudioSettings.h @@ -3,7 +3,6 @@ #pragma once #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" /// /// Audio settings container. @@ -11,6 +10,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AudioSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(AudioSettings); + API_AUTO_SERIALIZATION(); public: /// @@ -46,12 +46,4 @@ public: // [SettingsBase] void Apply() override; - - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(DisableAudio); - DESERIALIZE(DopplerFactor); - DESERIALIZE(MuteOnFocusLoss); - DESERIALIZE(EnableHRTF); - } }; diff --git a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp index d82f0244c..7c16f05ca 100644 --- a/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp +++ b/Source/Engine/Audio/XAudio2/AudioBackendXAudio2.cpp @@ -3,7 +3,7 @@ #if AUDIO_API_XAUDIO2 #include "AudioBackendXAudio2.h" -#include "Engine/Audio/AudioSettings.h" +#include "Engine/Audio/AudioBackendTools.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Core/Log.h" @@ -22,10 +22,11 @@ // Documentation: https://docs.microsoft.com/en-us/windows/desktop/xaudio2/xaudio2-apis-portal #include //#include -#include +//#include +// TODO: implement multi-channel support (eg. 5.1, 7.1) #define MAX_INPUT_CHANNELS 2 -#define MAX_OUTPUT_CHANNELS 8 +#define MAX_OUTPUT_CHANNELS 2 #define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS) #if ENABLE_ASSERTION #define XAUDIO2_CHECK_ERROR(method) \ @@ -36,18 +37,12 @@ #else #define XAUDIO2_CHECK_ERROR(method) #endif -#define FLAX_COORD_SCALE 0.01f // units are meters -#define FLAX_DST_TO_XAUDIO(x) x * FLAX_COORD_SCALE -#define FLAX_POS_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * FLAX_COORD_SCALE, vec.Y * FLAX_COORD_SCALE, vec.Z * FLAX_COORD_SCALE) -#define FLAX_VEL_TO_XAUDIO(vec) X3DAUDIO_VECTOR(vec.X * (FLAX_COORD_SCALE*FLAX_COORD_SCALE), vec.Y * (FLAX_COORD_SCALE*FLAX_COORD_SCALE), vec.Z * (FLAX_COORD_SCALE*FLAX_COORD_SCALE)) -#define FLAX_VEC_TO_XAUDIO(vec) (*((X3DAUDIO_VECTOR*)&vec)) namespace XAudio2 { - struct Listener + struct Listener : AudioBackendTools::Listener { AudioListener* AudioListener; - X3DAUDIO_LISTENER Data; Listener() { @@ -57,7 +52,6 @@ namespace XAudio2 void Init() { AudioListener = nullptr; - Data.pCone = nullptr; } bool IsFree() const @@ -67,21 +61,13 @@ namespace XAudio2 void UpdateTransform() { - const Vector3& position = AudioListener->GetPosition(); - const Quaternion& orientation = AudioListener->GetOrientation(); - const Vector3 front = orientation * Vector3::Forward; - const Vector3 top = orientation * Vector3::Up; - - Data.OrientFront = FLAX_VEC_TO_XAUDIO(front); - Data.OrientTop = FLAX_VEC_TO_XAUDIO(top); - Data.Position = FLAX_POS_TO_XAUDIO(position); + Position = AudioListener->GetPosition(); + Orientation = AudioListener->GetOrientation(); } void UpdateVelocity() { - const Vector3& velocity = AudioListener->GetVelocity(); - - Data.Velocity = FLAX_VEL_TO_XAUDIO(velocity); + Velocity = AudioListener->GetVelocity(); } }; @@ -123,23 +109,18 @@ namespace XAudio2 void PeekSamples(); }; - struct Source + struct Source : AudioBackendTools::Source { IXAudio2SourceVoice* Voice; - X3DAUDIO_EMITTER Data; WAVEFORMATEX Format; XAUDIO2_SEND_DESCRIPTOR Destination; - float Pitch; - float Pan; float StartTimeForQueueBuffer; float LastBufferStartTime; - float DopplerFactor; uint64 LastBufferStartSamplesPlayed; int32 BuffersProcessed; + int32 Channels; bool IsDirty; - bool Is3D; bool IsPlaying; - bool IsForceMono3D; VoiceCallback Callback; Source() @@ -150,8 +131,6 @@ namespace XAudio2 void Init() { Voice = nullptr; - Platform::MemoryClear(&Data, sizeof(Data)); - Data.CurveDistanceScaler = 1.0f; Destination.Flags = 0; Destination.pOutputVoice = nullptr; Pitch = 1.0f; @@ -172,21 +151,13 @@ namespace XAudio2 void UpdateTransform(const AudioSource* source) { - const Vector3& position = source->GetPosition(); - const Quaternion& orientation = source->GetOrientation(); - const Vector3 front = orientation * Vector3::Forward; - const Vector3 top = orientation * Vector3::Up; - - Data.OrientFront = FLAX_VEC_TO_XAUDIO(front); - Data.OrientTop = FLAX_VEC_TO_XAUDIO(top); - Data.Position = FLAX_POS_TO_XAUDIO(position); + Position = source->GetPosition(); + Orientation = source->GetOrientation(); } void UpdateVelocity(const AudioSource* source) { - const Vector3& velocity = source->GetVelocity(); - - Data.Velocity = FLAX_VEL_TO_XAUDIO(velocity); + Velocity = source->GetVelocity(); } }; @@ -214,11 +185,9 @@ namespace XAudio2 IXAudio2* Instance = nullptr; IXAudio2MasteringVoice* MasteringVoice = nullptr; - X3DAUDIO_HANDLE X3DInstance; - DWORD ChannelMask; - UINT32 SampleRate; - UINT32 Channels; + int32 Channels; bool ForceDirty = true; + AudioBackendTools::Settings Settings; Listener Listeners[AUDIO_MAX_LISTENERS]; CriticalSection Locker; ChunkedArray Sources; @@ -387,7 +356,7 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source) const auto& header = clip->AudioHeader; auto& format = aSource->Format; format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write) + format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write if FeatureFlags::SpatialMultiChannel is unset) format.nSamplesPerSec = header.Info.SampleRate; format.wBitsPerSample = header.Info.BitDepth; format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8)); @@ -408,26 +377,25 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source) if (FAILED(hr)) return; + sourceID++; // 0 is invalid ID so shift them + source->SourceIDs.Add(sourceID); + // Prepare source state aSource->Callback.Source = source; aSource->IsDirty = true; - aSource->Data.ChannelCount = format.nChannels; - aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance()); aSource->Is3D = source->Is3D(); - aSource->IsForceMono3D = header.Is3D && header.Info.NumChannels > 1; aSource->Pitch = source->GetPitch(); aSource->Pan = source->GetPan(); aSource->DopplerFactor = source->GetDopplerFactor(); + aSource->Volume = source->GetVolume(); + aSource->MinDistance = source->GetMinDistance(); + aSource->Attenuation = source->GetAttenuation(); + aSource->Channels = format.nChannels; aSource->UpdateTransform(source); aSource->UpdateVelocity(source); hr = aSource->Voice->SetVolume(source->GetVolume()); XAUDIO2_CHECK_ERROR(SetVolume); - // 0 is invalid ID so shift them - sourceID++; - - source->SourceIDs.Add(sourceID); - source->Restore(); } @@ -462,6 +430,7 @@ void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source) auto aSource = XAudio2::GetSource(source); if (aSource && aSource->Voice) { + aSource->Volume = source->GetVolume(); const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume()); XAUDIO2_CHECK_ERROR(SetVolume); } @@ -546,17 +515,10 @@ void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source) auto aSource = XAudio2::GetSource(source); if (aSource) { - // TODO: implement attenuation settings for 3d audio - auto clip = source->Clip.Get(); - if (clip && clip->IsLoaded()) - { - const auto& header = clip->AudioHeader; - aSource->Data.ChannelCount = source->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write) - aSource->IsForceMono3D = header.Is3D && header.Info.NumChannels > 1; - } aSource->Is3D = source->Is3D(); + aSource->MinDistance = source->GetMinDistance(); + aSource->Attenuation = source->GetAttenuation(); aSource->DopplerFactor = source->GetDopplerFactor(); - aSource->Data.InnerRadius = FLAX_DST_TO_XAUDIO(source->GetMinDistance()); aSource->IsDirty = true; } } @@ -655,7 +617,7 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source aSource->Voice->GetState(&state); const uint32 numChannels = clipInfo.NumChannels; const uint32 totalSamples = clipInfo.NumSamples / numChannels; - const uint32 sampleRate = clipInfo.SampleRate;// / clipInfo.NumChannels; + const uint32 sampleRate = clipInfo.SampleRate; // / clipInfo.NumChannels; state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast(Math::Max(1U, sampleRate)); } @@ -770,6 +732,8 @@ void AudioBackendXAudio2::Buffer_Delete(uint32 bufferId) void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) { + CHECK(info.NumChannels <= MAX_INPUT_CHANNELS); + XAudio2::Locker.Lock(); XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Locker.Unlock(); @@ -796,6 +760,7 @@ void AudioBackendXAudio2::Base_OnActiveDeviceChanged() void AudioBackendXAudio2::Base_SetDopplerFactor(float value) { + XAudio2::Settings.DopplerFactor = value; XAudio2::MarkAllDirty(); } @@ -803,6 +768,7 @@ void AudioBackendXAudio2::Base_SetVolume(float value) { if (XAudio2::MasteringVoice) { + XAudio2::Settings.Volume = 1.0f; // Volume is applied via MasteringVoice const HRESULT hr = XAudio2::MasteringVoice->SetVolume(value); XAUDIO2_CHECK_ERROR(SetVolume); } @@ -830,7 +796,8 @@ bool AudioBackendXAudio2::Base_Init() } XAUDIO2_VOICE_DETAILS details; XAudio2::MasteringVoice->GetVoiceDetails(&details); - XAudio2::SampleRate = details.InputSampleRate; +#if 0 + // TODO: implement multi-channel support (eg. 5.1, 7.1) XAudio2::Channels = details.InputChannels; hr = XAudio2::MasteringVoice->GetChannelMask(&XAudio2::ChannelMask); if (FAILED(hr)) @@ -838,19 +805,10 @@ bool AudioBackendXAudio2::Base_Init() LOG(Error, "Failed to get XAudio2 mastering voice channel mask. Error: 0x{0:x}", hr); return true; } - - // Initialize spatial audio subsystem - DWORD dwChannelMask; - XAudio2::MasteringVoice->GetChannelMask(&dwChannelMask); - hr = X3DAudioInitialize(dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, XAudio2::X3DInstance); - if (FAILED(hr)) - { - LOG(Error, "Failed to initalize XAudio2 3D support. Error: 0x{0:x}", hr); - return true; - } - - // Info - LOG(Info, "XAudio2: {0} channels at {1} kHz (channel mask {2})", XAudio2::Channels, XAudio2::SampleRate / 1000.0f, XAudio2::ChannelMask); +#else + XAudio2::Channels = 2; +#endif + LOG(Info, "XAudio2: {0} channels at {1} kHz", XAudio2::Channels, details.InputSampleRate / 1000.0f); // Dummy device devices.Resize(1); @@ -864,53 +822,19 @@ void AudioBackendXAudio2::Base_Update() { // Update dirty voices const auto listener = XAudio2::GetListener(); - const float dopplerFactor = AudioSettings::Get()->DopplerFactor; - float matrixCoefficients[MAX_CHANNELS_MATRIX_SIZE]; - X3DAUDIO_DSP_SETTINGS dsp = { 0 }; - dsp.DstChannelCount = XAudio2::Channels; - dsp.pMatrixCoefficients = matrixCoefficients; + float outputMatrix[MAX_CHANNELS_MATRIX_SIZE]; for (int32 i = 0; i < XAudio2::Sources.Count(); i++) { auto& source = XAudio2::Sources[i]; if (source.IsFree() || !(source.IsDirty || XAudio2::ForceDirty)) continue; - dsp.SrcChannelCount = source.Data.ChannelCount; - if (source.Is3D && listener) - { - // 3D sound - X3DAudioCalculate(XAudio2::X3DInstance, &listener->Data, &source.Data, X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER, &dsp); - } - else - { - // 2D sound - dsp.DopplerFactor = 1.0f; - Platform::MemoryClear(dsp.pMatrixCoefficients, sizeof(matrixCoefficients)); - dsp.pMatrixCoefficients[0] = 1.0f; - if (source.Format.nChannels == 1) - { - dsp.pMatrixCoefficients[1] = 1.0f; - } - else - { - dsp.pMatrixCoefficients[3] = 1.0f; - } - const float panLeft = Math::Min(1.0f - source.Pan, 1.0f); - const float panRight = Math::Min(1.0f + source.Pan, 1.0f); - if (source.Format.nChannels >= 2) - { - dsp.pMatrixCoefficients[0] *= panLeft; - dsp.pMatrixCoefficients[3] *= panRight; - } - } - if (source.IsForceMono3D) - { - // Hack to fix playback speed for 3D clip that has auto-converted stereo to mono at runtime - dsp.DopplerFactor *= 0.5f; - } - const float frequencyRatio = dopplerFactor * source.Pitch * dsp.DopplerFactor * source.DopplerFactor; - source.Voice->SetFrequencyRatio(frequencyRatio); - source.Voice->SetOutputMatrix(XAudio2::MasteringVoice, dsp.SrcChannelCount, dsp.DstChannelCount, dsp.pMatrixCoefficients); + auto mix = AudioBackendTools::CalculateSoundMix(XAudio2::Settings, *listener, source, XAudio2::Channels); + mix.VolumeIntoChannels(); + AudioBackendTools::MapChannels(source.Channels, XAudio2::Channels, mix.Channels, outputMatrix); + + source.Voice->SetFrequencyRatio(mix.Pitch); + source.Voice->SetOutputMatrix(XAudio2::MasteringVoice, source.Channels, XAudio2::Channels, outputMatrix); source.IsDirty = false; } diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index 688a3f7b5..7735d8b28 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -584,7 +584,43 @@ Asset::LoadResult BinaryAsset::loadAsset() ASSERT(Storage && _header.ID.IsValid() && _header.TypeName.HasChars()); auto lock = Storage->Lock(); - return load(); + auto chunksToPreload = getChunksToPreload(); + if (chunksToPreload != 0) + { + // Ensure that any chunks that were requested before are loaded in memory (in case streaming flushed them out after timeout) + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + const auto chunk = _header.Chunks[i]; + if (GET_CHUNK_FLAG(i) & chunksToPreload && chunk && chunk->IsMissing()) + Storage->LoadAssetChunk(chunk); + } + } + const LoadResult result = load(); +#if !BUILD_RELEASE + if (result == LoadResult::MissingDataChunk) + { + // Provide more insights on potentially missing asset data chunk + Char chunksBitMask[ASSET_FILE_DATA_CHUNKS + 1]; + Char chunksExistBitMask[ASSET_FILE_DATA_CHUNKS + 1]; + Char chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS + 1]; + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + if (const FlaxChunk* chunk = _header.Chunks[i]) + { + chunksBitMask[i] = '1'; + chunksExistBitMask[i] = chunk->ExistsInFile() ? '1' : '0'; + chunksLoadBitMask[i] = chunk->IsLoaded() ? '1' : '0'; + } + else + { + chunksBitMask[i] = chunksExistBitMask[i] = chunksLoadBitMask[i] = '0'; + } + } + chunksBitMask[ASSET_FILE_DATA_CHUNKS] = chunksExistBitMask[ASSET_FILE_DATA_CHUNKS] = chunksLoadBitMask[ASSET_FILE_DATA_CHUNKS] = 0; + LOG(Warning, "Asset reports missing data chunk. Chunks bitmask: {}, existing chunks: {} loaded chunks: {}. '{}'", chunksBitMask, chunksExistBitMask, chunksLoadBitMask, ToString()); + } +#endif + return result; } void BinaryAsset::releaseStorage() diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp index c9a10f721..188a52583 100644 --- a/Source/Engine/Content/Cache/AssetsCache.cpp +++ b/Source/Engine/Content/Cache/AssetsCache.cpp @@ -4,6 +4,8 @@ #include "Engine/Core/Log.h" #include "Engine/Core/DeleteMe.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Serialization/FileReadStream.h" @@ -19,7 +21,7 @@ void AssetsCache::Init() { Entry e; int32 count; - const DateTime loadStartTime = DateTime::Now(); + Stopwatch stopwatch; #if USE_EDITOR _path = Globals::ProjectCacheFolder / TEXT("AssetsCache.dat"); #else @@ -138,8 +140,8 @@ void AssetsCache::Init() } } - const int32 loadTimeInMs = static_cast((DateTime::Now() - loadStartTime).GetTotalMilliseconds()); - LOG(Info, "Asset Cache loaded {0} entries in {1} ms ({2} rejected)", _registry.Count(), loadTimeInMs, rejectedCount); + stopwatch.Stop(); + LOG(Info, "Asset Cache loaded {0} entries in {1}ms ({2} rejected)", _registry.Count(), stopwatch.GetMilliseconds(), rejectedCount); } bool AssetsCache::Save() diff --git a/Source/Engine/Content/Loading/ContentLoadingManager.cpp b/Source/Engine/Content/Loading/ContentLoadingManager.cpp index 7f02e28ca..68d06b3d2 100644 --- a/Source/Engine/Content/Loading/ContentLoadingManager.cpp +++ b/Source/Engine/Content/Loading/ContentLoadingManager.cpp @@ -163,7 +163,7 @@ bool ContentLoadingManagerService::Init() // Calculate amount of loading threads to use const CPUInfo cpuInfo = Platform::GetCPUInfo(); - const int32 count = Math::Clamp(static_cast(LOADING_THREAD_PER_LOGICAL_CORE * (float)cpuInfo.LogicalProcessorCount), 1, 12); + const int32 count = Math::Clamp(Math::CeilToInt(LOADING_THREAD_PER_LOGICAL_CORE * (float)cpuInfo.LogicalProcessorCount), 1, 12); LOG(Info, "Creating {0} content loading threads...", count); // Create loading threads diff --git a/Source/Engine/Content/Storage/AssetHeader.h b/Source/Engine/Content/Storage/AssetHeader.h index 0730be025..ad5c8efb9 100644 --- a/Source/Engine/Content/Storage/AssetHeader.h +++ b/Source/Engine/Content/Storage/AssetHeader.h @@ -6,6 +6,7 @@ #include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/String.h" #if USE_EDITOR +#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Collections/Array.h" #endif #include "FlaxChunk.h" @@ -81,36 +82,17 @@ public: /// /// Gets the amount of created asset chunks. /// - /// Created asset chunks - int32 GetChunksCount() const - { - int32 result = 0; - for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) - { - if (Chunks[i] != nullptr) - result++; - } - return result; - } + int32 GetChunksCount() const; /// /// Deletes all chunks. Warning! Chunks are managed internally, use with caution! /// - void DeleteChunks() - { - for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) - { - SAFE_DELETE(Chunks[i]); - } - } + void DeleteChunks(); /// /// Unlinks all chunks. /// - void UnlinkChunks() - { - Platform::MemoryClear(Chunks, sizeof(Chunks)); - } + void UnlinkChunks(); /// /// Gets string with a human-readable info about that header diff --git a/Source/Engine/Content/Storage/FlaxChunk.h b/Source/Engine/Content/Storage/FlaxChunk.h index f9ec5f154..5281aea28 100644 --- a/Source/Engine/Content/Storage/FlaxChunk.h +++ b/Source/Engine/Content/Storage/FlaxChunk.h @@ -78,9 +78,9 @@ public: FlaxChunkFlags Flags = FlaxChunkFlags::None; /// - /// The last usage time (atomic, ticks of DateTime in UTC). + /// The last usage time. /// - int64 LastAccessTime = 0; + double LastAccessTime = 0.0; /// /// The chunk data. diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index 0ba268e21..74b114f52 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -20,6 +20,30 @@ #endif #include +int32 AssetHeader::GetChunksCount() const +{ + int32 result = 0; + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + if (Chunks[i] != nullptr) + result++; + } + return result; +} + +void AssetHeader::DeleteChunks() +{ + for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++) + { + SAFE_DELETE(Chunks[i]); + } +} + +void AssetHeader::UnlinkChunks() +{ + Platform::MemoryClear(Chunks, sizeof(Chunks)); +} + String AssetHeader::ToString() const { return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount()); @@ -27,7 +51,7 @@ String AssetHeader::ToString() const void FlaxChunk::RegisterUsage() { - Platform::AtomicStore(&LastAccessTime, DateTime::NowUTC().Ticks); + LastAccessTime = Platform::GetTimeSeconds(); } const int32 FlaxStorage::MagicCode = 1180124739; @@ -235,7 +259,9 @@ uint32 FlaxStorage::GetRefCount() const bool FlaxStorage::ShouldDispose() const { - return Platform::AtomicRead((int64*)&_refCount) == 0 && Platform::AtomicRead((int64*)&_chunksLock) == 0 && DateTime::NowUTC() - _lastRefLostTime >= TimeSpan::FromMilliseconds(500); + return Platform::AtomicRead((int64*)&_refCount) == 0 && + Platform::AtomicRead((int64*)&_chunksLock) == 0 && + Platform::GetTimeSeconds() - _lastRefLostTime >= 0.5; // TTL in seconds } uint32 FlaxStorage::GetMemoryUsage() const @@ -1374,12 +1400,13 @@ void FlaxStorage::Tick() if (Platform::AtomicRead(&_chunksLock) != 0) return; - const auto now = DateTime::NowUTC(); + const double now = Platform::GetTimeSeconds(); bool wasAnyUsed = false; + const float unusedDataChunksLifetime = ContentStorageManager::UnusedDataChunksLifetime.GetTotalSeconds(); for (int32 i = 0; i < _chunks.Count(); i++) { - auto chunk = _chunks[i]; - const bool wasUsed = (now - DateTime(Platform::AtomicRead(&chunk->LastAccessTime))) < ContentStorageManager::UnusedDataChunksLifetime; + auto chunk = _chunks.Get()[i]; + const bool wasUsed = (now - chunk->LastAccessTime) < unusedDataChunksLifetime; if (!wasUsed && chunk->IsLoaded()) { chunk->Unload(); diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 53e1b6604..cc04e24a6 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -5,7 +5,6 @@ #include "Engine/Core/Object.h" #include "Engine/Core/Delegate.h" #include "Engine/Core/Types/String.h" -#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Serialization/FileReadStream.h" @@ -90,7 +89,7 @@ protected: // State int64 _refCount; int64 _chunksLock; - DateTime _lastRefLostTime; + double _lastRefLostTime; CriticalSection _loadLocker; // Storage @@ -115,7 +114,7 @@ private: Platform::InterlockedDecrement(&_refCount); if (Platform::AtomicRead(&_refCount) == 0) { - _lastRefLostTime = DateTime::NowUTC(); + _lastRefLostTime = Platform::GetTimeSeconds(); } } diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index 568630d88..86bc9571f 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -3,7 +3,6 @@ #pragma once #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Serialization.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Content/Asset.h" #include "Engine/Content/AssetReference.h" @@ -15,8 +14,15 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings); + API_AUTO_SERIALIZATION(); public: + /// + /// Name of the output app created by the build system. Used to rename main executable (eg. MyGame.exe) or final package name (eg. MyGame.apk). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. + /// + API_FIELD(Attributes="EditorOrder(0), EditorDisplay(\"General\")") + String OutputName = TEXT("${PROJECT_NAME}"); + /// /// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to. /// @@ -100,21 +106,4 @@ public: /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// static BuildSettings* Get(); - - // [SettingsBase] - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - DESERIALIZE(MaxAssetsPerPackage); - DESERIALIZE(MaxPackageSizeMB); - DESERIALIZE(ContentKey); - DESERIALIZE(ForDistribution); - DESERIALIZE(SkipPackaging); - DESERIALIZE(AdditionalAssets); - DESERIALIZE(AdditionalAssetFolders); - DESERIALIZE(ShadersNoOptimize); - DESERIALIZE(ShadersGenerateDebugData); - DESERIALIZE(SkipDefaultFonts); - DESERIALIZE(SkipDotnetPackaging); - DESERIALIZE(SkipUnusedDotnetLibsPackaging); - } }; diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 76fb0f437..11617612f 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -22,6 +22,7 @@ #include "Engine/Engine/Globals.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Streaming/StreamingSettings.h" +#include "Engine/Serialization/Serialization.h" #if FLAX_TESTS || USE_EDITOR #include "Engine/Platform/FileSystem.h" #endif diff --git a/Source/Engine/Core/Config/GameSettings.h b/Source/Engine/Core/Config/GameSettings.h index d28dcaa56..59e504b6b 100644 --- a/Source/Engine/Core/Config/GameSettings.h +++ b/Source/Engine/Core/Config/GameSettings.h @@ -12,9 +12,9 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API GameSettings : public SettingsBase { -DECLARE_SCRIPTING_TYPE_MINIMAL(GameSettings); -public: + DECLARE_SCRIPTING_TYPE_MINIMAL(GameSettings); +public: /// /// The product full name. /// @@ -59,7 +59,6 @@ public: Dictionary CustomSettings; public: - // Settings containers Guid Time; Guid Audio; @@ -87,7 +86,6 @@ public: Guid iOSPlatform; public: - /// /// Gets the instance of the game settings asset (null if missing). Object returned by this method is always loaded with valid data to use. /// diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index 39d7506d5..20e3977a7 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -11,9 +11,9 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LayersAndTagsSettings : public SettingsBase { -DECLARE_SCRIPTING_TYPE_MINIMAL(LayersAndTagsSettings); -public: + DECLARE_SCRIPTING_TYPE_MINIMAL(LayersAndTagsSettings); +public: /// /// The tag names. /// @@ -25,7 +25,6 @@ public: String Layers[32]; public: - /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 2a5a39a72..3edd83195 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -9,9 +9,10 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API TimeSettings : public SettingsBase { -DECLARE_SCRIPTING_TYPE_MINIMAL(TimeSettings); -public: + DECLARE_SCRIPTING_TYPE_MINIMAL(TimeSettings); + API_AUTO_SERIALIZATION(); +public: /// /// The target amount of the game logic updates per second (script updates frequency). /// @@ -43,7 +44,6 @@ public: float MaxUpdateDeltaTime = 0.1f; public: - /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. /// @@ -51,5 +51,4 @@ public: // [SettingsBase] void Apply() override; - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index 50b98f966..c8cf4419e 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -32,6 +32,8 @@ Delegate Log::Logger::OnError; bool Log::Logger::Init() { + LogStartTime = Time::StartupTime; + // Skip if disabled if (!IsLogEnabled()) return false; @@ -73,7 +75,6 @@ bool Log::Logger::Init() #endif // Create log file path - LogStartTime = Time::StartupTime; const String filename = TEXT("Log_") + LogStartTime.ToFileNameString() + TEXT(".txt"); LogFilePath = logsDirectory / filename; @@ -198,12 +199,11 @@ void Log::Logger::WriteFloor() void Log::Logger::ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w) { const TimeSpan time = DateTime::Now() - LogStartTime; - const int32 msgLength = msg.Length(); - fmt_flax::format(w, TEXT("[ {0} ]: [{1}] "), *time.ToString('a'), ToString(type)); // On Windows convert all '\n' into '\r\n' #if PLATFORM_WINDOWS + const int32 msgLength = msg.Length(); bool hasWindowsNewLine = false; for (int32 i = 1; i < msgLength && !hasWindowsNewLine; i++) hasWindowsNewLine |= msg.Get()[i - 1] != '\r' && msg.Get()[i] == '\n'; diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index 02a9e5a79..2c51bfa4a 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -18,13 +18,13 @@ API_STRUCT() struct FLAXENGINE_API Transform /// /// The translation vector of the transform. /// - API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\")") + API_FIELD(Attributes="EditorOrder(10), EditorDisplay(null, \"Position\"), ValueCategory(Utils.ValueCategory.Distance)") Vector3 Translation; /// /// The rotation of the transform. /// - API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\")") + API_FIELD(Attributes="EditorOrder(20), EditorDisplay(null, \"Rotation\"), ValueCategory(Utils.ValueCategory.Angle)") Quaternion Orientation; /// diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index 017af2e2e..c96e49032 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -17,7 +17,7 @@ Span Utilities::Private::HertzSizes(HertzSizesData, ARRAY_COUNT(Her namespace { CriticalSection PoolLocker; - DateTime LastUpdate; + double LastUpdate; float LastUpdateGameTime; Dictionary Pool(8192); uint64 PoolCounter = 0; @@ -114,7 +114,7 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta) bool ObjectsRemoval::Init() { - LastUpdate = DateTime::NowUTC(); + LastUpdate = Platform::GetTimeSeconds(); LastUpdateGameTime = 0; return false; } @@ -124,8 +124,8 @@ void ObjectsRemoval::LateUpdate() PROFILE_CPU(); // Delete all objects - const auto now = DateTime::NowUTC(); - const float dt = (now - LastUpdate).GetTotalSeconds(); + const double now = Platform::GetTimeSeconds(); + const float dt = (float)(now - LastUpdate); float gameDelta = Time::Update.DeltaTime.GetTotalSeconds(); if (Time::GetGamePaused()) gameDelta = 0; diff --git a/Source/Engine/Core/Types/Stopwatch.h b/Source/Engine/Core/Types/Stopwatch.h new file mode 100644 index 000000000..facf19181 --- /dev/null +++ b/Source/Engine/Core/Types/Stopwatch.h @@ -0,0 +1,64 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "BaseTypes.h" +#include "Engine/Platform/Platform.h" + +/// +/// High-resolution performance counter based on Platform::GetTimeSeconds. +/// +API_STRUCT(InBuild, Namespace="System.Diagnostics") struct FLAXENGINE_API Stopwatch +{ +private: + double _start, _end; + +public: + Stopwatch() + { + _start = _end = Platform::GetTimeSeconds(); + } + +public: + // Starts the counter. + void Start() + { + _start = Platform::GetTimeSeconds(); + } + + // Stops the counter. + void Stop() + { + _end = Platform::GetTimeSeconds(); + } + + /// + /// Gets the milliseconds time. + /// + FORCE_INLINE int32 GetMilliseconds() const + { + return (int32)((_end - _start) * 1000.0); + } + + /// + /// Gets the total number of milliseconds. + /// + FORCE_INLINE double GetTotalMilliseconds() const + { + return (float)((_end - _start) * 1000.0); + } + + /// + /// Gets the total number of seconds. + /// + FORCE_INLINE float GetTotalSeconds() const + { + return (float)(_end - _start); + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 46b15a6e2..ea8cdb196 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -127,7 +127,8 @@ PACK_STRUCT(struct Vertex { PACK_STRUCT(struct Data { Matrix ViewProjection; - Float3 Padding; + Float2 Padding; + float ClipPosZBias; bool EnableDepthTest; }); @@ -356,6 +357,19 @@ namespace Float3 CircleCache[DEBUG_DRAW_CIRCLE_VERTICES]; Array SphereTriangleCache; DebugSphereCache SphereCache[3]; + +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj) + { + DebugDrawPsLinesDefault.Release(); + DebugDrawPsLinesDepthTest.Release(); + DebugDrawPsWireTrianglesDefault.Release(); + DebugDrawPsWireTrianglesDepthTest.Release(); + DebugDrawPsTrianglesDefault.Release(); + DebugDrawPsTrianglesDepthTest.Release(); + } + +#endif }; extern int32 BoxTrianglesIndicesCache[]; @@ -615,17 +629,19 @@ void DebugDrawService::Update() GlobalContext.DebugDrawDefault.Update(deltaTime); GlobalContext.DebugDrawDepthTest.Update(deltaTime); - // Check if need to setup a resources + // Lazy-init resources if (DebugDrawShader == nullptr) { - // Shader DebugDrawShader = Content::LoadAsyncInternal(TEXT("Shaders/DebugDraw")); if (DebugDrawShader == nullptr) { LOG(Fatal, "Cannot load DebugDraw shader"); } +#if COMPILE_WITH_DEV_ENV + DebugDrawShader->OnReloading.Bind(&OnShaderReloading); +#endif } - if (DebugDrawVB == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded()) + if (DebugDrawPsWireTrianglesDepthTest.Depth == nullptr && DebugDrawShader && DebugDrawShader->IsLoaded()) { bool failed = false; const auto shader = DebugDrawShader->GetShader(); @@ -661,10 +677,11 @@ void DebugDrawService::Update() { LOG(Fatal, "Cannot setup DebugDraw service!"); } - - // Vertex buffer - DebugDrawVB = New((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB")); } + + // Vertex buffer + if (DebugDrawVB == nullptr) + DebugDrawVB = New((uint32)(DEBUG_DRAW_INITIAL_VB_CAPACITY * sizeof(Vertex)), (uint32)sizeof(Vertex), TEXT("DebugDraw.VB")); } void DebugDrawService::Dispose() @@ -723,7 +740,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe // Ensure to have shader loaded and any lines to render const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count(); const int32 debugDrawDefaultCount = Context->DebugDrawDefault.Count(); - if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0) + if (DebugDrawShader == nullptr || !DebugDrawShader->IsLoaded() || debugDrawDepthTestCount + debugDrawDefaultCount == 0 || DebugDrawPsWireTrianglesDepthTest.Depth == nullptr) return; if (renderContext.Buffers == nullptr || !DebugDrawVB) return; @@ -739,6 +756,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe } Context->LastViewPos = view.Position; Context->LastViewProj = view.Projection; + TaaJitterRemoveContext taaJitterRemove(view); // Fallback to task buffers if (target == nullptr && renderContext.Task) @@ -766,8 +784,9 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe const auto cb = DebugDrawShader->GetShader()->GetCB(0); Data data; Matrix vp; - Matrix::Multiply(view.View, view.NonJitteredProjection, vp); + Matrix::Multiply(view.View, view.Projection, vp); Matrix::Transpose(vp, data.ViewProjection); + data.ClipPosZBias = -0.2f; // Reduce Z-fighting artifacts (eg. editor grid) data.EnableDepthTest = enableDepthTest; context->UpdateCB(cb, &data); context->BindCB(0, cb); @@ -848,6 +867,8 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe { PROFILE_GPU_CPU_NAMED("Text"); auto features = Render2D::Features; + + // Disable vertex snapping when rendering 3D text Render2D::Features = (Render2D::RenderingFeatures)((uint32)features & ~(uint32)Render2D::RenderingFeatures::VertexSnapping); if (!DebugDrawFont) diff --git a/Source/Engine/Engine/InputAxis.cs b/Source/Engine/Engine/InputAxis.cs index 7dc51f025..ca77d2465 100644 --- a/Source/Engine/Engine/InputAxis.cs +++ b/Source/Engine/Engine/InputAxis.cs @@ -7,7 +7,7 @@ namespace FlaxEngine /// /// Virtual input axis binding. Helps with listening for a selected axis input. /// - public class InputAxis + public class InputAxis : IComparable, IComparable { /// /// The name of the axis to use. See . @@ -47,13 +47,13 @@ namespace FlaxEngine Input.AxisValueChanged += Handler; Name = name; } - + private void Handler(string name) { if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase)) ValueChanged?.Invoke(); } - + /// /// Finalizes an instance of the class. /// @@ -61,7 +61,7 @@ namespace FlaxEngine { Input.AxisValueChanged -= Handler; } - + /// /// Releases this object. /// @@ -70,5 +70,35 @@ namespace FlaxEngine Input.AxisValueChanged -= Handler; GC.SuppressFinalize(this); } + + /// + public int CompareTo(InputAxis other) + { + return string.Compare(Name, other.Name, StringComparison.Ordinal); + } + + /// + public int CompareTo(object obj) + { + return obj is InputAxis other ? CompareTo(other) : -1; + } + + /// + public override int GetHashCode() + { + return Name?.GetHashCode() ?? 0; + } + + /// + public override bool Equals(object obj) + { + return obj is InputAxis other && string.Equals(Name, other.Name, StringComparison.Ordinal); + } + + /// + public override string ToString() + { + return Name; + } } } diff --git a/Source/Engine/Engine/InputEvent.cs b/Source/Engine/Engine/InputEvent.cs index 10af5d531..e5653631f 100644 --- a/Source/Engine/Engine/InputEvent.cs +++ b/Source/Engine/Engine/InputEvent.cs @@ -7,7 +7,7 @@ namespace FlaxEngine /// /// Virtual input action binding. Helps with listening for a selected input event. /// - public class InputEvent + public class InputEvent : IComparable, IComparable { /// /// The name of the action to use. See . @@ -21,7 +21,7 @@ namespace FlaxEngine public bool Active => Input.GetAction(Name); /// - /// Returns the event state. Use Use , , to catch events without active waiting. + /// Returns the event state. Use , , to catch events without active waiting. /// public InputActionState State => Input.GetActionState(Name); @@ -35,12 +35,12 @@ namespace FlaxEngine /// Occurs when event is pressed (e.g. user pressed a key). Called before scripts update. /// public event Action Pressed; - + /// /// Occurs when event is being pressing (e.g. user pressing a key). Called before scripts update. /// public event Action Pressing; - + /// /// Occurs when event is released (e.g. user releases a key). Called before scripts update. /// @@ -102,5 +102,35 @@ namespace FlaxEngine Input.ActionTriggered -= Handler; GC.SuppressFinalize(this); } + + /// + public int CompareTo(InputEvent other) + { + return string.Compare(Name, other.Name, StringComparison.Ordinal); + } + + /// + public int CompareTo(object obj) + { + return obj is InputEvent other ? CompareTo(other) : -1; + } + + /// + public override int GetHashCode() + { + return Name?.GetHashCode() ?? 0; + } + + /// + public override bool Equals(object obj) + { + return obj is InputEvent other && string.Equals(Name, other.Name, StringComparison.Ordinal); + } + + /// + public override string ToString() + { + return Name; + } } } diff --git a/Source/Engine/Engine/NativeInterop.Unmanaged.cs b/Source/Engine/Engine/NativeInterop.Unmanaged.cs index 8adf99abc..bb20ced62 100644 --- a/Source/Engine/Engine/NativeInterop.Unmanaged.cs +++ b/Source/Engine/Engine/NativeInterop.Unmanaged.cs @@ -525,11 +525,11 @@ namespace FlaxEngine.Interop internal static void GetMethodParameterTypes(ManagedHandle methodHandle, IntPtr* typeHandles) { MethodHolder methodHolder = Unsafe.As(methodHandle.Target); - Type returnType = methodHolder.returnType; - IntPtr arr = (IntPtr)NativeAlloc(methodHolder.parameterTypes.Length, IntPtr.Size); - for (int i = 0; i < methodHolder.parameterTypes.Length; i++) + Type[] parameterTypes = methodHolder.parameterTypes; + IntPtr arr = (IntPtr)NativeAlloc(parameterTypes.Length, IntPtr.Size); + for (int i = 0; i < parameterTypes.Length; i++) { - ManagedHandle typeHandle = GetTypeManagedHandle(methodHolder.parameterTypes[i]); + ManagedHandle typeHandle = GetTypeManagedHandle(parameterTypes[i]); Unsafe.Write(IntPtr.Add(new IntPtr(arr), IntPtr.Size * i).ToPointer(), typeHandle); } *typeHandles = arr; diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 4903a576e..6df5275f5 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -54,15 +54,6 @@ void TimeSettings::Apply() ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; } -void TimeSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) -{ - DESERIALIZE(UpdateFPS); - DESERIALIZE(PhysicsFPS); - DESERIALIZE(DrawFPS); - DESERIALIZE(TimeScale); - DESERIALIZE(MaxUpdateDeltaTime); -} - void Time::TickData::OnBeforeRun(float targetFps, double currentTime) { Time = UnscaledTime = TimeSpan::Zero(); diff --git a/Source/Engine/Graphics/Config.h b/Source/Engine/Graphics/Config.h index 803f578b9..e55e2ecf4 100644 --- a/Source/Engine/Graphics/Config.h +++ b/Source/Engine/Graphics/Config.h @@ -2,6 +2,8 @@ #pragma once +#include "Engine/Platform/Defines.h" + // Maximum amount of binded render targets at the same time #define GPU_MAX_RT_BINDED 6 @@ -44,11 +46,10 @@ // Enable/disable force shaders recompilation #define GPU_FORCE_RECOMPILE_SHADERS 0 -// True if use BGRA back buffer format -#define GPU_USE_BGRA_BACK_BUFFER 1 - // Default back buffer pixel format +#ifndef GPU_DEPTH_BUFFER_PIXEL_FORMAT #define GPU_DEPTH_BUFFER_PIXEL_FORMAT PixelFormat::D32_Float +#endif // Enable/disable gpu resources naming #define GPU_ENABLE_RESOURCE_NAMING (!BUILD_RELEASE) @@ -62,10 +63,8 @@ #define GPU_MAX_TEXTURE_ARRAY_SIZE 1024 // Define default back buffer(s) format -#if GPU_USE_BGRA_BACK_BUFFER +#ifndef GPU_BACK_BUFFER_PIXEL_FORMAT #define GPU_BACK_BUFFER_PIXEL_FORMAT PixelFormat::B8G8R8A8_UNorm -#else -#define GPU_BACK_BUFFER_PIXEL_FORMAT PixelFormat::R8G8B8A8_UNorm #endif // Validate configuration diff --git a/Source/Engine/Graphics/Models/SkeletonData.h b/Source/Engine/Graphics/Models/SkeletonData.h index c0e3d107f..16e2cf732 100644 --- a/Source/Engine/Graphics/Models/SkeletonData.h +++ b/Source/Engine/Graphics/Models/SkeletonData.h @@ -11,7 +11,7 @@ /// /// Describes a single skeleton node data. Used by the runtime. /// -API_STRUCT() struct SkeletonNode +API_STRUCT() struct FLAXENGINE_API SkeletonNode { DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonNode); @@ -34,7 +34,7 @@ API_STRUCT() struct SkeletonNode /// /// Describes a single skeleton bone data. Used by the runtime. Skeleton bones are subset of the skeleton nodes collection that are actually used by the skinned model meshes. /// -API_STRUCT() struct SkeletonBone +API_STRUCT() struct FLAXENGINE_API SkeletonBone { DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonBone); @@ -71,7 +71,7 @@ struct TIsPODType /// /// Bones are ordered so that parents always come first, allowing for hierarchical updates in a simple loop. /// -class SkeletonData +class FLAXENGINE_API SkeletonData { public: /// diff --git a/Source/Engine/Graphics/PixelFormat.h b/Source/Engine/Graphics/PixelFormat.h index d19d2b8a3..1f96a4d82 100644 --- a/Source/Engine/Graphics/PixelFormat.h +++ b/Source/Engine/Graphics/PixelFormat.h @@ -513,6 +513,46 @@ API_ENUM() enum class PixelFormat : uint32 /// BC7_UNorm_sRGB = 99, + /// + /// A four-component ASTC (4x4 pixel block in 128 bits) block-compression format that supports RGBA channels. + /// + ASTC_4x4_UNorm = 100, + + /// + /// A four-component ASTC (4x4 pixel block in 128 bits) block-compression format that supports RGBA channels. Data in sRGB color space. + /// + ASTC_4x4_UNorm_sRGB = 101, + + /// + /// A four-component ASTC (6x6 pixel block in 128 bits) block-compression format that supports RGBA channels. + /// + ASTC_6x6_UNorm = 102, + + /// + /// A four-component ASTC (6x6 pixel block in 128 bits) block-compression format that supports RGBA channels. Data in sRGB color space. + /// + ASTC_6x6_UNorm_sRGB = 103, + + /// + /// A four-component ASTC (8x8 pixel block in 128 bits) block-compression format that supports RGBA channels. + /// + ASTC_8x8_UNorm = 104, + + /// + /// A four-component ASTC (8x8 pixel block in 128 bits) block-compression format that supports RGBA channels. Data in sRGB color space. + /// + ASTC_8x8_UNorm_sRGB = 105, + + /// + /// A four-component ASTC (10x10 pixel block in 128 bits) block-compression format that supports RGBA channels. + /// + ASTC_10x10_UNorm = 106, + + /// + /// A four-component ASTC (10x10 pixel block in 128 bits) block-compression format that supports RGBA channels. Data in sRGB color space. + /// + ASTC_10x10_UNorm_sRGB = 107, + /// /// The maximum format value (for internal use only). /// diff --git a/Source/Engine/Graphics/PixelFormatExtensions.cpp b/Source/Engine/Graphics/PixelFormatExtensions.cpp index 57433d239..f77c15683 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.cpp +++ b/Source/Engine/Graphics/PixelFormatExtensions.cpp @@ -5,19 +5,12 @@ // ReSharper disable CppClangTidyClangDiagnosticSwitchEnum -#define MAX_PIXEL_FORMATS 256 - namespace { - int32 sizeOfInBits[MAX_PIXEL_FORMATS]; - - int32 GetIndex(const PixelFormat format) - { - return (int32)format; - } + int32 sizeOfInBits[(int32)PixelFormat::MAX]; } -#define InitFormat(formats, bitCount) for(int i = 0; i < ARRAY_COUNT(formats); i++) { sizeOfInBits[GetIndex(formats[i])] = bitCount; } +#define InitFormat(formats, bitCount) for(int i = 0; i < ARRAY_COUNT(formats); i++) { sizeOfInBits[(int32)formats[i]] = bitCount; } void PixelFormatExtensions::Init() { @@ -35,7 +28,24 @@ void PixelFormatExtensions::Init() PixelFormat::R8_SNorm, PixelFormat::R8_Typeless, PixelFormat::R8_UInt, - PixelFormat::R8_UNorm + PixelFormat::R8_UNorm, + PixelFormat::BC2_Typeless, + PixelFormat::BC2_UNorm, + PixelFormat::BC2_UNorm_sRGB, + PixelFormat::BC3_Typeless, + PixelFormat::BC3_UNorm, + PixelFormat::BC3_UNorm_sRGB, + PixelFormat::BC5_SNorm, + PixelFormat::BC5_Typeless, + PixelFormat::BC5_UNorm, + PixelFormat::BC6H_Sf16, + PixelFormat::BC6H_Typeless, + PixelFormat::BC6H_Uf16, + PixelFormat::BC7_Typeless, + PixelFormat::BC7_UNorm, + PixelFormat::BC7_UNorm_sRGB, + PixelFormat::ASTC_4x4_UNorm, + PixelFormat::ASTC_4x4_UNorm_sRGB, }; InitFormat(formats2, 8); @@ -53,8 +63,7 @@ void PixelFormatExtensions::Init() PixelFormat::R8G8_SNorm, PixelFormat::R8G8_Typeless, PixelFormat::R8G8_UInt, - PixelFormat::R8G8_UNorm - + PixelFormat::R8G8_UNorm, }; InitFormat(formats3, 16); @@ -140,30 +149,11 @@ void PixelFormatExtensions::Init() PixelFormat::BC4_UNorm, }; InitFormat(formats8, 4); - - PixelFormat formats9[] = { - PixelFormat::BC2_Typeless, - PixelFormat::BC2_UNorm, - PixelFormat::BC2_UNorm_sRGB, - PixelFormat::BC3_Typeless, - PixelFormat::BC3_UNorm, - PixelFormat::BC3_UNorm_sRGB, - PixelFormat::BC5_SNorm, - PixelFormat::BC5_Typeless, - PixelFormat::BC5_UNorm, - PixelFormat::BC6H_Sf16, - PixelFormat::BC6H_Typeless, - PixelFormat::BC6H_Uf16, - PixelFormat::BC7_Typeless, - PixelFormat::BC7_UNorm, - PixelFormat::BC7_UNorm_sRGB, - }; - InitFormat(formats9, 8); } int32 PixelFormatExtensions::SizeInBits(PixelFormat format) { - return sizeOfInBits[GetIndex(format)]; + return sizeOfInBits[(int32)format]; } int32 PixelFormatExtensions::AlphaSizeInBits(const PixelFormat format) @@ -246,6 +236,7 @@ bool PixelFormatExtensions::HasStencil(const PixelFormat format) switch (format) { case PixelFormat::D24_UNorm_S8_UInt: + case PixelFormat::D32_Float_S8X24_UInt: return true; default: return false; @@ -319,6 +310,14 @@ bool PixelFormatExtensions::IsCompressed(const PixelFormat format) case PixelFormat::BC7_Typeless: case PixelFormat::BC7_UNorm: case PixelFormat::BC7_UNorm_sRGB: + case PixelFormat::ASTC_4x4_UNorm: + case PixelFormat::ASTC_4x4_UNorm_sRGB: + case PixelFormat::ASTC_6x6_UNorm: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + case PixelFormat::ASTC_8x8_UNorm: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + case PixelFormat::ASTC_10x10_UNorm: + case PixelFormat::ASTC_10x10_UNorm_sRGB: return true; default: return false; @@ -356,6 +355,24 @@ bool PixelFormatExtensions::IsCompressedBC(PixelFormat format) } } +bool PixelFormatExtensions::IsCompressedASTC(PixelFormat format) +{ + switch (format) + { + case PixelFormat::ASTC_4x4_UNorm: + case PixelFormat::ASTC_4x4_UNorm_sRGB: + case PixelFormat::ASTC_6x6_UNorm: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + case PixelFormat::ASTC_8x8_UNorm: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + case PixelFormat::ASTC_10x10_UNorm: + case PixelFormat::ASTC_10x10_UNorm_sRGB: + return true; + default: + return false; + } +} + bool PixelFormatExtensions::IsPacked(const PixelFormat format) { return format == PixelFormat::R8G8_B8G8_UNorm || format == PixelFormat::G8R8_G8B8_UNorm; @@ -382,6 +399,10 @@ bool PixelFormatExtensions::IsSRGB(const PixelFormat format) case PixelFormat::B8G8R8A8_UNorm_sRGB: case PixelFormat::B8G8R8X8_UNorm_sRGB: case PixelFormat::BC7_UNorm_sRGB: + case PixelFormat::ASTC_4x4_UNorm_sRGB: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + case PixelFormat::ASTC_10x10_UNorm_sRGB: return true; default: return false; @@ -392,6 +413,8 @@ bool PixelFormatExtensions::IsHDR(const PixelFormat format) { switch (format) { + case PixelFormat::R11G11B10_Float: + case PixelFormat::R10G10B10A2_UNorm: case PixelFormat::R16G16B16A16_Float: case PixelFormat::R32G32B32A32_Float: case PixelFormat::R16G16_Float: @@ -399,7 +422,6 @@ bool PixelFormatExtensions::IsHDR(const PixelFormat format) case PixelFormat::BC6H_Sf16: case PixelFormat::BC6H_Uf16: return true; - default: return false; } @@ -527,38 +549,7 @@ bool PixelFormatExtensions::IsInteger(const PixelFormat format) } } -int PixelFormatExtensions::ComputeScanlineCount(const PixelFormat format, int32 height) -{ - switch (format) - { - case PixelFormat::BC1_Typeless: - case PixelFormat::BC1_UNorm: - case PixelFormat::BC1_UNorm_sRGB: - case PixelFormat::BC2_Typeless: - case PixelFormat::BC2_UNorm: - case PixelFormat::BC2_UNorm_sRGB: - case PixelFormat::BC3_Typeless: - case PixelFormat::BC3_UNorm: - case PixelFormat::BC3_UNorm_sRGB: - case PixelFormat::BC4_Typeless: - case PixelFormat::BC4_UNorm: - case PixelFormat::BC4_SNorm: - case PixelFormat::BC5_Typeless: - case PixelFormat::BC5_UNorm: - case PixelFormat::BC5_SNorm: - case PixelFormat::BC6H_Typeless: - case PixelFormat::BC6H_Uf16: - case PixelFormat::BC6H_Sf16: - case PixelFormat::BC7_Typeless: - case PixelFormat::BC7_UNorm: - case PixelFormat::BC7_UNorm_sRGB: - return Math::Max(1, (height + 3) / 4); - default: - return height; - } -} - -int PixelFormatExtensions::ComputeComponentsCount(const PixelFormat format) +int32 PixelFormatExtensions::ComputeComponentsCount(const PixelFormat format) { switch (format) { @@ -599,6 +590,14 @@ int PixelFormatExtensions::ComputeComponentsCount(const PixelFormat format) case PixelFormat::B8G8R8A8_UNorm_sRGB: case PixelFormat::B8G8R8X8_Typeless: case PixelFormat::B8G8R8X8_UNorm_sRGB: + case PixelFormat::ASTC_4x4_UNorm: + case PixelFormat::ASTC_4x4_UNorm_sRGB: + case PixelFormat::ASTC_6x6_UNorm: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + case PixelFormat::ASTC_8x8_UNorm: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + case PixelFormat::ASTC_10x10_UNorm: + case PixelFormat::ASTC_10x10_UNorm_sRGB: return 4; case PixelFormat::R32G32B32_Typeless: case PixelFormat::R32G32B32_Float: @@ -685,7 +684,18 @@ int32 PixelFormatExtensions::ComputeBlockSize(PixelFormat format) case PixelFormat::BC7_Typeless: case PixelFormat::BC7_UNorm: case PixelFormat::BC7_UNorm_sRGB: + case PixelFormat::ASTC_4x4_UNorm: + case PixelFormat::ASTC_4x4_UNorm_sRGB: return 4; + case PixelFormat::ASTC_6x6_UNorm: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + return 6; + case PixelFormat::ASTC_8x8_UNorm: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + return 8; + case PixelFormat::ASTC_10x10_UNorm: + case PixelFormat::ASTC_10x10_UNorm_sRGB: + return 10; default: return 1; } @@ -709,6 +719,14 @@ PixelFormat PixelFormatExtensions::TosRGB(const PixelFormat format) return PixelFormat::B8G8R8X8_UNorm_sRGB; case PixelFormat::BC7_UNorm: return PixelFormat::BC7_UNorm_sRGB; + case PixelFormat::ASTC_4x4_UNorm: + return PixelFormat::ASTC_4x4_UNorm_sRGB; + case PixelFormat::ASTC_6x6_UNorm: + return PixelFormat::ASTC_6x6_UNorm_sRGB; + case PixelFormat::ASTC_8x8_UNorm: + return PixelFormat::ASTC_8x8_UNorm_sRGB; + case PixelFormat::ASTC_10x10_UNorm: + return PixelFormat::ASTC_10x10_UNorm_sRGB; default: return format; } @@ -732,6 +750,14 @@ PixelFormat PixelFormatExtensions::ToNonsRGB(const PixelFormat format) return PixelFormat::B8G8R8X8_UNorm; case PixelFormat::BC7_UNorm_sRGB: return PixelFormat::BC7_UNorm; + case PixelFormat::ASTC_4x4_UNorm_sRGB: + return PixelFormat::ASTC_4x4_UNorm; + case PixelFormat::ASTC_6x6_UNorm_sRGB: + return PixelFormat::ASTC_6x6_UNorm; + case PixelFormat::ASTC_8x8_UNorm_sRGB: + return PixelFormat::ASTC_8x8_UNorm; + case PixelFormat::ASTC_10x10_UNorm_sRGB: + return PixelFormat::ASTC_10x10_UNorm; default: return format; } @@ -823,6 +849,10 @@ PixelFormat PixelFormatExtensions::MakeTypeless(const PixelFormat format) case PixelFormat::BC7_UNorm: case PixelFormat::BC7_UNorm_sRGB: return PixelFormat::BC7_Typeless; + case PixelFormat::D24_UNorm_S8_UInt: + return PixelFormat::R24G8_Typeless; + case PixelFormat::D32_Float_S8X24_UInt: + return PixelFormat::R32G8X24_Typeless; default: return format; } @@ -890,9 +920,9 @@ PixelFormat PixelFormatExtensions::MakeTypelessUNorm(const PixelFormat format) } } -PixelFormat PixelFormatExtensions::FindShaderResourceFormat(const PixelFormat format, bool isSRGB) +PixelFormat PixelFormatExtensions::FindShaderResourceFormat(const PixelFormat format, bool sRGB) { - if (isSRGB) + if (sRGB) { switch (format) { @@ -975,3 +1005,55 @@ PixelFormat PixelFormatExtensions::FindDepthStencilFormat(const PixelFormat form } return format; } + +PixelFormat PixelFormatExtensions::FindUncompressedFormat(PixelFormat format) +{ + switch (format) + { + case PixelFormat::BC1_Typeless: + case PixelFormat::BC2_Typeless: + case PixelFormat::BC3_Typeless: + return PixelFormat::R8G8B8A8_Typeless; + case PixelFormat::BC1_UNorm: + case PixelFormat::BC2_UNorm: + case PixelFormat::BC3_UNorm: + return PixelFormat::R8G8B8A8_UNorm; + case PixelFormat::BC1_UNorm_sRGB: + case PixelFormat::BC2_UNorm_sRGB: + case PixelFormat::BC3_UNorm_sRGB: + return PixelFormat::R8G8B8A8_UNorm_sRGB; + case PixelFormat::BC4_Typeless: + return PixelFormat::R8_Typeless; + case PixelFormat::BC4_UNorm: + return PixelFormat::R8_UNorm; + case PixelFormat::BC4_SNorm: + return PixelFormat::R8_SNorm; + case PixelFormat::BC5_Typeless: + return PixelFormat::R16G16_Typeless; + case PixelFormat::BC5_UNorm: + return PixelFormat::R16G16_UNorm; + case PixelFormat::BC5_SNorm: + return PixelFormat::R16G16_SNorm; + case PixelFormat::BC7_Typeless: + case PixelFormat::BC6H_Typeless: + return PixelFormat::R16G16B16A16_Typeless; + case PixelFormat::BC7_UNorm: + case PixelFormat::BC6H_Uf16: + case PixelFormat::BC6H_Sf16: + return PixelFormat::R16G16B16A16_Float; + case PixelFormat::BC7_UNorm_sRGB: + return PixelFormat::R16G16B16A16_UNorm; + case PixelFormat::ASTC_4x4_UNorm: + case PixelFormat::ASTC_6x6_UNorm: + case PixelFormat::ASTC_8x8_UNorm: + case PixelFormat::ASTC_10x10_UNorm: + return PixelFormat::R8G8B8A8_UNorm; + case PixelFormat::ASTC_4x4_UNorm_sRGB: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + case PixelFormat::ASTC_10x10_UNorm_sRGB: + return PixelFormat::R8G8B8A8_UNorm_sRGB; + default: + return format; + } +} diff --git a/Source/Engine/Graphics/PixelFormatExtensions.h b/Source/Engine/Graphics/PixelFormatExtensions.h index 5fdf8a68c..498b9270c 100644 --- a/Source/Engine/Graphics/PixelFormatExtensions.h +++ b/Source/Engine/Graphics/PixelFormatExtensions.h @@ -72,7 +72,7 @@ public: /// The . /// Enable/disable partially typeless formats. /// true if the specified is Typeless; otherwise, false. - API_FUNCTION() static bool IsTypeless(PixelFormat format, bool partialTypeless); + API_FUNCTION() static bool IsTypeless(PixelFormat format, bool partialTypeless = true); /// /// Returns true if the is valid. @@ -95,6 +95,13 @@ public: /// True if the is a compressed format from BC formats family. API_FUNCTION() static bool IsCompressedBC(PixelFormat format); + /// + /// Returns true if the is a compressed format from ASTC formats family (various block sizes). + /// + /// The format to check for compressed. + /// True if the is a compressed format from ASTC formats family. + API_FUNCTION() static bool IsCompressedASTC(PixelFormat format); + /// /// Determines whether the specified is packed. /// @@ -158,20 +165,12 @@ public: /// True if given format contains integer data type (signed or unsigned), otherwise false. API_FUNCTION() static bool IsInteger(PixelFormat format); - /// - /// Computes the scanline count (number of scanlines). - /// - /// The . - /// The height. - /// The scanline count. - API_FUNCTION() static int ComputeScanlineCount(PixelFormat format, int32 height); - /// /// Computes the format components count (number of R, G, B, A channels). /// /// The . /// The components count. - API_FUNCTION() static int ComputeComponentsCount(PixelFormat format); + API_FUNCTION() static int32 ComputeComponentsCount(PixelFormat format); /// /// Computes the amount of pixels per-axis stored in the a single block of the format (eg. 4 for BC-family). Returns 1 for uncompressed formats. @@ -216,7 +215,8 @@ public: API_FUNCTION() static PixelFormat MakeTypelessUNorm(PixelFormat format); public: - static PixelFormat FindShaderResourceFormat(PixelFormat format, bool bSRGB); + static PixelFormat FindShaderResourceFormat(PixelFormat format, bool sRGB); static PixelFormat FindUnorderedAccessFormat(PixelFormat format); static PixelFormat FindDepthStencilFormat(PixelFormat format); + static PixelFormat FindUncompressedFormat(PixelFormat format); }; diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index 17bcdb3a9..d1b8c8037 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -960,7 +960,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The speed at which the exposure changes when the scene brightness moves from a bright area to a dark area (brightness goes down). /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)EyeAdaptationSettingsOverride.SpeedDown)") - float SpeedDown = 1.0f; + float SpeedDown = 10.0f; /// /// The pre-exposure value applied to the scene color before performing post-processing (such as bloom, lens flares, etc.). @@ -984,7 +984,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The maximum brightness for the auto exposure which limits the upper brightness the eye can adapt within. /// API_FIELD(Attributes="Limit(0, 100.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)EyeAdaptationSettingsOverride.MaxBrightness), EditorDisplay(null, \"Maximum Brightness\")") - float MaxBrightness = 2.0f; + float MaxBrightness = 15.0f; /// /// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode. @@ -996,7 +996,7 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable /// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode. /// API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)") - float HistogramHighPercent = 98.0f; + float HistogramHighPercent = 90.0f; public: /// @@ -1079,10 +1079,10 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable CameraArtifactsSettingsOverride OverrideFlags = Override::None; /// - /// Strength of the vignette effect. Value 0 hides it. The default value is 0.8. + /// Strength of the vignette effect. Value 0 hides it. The default value is 0.4. /// API_FIELD(Attributes="Limit(0, 2, 0.001f), EditorOrder(0), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteIntensity)") - float VignetteIntensity = 0.8f; + float VignetteIntensity = 0.4f; /// /// Color of the vignette. @@ -1230,25 +1230,25 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable /// Strength of the effect. A value of 0 disables it. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)") - float Intensity = 1.0f; + float Intensity = 0.5f; /// /// Amount of lens flares ghosts. /// API_FIELD(Attributes="Limit(0, 16), EditorOrder(1), PostProcessSetting((int)LensFlaresSettingsOverride.Ghosts)") - int32 Ghosts = 8; + int32 Ghosts = 4; /// /// Lens flares halo width. /// API_FIELD(Attributes="EditorOrder(2), PostProcessSetting((int)LensFlaresSettingsOverride.HaloWidth)") - float HaloWidth = 0.16f; + float HaloWidth = 0.04f; /// /// Lens flares halo intensity. /// API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(3), PostProcessSetting((int)LensFlaresSettingsOverride.HaloIntensity)") - float HaloIntensity = 0.666f; + float HaloIntensity = 0.5f; /// /// Ghost samples dispersal parameter. @@ -1584,7 +1584,7 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable /// The blur effect strength. A value of 0 disables it, while higher values increase the effect. /// API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)") - float Scale = 1.0f; + float Scale = 0.5f; /// /// The amount of sample points used during motion blur rendering. It affects blur quality and performance. @@ -1889,13 +1889,13 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable /// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output. /// API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\")") - float TAA_JitterSpread = 0.75f; + float TAA_JitterSpread = 1.0f; /// /// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts. /// API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\")") - float TAA_Sharpness = 0.0f; + float TAA_Sharpness = 0.1f; /// /// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion. @@ -1907,7 +1907,7 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable /// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion. /// API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")") - float TAA_MotionBlending = 0.7f; + float TAA_MotionBlending = 0.85f; public: /// diff --git a/Source/Engine/Graphics/RenderTargetPool.cpp b/Source/Engine/Graphics/RenderTargetPool.cpp index 6b7a128e8..0d31f4077 100644 --- a/Source/Engine/Graphics/RenderTargetPool.cpp +++ b/Source/Engine/Graphics/RenderTargetPool.cpp @@ -4,6 +4,7 @@ #include "GPUDevice.h" #include "Engine/Core/Log.h" #include "Engine/Engine/Engine.h" +#include "Engine/Profiler/ProfilerCPU.h" struct Entry { @@ -20,9 +21,11 @@ namespace void RenderTargetPool::Flush(bool force, int32 framesOffset) { + PROFILE_CPU(); if (framesOffset < 0) framesOffset = 3 * 60; // For how many frames RTs should be cached (by default) - const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset; + const uint64 frameCount = Engine::FrameCount; + const uint64 maxReleaseFrame = frameCount - Math::Min(frameCount, framesOffset); force |= Engine::ShouldExit(); for (int32 i = 0; i < TemporaryRTs.Count(); i++) @@ -40,6 +43,8 @@ void RenderTargetPool::Flush(bool force, int32 framesOffset) GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc) { + PROFILE_CPU(); + // Find free render target with the same properties const uint32 descHash = GetHash(desc); for (int32 i = 0; i < TemporaryRTs.Count(); i++) @@ -85,7 +90,6 @@ void RenderTargetPool::Release(GPUTexture* rt) { if (!rt) return; - for (int32 i = 0; i < TemporaryRTs.Count(); i++) { auto& e = TemporaryRTs[i]; @@ -98,6 +102,5 @@ void RenderTargetPool::Release(GPUTexture* rt) return; } } - LOG(Error, "Trying to release temporary render target which has not been registered in service!"); } diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index dfb771615..f43fc8c75 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -306,7 +306,6 @@ void RenderTools::ComputePitch(PixelFormat format, int32 width, int32 height, ui slicePitch = rowPitch * nbh; } break; - case PixelFormat::BC2_Typeless: case PixelFormat::BC2_UNorm: case PixelFormat::BC2_UNorm_sRGB: @@ -330,14 +329,28 @@ void RenderTools::ComputePitch(PixelFormat format, int32 width, int32 height, ui slicePitch = rowPitch * nbh; } break; - + case PixelFormat::ASTC_4x4_UNorm: + case PixelFormat::ASTC_4x4_UNorm_sRGB: + case PixelFormat::ASTC_6x6_UNorm: + case PixelFormat::ASTC_6x6_UNorm_sRGB: + case PixelFormat::ASTC_8x8_UNorm: + case PixelFormat::ASTC_8x8_UNorm_sRGB: + case PixelFormat::ASTC_10x10_UNorm: + case PixelFormat::ASTC_10x10_UNorm_sRGB: + { + const int32 blockSize = PixelFormatExtensions::ComputeBlockSize(format); + uint32 nbw = Math::Max(1, Math::DivideAndRoundUp(width, blockSize)); + uint32 nbh = Math::Max(1, Math::DivideAndRoundUp(height, blockSize)); + rowPitch = nbw * 16; // All ASTC blocks use 128 bits + slicePitch = rowPitch * nbh; + } + break; case PixelFormat::R8G8_B8G8_UNorm: case PixelFormat::G8R8_G8B8_UNorm: ASSERT(PixelFormatExtensions::IsPacked(format)); rowPitch = ((width + 1) >> 1) * 4; slicePitch = rowPitch * height; break; - default: ASSERT(PixelFormatExtensions::IsValid(format)); ASSERT(!PixelFormatExtensions::IsCompressed(format) && !PixelFormatExtensions::IsPacked(format) && !PixelFormatExtensions::IsPlanar(format)); diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index d59a16463..730895267 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -18,6 +18,7 @@ void RenderView::Prepare(RenderContext& renderContext) // Check if use TAA (need to modify the projection matrix) Float2 taaJitter; NonJitteredProjection = Projection; + IsTaaResolved = false; if (renderContext.List->Setup.UseTemporalAAJitter) { // Move to the next frame @@ -26,7 +27,7 @@ void RenderView::Prepare(RenderContext& renderContext) TaaFrameIndex = 0; // Calculate jitter - const float jitterSpread = renderContext.List->Settings.AntiAliasing.TAA_JitterSpread / 0.75f; + const float jitterSpread = renderContext.List->Settings.AntiAliasing.TAA_JitterSpread; const float jitterX = (RendererUtils::TemporalHalton(TaaFrameIndex + 1, 2) - 0.5f) * jitterSpread; const float jitterY = (RendererUtils::TemporalHalton(TaaFrameIndex + 1, 3) - 0.5f) * jitterSpread; taaJitter = Float2(jitterX * 2.0f / width, jitterY * 2.0f / height); @@ -82,6 +83,18 @@ void RenderView::PrepareCache(const RenderContext& renderContext, float width, f MainScreenSize = mainView->ScreenSize; } +void RenderView::UpdateCachedData() +{ + Matrix::Invert(View, IV); + Matrix::Invert(Projection, IP); + Matrix viewProjection; + Matrix::Multiply(View, Projection, viewProjection); + Frustum.SetMatrix(viewProjection); + Matrix::Invert(viewProjection, IVP); + CullingFrustum = Frustum; + NonJitteredProjection = Projection; +} + void RenderView::SetUp(const Matrix& viewProjection) { // Copy data @@ -201,3 +214,27 @@ void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const const Float3 translation = transform.Translation - Origin; Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); } + +TaaJitterRemoveContext::TaaJitterRemoveContext(const RenderView& view) +{ + if (view.IsTaaResolved) + { + // Cancel-out sub-pixel jitter when drawing geometry after TAA has been resolved + _view = (RenderView*)&view; + _prevProjection = view.Projection; + _prevNonJitteredProjection = view.NonJitteredProjection; + _view->Projection = _prevNonJitteredProjection; + _view->UpdateCachedData(); + } +} + +TaaJitterRemoveContext::~TaaJitterRemoveContext() +{ + if (_view) + { + // Restore projection + _view->Projection = _prevProjection; + _view->UpdateCachedData(); + _view->NonJitteredProjection = _prevNonJitteredProjection; + } +} diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index d73b45c53..0b37e8b28 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -117,6 +117,11 @@ public: /// API_FIELD() bool IsCullingDisabled = false; + /// + /// True if TAA has been resolved when rendering view and frame doesn't contain jitter anymore. Rendering geometry after this point should not use jitter anymore (eg. editor gizmos or custom geometry as overlay). + /// + API_FIELD() bool IsTaaResolved = false; + /// /// The static flags mask used to hide objects that don't have a given static flags. Eg. use StaticFlags::Lightmap to render only objects that can use lightmap. /// @@ -160,7 +165,7 @@ public: API_FIELD() DEPRECATED float ShadowModelLODDistanceFactor = 1.0f; /// - /// The Temporal Anti-Aliasing jitter frame index. + /// Temporal Anti-Aliasing jitter frame index. /// API_FIELD() int32 TaaFrameIndex = 0; @@ -261,6 +266,11 @@ public: RenderView& operator=(const RenderView& other) = default; PRAGMA_ENABLE_DEPRECATION_WARNINGS + /// + /// Updates the cached data for the view (inverse matrices, etc.). + /// + void UpdateCachedData(); + // Set up view with custom params // @param viewProjection View * Projection matrix void SetUp(const Matrix& viewProjection); @@ -344,3 +354,15 @@ public: world.M43 -= Origin.Z; } }; + +// Removes TAA jitter from the RenderView when drawing geometry after TAA has been resolved to prevent unwanted jittering. +struct TaaJitterRemoveContext +{ +private: + RenderView* _view = nullptr; + Matrix _prevProjection, _prevNonJitteredProjection; + +public: + TaaJitterRemoveContext(const RenderView& view); + ~TaaJitterRemoveContext(); +}; diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp index 0e6ad8f3a..2af300f06 100644 --- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp +++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp @@ -10,6 +10,7 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Materials/MaterialShader.h" #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" +#include "FlaxEngine.Gen.h" const Char* ShaderProfileCacheDirNames[] = { @@ -179,6 +180,7 @@ bool ShaderCacheManagerService::Init() // Validate the database cache version (need to recompile all shaders on shader cache format change) struct CacheVersion { + int32 EngineVersion = -1; int32 ShaderCacheVersion = -1; int32 MaterialGraphVersion = -1; int32 ParticleGraphVersion = -1; @@ -193,7 +195,8 @@ bool ShaderCacheManagerService::Init() LOG(Warning, "Failed to read the shaders cache database version file."); } } - if (cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION + if (cacheVersion.EngineVersion != FLAXENGINE_VERSION_BUILD + || cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION || cacheVersion.MaterialGraphVersion != MATERIAL_GRAPH_VERSION || cacheVersion.ParticleGraphVersion != PARTICLE_GPU_GRAPH_VERSION ) @@ -209,6 +212,7 @@ bool ShaderCacheManagerService::Init() LOG(Error, "Failed to createe the shaders cache database directory."); } + cacheVersion.EngineVersion = FLAXENGINE_VERSION_BUILD; cacheVersion.ShaderCacheVersion = GPU_SHADER_CACHE_VERSION; cacheVersion.MaterialGraphVersion = MATERIAL_GRAPH_VERSION; cacheVersion.ParticleGraphVersion = PARTICLE_GPU_GRAPH_VERSION; diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index e6b963635..23302deb8 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -315,14 +315,12 @@ int32 GPUTexture::ComputeBufferOffset(int32 subresource, int32 rowAlign, int32 s int32 GPUTexture::ComputeBufferTotalSize(int32 rowAlign, int32 sliceAlign) const { int32 result = 0; - for (int32 mipLevel = 0; mipLevel < MipLevels(); mipLevel++) { const int32 slicePitch = ComputeSlicePitch(mipLevel, rowAlign); const int32 depth = CalculateMipSize(Depth(), mipLevel); result += Math::AlignUp(slicePitch * depth, sliceAlign); } - return result * ArraySize(); } @@ -333,8 +331,11 @@ int32 GPUTexture::ComputeSlicePitch(int32 mipLevel, int32 rowAlign) const int32 GPUTexture::ComputeRowPitch(int32 mipLevel, int32 rowAlign) const { - const int32 formatSize = PixelFormatExtensions::SizeInBytes(Format()); - return Math::AlignUp(CalculateMipSize(Width(), mipLevel) * formatSize, rowAlign); + int32 mipWidth = CalculateMipSize(Width(), mipLevel); + int32 mipHeight = CalculateMipSize(Height(), mipLevel); + uint32 rowPitch, slicePitch; + RenderTools::ComputePitch(Format(), mipWidth, mipHeight, rowPitch, slicePitch); + return Math::AlignUp(rowPitch, rowAlign); } bool GPUTexture::Init(const GPUTextureDescription& desc) diff --git a/Source/Engine/Graphics/Textures/StreamingTexture.cpp b/Source/Engine/Graphics/Textures/StreamingTexture.cpp index 9bbc5e59c..14874abf1 100644 --- a/Source/Engine/Graphics/Textures/StreamingTexture.cpp +++ b/Source/Engine/Graphics/Textures/StreamingTexture.cpp @@ -113,8 +113,9 @@ bool StreamingTexture::Create(const TextureHeader& header) if (_isBlockCompressed) { // Ensure that streaming doesn't go too low because the hardware expects the texture to be min in size of compressed texture block + const int32 blockSize = PixelFormatExtensions::ComputeBlockSize(_header.Format); int32 lastMip = header.MipLevels - 1; - while ((header.Width >> lastMip) < 4 && (header.Height >> lastMip) < 4 && lastMip > 0) + while ((header.Width >> lastMip) < blockSize && (header.Height >> lastMip) < blockSize && lastMip > 0) lastMip--; _minMipCountBlockCompressed = Math::Min(header.MipLevels - lastMip + 1, header.MipLevels); } @@ -297,7 +298,11 @@ Task* StreamingTexture::UpdateAllocation(int32 residency) if (texture->Init(desc)) { Streaming.Error = true; - LOG(Error, "Cannot allocate texture {0}.", ToString()); +#if GPU_ENABLE_RESOURCE_NAMING + LOG(Error, "Cannot allocate texture {0}", texture->GetName()); +#else + LOG(Error, "Cannot allocate texture"); +#endif } if (allocatedResidency != 0) { diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp index 0fd25001b..c38b3edef 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp @@ -135,7 +135,7 @@ bool GPUBufferDX12::OnInit() // Create resource ID3D12Resource* resource; - D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COPY_DEST; + D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON; VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource))); // Set state diff --git a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h index ad0c49b0a..ad2d68652 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Android/AndroidVulkanPlatform.h @@ -6,6 +6,9 @@ #if GRAPHICS_API_VULKAN && PLATFORM_ANDROID +// Support more backbuffers in case driver decides to use more +#define VULKAN_BACK_BUFFERS_COUNT_MAX 8 + /// /// The implementation for the Vulkan API support for Android platform. /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp index 7074adf30..dcbbf18cc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.cpp @@ -6,8 +6,11 @@ #include "RenderToolsVulkan.h" #include "QueueVulkan.h" #include "GPUContextVulkan.h" +#if VULKAN_USE_QUERIES #include "GPUTimerQueryVulkan.h" +#endif #include "DescriptorSetVulkan.h" +#include "Engine/Profiler/ProfilerCPU.h" void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore) { @@ -18,6 +21,7 @@ void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, Semaphore void CmdBufferVulkan::Begin() { + PROFILE_CPU(); ASSERT(_state == State::ReadyForBegin); VkCommandBufferBeginInfo beginInfo; @@ -28,7 +32,7 @@ void CmdBufferVulkan::Begin() // Acquire a descriptor pool set on if (_descriptorPoolSetContainer == nullptr) { - AcquirePoolSet(); + _descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer(); } _state = State::IsInsideBegin; @@ -41,6 +45,7 @@ void CmdBufferVulkan::Begin() void CmdBufferVulkan::End() { + PROFILE_CPU(); ASSERT(IsOutsideRenderPass()); #if GPU_ALLOW_PROFILE_EVENTS && VK_EXT_debug_utils @@ -56,18 +61,16 @@ void CmdBufferVulkan::End() void CmdBufferVulkan::BeginRenderPass(RenderPassVulkan* renderPass, FramebufferVulkan* framebuffer, uint32 clearValueCount, VkClearValue* clearValues) { ASSERT(IsOutsideRenderPass()); - VkRenderPassBeginInfo info; RenderToolsVulkan::ZeroStruct(info, VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO); - info.renderPass = renderPass->GetHandle(); - info.framebuffer = framebuffer->GetHandle(); + info.renderPass = renderPass->Handle; + info.framebuffer = framebuffer->Handle; info.renderArea.offset.x = 0; info.renderArea.offset.y = 0; info.renderArea.extent.width = framebuffer->Extent.width; info.renderArea.extent.height = framebuffer->Extent.height; info.clearValueCount = clearValueCount; info.pClearValues = clearValues; - vkCmdBeginRenderPass(_commandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); _state = State::IsInsideRenderPass; } @@ -79,12 +82,6 @@ void CmdBufferVulkan::EndRenderPass() _state = State::IsInsideBegin; } -void CmdBufferVulkan::AcquirePoolSet() -{ - ASSERT(!_descriptorPoolSetContainer); - _descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer(); -} - #if GPU_ALLOW_PROFILE_EVENTS void CmdBufferVulkan::BeginEvent(const Char* name) @@ -129,15 +126,14 @@ void CmdBufferVulkan::RefreshFenceStatus() { if (_state == State::Submitted) { - auto fenceManager = _fence->GetOwner(); - if (fenceManager->IsFenceSignaled(_fence)) + PROFILE_CPU(); + if (_device->FenceManager.IsFenceSignaled(_fence)) { _state = State::ReadyForBegin; - _submittedWaitSemaphores.Clear(); vkResetCommandBuffer(_commandBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); - _fence->GetOwner()->ResetFence(_fence); + _device->FenceManager.ResetFence(_fence); _fenceSignaledCounter++; if (_descriptorPoolSetContainer) @@ -149,7 +145,7 @@ void CmdBufferVulkan::RefreshFenceStatus() } else { - ASSERT(!_fence->IsSignaled()); + ASSERT(!_fence->IsSignaled); } } @@ -158,8 +154,8 @@ CmdBufferVulkan::CmdBufferVulkan(GPUDeviceVulkan* device, CmdBufferPoolVulkan* p , _commandBuffer(VK_NULL_HANDLE) , _state(State::ReadyForBegin) , _fence(nullptr) - , _fenceSignaledCounter(0) , _submittedFenceCounter(0) + , _fenceSignaledCounter(0) , _commandBufferPool(pool) { VkCommandBufferAllocateInfo createCmdBufInfo; @@ -167,7 +163,6 @@ CmdBufferVulkan::CmdBufferVulkan(GPUDeviceVulkan* device, CmdBufferPoolVulkan* p createCmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; createCmdBufInfo.commandBufferCount = 1; createCmdBufInfo.commandPool = _commandBufferPool->GetHandle(); - VALIDATE_VULKAN_RESULT(vkAllocateCommandBuffers(_device->Device, &createCmdBufInfo, &_commandBuffer)); _fence = _device->FenceManager.AllocateFence(); } @@ -216,14 +211,11 @@ CmdBufferPoolVulkan::CmdBufferPoolVulkan(GPUDeviceVulkan* device) CmdBufferPoolVulkan::~CmdBufferPoolVulkan() { for (int32 i = 0; i < _cmdBuffers.Count(); i++) - { Delete(_cmdBuffers[i]); - } - vkDestroyCommandPool(_device->Device, _handle, nullptr); } -void CmdBufferPoolVulkan::RefreshFenceStatus(CmdBufferVulkan* skipCmdBuffer) +void CmdBufferPoolVulkan::RefreshFenceStatus(const CmdBufferVulkan* skipCmdBuffer) { for (int32 i = 0; i < _cmdBuffers.Count(); i++) { @@ -246,19 +238,18 @@ CmdBufferManagerVulkan::CmdBufferManagerVulkan(GPUDeviceVulkan* device, GPUConte void CmdBufferManagerVulkan::SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaphore) { + PROFILE_CPU(); ASSERT(_activeCmdBuffer); if (!_activeCmdBuffer->IsSubmitted() && _activeCmdBuffer->HasBegun()) { if (_activeCmdBuffer->IsInsideRenderPass()) - { _activeCmdBuffer->EndRenderPass(); - } #if VULKAN_USE_QUERIES // Pause all active queries for (int32 i = 0; i < _queriesInProgress.Count(); i++) { - _queriesInProgress[i]->Interrupt(_activeCmdBuffer); + _queriesInProgress.Get()[i]->Interrupt(_activeCmdBuffer); } #endif @@ -273,12 +264,12 @@ void CmdBufferManagerVulkan::SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaph _queue->Submit(_activeCmdBuffer); } } - _activeCmdBuffer = nullptr; } void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float timeInSecondsToWait) { + PROFILE_CPU(); ASSERT(cmdBuffer->IsSubmitted()); const bool failed = _device->FenceManager.WaitForFence(cmdBuffer->GetFence(), (uint64)(timeInSecondsToWait * 1e9)); ASSERT(!failed); @@ -287,9 +278,10 @@ void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer() { + PROFILE_CPU(); for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++) { - auto cmdBuffer = _pool._cmdBuffers[i]; + auto cmdBuffer = _pool._cmdBuffers.Get()[i]; cmdBuffer->RefreshFenceStatus(); if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin) { @@ -311,7 +303,7 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer() // Resume any paused queries with the new command buffer for (int32 i = 0; i < _queriesInProgress.Count(); i++) { - _queriesInProgress[i]->Resume(_activeCmdBuffer); + _queriesInProgress.Get()[i]->Resume(_activeCmdBuffer); } #endif } diff --git a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h index 3995cca73..47ff986be 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/CmdBufferVulkan.h @@ -21,7 +21,6 @@ class CmdBufferVulkan friend QueueVulkan; public: - enum class State { ReadyForBegin, @@ -32,7 +31,6 @@ public: }; private: - GPUDeviceVulkan* _device; VkCommandBuffer _commandBuffer; State _state; @@ -57,13 +55,10 @@ private: DescriptorPoolSetContainerVulkan* _descriptorPoolSetContainer = nullptr; public: - CmdBufferVulkan(GPUDeviceVulkan* device, CmdBufferPoolVulkan* pool); - ~CmdBufferVulkan(); public: - CmdBufferPoolVulkan* GetOwner() const { return _commandBufferPool; @@ -120,7 +115,6 @@ public: } public: - void AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore); void Begin(); @@ -129,13 +123,11 @@ public: void BeginRenderPass(RenderPassVulkan* renderPass, FramebufferVulkan* framebuffer, uint32 clearValueCount, VkClearValue* clearValues); void EndRenderPass(); - DescriptorPoolSetContainerVulkan* GetDescriptorPoolSet() const + FORCE_INLINE DescriptorPoolSetContainerVulkan* GetDescriptorPoolSet() const { return _descriptorPoolSetContainer; } - void AcquirePoolSet(); - #if GPU_ALLOW_PROFILE_EVENTS void BeginEvent(const Char* name); void EndEvent(); @@ -148,7 +140,6 @@ class CmdBufferPoolVulkan { friend class CmdBufferManagerVulkan; private: - GPUDeviceVulkan* _device; VkCommandPool _handle; Array _cmdBuffers; @@ -158,25 +149,21 @@ private: void Create(uint32 queueFamilyIndex); public: - CmdBufferPoolVulkan(GPUDeviceVulkan* device); ~CmdBufferPoolVulkan(); public: - inline VkCommandPool GetHandle() const { - ASSERT(_handle != VK_NULL_HANDLE); return _handle; } - void RefreshFenceStatus(CmdBufferVulkan* skipCmdBuffer = nullptr); + void RefreshFenceStatus(const CmdBufferVulkan* skipCmdBuffer = nullptr); }; class CmdBufferManagerVulkan { private: - GPUDeviceVulkan* _device; CmdBufferPoolVulkan _pool; QueueVulkan* _queue; @@ -184,11 +171,9 @@ private: Array _queriesInProgress; public: - CmdBufferManagerVulkan(GPUDeviceVulkan* device, GPUContextVulkan* context); public: - FORCE_INLINE VkCommandPool GetHandle() const { return _pool.GetHandle(); @@ -209,7 +194,7 @@ public: return _queriesInProgress.Count() != 0; } - CmdBufferVulkan* GetCmdBuffer() + FORCE_INLINE CmdBufferVulkan* GetCmdBuffer() { if (!_activeCmdBuffer) PrepareForNewActiveCommandBuffer(); @@ -217,16 +202,12 @@ public: } public: - void SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaphore = nullptr); - void WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float timeInSecondsToWait = 1.0f); - void RefreshFenceStatus(CmdBufferVulkan* skipCmdBuffer = nullptr) { _pool.RefreshFenceStatus(skipCmdBuffer); } - void PrepareForNewActiveCommandBuffer(); void OnQueryBegin(GPUTimerQueryVulkan* query); diff --git a/Source/Engine/GraphicsDevice/Vulkan/Config.h b/Source/Engine/GraphicsDevice/Vulkan/Config.h index 69a9f082c..d31cb9a06 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Config.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Config.h @@ -18,23 +18,23 @@ #ifndef VULKAN_BACK_BUFFERS_COUNT #define VULKAN_BACK_BUFFERS_COUNT 2 #endif -#define VULKAN_BACK_BUFFERS_COUNT_MAX 16 +#ifndef VULKAN_BACK_BUFFERS_COUNT_MAX +#define VULKAN_BACK_BUFFERS_COUNT_MAX 4 +#endif /// /// Default amount of frames to wait until resource delete. /// -#define VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT 10 +#define VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT 20 #define VULKAN_ENABLE_API_DUMP 0 #define VULKAN_RESET_QUERY_POOLS 0 -#define VULKAN_HASH_POOLS_WITH_TYPES_USAGE_ID 1 +#define VULKAN_HASH_POOLS_WITH_LAYOUT_TYPES 1 #define VULKAN_USE_DEBUG_LAYER GPU_ENABLE_DIAGNOSTICS +#define VULKAN_USE_DEBUG_DATA (GPU_ENABLE_DIAGNOSTICS && COMPILE_WITH_DEV_ENV) #ifndef VULKAN_USE_QUERIES #define VULKAN_USE_QUERIES 1 #endif -#ifndef VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 -#define VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 0 -#endif #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp index c72d9a722..0648d917b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.cpp @@ -12,29 +12,12 @@ #include "Engine/Threading/Threading.h" #include "Engine/Engine/Engine.h" -void DescriptorSetLayoutInfoVulkan::CacheTypesUsageID() -{ - static CriticalSection locker; - static uint32 uniqueID = 1; - static Dictionary typesUsageHashMap; - - const uint32 typesUsageHash = Crc::MemCrc32(_layoutTypes, sizeof(_layoutTypes)); - ScopeLock lock(locker); - uint32 id; - if (!typesUsageHashMap.TryGet(typesUsageHash, id)) - { - id = uniqueID++; - typesUsageHashMap.Add(typesUsageHash, id); - } - _typesUsageID = id; -} - void DescriptorSetLayoutInfoVulkan::AddBindingsForStage(VkShaderStageFlagBits stageFlags, DescriptorSet::Stage descSet, const SpirvShaderDescriptorInfo* descriptorInfo) { const int32 descriptorSetIndex = descSet; - if (descriptorSetIndex >= _setLayouts.Count()) - _setLayouts.Resize(descriptorSetIndex + 1); - SetLayout& descSetLayout = _setLayouts[descriptorSetIndex]; + if (descriptorSetIndex >= SetLayouts.Count()) + SetLayouts.Resize(descriptorSetIndex + 1); + SetLayout& descSetLayout = SetLayouts[descriptorSetIndex]; VkDescriptorSetLayoutBinding binding; binding.stageFlags = stageFlags; @@ -46,77 +29,75 @@ void DescriptorSetLayoutInfoVulkan::AddBindingsForStage(VkShaderStageFlagBits st binding.descriptorType = descriptor.DescriptorType; binding.descriptorCount = descriptor.Count; - _layoutTypes[binding.descriptorType]++; + LayoutTypes[binding.descriptorType]++; descSetLayout.LayoutBindings.Add(binding); - _hash = Crc::MemCrc32(&binding, sizeof(binding), _hash); + Hash = Crc::MemCrc32(&binding, sizeof(binding), Hash); } } bool DescriptorSetLayoutInfoVulkan::operator==(const DescriptorSetLayoutInfoVulkan& other) const { - if (other._setLayouts.Count() != _setLayouts.Count()) + if (other.SetLayouts.Count() != SetLayouts.Count()) return false; - if (other._typesUsageID != _typesUsageID) +#if VULKAN_HASH_POOLS_WITH_LAYOUT_TYPES + if (other.SetLayoutsHash != SetLayoutsHash) return false; - - for (int32 index = 0; index < other._setLayouts.Count(); index++) +#endif + for (int32 index = 0; index < other.SetLayouts.Count(); index++) { - const int32 bindingsCount = _setLayouts[index].LayoutBindings.Count(); - if (other._setLayouts[index].LayoutBindings.Count() != bindingsCount) + const int32 bindingsCount = SetLayouts[index].LayoutBindings.Count(); + if (other.SetLayouts[index].LayoutBindings.Count() != bindingsCount) return false; - if (bindingsCount != 0 && Platform::MemoryCompare(other._setLayouts[index].LayoutBindings.Get(), _setLayouts[index].LayoutBindings.Get(), bindingsCount * sizeof(VkDescriptorSetLayoutBinding))) + if (bindingsCount != 0 && Platform::MemoryCompare(other.SetLayouts[index].LayoutBindings.Get(), SetLayouts[index].LayoutBindings.Get(), bindingsCount * sizeof(VkDescriptorSetLayoutBinding))) return false; } - return true; } DescriptorSetLayoutVulkan::DescriptorSetLayoutVulkan(GPUDeviceVulkan* device) - : _device(device) + : Device(device) { } DescriptorSetLayoutVulkan::~DescriptorSetLayoutVulkan() { - for (VkDescriptorSetLayout& handle : _handles) - { - _device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::DescriptorSetLayout, handle); - } + for (VkDescriptorSetLayout& handle : Handles) + Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::DescriptorSetLayout, handle); } void DescriptorSetLayoutVulkan::Compile() { - ASSERT(_handles.IsEmpty()); +#if !BUILD_RELEASE + ASSERT(Handles.IsEmpty()); + const VkPhysicalDeviceLimits& limits = Device->PhysicalDeviceLimits; + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_SAMPLER] + LayoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] < limits.maxDescriptorSetSamplers); + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]+ LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] < limits.maxDescriptorSetUniformBuffers); + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] < limits.maxDescriptorSetUniformBuffersDynamic); + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER] + LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] < limits.maxDescriptorSetStorageBuffers); + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] < limits.maxDescriptorSetStorageBuffersDynamic); + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] + LayoutTypes[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE] + LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER] < limits.maxDescriptorSetSampledImages); + ASSERT(LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE] + LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER]< limits.maxDescriptorSetStorageImages); +#endif - // Validate device limits for the engine - const VkPhysicalDeviceLimits& limits = _device->PhysicalDeviceLimits; - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_SAMPLER] + _layoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] < limits.maxDescriptorSetSamplers); - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]+ _layoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] < limits.maxDescriptorSetUniformBuffers); - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] < limits.maxDescriptorSetUniformBuffersDynamic); - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER] + _layoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] < limits.maxDescriptorSetStorageBuffers); - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] < limits.maxDescriptorSetStorageBuffersDynamic); - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] + _layoutTypes[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE] + _layoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER] < limits.maxDescriptorSetSampledImages); - ASSERT(_layoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE] + _layoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER]< limits.maxDescriptorSetStorageImages); - - _handles.Resize(_setLayouts.Count()); - for (int32 i = 0; i < _setLayouts.Count(); i++) + Handles.Resize(SetLayouts.Count()); + for (int32 i = 0; i < SetLayouts.Count(); i++) { - auto& layout = _setLayouts[i]; + auto& layout = SetLayouts.Get()[i]; VkDescriptorSetLayoutCreateInfo layoutInfo; RenderToolsVulkan::ZeroStruct(layoutInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); layoutInfo.bindingCount = layout.LayoutBindings.Count(); layoutInfo.pBindings = layout.LayoutBindings.Get(); - VALIDATE_VULKAN_RESULT(vkCreateDescriptorSetLayout(_device->Device, &layoutInfo, nullptr, &_handles[i])); + VALIDATE_VULKAN_RESULT(vkCreateDescriptorSetLayout(Device->Device, &layoutInfo, nullptr, &Handles[i])); } - if (_typesUsageID == ~0) - { - CacheTypesUsageID(); - } +#if VULKAN_HASH_POOLS_WITH_LAYOUT_TYPES + if (SetLayoutsHash == 0) + SetLayoutsHash = Crc::MemCrc32(LayoutTypes, sizeof(LayoutTypes)); +#endif - RenderToolsVulkan::ZeroStruct(_allocateInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); - _allocateInfo.descriptorSetCount = _handles.Count(); - _allocateInfo.pSetLayouts = _handles.Get(); + RenderToolsVulkan::ZeroStruct(AllocateInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + AllocateInfo.descriptorSetCount = Handles.Count(); + AllocateInfo.pSetLayouts = Handles.Get(); } DescriptorPoolVulkan::DescriptorPoolVulkan(GPUDeviceVulkan* device, const DescriptorSetLayoutVulkan& layout) @@ -131,11 +112,11 @@ DescriptorPoolVulkan::DescriptorPoolVulkan(GPUDeviceVulkan* device, const Descri // The maximum amount of descriptor sets layout allocations to hold const uint32 MaxSetsAllocations = 256; - _descriptorSetsMax = MaxSetsAllocations * (VULKAN_HASH_POOLS_WITH_TYPES_USAGE_ID ? 1 : _layout.GetLayouts().Count()); + _descriptorSetsMax = MaxSetsAllocations * (VULKAN_HASH_POOLS_WITH_LAYOUT_TYPES ? 1 : _layout.SetLayouts.Count()); for (uint32 typeIndex = VULKAN_DESCRIPTOR_TYPE_BEGIN; typeIndex <= VULKAN_DESCRIPTOR_TYPE_END; typeIndex++) { const VkDescriptorType descriptorType = (VkDescriptorType)typeIndex; - const uint32 typesUsed = _layout.GetTypesUsed(descriptorType); + const uint32 typesUsed = _layout.LayoutTypes[descriptorType]; if (typesUsed > 0) { VkDescriptorPoolSize& type = types.AddOne(); @@ -167,22 +148,22 @@ void DescriptorPoolVulkan::Track(const DescriptorSetLayoutVulkan& layout) #if !BUILD_RELEASE for (uint32 typeIndex = VULKAN_DESCRIPTOR_TYPE_BEGIN; typeIndex <= VULKAN_DESCRIPTOR_TYPE_END; typeIndex++) { - ASSERT(_layout.GetTypesUsed((VkDescriptorType)typeIndex) == layout.GetTypesUsed((VkDescriptorType)typeIndex)); + ASSERT(_layout.LayoutTypes[typeIndex] == layout.LayoutTypes[typeIndex]); } #endif - _allocatedDescriptorSetsCount += layout.GetLayouts().Count(); + _allocatedDescriptorSetsCount += layout.SetLayouts.Count(); _allocatedDescriptorSetsCountMax = Math::Max(_allocatedDescriptorSetsCount, _allocatedDescriptorSetsCountMax); } void DescriptorPoolVulkan::TrackRemoveUsage(const DescriptorSetLayoutVulkan& layout) { - // Check and increment our current type usage +#if !BUILD_RELEASE for (uint32 typeIndex = VULKAN_DESCRIPTOR_TYPE_BEGIN; typeIndex <= VULKAN_DESCRIPTOR_TYPE_END; typeIndex++) { - ASSERT(_layout.GetTypesUsed((VkDescriptorType)typeIndex) == layout.GetTypesUsed((VkDescriptorType)typeIndex)); + ASSERT(_layout.LayoutTypes[typeIndex] == layout.LayoutTypes[typeIndex]); } - - _allocatedDescriptorSetsCount -= layout.GetLayouts().Count(); +#endif + _allocatedDescriptorSetsCount -= layout.SetLayouts.Count(); } void DescriptorPoolVulkan::Reset() @@ -214,11 +195,10 @@ TypedDescriptorPoolSetVulkan::~TypedDescriptorPoolSetVulkan() bool TypedDescriptorPoolSetVulkan::AllocateDescriptorSets(const DescriptorSetLayoutVulkan& layout, VkDescriptorSet* outSets) { - const auto& layoutHandles = layout.GetHandles(); - if (layoutHandles.HasItems()) + if (layout.Handles.HasItems()) { auto* pool = _poolListCurrent->Element; - while (!pool->AllocateDescriptorSets(layout.GetAllocateInfo(), outSets)) + while (!pool->AllocateDescriptorSets(layout.AllocateInfo, outSets)) { pool = GetFreePool(true); } @@ -279,7 +259,7 @@ DescriptorPoolSetContainerVulkan::~DescriptorPoolSetContainerVulkan() TypedDescriptorPoolSetVulkan* DescriptorPoolSetContainerVulkan::AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout) { - const uint32 hash = VULKAN_HASH_POOLS_WITH_TYPES_USAGE_ID ? layout.GetTypesUsageID() : GetHash(layout); + const uint32 hash = VULKAN_HASH_POOLS_WITH_LAYOUT_TYPES ? layout.SetLayoutsHash : GetHash(layout); TypedDescriptorPoolSetVulkan* typedPool; if (!_typedDescriptorPools.TryGet(hash, typedPool)) { @@ -317,7 +297,6 @@ DescriptorPoolsManagerVulkan::~DescriptorPoolsManagerVulkan() DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetContainer() { ScopeLock lock(_locker); - for (auto* poolSet : _poolSets) { if (poolSet->IsUnused()) @@ -327,7 +306,6 @@ DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetCo return *poolSet; } } - const auto poolSet = New(_device); _poolSets.Add(poolSet); return *poolSet; @@ -354,28 +332,24 @@ void DescriptorPoolsManagerVulkan::GC() } PipelineLayoutVulkan::PipelineLayoutVulkan(GPUDeviceVulkan* device, const DescriptorSetLayoutInfoVulkan& layout) - : _device(device) - , _handle(VK_NULL_HANDLE) - , _descriptorSetLayout(device) + : Device(device) + , Handle(VK_NULL_HANDLE) + , DescriptorSetLayout(device) { - _descriptorSetLayout.CopyFrom(layout); - _descriptorSetLayout.Compile(); - - const auto& layoutHandles = _descriptorSetLayout.GetHandles(); + DescriptorSetLayout.CopyFrom(layout); + DescriptorSetLayout.Compile(); VkPipelineLayoutCreateInfo createInfo; RenderToolsVulkan::ZeroStruct(createInfo, VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); - createInfo.setLayoutCount = layoutHandles.Count(); - createInfo.pSetLayouts = layoutHandles.Get(); - VALIDATE_VULKAN_RESULT(vkCreatePipelineLayout(_device->Device, &createInfo, nullptr, &_handle)); + createInfo.setLayoutCount = DescriptorSetLayout.Handles.Count(); + createInfo.pSetLayouts = DescriptorSetLayout.Handles.Get(); + VALIDATE_VULKAN_RESULT(vkCreatePipelineLayout(device->Device, &createInfo, nullptr, &Handle)); } PipelineLayoutVulkan::~PipelineLayoutVulkan() { - if (_handle != VK_NULL_HANDLE) - { - _device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::PipelineLayout, _handle); - } + if (Handle != VK_NULL_HANDLE) + Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::PipelineLayout, Handle); } uint32 DescriptorSetWriterVulkan::SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, VkBufferView* texelBufferView, uint8* bindingToDynamicOffset) diff --git a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h index b1530728d..8a5d706d6 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/DescriptorSetVulkan.h @@ -54,106 +54,63 @@ namespace DescriptorSet class DescriptorSetLayoutInfoVulkan { public: - struct SetLayout { Array LayoutBindings; }; -protected: - - uint32 _layoutTypes[VULKAN_DESCRIPTOR_TYPE_END]; - Array _setLayouts; - uint32 _hash = 0; - uint32 _typesUsageID = ~0; - - void CacheTypesUsageID(); + uint32 Hash = 0; + uint32 SetLayoutsHash = 0; + uint32 LayoutTypes[VULKAN_DESCRIPTOR_TYPE_END]; + Array SetLayouts; public: - DescriptorSetLayoutInfoVulkan() { - Platform::MemoryClear(_layoutTypes, sizeof(_layoutTypes)); + Platform::MemoryClear(LayoutTypes, sizeof(LayoutTypes)); } -public: - - inline uint32 GetTypesUsed(VkDescriptorType type) const - { - return _layoutTypes[type]; - } - - const Array& GetLayouts() const - { - return _setLayouts; - } - - inline uint32 GetTypesUsageID() const - { - return _typesUsageID; - } - -public: - void AddBindingsForStage(VkShaderStageFlagBits stageFlags, DescriptorSet::Stage descSet, const SpirvShaderDescriptorInfo* descriptorInfo); void CopyFrom(const DescriptorSetLayoutInfoVulkan& info) { - Platform::MemoryCopy(_layoutTypes, info._layoutTypes, sizeof(_layoutTypes)); - _hash = info._hash; - _typesUsageID = info._typesUsageID; - _setLayouts = info._setLayouts; + Platform::MemoryCopy(LayoutTypes, info.LayoutTypes, sizeof(LayoutTypes)); + Hash = info.Hash; + SetLayoutsHash = info.SetLayoutsHash; + SetLayouts = info.SetLayouts; } bool operator==(const DescriptorSetLayoutInfoVulkan& other) const; friend inline uint32 GetHash(const DescriptorSetLayoutInfoVulkan& key) { - return key._hash; + return key.Hash; } }; class DescriptorSetLayoutVulkan : public DescriptorSetLayoutInfoVulkan { public: + typedef Array> HandlesArray; - typedef Array> DescriptorSetLayoutHandlesArray; - -private: - - GPUDeviceVulkan* _device; - DescriptorSetLayoutHandlesArray _handles; - VkDescriptorSetAllocateInfo _allocateInfo; - -public: + GPUDeviceVulkan* Device; + HandlesArray Handles; + VkDescriptorSetAllocateInfo AllocateInfo; DescriptorSetLayoutVulkan(GPUDeviceVulkan* device); ~DescriptorSetLayoutVulkan(); -public: - - inline const DescriptorSetLayoutHandlesArray& GetHandles() const - { - return _handles; - } - - inline const VkDescriptorSetAllocateInfo& GetAllocateInfo() const - { - return _allocateInfo; - } + void Compile(); friend inline uint32 GetHash(const DescriptorSetLayoutVulkan& key) { - return key._hash; + return key.Hash; } - - void Compile(); }; class DescriptorPoolVulkan { private: - GPUDeviceVulkan* _device; VkDescriptorPool _handle; @@ -164,12 +121,10 @@ private: const DescriptorSetLayoutVulkan& _layout; public: - DescriptorPoolVulkan(GPUDeviceVulkan* device, const DescriptorSetLayoutVulkan& layout); ~DescriptorPoolVulkan(); public: - inline VkDescriptorPool GetHandle() const { return _handle; @@ -182,7 +137,7 @@ public: inline bool CanAllocate(const DescriptorSetLayoutVulkan& layout) const { - return _descriptorSetsMax > _allocatedDescriptorSetsCount + layout.GetLayouts().Count(); + return _descriptorSetsMax > _allocatedDescriptorSetsCount + layout.SetLayouts.Count(); } inline uint32 GetAllocatedDescriptorSetsCount() const @@ -191,7 +146,6 @@ public: } public: - void Track(const DescriptorSetLayoutVulkan& layout); void TrackRemoveUsage(const DescriptorSetLayoutVulkan& layout); void Reset(); @@ -205,7 +159,6 @@ class TypedDescriptorPoolSetVulkan friend DescriptorPoolSetContainerVulkan; private: - GPUDeviceVulkan* _device; const DescriptorPoolSetContainerVulkan* _owner; const DescriptorSetLayoutVulkan& _layout; @@ -213,7 +166,6 @@ private: class PoolList { public: - DescriptorPoolVulkan* Element; PoolList* Next; @@ -228,7 +180,6 @@ private: PoolList* _poolListCurrent = nullptr; public: - TypedDescriptorPoolSetVulkan(GPUDeviceVulkan* device, const DescriptorPoolSetContainerVulkan* owner, const DescriptorSetLayoutVulkan& layout) : _device(device) , _owner(owner) @@ -247,7 +198,6 @@ public: } private: - DescriptorPoolVulkan* GetFreePool(bool forceNewPool = false); DescriptorPoolVulkan* PushNewPool(); void Reset(); @@ -256,20 +206,16 @@ private: class DescriptorPoolSetContainerVulkan { private: - GPUDeviceVulkan* _device; Dictionary _typedDescriptorPools; uint64 _lastFrameUsed; bool _used; public: - DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device); - ~DescriptorPoolSetContainerVulkan(); public: - TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout); void Reset(); void SetUsed(bool used); @@ -288,13 +234,11 @@ public: class DescriptorPoolsManagerVulkan { private: - GPUDeviceVulkan* _device = nullptr; CriticalSection _locker; Array _poolSets; public: - DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device); ~DescriptorPoolsManagerVulkan(); @@ -305,35 +249,13 @@ public: class PipelineLayoutVulkan { -private: - - GPUDeviceVulkan* _device; - VkPipelineLayout _handle; - DescriptorSetLayoutVulkan _descriptorSetLayout; - public: + GPUDeviceVulkan* Device; + VkPipelineLayout Handle; + DescriptorSetLayoutVulkan DescriptorSetLayout; PipelineLayoutVulkan(GPUDeviceVulkan* device, const DescriptorSetLayoutInfoVulkan& layout); ~PipelineLayoutVulkan(); - -public: - - inline VkPipelineLayout GetHandle() const - { - return _handle; - } - -public: - - inline const DescriptorSetLayoutVulkan& GetDescriptorSetLayout() const - { - return _descriptorSetLayout; - } - - inline bool HasDescriptors() const - { - return _descriptorSetLayout.GetLayouts().HasItems(); - } }; struct DescriptorSetWriteContainerVulkan @@ -357,14 +279,12 @@ struct DescriptorSetWriteContainerVulkan class DescriptorSetWriterVulkan { public: - VkWriteDescriptorSet* WriteDescriptors = nullptr; byte* BindingToDynamicOffset = nullptr; uint32* DynamicOffsets = nullptr; uint32 WritesCount = 0; public: - uint32 SetupDescriptorWrites(const SpirvShaderDescriptorInfo& info, VkWriteDescriptorSet* writeDescriptors, VkDescriptorImageInfo* imageInfo, VkDescriptorBufferInfo* bufferInfo, VkBufferView* texelBufferView, byte* bindingToDynamicOffset); bool WriteUniformBuffer(uint32 descriptorIndex, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, uint32 index = 0) const @@ -451,9 +371,7 @@ public: void SetDescriptorSet(VkDescriptorSet descriptorSet) const { for (uint32 i = 0; i < WritesCount; i++) - { WriteDescriptors[i].dstSet = descriptorSet; - } } }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h index c716eb1c2..e109293a6 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUAdapterVulkan.h @@ -14,7 +14,6 @@ class GPUAdapterVulkan : public GPUAdapter { public: - /// /// Initializes a new instance of the class. /// @@ -38,7 +37,6 @@ public: } public: - /// /// The GPU device handle. /// @@ -55,7 +53,6 @@ public: String Description; public: - // [GPUAdapter] bool IsValid() const override { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h index 9fe250efc..a75e2663e 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUBufferVulkan.h @@ -13,7 +13,6 @@ class GPUBufferViewVulkan : public GPUBufferView, public DescriptorOwnerResourceVulkan { public: - GPUBufferViewVulkan() { } @@ -26,7 +25,6 @@ public: #endif public: - GPUDeviceVulkan* Device = nullptr; GPUBufferVulkan* Owner = nullptr; VkBuffer Buffer = VK_NULL_HANDLE; @@ -34,13 +32,10 @@ public: VkDeviceSize Size = 0; public: - void Init(GPUDeviceVulkan* device, GPUBufferVulkan* owner, VkBuffer buffer, VkDeviceSize size, VkBufferUsageFlags usage, PixelFormat format); - void Release(); public: - // [GPUResourceView] void* GetNativePtr() const override { @@ -59,13 +54,11 @@ public: class GPUBufferVulkan : public GPUResourceVulkan { private: - VkBuffer _buffer = VK_NULL_HANDLE; VmaAllocation _allocation = VK_NULL_HANDLE; GPUBufferViewVulkan _view; public: - /// /// Initializes a new instance of the class. /// @@ -77,7 +70,6 @@ public: } public: - /// /// Gets the Vulkan buffer handle. /// @@ -105,14 +97,12 @@ public: GPUBufferVulkan* Counter = nullptr; public: - // [GPUBuffer] GPUBufferView* View() const override; void* Map(GPUResourceMapMode mode) override; void Unmap() override; protected: - // [GPUBuffer] bool OnInit() override; void OnReleaseGPU() override; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 0a94c2df7..cb223124f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -20,18 +20,18 @@ // Ensure to match the indirect commands arguments layout static_assert(sizeof(GPUDispatchIndirectArgs) == sizeof(VkDispatchIndirectCommand), "Wrong size of GPUDrawIndirectArgs."); static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountX) == OFFSET_OF(VkDispatchIndirectCommand, x), "Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountX"); -static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountY) == OFFSET_OF(VkDispatchIndirectCommand, y),"Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountY"); +static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountY) == OFFSET_OF(VkDispatchIndirectCommand, y), "Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountY"); static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountZ) == OFFSET_OF(VkDispatchIndirectCommand, z), "Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountZ"); // static_assert(sizeof(GPUDrawIndirectArgs) == sizeof(VkDrawIndirectCommand), "Wrong size of GPUDrawIndirectArgs."); static_assert(OFFSET_OF(GPUDrawIndirectArgs, VerticesCount) == OFFSET_OF(VkDrawIndirectCommand, vertexCount), "Wrong offset for GPUDrawIndirectArgs::VerticesCount"); -static_assert(OFFSET_OF(GPUDrawIndirectArgs, InstanceCount) == OFFSET_OF(VkDrawIndirectCommand, instanceCount),"Wrong offset for GPUDrawIndirectArgs::InstanceCount"); +static_assert(OFFSET_OF(GPUDrawIndirectArgs, InstanceCount) == OFFSET_OF(VkDrawIndirectCommand, instanceCount), "Wrong offset for GPUDrawIndirectArgs::InstanceCount"); static_assert(OFFSET_OF(GPUDrawIndirectArgs, StartVertex) == OFFSET_OF(VkDrawIndirectCommand, firstVertex), "Wrong offset for GPUDrawIndirectArgs::StartVertex"); static_assert(OFFSET_OF(GPUDrawIndirectArgs, StartInstance) == OFFSET_OF(VkDrawIndirectCommand, firstInstance), "Wrong offset for GPUDrawIndirectArgs::StartInstance"); // static_assert(sizeof(GPUDrawIndexedIndirectArgs) == sizeof(VkDrawIndexedIndirectCommand), "Wrong size of GPUDrawIndexedIndirectArgs."); static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, IndicesCount) == OFFSET_OF(VkDrawIndexedIndirectCommand, indexCount), "Wrong offset for GPUDrawIndexedIndirectArgs::IndicesCount"); -static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, InstanceCount) == OFFSET_OF(VkDrawIndexedIndirectCommand, instanceCount),"Wrong offset for GPUDrawIndexedIndirectArgs::InstanceCount"); +static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, InstanceCount) == OFFSET_OF(VkDrawIndexedIndirectCommand, instanceCount), "Wrong offset for GPUDrawIndexedIndirectArgs::InstanceCount"); static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartIndex) == OFFSET_OF(VkDrawIndexedIndirectCommand, firstIndex), "Wrong offset for GPUDrawIndexedIndirectArgs::StartIndex"); static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartVertex) == OFFSET_OF(VkDrawIndexedIndirectCommand, vertexOffset), "Wrong offset for GPUDrawIndexedIndirectArgs::StartVertex"); static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartInstance) == OFFSET_OF(VkDrawIndexedIndirectCommand, firstInstance), "Wrong offset for GPUDrawIndexedIndirectArgs::StartInstance"); @@ -72,55 +72,19 @@ const Char* ToString(VkImageLayout layout) #endif -void PipelineBarrierVulkan::AddImageBarrier(VkImage image, const VkImageSubresourceRange& range, VkImageLayout srcLayout, VkImageLayout dstLayout, GPUTextureViewVulkan* handle) -{ -#if VK_ENABLE_BARRIERS_DEBUG - ImageBarriersDebug.Add(handle); -#endif - VkImageMemoryBarrier& imageBarrier = ImageBarriers.AddOne(); - RenderToolsVulkan::ZeroStruct(imageBarrier, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); - imageBarrier.image = image; - imageBarrier.subresourceRange = range; - imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageBarrier.oldLayout = srcLayout; - imageBarrier.newLayout = dstLayout; - SourceStage |= RenderToolsVulkan::GetImageBarrierFlags(srcLayout, imageBarrier.srcAccessMask); - DestStage |= RenderToolsVulkan::GetImageBarrierFlags(dstLayout, imageBarrier.dstAccessMask); -#if VK_ENABLE_BARRIERS_DEBUG - LOG(Warning, "Image Barrier: 0x{0:x}, {1} -> {2} for baseMipLevel: {3}, baseArrayLayer: {4}, levelCount: {5}, layerCount: {6} ({7})", - (uintptr)image, - ToString(srcLayout), - ToString(dstLayout), - range.baseMipLevel, - range.baseArrayLayer, - range.levelCount, - range.layerCount, - handle && handle->Owner->AsGPUResource() ? handle->Owner->AsGPUResource()->ToString() : String::Empty - ); -#endif -} - -void PipelineBarrierVulkan::AddBufferBarrier(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkAccessFlags srcAccess, VkAccessFlags dstAccess) -{ - VkBufferMemoryBarrier& bufferBarrier = BufferBarriers.AddOne(); - RenderToolsVulkan::ZeroStruct(bufferBarrier, VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER); - bufferBarrier.buffer = buffer; - bufferBarrier.offset = offset; - bufferBarrier.size = size; - bufferBarrier.srcAccessMask = srcAccess; - bufferBarrier.dstAccessMask = dstAccess; - bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - SourceStage |= RenderToolsVulkan::GetBufferBarrierFlags(srcAccess); - DestStage |= RenderToolsVulkan::GetBufferBarrierFlags(dstAccess); -} - -void PipelineBarrierVulkan::Execute(CmdBufferVulkan* cmdBuffer) +void PipelineBarrierVulkan::Execute(const CmdBufferVulkan* cmdBuffer) { ASSERT(cmdBuffer->IsOutsideRenderPass()); vkCmdPipelineBarrier(cmdBuffer->GetHandle(), SourceStage, DestStage, 0, 0, nullptr, BufferBarriers.Count(), BufferBarriers.Get(), ImageBarriers.Count(), ImageBarriers.Get()); - Reset(); + + // Reset + SourceStage = 0; + DestStage = 0; + ImageBarriers.Clear(); + BufferBarriers.Clear(); +#if VK_ENABLE_BARRIERS_DEBUG + ImageBarriersDebug.Clear(); +#endif } GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue) @@ -154,7 +118,7 @@ GPUContextVulkan::~GPUContextVulkan() Delete(_cmdBufferManager); } -void GPUContextVulkan::AddImageBarrier(VkImage image, VkImageLayout srcLayout, VkImageLayout dstLayout, VkImageSubresourceRange& subresourceRange, GPUTextureViewVulkan* handle) +void GPUContextVulkan::AddImageBarrier(VkImage image, VkImageLayout srcLayout, VkImageLayout dstLayout, const VkImageSubresourceRange& subresourceRange, GPUTextureViewVulkan* handle) { #if VK_ENABLE_BARRIERS_BATCHING // Auto-flush on overflow @@ -168,7 +132,31 @@ void GPUContextVulkan::AddImageBarrier(VkImage image, VkImageLayout srcLayout, V #endif // Insert barrier - _barriers.AddImageBarrier(image, subresourceRange, srcLayout, dstLayout, handle); +#if VK_ENABLE_BARRIERS_DEBUG + _barriers.ImageBarriersDebug.Add(handle); +#endif + VkImageMemoryBarrier& imageBarrier = _barriers.ImageBarriers.AddOne(); + RenderToolsVulkan::ZeroStruct(imageBarrier, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); + imageBarrier.image = image; + imageBarrier.subresourceRange = subresourceRange; + imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageBarrier.oldLayout = srcLayout; + imageBarrier.newLayout = dstLayout; + _barriers.SourceStage |= RenderToolsVulkan::GetImageBarrierFlags(srcLayout, imageBarrier.srcAccessMask); + _barriers.DestStage |= RenderToolsVulkan::GetImageBarrierFlags(dstLayout, imageBarrier.dstAccessMask); +#if VK_ENABLE_BARRIERS_DEBUG + LOG(Warning, "Image Barrier: 0x{0:x}, {1} -> {2} for baseMipLevel: {3}, baseArrayLayer: {4}, levelCount: {5}, layerCount: {6} ({7})", + (uintptr)image, + ::ToString(srcLayout), + ::ToString(dstLayout), + subresourceRange.baseMipLevel, + subresourceRange.baseArrayLayer, + subresourceRange.levelCount, + subresourceRange.layerCount, + handle && handle->Owner->AsGPUResource() ? handle->Owner->AsGPUResource()->ToString() : String::Empty + ); +#endif #if !VK_ENABLE_BARRIERS_BATCHING // Auto-flush without batching @@ -306,7 +294,17 @@ void GPUContextVulkan::AddBufferBarrier(GPUBufferVulkan* buffer, VkAccessFlags d #endif // Insert barrier - _barriers.AddBufferBarrier(buffer->GetHandle(), 0, buffer->GetSize(), buffer->Access, dstAccess); + VkBufferMemoryBarrier& bufferBarrier = _barriers.BufferBarriers.AddOne(); + RenderToolsVulkan::ZeroStruct(bufferBarrier, VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER); + bufferBarrier.buffer = buffer->GetHandle(); + bufferBarrier.offset = 0; + bufferBarrier.size = buffer->GetSize(); + bufferBarrier.srcAccessMask = buffer->Access; + bufferBarrier.dstAccessMask = dstAccess; + bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + _barriers.SourceStage |= RenderToolsVulkan::GetBufferBarrierFlags(buffer->Access); + _barriers.DestStage |= RenderToolsVulkan::GetBufferBarrierFlags(dstAccess); buffer->Access = dstAccess; #if !VK_ENABLE_BARRIERS_BATCHING @@ -337,13 +335,12 @@ DescriptorPoolVulkan* GPUContextVulkan::AllocateDescriptorSets(const VkDescripto VkDescriptorSetAllocateInfo allocateInfo = descriptorSetAllocateInfo; DescriptorPoolVulkan* pool = nullptr; - const uint32 hash = VULKAN_HASH_POOLS_WITH_TYPES_USAGE_ID ? layout.GetTypesUsageID() : GetHash(layout); + const uint32 hash = VULKAN_HASH_POOLS_WITH_LAYOUT_TYPES ? layout.SetLayoutsHash : GetHash(layout); DescriptorPoolArray* typedDescriptorPools = _descriptorPools.TryGet(hash); if (typedDescriptorPools != nullptr) { pool = typedDescriptorPools->HasItems() ? typedDescriptorPools->Last() : nullptr; - if (pool && pool->CanAllocate(layout)) { allocateInfo.descriptorPool = pool->GetHandle(); @@ -363,7 +360,6 @@ DescriptorPoolVulkan* GPUContextVulkan::AllocateDescriptorSets(const VkDescripto } else { - // Spec says any negative value could be due to fragmentation, so create a new Pool. If it fails here then we really are out of memory! pool = New(_device, layout); typedDescriptorPools->Add(pool); allocateInfo.descriptorPool = pool->GetHandle(); @@ -404,8 +400,18 @@ void GPUContextVulkan::BeginRenderPass() if (_rtDepth) { handle = _rtDepth; - layout.ReadDepth = true; // TODO: use proper depthStencilAccess flags - layout.WriteDepth = handle->LayoutRTV == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // TODO: do it in a proper way + layout.ReadDepth = true; + layout.ReadStencil = PixelFormatExtensions::HasStencil(handle->GetFormat()); + layout.WriteDepth = handle->LayoutRTV == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || handle->LayoutRTV == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL || handle->LayoutRTV == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + layout.WriteStencil = handle->LayoutRTV == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL || handle->LayoutRTV == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL || handle->LayoutRTV == VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL; + if (_currentState && 0) + { + // TODO: use this but only if state doesn't change during whole render pass (eg. 1st draw call might not draw depth but 2nd might) + layout.ReadDepth &= _currentState->DepthReadEnable; + layout.ReadStencil &= _currentState->StencilReadEnable; + layout.WriteDepth &= _currentState->DepthWriteEnable; + layout.WriteStencil &= _currentState->StencilWriteEnable; + } framebufferKey.AttachmentCount++; framebufferKey.Attachments[_rtCount] = handle->GetFramebufferView(); AddImageBarrier(handle, handle->LayoutRTV); @@ -571,55 +577,6 @@ void GPUContextVulkan::UpdateDescriptorSets(const SpirvShaderDescriptorInfo& des } } -void GPUContextVulkan::UpdateDescriptorSets(GPUPipelineStateVulkan* pipelineState) -{ - const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - const auto pipelineLayout = pipelineState->GetLayout(); - ASSERT(pipelineLayout); - bool needsWrite = false; - - // No current descriptor pools set - acquire one and reset - const bool newDescriptorPool = pipelineState->AcquirePoolSet(cmdBuffer); - needsWrite |= newDescriptorPool; - - // Update descriptors for every used shader stage - uint32 remainingHasDescriptorsPerStageMask = pipelineState->HasDescriptorsPerStageMask; - for (int32 stage = 0; stage < DescriptorSet::GraphicsStagesCount && remainingHasDescriptorsPerStageMask; stage++) - { - // Only process stages that exist in this pipeline and use descriptors - if (remainingHasDescriptorsPerStageMask & 1) - { - UpdateDescriptorSets(*pipelineState->DescriptorInfoPerStage[stage], pipelineState->DSWriter[stage], needsWrite); - } - - remainingHasDescriptorsPerStageMask >>= 1; - } - - // Allocate sets if need to - //if (needsWrite) // TODO: write on change only? - { - if (!pipelineState->AllocateDescriptorSets()) - { - return; - } - uint32 remainingStagesMask = pipelineState->HasDescriptorsPerStageMask; - uint32 stage = 0; - while (remainingStagesMask) - { - if (remainingStagesMask & 1) - { - const VkDescriptorSet descriptorSet = pipelineState->DescriptorSetHandles[stage]; - pipelineState->DSWriter[stage].SetDescriptorSet(descriptorSet); - } - - stage++; - remainingStagesMask >>= 1; - } - - vkUpdateDescriptorSets(_device->Device, pipelineState->DSWriteContainer.DescriptorWrites.Count(), pipelineState->DSWriteContainer.DescriptorWrites.Get(), 0, nullptr); - } -} - void GPUContextVulkan::UpdateDescriptorSets(ComputePipelineStateVulkan* pipelineState) { const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); @@ -654,8 +611,6 @@ void GPUContextVulkan::OnDrawCall() GPUPipelineStateVulkan* pipelineState = _currentState; ASSERT(pipelineState && pipelineState->IsValid()); const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - const auto pipelineLayout = pipelineState->GetLayout(); - ASSERT(pipelineLayout); // End previous render pass if render targets layout was modified if (_rtDirtyFlag && cmdBuffer->IsInsideRenderPass()) @@ -663,7 +618,35 @@ void GPUContextVulkan::OnDrawCall() if (pipelineState->HasDescriptorsPerStageMask) { - UpdateDescriptorSets(pipelineState); + // Get descriptor pools set + bool needsWrite = pipelineState->AcquirePoolSet(cmdBuffer); + + // Update descriptors for every used shader stage + uint32 remainingHasDescriptorsPerStageMask = pipelineState->HasDescriptorsPerStageMask; + for (int32 stage = 0; stage < DescriptorSet::GraphicsStagesCount && remainingHasDescriptorsPerStageMask; stage++) + { + if (remainingHasDescriptorsPerStageMask & 1) + UpdateDescriptorSets(*pipelineState->DescriptorInfoPerStage[stage], pipelineState->DSWriter[stage], needsWrite); + remainingHasDescriptorsPerStageMask >>= 1; + } + + // Allocate sets if need to + //if (needsWrite) // TODO: write on change only? + { + if (!pipelineState->CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*pipelineState->DescriptorSetsLayout, pipelineState->DescriptorSetHandles.Get())) + return; + uint32 remainingStagesMask = pipelineState->HasDescriptorsPerStageMask; + uint32 stage = 0; + while (remainingStagesMask) + { + if (remainingStagesMask & 1) + pipelineState->DSWriter[stage].SetDescriptorSet(pipelineState->DescriptorSetHandles[stage]); + remainingStagesMask >>= 1; + stage++; + } + + vkUpdateDescriptorSets(_device->Device, pipelineState->DSWriteContainer.DescriptorWrites.Count(), pipelineState->DSWriteContainer.DescriptorWrites.Get(), 0, nullptr); + } } // Bind any missing vertex buffers to null if required by the current state @@ -689,26 +672,30 @@ void GPUContextVulkan::OnDrawCall() } // Bind pipeline - if (_psDirtyFlag && _currentState && (_rtDepth || _rtCount)) + if (_psDirtyFlag && pipelineState && (_rtDepth || _rtCount)) { _psDirtyFlag = false; const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer(); - const auto pipeline = _currentState->GetState(_renderPass); + const auto pipeline = pipelineState->GetState(_renderPass); vkCmdBindPipeline(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); RENDER_STAT_PS_STATE_CHANGE(); } - //UpdateDynamicStates(); - // Bind descriptors sets to the graphics pipeline if (pipelineState->HasDescriptorsPerStageMask) { - pipelineState->Bind(cmdBuffer); + vkCmdBindDescriptorSets( + cmdBuffer->GetHandle(), + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineState->GetLayout()->Handle, + 0, + pipelineState->DescriptorSetHandles.Count(), + pipelineState->DescriptorSetHandles.Get(), + pipelineState->DynamicOffsets.Count(), + pipelineState->DynamicOffsets.Get()); } - // Clear flag _rtDirtyFlag = false; - #if VK_ENABLE_BARRIERS_DEBUG LOG(Warning, "Draw"); #endif @@ -1324,13 +1311,17 @@ void GPUContextVulkan::UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 vkCmdPipelineBarrier(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &barrierBefore, 0, nullptr, 0, nullptr); // Use direct update for small buffers - if (size <= 16 * 1024) + const uint32 alignedSize = Math::AlignUp(size, 4); + if (alignedSize > buffer->GetSize()) + { + int a= 1; + } + if (size <= 16 * 1024 && alignedSize <= buffer->GetSize()) { //AddBufferBarrier(bufferVulkan, VK_ACCESS_TRANSFER_WRITE_BIT); //FlushBarriers(); - size = Math::AlignUp(size, 4); - vkCmdUpdateBuffer(cmdBuffer->GetHandle(), bufferVulkan->GetHandle(), offset, size, data); + vkCmdUpdateBuffer(cmdBuffer->GetHandle(), bufferVulkan->GetHandle(), offset, alignedSize, data); } else { diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h index 3a454224d..e28bbc1a5 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h @@ -40,43 +40,25 @@ class DescriptorSetLayoutVulkan; /// struct PipelineBarrierVulkan { -public: - VkPipelineStageFlags SourceStage = 0; VkPipelineStageFlags DestStage = 0; Array> ImageBarriers; Array> BufferBarriers; #if VK_ENABLE_BARRIERS_DEBUG - Array> ImageBarriersDebug; + Array> ImageBarriersDebug; #endif -public: - - inline void Reset() - { - SourceStage = 0; - DestStage = 0; - ImageBarriers.Clear(); - BufferBarriers.Clear(); -#if VK_ENABLE_BARRIERS_DEBUG - ImageBarriersDebug.Clear(); -#endif - } - - void AddImageBarrier(VkImage image, const VkImageSubresourceRange& range, VkImageLayout srcLayout, VkImageLayout dstLayout, GPUTextureViewVulkan* handle); - void AddBufferBarrier(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, VkAccessFlags srcAccess, VkAccessFlags dstAccess); - - inline bool IsFull() const + FORCE_INLINE bool IsFull() const { return ImageBarriers.Count() == VK_BARRIER_BUFFER_SIZE || BufferBarriers.Count() == VK_BARRIER_BUFFER_SIZE; } - inline bool HasBarrier() const + FORCE_INLINE bool HasBarrier() const { return ImageBarriers.Count() + BufferBarriers.Count() != 0; } - void Execute(CmdBufferVulkan* cmdBuffer); + void Execute(const CmdBufferVulkan* cmdBuffer); }; /// @@ -85,7 +67,6 @@ public: class GPUContextVulkan : public GPUContext { private: - GPUDeviceVulkan* _device; QueueVulkan* _queue; CmdBufferManagerVulkan* _cmdBufferManager; @@ -116,7 +97,6 @@ private: Dictionary _descriptorPools; public: - /// /// Initializes a new instance of the class. /// @@ -130,18 +110,17 @@ public: ~GPUContextVulkan(); public: - - QueueVulkan* GetQueue() const + FORCE_INLINE QueueVulkan* GetQueue() const { return _queue; } - CmdBufferManagerVulkan* GetCmdBufferManager() const + FORCE_INLINE CmdBufferManagerVulkan* GetCmdBufferManager() const { return _cmdBufferManager; } - void AddImageBarrier(VkImage image, VkImageLayout srcLayout, VkImageLayout dstLayout, VkImageSubresourceRange& subresourceRange, GPUTextureViewVulkan* handle); + void AddImageBarrier(VkImage image, VkImageLayout srcLayout, VkImageLayout dstLayout, const VkImageSubresourceRange& subresourceRange, GPUTextureViewVulkan* handle); void AddImageBarrier(GPUTextureViewVulkan* handle, VkImageLayout dstLayout); void AddImageBarrier(GPUTextureVulkan* texture, int32 mipSlice, int32 arraySlice, VkImageLayout dstLayout); void AddImageBarrier(GPUTextureVulkan* texture, VkImageLayout dstLayout); @@ -157,14 +136,11 @@ public: void EndRenderPass(); private: - void UpdateDescriptorSets(const struct SpirvShaderDescriptorInfo& descriptorInfo, class DescriptorSetWriterVulkan& dsWriter, bool& needsWrite); - void UpdateDescriptorSets(GPUPipelineStateVulkan* pipelineState); void UpdateDescriptorSets(ComputePipelineStateVulkan* pipelineState); void OnDrawCall(); public: - // [GPUContext] void FrameBegin() override; void FrameEnd() override; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp index e0ca98abc..9b590cb16 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.Layers.cpp @@ -575,25 +575,15 @@ void GPUDeviceVulkan::GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array& deviceExtensions) { Platform::MemoryClear(&OptionalDeviceExtensions, sizeof(OptionalDeviceExtensions)); - - const auto HasExtension = [&deviceExtensions](const char* name) -> bool - { - const Function CheckCallback = [&name](const char* const& extension) -> bool - { - return StringUtils::Compare(extension, name) == 0; - }; - return ArrayExtensions::Any(deviceExtensions, CheckCallback); - }; - #if VK_KHR_maintenance1 - OptionalDeviceExtensions.HasKHRMaintenance1 = HasExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME); + OptionalDeviceExtensions.HasKHRMaintenance1 = RenderToolsVulkan::HasExtension(deviceExtensions, VK_KHR_MAINTENANCE1_EXTENSION_NAME); #endif #if VK_KHR_maintenance2 - OptionalDeviceExtensions.HasKHRMaintenance2 = HasExtension(VK_KHR_MAINTENANCE2_EXTENSION_NAME); + OptionalDeviceExtensions.HasKHRMaintenance2 = RenderToolsVulkan::HasExtension(deviceExtensions, VK_KHR_MAINTENANCE2_EXTENSION_NAME); #endif - OptionalDeviceExtensions.HasMirrorClampToEdge = HasExtension(VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME); + OptionalDeviceExtensions.HasMirrorClampToEdge = RenderToolsVulkan::HasExtension(deviceExtensions, VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME); #if VK_EXT_validation_cache - OptionalDeviceExtensions.HasEXTValidationCache = HasExtension(VK_EXT_VALIDATION_CACHE_EXTENSION_NAME); + OptionalDeviceExtensions.HasEXTValidationCache = RenderToolsVulkan::HasExtension(deviceExtensions, VK_EXT_VALIDATION_CACHE_EXTENSION_NAME); #endif } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp index dce261912..71a154ae8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.cpp @@ -250,23 +250,18 @@ void SetupDebugLayerCallback() { default: createInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; - // Fall-through... case 4: createInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; - // Fall-through... case 3: createInfo.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - // Fall-through... case 2: createInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; createInfo.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; - // Fall-through... case 1: createInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; createInfo.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; break; case 0: - // Nothing to do break; } const VkResult result = vkCreateDebugUtilsMessengerEXT(GPUDeviceVulkan::Instance, &createInfo, nullptr, &Messenger); @@ -288,21 +283,16 @@ void SetupDebugLayerCallback() { default: createInfo.flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT; - // Fall-through... case 4: createInfo.flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT; - // Fall-through... case 3: createInfo.flags |= VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; - // Fall-through... case 2: createInfo.flags |= VK_DEBUG_REPORT_WARNING_BIT_EXT; - // Fall-through... case 1: createInfo.flags |= VK_DEBUG_REPORT_ERROR_BIT_EXT; break; case 0: - // Nothing to do break; } const VkResult result = vkCreateDebugReportCallbackEXT(GPUDeviceVulkan::Instance, &createInfo, nullptr, &MsgCallback); @@ -354,16 +344,14 @@ DeferredDeletionQueueVulkan::~DeferredDeletionQueueVulkan() ASSERT(_entries.IsEmpty()); } -void DeferredDeletionQueueVulkan::ReleaseResources(bool deleteImmediately) +void DeferredDeletionQueueVulkan::ReleaseResources(bool immediately) { - ScopeLock lock(_locker); const uint64 checkFrame = Engine::FrameCount - VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT; + ScopeLock lock(_locker); for (int32 i = 0; i < _entries.Count(); i++) { - Entry* e = &_entries[i]; - - if (deleteImmediately || (checkFrame > e->FrameNumber && (e->CmdBuffer == nullptr || e->FenceCounter < e->CmdBuffer->GetFenceSignaledCounter())) - ) + Entry* e = &_entries.Get()[i]; + if (immediately || (checkFrame > e->FrameNumber && (e->CmdBuffer == nullptr || e->FenceCounter < e->CmdBuffer->GetFenceSignaledCounter()))) { if (e->AllocationHandle == VK_NULL_HANDLE) { @@ -402,14 +390,15 @@ void DeferredDeletionQueueVulkan::ReleaseResources(bool deleteImmediately) { vmaDestroyBuffer(_device->Allocator, (VkBuffer)e->Handle, e->AllocationHandle); } +#if !BUILD_RELEASE else { CRASH; } +#endif } _entries.RemoveAt(i--); - if (_entries.IsEmpty()) break; } @@ -418,19 +407,17 @@ void DeferredDeletionQueueVulkan::ReleaseResources(bool deleteImmediately) void DeferredDeletionQueueVulkan::EnqueueGenericResource(Type type, uint64 handle, VmaAllocation allocation) { - ASSERT(handle != 0); - const auto queue = _device->GraphicsQueue; + ASSERT_LOW_LAYER(handle != 0); Entry entry; - queue->GetLastSubmittedInfo(entry.CmdBuffer, entry.FenceCounter); + _device->GraphicsQueue->GetLastSubmittedInfo(entry.CmdBuffer, entry.FenceCounter); entry.Handle = handle; entry.AllocationHandle = allocation; entry.StructureType = type; entry.FrameNumber = Engine::FrameCount; ScopeLock lock(_locker); - -#if BUILD_DEBUG +#if BUILD_DEBUG && 0 const Function ContainsHandle = [handle](const Entry& e) { return e.Handle == handle; @@ -443,14 +430,10 @@ void DeferredDeletionQueueVulkan::EnqueueGenericResource(Type type, uint64 handl uint32 GetHash(const RenderTargetLayoutVulkan& key) { uint32 hash = (int32)key.MSAA * 11; - CombineHash(hash, (uint32)key.ReadDepth); - CombineHash(hash, (uint32)key.WriteDepth); - CombineHash(hash, (uint32)key.BlendEnable); + CombineHash(hash, key.Flags); CombineHash(hash, (uint32)key.DepthFormat * 93473262); - CombineHash(hash, key.RTsCount * 136); CombineHash(hash, key.Extent.width); CombineHash(hash, key.Extent.height); - CombineHash(hash, key.Layers); for (int32 i = 0; i < ARRAY_COUNT(key.RTVsFormats); i++) CombineHash(hash, (uint32)key.RTVsFormats[i]); return hash; @@ -465,9 +448,9 @@ uint32 GetHash(const FramebufferVulkan::Key& key) return hash; } -FramebufferVulkan::FramebufferVulkan(GPUDeviceVulkan* device, Key& key, VkExtent2D& extent, uint32 layers) - : _device(device) - , _handle(VK_NULL_HANDLE) +FramebufferVulkan::FramebufferVulkan(GPUDeviceVulkan* device, const Key& key, const VkExtent2D& extent, uint32 layers) + : Device(device) + , Handle(VK_NULL_HANDLE) , Extent(extent) , Layers(layers) { @@ -475,18 +458,18 @@ FramebufferVulkan::FramebufferVulkan(GPUDeviceVulkan* device, Key& key, VkExtent VkFramebufferCreateInfo createInfo; RenderToolsVulkan::ZeroStruct(createInfo, VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO); - createInfo.renderPass = key.RenderPass->GetHandle(); + createInfo.renderPass = key.RenderPass->Handle; createInfo.attachmentCount = key.AttachmentCount; createInfo.pAttachments = key.Attachments; createInfo.width = extent.width; createInfo.height = extent.height; createInfo.layers = layers; - VALIDATE_VULKAN_RESULT(vkCreateFramebuffer(device->Device, &createInfo, nullptr, &_handle)); + VALIDATE_VULKAN_RESULT(vkCreateFramebuffer(device->Device, &createInfo, nullptr, &Handle)); } FramebufferVulkan::~FramebufferVulkan() { - _device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::Framebuffer, _handle); + Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::Framebuffer, Handle); } bool FramebufferVulkan::HasReference(VkImageView imageView) const @@ -500,8 +483,8 @@ bool FramebufferVulkan::HasReference(VkImageView imageView) const } RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLayoutVulkan& layout) - : _device(device) - , _handle(VK_NULL_HANDLE) + : Device(device) + , Handle(VK_NULL_HANDLE) , Layout(layout) { const int32 colorAttachmentsCount = layout.RTsCount; @@ -544,23 +527,48 @@ RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLa if (hasDepthStencilAttachment) { VkImageLayout depthStencilLayout; - if (layout.ReadDepth && !layout.WriteDepth) +#if 0 + // TODO: enable extension and use separateDepthStencilLayouts from Vulkan 1.2 + if (layout.ReadStencil || layout.WriteStencil) + { + if (layout.WriteDepth && layout.WriteStencil) + depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + else if (layout.WriteDepth && !layout.WriteStencil) + depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL; + else if (layout.WriteStencil && !layout.WriteDepth) + depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL; + else if (layout.ReadDepth) + depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + else + depthStencilLayout = VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL; + } + else + { + // Depth-only + if (layout.ReadDepth && !layout.WriteDepth) + depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL; + else + depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + } +#else + if ((layout.ReadDepth || layout.ReadStencil) && !(layout.WriteDepth || layout.WriteStencil)) depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; else depthStencilLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +#endif // Use last slot for depth stencil attachment - VkAttachmentDescription& depthAttachment = attachments[colorAttachmentsCount]; - depthAttachment.flags = 0; - depthAttachment.format = RenderToolsVulkan::ToVulkanFormat(layout.DepthFormat); - depthAttachment.samples = (VkSampleCountFlagBits)layout.MSAA; - // TODO: fix those operations for load and store - depthAttachment.loadOp = layout.ReadDepth || true ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.storeOp = layout.WriteDepth || true ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // TODO: Handle stencil - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = depthStencilLayout; - depthAttachment.finalLayout = depthStencilLayout; + VkAttachmentDescription& attachment = attachments[colorAttachmentsCount]; + attachment.flags = 0; + attachment.format = RenderToolsVulkan::ToVulkanFormat(layout.DepthFormat); + attachment.samples = (VkSampleCountFlagBits)layout.MSAA; + attachment.loadOp = layout.ReadDepth || layout.ReadStencil ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE; + //attachment.storeOp = layout.WriteDepth || layout.WriteStencil ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // For some reason, read-only depth results in artifacts + attachment.stencilLoadOp = layout.ReadStencil ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.stencilStoreOp = layout.WriteStencil ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = depthStencilLayout; + attachment.finalLayout = depthStencilLayout; depthStencilReference.attachment = colorAttachmentsCount; depthStencilReference.layout = depthStencilLayout; subpassDesc.pDepthStencilAttachment = &depthStencilReference; @@ -572,12 +580,15 @@ RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLa createInfo.pAttachments = attachments; createInfo.subpassCount = 1; createInfo.pSubpasses = &subpassDesc; - VALIDATE_VULKAN_RESULT(vkCreateRenderPass(device->Device, &createInfo, nullptr, &_handle)); + VALIDATE_VULKAN_RESULT(vkCreateRenderPass(device->Device, &createInfo, nullptr, &Handle)); +#if VULKAN_USE_DEBUG_DATA + DebugCreateInfo = createInfo; +#endif } RenderPassVulkan::~RenderPassVulkan() { - _device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::RenderPass, _handle); + Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::RenderPass, Handle); } QueryPoolVulkan::QueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, VkQueryType type) @@ -987,11 +998,10 @@ void StagingManagerVulkan::ProcessPendingFree() } // Free staging buffers that has not been used for a few frames - const uint64 SafeFramesCount = 30; for (int32 i = _freeBuffers.Count() - 1; i >= 0; i--) { - auto& e = _freeBuffers[i]; - if (e.FrameNumber + SafeFramesCount < Engine::FrameCount) + auto& e = _freeBuffers.Get()[i]; + if (e.FrameNumber + VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT < Engine::FrameCount) { auto buffer = e.Buffer; @@ -1016,7 +1026,7 @@ void StagingManagerVulkan::Dispose() { ScopeLock lock(_locker); -#if !BUILD_RELEASE +#if BUILD_DEBUG LOG(Info, "Vulkan staging buffers peek memory usage: {0}, allocs: {1}, frees: {2}", Utilities::BytesToText(_allBuffersPeekSize), Utilities::BytesToText(_allBuffersAllocSize), Utilities::BytesToText(_allBuffersFreeSize)); #endif @@ -1067,7 +1077,7 @@ GPUDevice* GPUDeviceVulkan::Create() #endif // Engine registration - const StringAsANSI<256> appName(*Globals::ProductName); + const StringAsANSI<> appName(*Globals::ProductName); VkApplicationInfo appInfo; RenderToolsVulkan::ZeroStruct(appInfo, VK_STRUCTURE_TYPE_APPLICATION_INFO); appInfo.pApplicationName = appName.Get(); @@ -1082,24 +1092,13 @@ GPUDevice* GPUDeviceVulkan::Create() instInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif instInfo.pApplicationInfo = &appInfo; - GetInstanceLayersAndExtensions(InstanceExtensions, InstanceLayers, SupportsDebugUtilsExt); - - const auto hasExtension = [](const Array& extensions, const char* name) -> bool - { - const Function callback = [&name](const char* const& extension) -> bool - { - return extension && StringUtils::Compare(extension, name) == 0; - }; - return ArrayExtensions::Any(extensions, callback); - }; - instInfo.enabledExtensionCount = InstanceExtensions.Count(); instInfo.ppEnabledExtensionNames = instInfo.enabledExtensionCount > 0 ? static_cast(InstanceExtensions.Get()) : nullptr; instInfo.enabledLayerCount = InstanceLayers.Count(); instInfo.ppEnabledLayerNames = instInfo.enabledLayerCount > 0 ? InstanceLayers.Get() : nullptr; #if VULKAN_USE_DEBUG_LAYER - SupportsDebugCallbackExt = !SupportsDebugUtilsExt && hasExtension(InstanceExtensions, VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + SupportsDebugCallbackExt = !SupportsDebugUtilsExt && RenderToolsVulkan::HasExtension(InstanceExtensions, VK_EXT_DEBUG_REPORT_EXTENSION_NAME); #endif // Create Vulkan instance @@ -1647,9 +1646,7 @@ bool GPUDeviceVulkan::Init() queue.pQueuePriorities = currentPriority; const VkQueueFamilyProperties& properties = QueueFamilyProps[queue.queueFamilyIndex]; for (int32 queueIndex = 0; queueIndex < (int32)properties.queueCount; queueIndex++) - { *currentPriority++ = 1.0f; - } } deviceInfo.queueCreateInfoCount = queueFamilyInfos.Count(); deviceInfo.pQueueCreateInfos = queueFamilyInfos.Get(); @@ -1836,12 +1833,17 @@ bool GPUDeviceVulkan::Init() INIT_FUNC(vkDestroyImage); INIT_FUNC(vkCmdCopyBuffer); #if VMA_DEDICATED_ALLOCATION +#if PLATFORM_SWITCH + vulkanFunctions.vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2; + vulkanFunctions.vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2; +#else INIT_FUNC(vkGetBufferMemoryRequirements2KHR); INIT_FUNC(vkGetImageMemoryRequirements2KHR); #endif +#endif #undef INIT_FUNC VmaAllocatorCreateInfo allocatorInfo = {}; - allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_0; + allocatorInfo.vulkanApiVersion = VULKAN_API_VERSION; allocatorInfo.physicalDevice = gpu; allocatorInfo.instance = Instance; allocatorInfo.device = Device; @@ -2033,6 +2035,7 @@ void GPUDeviceVulkan::WaitForGPU() { if (Device != VK_NULL_HANDLE) { + PROFILE_CPU(); VALIDATE_VULKAN_RESULT(vkDeviceWaitIdle(Device)); } } @@ -2080,7 +2083,6 @@ GPUConstantBuffer* GPUDeviceVulkan::CreateConstantBuffer(uint32 size, const Stri SemaphoreVulkan::SemaphoreVulkan(GPUDeviceVulkan* device) : _device(device) { - // Create semaphore VkSemaphoreCreateInfo info; RenderToolsVulkan::ZeroStruct(info, VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO); VALIDATE_VULKAN_RESULT(vkCreateSemaphore(device->Device, &info, nullptr, &_semaphoreHandle)); @@ -2093,21 +2095,6 @@ SemaphoreVulkan::~SemaphoreVulkan() _semaphoreHandle = VK_NULL_HANDLE; } -FenceVulkan::~FenceVulkan() -{ - ASSERT(_handle == VK_NULL_HANDLE); -} - -FenceVulkan::FenceVulkan(GPUDeviceVulkan* device, FenceManagerVulkan* owner, bool createSignaled) - : _signaled(createSignaled) - , _owner(owner) -{ - VkFenceCreateInfo info; - RenderToolsVulkan::ZeroStruct(info, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO); - info.flags = createSignaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; - VALIDATE_VULKAN_RESULT(vkCreateFence(device->Device, &info, nullptr, &_handle)); -} - FenceManagerVulkan::~FenceManagerVulkan() { ASSERT(_usedFences.IsEmpty()); @@ -2116,68 +2103,63 @@ FenceManagerVulkan::~FenceManagerVulkan() void FenceManagerVulkan::Dispose() { ScopeLock lock(_device->_fenceLock); - ASSERT(_usedFences.IsEmpty()); for (FenceVulkan* fence : _freeFences) - { DestroyFence(fence); - } _freeFences.Clear(); } FenceVulkan* FenceManagerVulkan::AllocateFence(bool createSignaled) { ScopeLock lock(_device->_fenceLock); - FenceVulkan* fence; if (_freeFences.HasItems()) { fence = _freeFences.Last(); _freeFences.RemoveLast(); _usedFences.Add(fence); - if (createSignaled) - { - fence->_signaled = true; - } - - return fence; + fence->IsSignaled = true; + } + else + { + fence = New(); + fence->IsSignaled = createSignaled; + VkFenceCreateInfo info; + RenderToolsVulkan::ZeroStruct(info, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO); + info.flags = createSignaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0; + VALIDATE_VULKAN_RESULT(vkCreateFence(_device->Device, &info, nullptr, &fence->Handle)); + _usedFences.Add(fence); } - - fence = New(_device, this, createSignaled); - _usedFences.Add(fence); return fence; } -bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds) +bool FenceManagerVulkan::WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds) const { ASSERT(_usedFences.Contains(fence)); - ASSERT(!fence->_signaled); - - const VkResult result = vkWaitForFences(_device->Device, 1, &fence->_handle, true, timeInNanoseconds); + ASSERT(!fence->IsSignaled); + const VkResult result = vkWaitForFences(_device->Device, 1, &fence->Handle, true, timeInNanoseconds); LOG_VULKAN_RESULT(result); if (result == VK_SUCCESS) { - fence->_signaled = true; + fence->IsSignaled = true; return false; } - return true; } -void FenceManagerVulkan::ResetFence(FenceVulkan* fence) +void FenceManagerVulkan::ResetFence(FenceVulkan* fence) const { - if (fence->_signaled) + if (fence->IsSignaled) { - VALIDATE_VULKAN_RESULT(vkResetFences(_device->Device, 1, &fence->_handle)); - fence->_signaled = false; + VALIDATE_VULKAN_RESULT(vkResetFences(_device->Device, 1, &fence->Handle)); + fence->IsSignaled = false; } } void FenceManagerVulkan::ReleaseFence(FenceVulkan*& fence) { ScopeLock lock(_device->_fenceLock); - ResetFence(fence); _usedFences.Remove(fence); _freeFences.Add(fence); @@ -2187,37 +2169,31 @@ void FenceManagerVulkan::ReleaseFence(FenceVulkan*& fence) void FenceManagerVulkan::WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds) { ScopeLock lock(_device->_fenceLock); - - if (!fence->IsSignaled()) - { + if (!fence->IsSignaled) WaitForFence(fence, timeInNanoseconds); - } - ResetFence(fence); _usedFences.Remove(fence); _freeFences.Add(fence); fence = nullptr; } -bool FenceManagerVulkan::CheckFenceState(FenceVulkan* fence) +bool FenceManagerVulkan::CheckFenceState(FenceVulkan* fence) const { ASSERT(_usedFences.Contains(fence)); - ASSERT(!fence->_signaled); - - const VkResult result = vkGetFenceStatus(_device->Device, fence->GetHandle()); + ASSERT(!fence->IsSignaled); + const VkResult result = vkGetFenceStatus(_device->Device, fence->Handle); if (result == VK_SUCCESS) { - fence->_signaled = true; + fence->IsSignaled = true; return true; } - return false; } -void FenceManagerVulkan::DestroyFence(FenceVulkan* fence) +void FenceManagerVulkan::DestroyFence(FenceVulkan* fence) const { - vkDestroyFence(_device->Device, fence->GetHandle(), nullptr); - fence->_handle = VK_NULL_HANDLE; + vkDestroyFence(_device->Device, fence->Handle, nullptr); + fence->Handle = VK_NULL_HANDLE; Delete(fence); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h index 405209c70..a77744766 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h @@ -18,7 +18,6 @@ class GPUAdapterVulkan; class GPUSwapChainVulkan; class CmdBufferVulkan; class QueueVulkan; -class FenceVulkan; class GPUTextureVulkan; class GPUBufferVulkan; class GPUTimerQueryVulkan; @@ -31,12 +30,10 @@ class DescriptorPoolsManagerVulkan; class SemaphoreVulkan { private: - GPUDeviceVulkan* _device; VkSemaphore _semaphoreHandle; public: - /// /// Initializes a new instance of the class. /// @@ -58,63 +55,23 @@ public: } }; -class FenceVulkan +struct FenceVulkan { - friend FenceManagerVulkan; - -private: - - VkFence _handle; - bool _signaled; - FenceManagerVulkan* _owner; - -public: - - FenceVulkan(GPUDeviceVulkan* device, FenceManagerVulkan* owner, bool createSignaled); - - ~FenceVulkan(); - -public: - - inline VkFence GetHandle() const - { - return _handle; - } - - inline bool IsSignaled() const - { - return _signaled; - } - - FenceManagerVulkan* GetOwner() const - { - return _owner; - } + VkFence Handle; + bool IsSignaled; }; class FenceManagerVulkan { private: - - GPUDeviceVulkan* _device; + GPUDeviceVulkan* _device = nullptr; Array _freeFences; Array _usedFences; public: - - FenceManagerVulkan() - : _device(nullptr) - { - } - ~FenceManagerVulkan(); public: - - /// - /// Initializes the specified device. - /// - /// The graphics device. void Init(GPUDeviceVulkan* device) { _device = device; @@ -124,20 +81,15 @@ public: FenceVulkan* AllocateFence(bool createSignaled = false); - inline bool IsFenceSignaled(FenceVulkan* fence) + FORCE_INLINE bool IsFenceSignaled(FenceVulkan* fence) const { - if (fence->IsSignaled()) - { - return true; - } - - return CheckFenceState(fence); + return fence->IsSignaled || CheckFenceState(fence); } // Returns true if waiting timed out or failed, false otherwise. - bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds); + bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds) const; - void ResetFence(FenceVulkan* fence); + void ResetFence(FenceVulkan* fence) const; // Sets the fence handle to null void ReleaseFence(FenceVulkan*& fence); @@ -146,17 +98,15 @@ public: void WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds); private: - // Returns true if fence was signaled, otherwise false. - bool CheckFenceState(FenceVulkan* fence); + bool CheckFenceState(FenceVulkan* fence) const; - void DestroyFence(FenceVulkan* fence); + void DestroyFence(FenceVulkan* fence) const; }; class DeferredDeletionQueueVulkan { public: - enum Type { RenderPass, @@ -176,7 +126,6 @@ public: }; private: - struct Entry { uint64 FenceCounter; @@ -192,7 +141,6 @@ private: Array _entries; public: - /// /// Initializes a new instance of the class. /// @@ -205,7 +153,6 @@ public: ~DeferredDeletionQueueVulkan(); public: - template inline void EnqueueResource(Type type, T handle) { @@ -220,30 +167,34 @@ public: EnqueueGenericResource(type, (uint64)handle, allocation); } - auto ReleaseResources(bool deleteImmediately = false) -> void; + void ReleaseResources(bool immediately = false); private: - void EnqueueGenericResource(Type type, uint64 handle, VmaAllocation allocation); }; -class RenderTargetLayoutVulkan +struct RenderTargetLayoutVulkan { -public: - - int32 RTsCount; + union + { + struct + { + uint32 Layers : 10; // Limited by GPU_MAX_TEXTURE_ARRAY_SIZE + uint32 RTsCount : 3; // Limited by GPU_MAX_RT_BINDED + uint32 ReadDepth : 1; + uint32 WriteDepth : 1; + uint32 ReadStencil : 1; + uint32 WriteStencil : 1; + uint32 BlendEnable : 1; + }; + uint32 Flags; + }; MSAALevel MSAA; - bool ReadDepth; - bool WriteDepth; - bool BlendEnable; PixelFormat DepthFormat; PixelFormat RTVsFormats[GPU_MAX_RT_BINDED]; VkExtent2D Extent; - uint32 Layers; -public: - - bool operator==(const RenderTargetLayoutVulkan& other) const + FORCE_INLINE bool operator==(const RenderTargetLayoutVulkan& other) const { return Platform::MemoryCompare(this, &other, sizeof(RenderTargetLayoutVulkan)) == 0; } @@ -254,44 +205,27 @@ uint32 GetHash(const RenderTargetLayoutVulkan& key); class FramebufferVulkan { public: - struct Key { RenderPassVulkan* RenderPass; int32 AttachmentCount; VkImageView Attachments[GPU_MAX_RT_BINDED + 1]; - public: - - bool operator==(const Key& other) const + FORCE_INLINE bool operator==(const Key& other) const { return Platform::MemoryCompare(this, &other, sizeof(Key)) == 0; } }; -private: - - GPUDeviceVulkan* _device; - VkFramebuffer _handle; - -public: - - FramebufferVulkan(GPUDeviceVulkan* device, Key& key, VkExtent2D& extent, uint32 layers); + FramebufferVulkan(GPUDeviceVulkan* device, const Key& key, const VkExtent2D& extent, uint32 layers); ~FramebufferVulkan(); -public: - + GPUDeviceVulkan* Device; + VkFramebuffer Handle; VkImageView Attachments[GPU_MAX_RT_BINDED + 1]; VkExtent2D Extent; uint32 Layers; -public: - - inline VkFramebuffer GetHandle() - { - return _handle; - } - bool HasReference(VkImageView imageView) const; }; @@ -299,32 +233,21 @@ uint32 GetHash(const FramebufferVulkan::Key& key); class RenderPassVulkan { -private: - - GPUDeviceVulkan* _device; - VkRenderPass _handle; - public: - + GPUDeviceVulkan* Device; + VkRenderPass Handle; RenderTargetLayoutVulkan Layout; - -public: +#if VULKAN_USE_DEBUG_DATA + VkRenderPassCreateInfo DebugCreateInfo; +#endif RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLayoutVulkan& layout); ~RenderPassVulkan(); - -public: - - inline VkRenderPass GetHandle() const - { - return _handle; - } }; class QueryPoolVulkan { protected: - struct Range { uint32 Start; @@ -342,12 +265,10 @@ protected: #endif public: - QueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, VkQueryType type); ~QueryPoolVulkan(); public: - inline VkQueryPool GetHandle() const { return _handle; @@ -361,7 +282,6 @@ public: class BufferedQueryPoolVulkan : public QueryPoolVulkan { private: - Array _queryOutput; Array _usedQueryBits; Array _startedQueryBits; @@ -371,7 +291,6 @@ private: int32 _lastBeginIndex; public: - BufferedQueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, VkQueryType type); bool AcquireQuery(uint32& resultIndex); void ReleaseQuery(uint32 queryIndex); @@ -386,7 +305,6 @@ public: class HelperResourcesVulkan { private: - GPUDeviceVulkan* _device; GPUTextureVulkan* _dummyTextures[6]; GPUBufferVulkan* _dummyBuffer; @@ -394,11 +312,9 @@ private: VkSampler _staticSamplers[GPU_STATIC_SAMPLERS_COUNT]; public: - HelperResourcesVulkan(GPUDeviceVulkan* device); public: - VkSampler* GetStaticSamplers(); GPUTextureVulkan* GetDummyTexture(SpirvShaderResourceType type); GPUBufferVulkan* GetDummyBuffer(); @@ -412,7 +328,6 @@ public: class StagingManagerVulkan { private: - struct PendingEntry { GPUBuffer* Buffer; @@ -439,7 +354,6 @@ private: #endif public: - StagingManagerVulkan(GPUDeviceVulkan* device); GPUBuffer* AcquireBuffer(uint32 size, GPUResourceUsage usage); void ReleaseBuffer(CmdBufferVulkan* cmdBuffer, GPUBuffer*& buffer); @@ -457,7 +371,6 @@ class GPUDeviceVulkan : public GPUDevice friend FenceManagerVulkan; private: - CriticalSection _fenceLock; mutable void* _nativePtr[2]; @@ -467,7 +380,6 @@ private: // TODO: use mutex to protect those collections BUT use 2 pools per cache: one lock-free with lookup only and second protected with mutex synced on frame end! public: - static GPUDevice* Create(); /// @@ -483,14 +395,11 @@ public: ~GPUDeviceVulkan(); public: - struct OptionalVulkanDeviceExtensions { uint32 HasKHRMaintenance1 : 1; uint32 HasKHRMaintenance2 : 1; uint32 HasMirrorClampToEdge : 1; - uint32 HasKHRExternalMemoryCapabilities : 1; - uint32 HasKHRGetPhysicalDeviceProperties2 : 1; uint32 HasEXTValidationCache : 1; }; @@ -501,7 +410,6 @@ public: static OptionalVulkanDeviceExtensions OptionalDeviceExtensions; public: - /// /// The Vulkan instance. /// @@ -518,7 +426,6 @@ public: static Array InstanceLayers; public: - /// /// The main Vulkan commands context. /// @@ -629,11 +536,9 @@ public: // Try to use pool with available space inside for (int32 i = 0; i < pools.Count(); i++) { - auto pool = pools[i]; + auto pool = pools.Get()[i]; if (pool->HasRoom()) - { return pool; - } } // Create new pool @@ -658,7 +563,6 @@ public: void OnImageViewDestroy(VkImageView imageView); public: - /// /// Setups the present queue to be ready for the given window surface. /// @@ -673,7 +577,7 @@ public: /// If set to true the optimal tiling should be used, otherwise use linear tiling. /// The output format. PixelFormat GetClosestSupportedPixelFormat(PixelFormat format, GPUTextureFlags flags, bool optimalTiling); - + /// /// Saves the pipeline cache. /// @@ -689,11 +593,9 @@ public: #endif private: - bool IsVkFormatSupported(VkFormat vkFormat, VkFormatFeatureFlags wantedFeatureFlags, bool optimalTiling) const; public: - // [GPUDevice] GPUContext* GetMainContext() override; GPUAdapter* GetAdapter() const override; @@ -719,7 +621,6 @@ template class GPUResourceVulkan : public GPUResourceBase { public: - /// /// Initializes a new instance of the class. /// @@ -737,7 +638,6 @@ public: class DescriptorOwnerResourceVulkan { public: - /// /// Finalizes an instance of the class. /// @@ -746,7 +646,6 @@ public: } public: - /// /// Gets the sampler descriptor. /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp index b719cd4de..5fd40fdbb 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.cpp @@ -54,7 +54,7 @@ ComputePipelineStateVulkan* GPUShaderProgramCSVulkan::GetOrCreateState() VkComputePipelineCreateInfo desc; RenderToolsVulkan::ZeroStruct(desc, VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO); desc.basePipelineIndex = -1; - desc.layout = layout->GetHandle(); + desc.layout = layout->Handle; RenderToolsVulkan::ZeroStruct(desc.stage, VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO); auto& stage = desc.stage; RenderToolsVulkan::ZeroStruct(stage, VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO); @@ -72,8 +72,8 @@ ComputePipelineStateVulkan* GPUShaderProgramCSVulkan::GetOrCreateState() // Setup the state _pipelineState = New(_device, pipeline, layout); _pipelineState->DescriptorInfo = &DescriptorInfo; - _pipelineState->DescriptorSetsLayout = &layout->GetDescriptorSetLayout(); - _pipelineState->DescriptorSetHandles.AddZeroed(_pipelineState->DescriptorSetsLayout->GetHandles().Count()); + _pipelineState->DescriptorSetsLayout = &layout->DescriptorSetLayout; + _pipelineState->DescriptorSetHandles.AddZeroed(_pipelineState->DescriptorSetsLayout->Handles.Count()); uint32 dynamicOffsetsCount = 0; if (DescriptorInfo.DescriptorTypesCount != 0) { @@ -136,9 +136,7 @@ PipelineLayoutVulkan* GPUPipelineStateVulkan::GetLayout() #define INIT_SHADER_STAGE(set, bit) \ if (DescriptorInfoPerStage[DescriptorSet::set]) \ - { \ - descriptorSetLayoutInfo.AddBindingsForStage(bit, DescriptorSet::set, DescriptorInfoPerStage[DescriptorSet::set]); \ - } + descriptorSetLayoutInfo.AddBindingsForStage(bit, DescriptorSet::set, DescriptorInfoPerStage[DescriptorSet::set]) INIT_SHADER_STAGE(Vertex, VK_SHADER_STAGE_VERTEX_BIT); INIT_SHADER_STAGE(Hull, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT); INIT_SHADER_STAGE(Domain, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT); @@ -148,8 +146,8 @@ PipelineLayoutVulkan* GPUPipelineStateVulkan::GetLayout() _layout = _device->GetOrCreateLayout(descriptorSetLayoutInfo); ASSERT(_layout); - DescriptorSetsLayout = &_layout->GetDescriptorSetLayout(); - DescriptorSetHandles.AddZeroed(DescriptorSetsLayout->GetHandles().Count()); + DescriptorSetsLayout = &_layout->DescriptorSetLayout; + DescriptorSetHandles.AddZeroed(DescriptorSetsLayout->Handles.Count()); return _layout; } @@ -176,12 +174,12 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass) // Update description to match the pipeline _descColorBlend.attachmentCount = renderPass->Layout.RTsCount; _descMultisample.rasterizationSamples = (VkSampleCountFlagBits)renderPass->Layout.MSAA; - _desc.renderPass = renderPass->GetHandle(); + _desc.renderPass = renderPass->Handle; // Check if has missing layout if (_desc.layout == VK_NULL_HANDLE) { - _desc.layout = GetLayout()->GetHandle(); + _desc.layout = GetLayout()->Handle; } // Create object @@ -323,6 +321,10 @@ bool GPUPipelineStateVulkan::Init(const Description& desc) _descDepthStencil.front.passOp = ToVulkanStencilOp(desc.StencilPassOp); _descDepthStencil.front = _descDepthStencil.back; _desc.pDepthStencilState = &_descDepthStencil; + DepthReadEnable = desc.DepthEnable && desc.DepthFunc != ComparisonFunc::Always; + DepthWriteEnable = _descDepthStencil.depthWriteEnable; + StencilReadEnable = desc.StencilEnable && desc.StencilReadMask != 0 && desc.StencilFunc != ComparisonFunc::Always; + StencilWriteEnable = desc.StencilEnable && desc.StencilWriteMask != 0; // Rasterization RenderToolsVulkan::ZeroStruct(_descRasterization, VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h index fe844b7f6..e3386064b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUPipelineStateVulkan.h @@ -14,28 +14,15 @@ class PipelineLayoutVulkan; class ComputePipelineStateVulkan { private: - GPUDeviceVulkan* _device; VkPipeline _handle; PipelineLayoutVulkan* _layout; public: - - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. - /// The pipeline object. - /// The pipeline layout. ComputePipelineStateVulkan(GPUDeviceVulkan* device, VkPipeline pipeline, PipelineLayoutVulkan* layout); - - /// - /// Finalizes an instance of the class. - /// ~ComputePipelineStateVulkan(); public: - /// /// The cached shader descriptor infos for compute shader. /// @@ -71,13 +58,12 @@ public: Array DynamicOffsets; public: - void Bind(CmdBufferVulkan* cmdBuffer) { vkCmdBindDescriptorSets( cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_COMPUTE, - GetLayout()->GetHandle(), + GetLayout()->Handle, 0, DescriptorSetHandles.Count(), DescriptorSetHandles.Get(), @@ -86,7 +72,6 @@ public: } public: - VkPipeline GetHandle() const { return _handle; @@ -104,7 +89,6 @@ public: class GPUPipelineStateVulkan : public GPUResourceVulkan { private: - Dictionary _pipelines; VkGraphicsPipelineCreateInfo _desc; VkPipelineShaderStageCreateInfo _shaderStages[ShaderStage_Count - 1]; @@ -121,7 +105,6 @@ private: PipelineLayoutVulkan* _layout; public: - /// /// Initializes a new instance of the class. /// @@ -129,13 +112,16 @@ public: GPUPipelineStateVulkan(GPUDeviceVulkan* device); public: - /// /// The bitmask of stages that exist in this pipeline. /// uint32 UsedStagesMask; - bool BlendEnable; + uint32 BlendEnable : 1; + uint32 DepthReadEnable : 1; + uint32 DepthWriteEnable : 1; + uint32 StencilReadEnable : 1; + uint32 StencilWriteEnable : 1; /// /// The bitmask of stages that have descriptors. @@ -164,43 +150,25 @@ public: TypedDescriptorPoolSetVulkan* CurrentTypedDescriptorPoolSet = nullptr; Array DescriptorSetHandles; + Array DynamicOffsets; + +public: inline bool AcquirePoolSet(CmdBufferVulkan* cmdBuffer) { + // Lazy init + if (!DescriptorSetsLayout) + GetLayout(); + // Pipeline state has no current descriptor pools set or set owner is not current - acquire a new pool set DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet(); if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet) { - ASSERT(cmdBufferPoolSet); CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout); return true; } - return false; } - inline bool AllocateDescriptorSets() - { - ASSERT(CurrentTypedDescriptorPoolSet); - return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get()); - } - - Array DynamicOffsets; - -public: - - void Bind(CmdBufferVulkan* cmdBuffer) - { - vkCmdBindDescriptorSets( - cmdBuffer->GetHandle(), - VK_PIPELINE_BIND_POINT_GRAPHICS, - GetLayout()->GetHandle(), - 0, - DescriptorSetHandles.Count(), - DescriptorSetHandles.Get(), - DynamicOffsets.Count(), - DynamicOffsets.Get()); - } - /// /// Gets the Vulkan pipeline layout for this pipeline state. /// @@ -215,13 +183,11 @@ public: VkPipeline GetState(RenderPassVulkan* renderPass); public: - // [GPUPipelineState] bool IsValid() const final override; bool Init(const Description& desc) final override; protected: - // [GPUResourceVulkan] void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSamplerVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUSamplerVulkan.h index 36ee9cf3e..2ef48d784 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSamplerVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSamplerVulkan.h @@ -13,7 +13,6 @@ class GPUSamplerVulkan : public GPUResourceVulkan { public: - GPUSamplerVulkan(GPUDeviceVulkan* device) : GPUResourceVulkan(device, StringView::Empty) { @@ -22,7 +21,6 @@ public: VkSampler Sampler = VK_NULL_HANDLE; protected: - // [GPUSamplerVulkan] bool OnInit() override; void OnReleaseGPU() override; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h index 8e87b324e..9c8649604 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderProgramVulkan.h @@ -18,11 +18,9 @@ template class GPUShaderProgramVulkan : public BaseType { protected: - GPUDeviceVulkan* _device; public: - /// /// Initializes a new instance of the class. /// @@ -50,7 +48,6 @@ public: } public: - /// /// The Vulkan shader module. /// @@ -62,7 +59,6 @@ public: SpirvShaderDescriptorInfo DescriptorInfo; public: - // [BaseType] uint32 GetBufferSize() const override { @@ -81,7 +77,6 @@ public: class GPUShaderProgramVSVulkan : public GPUShaderProgramVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -95,13 +90,11 @@ public: } public: - VkPipelineVertexInputStateCreateInfo VertexInputState; VkVertexInputBindingDescription VertexBindingDescriptions[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; VkVertexInputAttributeDescription VertexAttributeDescriptions[VERTEX_SHADER_MAX_INPUT_ELEMENTS]; public: - // [GPUShaderProgramVulkan] void* GetInputLayout() const override { @@ -120,7 +113,6 @@ public: class GPUShaderProgramHSVulkan : public GPUShaderProgramVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -142,7 +134,6 @@ public: class GPUShaderProgramDSVulkan : public GPUShaderProgramVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -162,7 +153,6 @@ public: class GPUShaderProgramGSVulkan : public GPUShaderProgramVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -182,7 +172,6 @@ public: class GPUShaderProgramPSVulkan : public GPUShaderProgramVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -202,11 +191,9 @@ public: class GPUShaderProgramCSVulkan : public GPUShaderProgramVulkan { private: - ComputePipelineStateVulkan* _pipelineState; public: - /// /// Initializes a new instance of the class. /// @@ -226,7 +213,6 @@ public: ~GPUShaderProgramCSVulkan(); public: - /// /// Gets the state of the pipeline for the compute shader execution or creates a new one if missing. /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h index b59c13bf7..ee70e8125 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUShaderVulkan.h @@ -17,7 +17,6 @@ class UniformBufferUploaderVulkan : public GPUResourceVulkan, public ResourceOwnerVulkan { public: - struct Allocation { /// @@ -42,7 +41,6 @@ public: }; private: - VkBuffer _buffer; VmaAllocation _allocation; uint64 _size; @@ -53,7 +51,6 @@ private: uint64 _fenceCounter; public: - /// /// Initializes a new instance of the class. /// @@ -61,11 +58,9 @@ public: UniformBufferUploaderVulkan(GPUDeviceVulkan* device); public: - Allocation Allocate(uint64 size, uint32 alignment, GPUContextVulkan* context); public: - // [GPUResourceVulkan] GPUResourceType GetResourceType() const final override { @@ -79,7 +74,6 @@ public: } protected: - // [GPUResourceVulkan] void OnReleaseGPU() override; }; @@ -90,7 +84,6 @@ protected: class GPUConstantBufferVulkan : public GPUResourceVulkan, public DescriptorOwnerResourceVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -103,14 +96,12 @@ public: } public: - /// /// The last uploaded data inside the shared uniforms uploading ring buffer. /// UniformBufferUploaderVulkan::Allocation Allocation; public: - // [DescriptorOwnerResourceVulkan] void DescriptorAsDynamicUniformBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range, uint32& dynamicOffset) override { @@ -127,7 +118,6 @@ public: class GPUShaderVulkan : public GPUResourceVulkan { public: - /// /// Initializes a new instance of the class. /// @@ -139,7 +129,6 @@ public: } protected: - // [GPUShader] GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, byte* cacheBytes, uint32 cacheSize, MemoryReadStream& stream) override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp index 8f85f4dbe..2379f9b33 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp @@ -11,6 +11,7 @@ #include "Engine/Core/Log.h" #include "Engine/Graphics/GPULimits.h" #include "Engine/Scripting/Enums.h" +#include "Engine/Profiler/ProfilerCPU.h" void BackBufferVulkan::Setup(GPUSwapChainVulkan* window, VkImage backbuffer, PixelFormat format, VkExtent3D extent) { @@ -103,6 +104,7 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() { if (_acquiredImageIndex == -1) { + PROFILE_CPU(); if (TryPresent(DoAcquireImageIndex) < 0) { LOG(Fatal, "Swapchain acquire image index failed!"); @@ -125,7 +127,6 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView() cmdBufferManager->PrepareForNewActiveCommandBuffer(); ASSERT(cmdBufferManager->HasPendingActiveCmdBuffer() && cmdBufferManager->GetActiveCmdBuffer()->GetState() == CmdBufferVulkan::State::IsInsideBegin); } - return &_backBuffers[_acquiredImageIndex].Handle; } @@ -174,7 +175,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) auto windowHandle = _window->GetNativePtr(); if (!windowHandle) return false; - + PROFILE_CPU(); GPUDeviceLock lock(_device); const auto device = _device->Device; @@ -208,8 +209,6 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) { uint32 surfaceFormatsCount; VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, _surface, &surfaceFormatsCount, nullptr)); - ASSERT(surfaceFormatsCount > 0); - Array> surfaceFormats; surfaceFormats.AddZeroed(surfaceFormatsCount); VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, _surface, &surfaceFormatsCount, surfaceFormats.Get())); @@ -229,7 +228,6 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) break; } } - if (!found) { LOG(Warning, "Requested pixel format {0} not supported by this swapchain. Falling back to supported swapchain formats...", ScriptingEnum::ToString(resultFormat)); @@ -255,22 +253,18 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) { resultFormat = static_cast(pixelFormat); result = surfaceFormats[i]; - LOG(Info, "No swapchain format requested, picking up Vulkan format {0}", (uint32)result.format); + LOG(Info, "No swapchain format requested, picking up format {} (vk={})", ScriptingEnum::ToString(resultFormat), (int32)result.format); break; } } - if (resultFormat != PixelFormat::Unknown) - { break; - } } } if (resultFormat == PixelFormat::Unknown) { LOG(Warning, "Can't find a proper pixel format for the swapchain, trying to pick up the first available"); - const VkFormat format = RenderToolsVulkan::ToVulkanFormat(resultFormat); bool supported = false; for (int32 i = 0; i < surfaceFormats.Count(); i++) @@ -283,24 +277,14 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) } } ASSERT(supported); - String msg; for (int32 index = 0; index < surfaceFormats.Count(); index++) { - if (index == 0) - { - msg += TEXT("("); - } - else - { - msg += TEXT(", "); - } + msg += index == 0 ? TEXT("(") : TEXT(", "); msg += StringUtils::ToString((int32)surfaceFormats[index].format); } if (surfaceFormats.HasItems()) - { msg += TEXT(")"); - } LOG(Error, "Unable to find a pixel format for the swapchain; swapchain returned {0} Vulkan formats {1}", surfaceFormats.Count(), *msg); } } @@ -315,16 +299,12 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) { uint32 presentModesCount = 0; VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, _surface, &presentModesCount, nullptr)); - ASSERT(presentModesCount > 0); - Array> presentModes; presentModes.Resize(presentModesCount); VALIDATE_VULKAN_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, _surface, &presentModesCount, presentModes.Get())); - bool foundPresentModeMailbox = false; bool foundPresentModeImmediate = false; bool foundPresentModeFifo = false; - for (size_t i = 0; i < presentModesCount; i++) { switch (presentModes[(int32)i]) @@ -340,7 +320,6 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) break; } } - if (foundPresentModeMailbox) { presentMode = VK_PRESENT_MODE_MAILBOX_KHR; @@ -367,13 +346,18 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) height = Math::Clamp(height, surfProperties.minImageExtent.height, surfProperties.maxImageExtent.height); if (width <= 0 || height <= 0) { - LOG(Error, "Vulkan SwapChain dimensions are invalid {}x{} (minImageExtent={}x{}, maxImageExtent={}x{})", width, height, surfProperties.minImageExtent.width, surfProperties.minImageExtent.height, surfProperties.maxImageExtent.width, surfProperties.maxImageExtent.height); + LOG(Error, "Vulkan swapchain dimensions are invalid {}x{} (minImageExtent={}x{}, maxImageExtent={}x{})", width, height, surfProperties.minImageExtent.width, surfProperties.minImageExtent.height, surfProperties.maxImageExtent.width, surfProperties.maxImageExtent.height); return true; } + ASSERT(surfProperties.minImageCount <= VULKAN_BACK_BUFFERS_COUNT_MAX); VkSwapchainCreateInfoKHR swapChainInfo; RenderToolsVulkan::ZeroStruct(swapChainInfo, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); swapChainInfo.surface = _surface; - swapChainInfo.minImageCount = Math::Clamp(VULKAN_BACK_BUFFERS_COUNT, surfProperties.minImageCount, Math::Min(surfProperties.maxImageCount, VULKAN_BACK_BUFFERS_COUNT_MAX)); + swapChainInfo.minImageCount = surfProperties.maxImageCount > 0 // A value of 0 means that there is no limit on the number of image + ? Math::Min(VULKAN_BACK_BUFFERS_COUNT, surfProperties.maxImageCount) + : VULKAN_BACK_BUFFERS_COUNT; + swapChainInfo.minImageCount = Math::Max(swapChainInfo.minImageCount, surfProperties.minImageCount); + swapChainInfo.minImageCount = Math::Min(swapChainInfo.minImageCount, VULKAN_BACK_BUFFERS_COUNT_MAX); swapChainInfo.imageFormat = result.format; swapChainInfo.imageColorSpace = result.colorSpace; swapChainInfo.imageExtent.width = width; @@ -407,11 +391,13 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) { uint32 imagesCount; VALIDATE_VULKAN_RESULT(vkGetSwapchainImagesKHR(device, _swapChain, &imagesCount, nullptr)); - ASSERT(imagesCount >= VULKAN_BACK_BUFFERS_COUNT && imagesCount <= VULKAN_BACK_BUFFERS_COUNT_MAX); - - Array> images; - images.Resize(imagesCount); - VALIDATE_VULKAN_RESULT(vkGetSwapchainImagesKHR(device, _swapChain, &imagesCount, images.Get())); + if (imagesCount < 1 || imagesCount > VULKAN_BACK_BUFFERS_COUNT_MAX) + { + LOG(Warning, "Vulkan swapchain got invalid amount of backbuffers {} instead of {} (min {})", imagesCount, VULKAN_BACK_BUFFERS_COUNT, swapChainInfo.minImageCount); + return true; + } + VkImage images[VULKAN_BACK_BUFFERS_COUNT_MAX]; + VALIDATE_VULKAN_RESULT(vkGetSwapchainImagesKHR(device, _swapChain, &imagesCount, images)); _backBuffers.Resize(imagesCount); VkExtent3D extent; @@ -420,7 +406,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) extent.depth = 1; for (uint32 i = 0; i < imagesCount; i++) { - _backBuffers[i].Setup(this, images[i], _format, extent); + _backBuffers.Get()[i].Setup(this, images[i], _format, extent); } } @@ -433,9 +419,7 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height) GPUSwapChainVulkan::Status GPUSwapChainVulkan::Present(QueueVulkan* presentQueue, SemaphoreVulkan* backBufferRenderingDoneSemaphore) { if (_currentImageIndex == -1) - { return Status::Ok; - } VkPresentInfoKHR presentInfo; RenderToolsVulkan::ZeroStruct(presentInfo, VK_STRUCTURE_TYPE_PRESENT_INFO_KHR); @@ -451,7 +435,6 @@ GPUSwapChainVulkan::Status GPUSwapChainVulkan::Present(QueueVulkan* presentQueue presentInfo.pImageIndices = (uint32*)&_currentImageIndex; const VkResult presentResult = vkQueuePresentKHR(presentQueue->GetHandle(), &presentInfo); - if (presentResult == VK_ERROR_OUT_OF_DATE_KHR) { return Status::Outdated; @@ -472,7 +455,7 @@ GPUSwapChainVulkan::Status GPUSwapChainVulkan::Present(QueueVulkan* presentQueue int32 GPUSwapChainVulkan::DoAcquireImageIndex(GPUSwapChainVulkan* viewport, void* customData) { - return viewport->_acquiredImageIndex = viewport->AcquireNextImage(&viewport->_acquiredSemaphore); + return viewport->_acquiredImageIndex = viewport->AcquireNextImage(viewport->_acquiredSemaphore); } int32 GPUSwapChainVulkan::DoPresent(GPUSwapChainVulkan* viewport, void* customData) @@ -484,7 +467,6 @@ int32 GPUSwapChainVulkan::TryPresent(Function { int32 attemptsPending = 4; int32 status = job(this, customData); - while (status < 0 && attemptsPending > 0) { if (status == (int32)Status::Outdated) @@ -513,18 +495,17 @@ int32 GPUSwapChainVulkan::TryPresent(Function _device->WaitForGPU(); status = job(this, customData); - attemptsPending--; } - return status; } -int32 GPUSwapChainVulkan::AcquireNextImage(SemaphoreVulkan** outSemaphore) +int32 GPUSwapChainVulkan::AcquireNextImage(SemaphoreVulkan*& outSemaphore) { + PROFILE_CPU(); ASSERT(_swapChain && _backBuffers.HasItems()); - uint32 imageIndex = 0; + uint32 imageIndex = _currentImageIndex; const int32 prevSemaphoreIndex = _semaphoreIndex; _semaphoreIndex = (_semaphoreIndex + 1) % _backBuffers.Count(); const auto semaphore = _backBuffers[_semaphoreIndex].ImageAcquiredSemaphore; @@ -536,21 +517,17 @@ int32 GPUSwapChainVulkan::AcquireNextImage(SemaphoreVulkan** outSemaphore) semaphore->GetHandle(), VK_NULL_HANDLE, &imageIndex); - if (result == VK_ERROR_OUT_OF_DATE_KHR) { _semaphoreIndex = prevSemaphoreIndex; return (int32)Status::Outdated; } - if (result == VK_ERROR_SURFACE_LOST_KHR) { _semaphoreIndex = prevSemaphoreIndex; return (int32)Status::LostSurface; } - - *outSemaphore = semaphore; - + outSemaphore = semaphore; if (result == VK_ERROR_VALIDATION_FAILED_EXT) { LOG(Fatal, "vkAcquireNextImageKHR failed with validation error"); @@ -571,6 +548,7 @@ void GPUSwapChainVulkan::Present(bool vsync) // Skip if there was no rendering to the backbuffer if (_acquiredImageIndex == -1) return; + PROFILE_CPU(); // Ensure that backbuffer has been acquired before presenting it to the window const auto backBuffer = (GPUTextureViewVulkan*)GetBackBufferView(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h index 18850cfcf..862b17970 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.h @@ -14,7 +14,6 @@ class BackBufferVulkan : public ResourceOwnerVulkan { public: - /// /// The device. /// @@ -36,12 +35,10 @@ public: GPUTextureViewVulkan Handle; public: - void Setup(GPUSwapChainVulkan* window, VkImage backbuffer, PixelFormat format, VkExtent3D extent); void Release(); public: - // [ResourceOwnerVulkan] GPUResource* AsGPUResource() const override { @@ -58,7 +55,6 @@ class GPUSwapChainVulkan : public GPUResourceVulkan, public Resour friend GPUDeviceVulkan; private: - VkSurfaceKHR _surface; VkSwapchainKHR _swapChain; int32 _currentImageIndex; @@ -68,11 +64,9 @@ private: SemaphoreVulkan* _acquiredSemaphore; public: - GPUSwapChainVulkan(GPUDeviceVulkan* device, Window* window); public: - /// /// Gets the Vulkan surface. /// @@ -92,7 +86,6 @@ public: } public: - enum class Status { Ok = 0, @@ -105,15 +98,13 @@ public: static int32 DoAcquireImageIndex(GPUSwapChainVulkan* viewport, void* customData); static int32 DoPresent(GPUSwapChainVulkan* viewport, void* customData); int32 TryPresent(Function job, void* customData = nullptr, bool skipOnOutOfDate = false); - int32 AcquireNextImage(SemaphoreVulkan** outSemaphore); + int32 AcquireNextImage(SemaphoreVulkan*& outSemaphore); private: - void ReleaseBackBuffer(); bool CreateSwapChain(int32 width, int32 height); public: - // [GPUSwapChain] bool IsFullscreen() override; void SetFullscreen(bool isFullscreen) override; @@ -129,7 +120,6 @@ public: } protected: - // [GPUResourceVulkan] void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp index 3755bb897..cbd6d210f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.cpp @@ -24,6 +24,10 @@ void GPUTextureViewVulkan::Init(GPUDeviceVulkan* device, ResourceOwnerVulkan* ow Extent.height = Math::Max(1, extent.height >> firstMipIndex); Extent.depth = Math::Max(1, extent.depth >> firstMipIndex); Layers = arraySize; +#if VULKAN_USE_DEBUG_DATA + Format = format; + ReadOnlyDepth = readOnlyDepth; +#endif RenderToolsVulkan::ZeroStruct(Info, VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO); Info.image = image; @@ -56,12 +60,26 @@ void GPUTextureViewVulkan::Init(GPUDeviceVulkan* device, ResourceOwnerVulkan* ow if (PixelFormatExtensions::IsDepthStencil(format)) { range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; +#if 0 + // TODO: enable extension and use separateDepthStencilLayouts from Vulkan 1.2 if (PixelFormatExtensions::HasStencil(format)) { range.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + LayoutRTV = readOnlyDepth ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + LayoutSRV = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; } + else + { + LayoutRTV = readOnlyDepth ? VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + LayoutSRV = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL; + } +#else + + if (PixelFormatExtensions::HasStencil(format)) + range.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; LayoutRTV = readOnlyDepth ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - LayoutSRV = readOnlyDepth ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + LayoutSRV = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; +#endif } else { @@ -113,13 +131,18 @@ void GPUTextureViewVulkan::Release() { Device->OnImageViewDestroy(ViewFramebuffer); Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, ViewFramebuffer); + ViewFramebuffer = VK_NULL_HANDLE; + } + if (ViewSRV != View && ViewSRV != VK_NULL_HANDLE) + { + Device->OnImageViewDestroy(ViewSRV); + Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, ViewSRV); + ViewSRV = VK_NULL_HANDLE; } Device->OnImageViewDestroy(View); Device->DeferredDeletionQueue.EnqueueResource(DeferredDeletionQueueVulkan::Type::ImageView, View); - View = VK_NULL_HANDLE; - ViewFramebuffer = VK_NULL_HANDLE; #if BUILD_DEBUG Device = nullptr; @@ -133,15 +156,26 @@ void GPUTextureViewVulkan::DescriptorAsImage(GPUContextVulkan* context, VkImageV { imageView = View; layout = LayoutSRV; - + const VkImageAspectFlags aspectMask = Info.subresourceRange.aspectMask; + if (aspectMask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) + { + // Transition depth-only when binding depth buffer with stencil + if (ViewSRV == VK_NULL_HANDLE) + { + VkImageViewCreateInfo createInfo = Info; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + VALIDATE_VULKAN_RESULT(vkCreateImageView(Device->Device, &createInfo, nullptr, &ViewSRV)); + } + imageView = ViewSRV; + } context->AddImageBarrier(this, LayoutSRV); + Info.subresourceRange.aspectMask = aspectMask; } void GPUTextureViewVulkan::DescriptorAsStorageImage(GPUContextVulkan* context, VkImageView& imageView, VkImageLayout& layout) { imageView = View; layout = VK_IMAGE_LAYOUT_GENERAL; - context->AddImageBarrier(this, VK_IMAGE_LAYOUT_GENERAL); } @@ -152,7 +186,6 @@ bool GPUTextureVulkan::GetData(int32 arrayOrDepthSliceIndex, int32 mipMapIndex, LOG(Warning, "Texture::GetData is valid only for staging resources."); return true; } - GPUDeviceLock lock(_device); // Internally it's a buffer, so adapt resource index and offset @@ -209,7 +242,6 @@ void GPUTextureVulkan::DescriptorAsStorageImage(GPUContextVulkan* context, VkIma ASSERT(_handleUAV.Owner == this); imageView = _handleUAV.View; layout = VK_IMAGE_LAYOUT_GENERAL; - context->AddImageBarrier(this, VK_IMAGE_LAYOUT_GENERAL); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h index baf6968b4..0c77a73e2 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTextureVulkan.h @@ -15,7 +15,6 @@ class GPUTextureViewVulkan : public GPUTextureView, public DescriptorOwnerResourceVulkan { public: - GPUTextureViewVulkan() { } @@ -44,21 +43,24 @@ public: #endif public: - GPUDeviceVulkan* Device = nullptr; ResourceOwnerVulkan* Owner = nullptr; VkImage Image = VK_NULL_HANDLE; VkImageView View = VK_NULL_HANDLE; VkImageView ViewFramebuffer = VK_NULL_HANDLE; + VkImageView ViewSRV = VK_NULL_HANDLE; VkExtent3D Extent; uint32 Layers; VkImageViewCreateInfo Info; int32 SubresourceIndex; VkImageLayout LayoutRTV; VkImageLayout LayoutSRV; +#if VULKAN_USE_DEBUG_DATA + PixelFormat Format; + bool ReadOnlyDepth; +#endif public: - void Init(GPUDeviceVulkan* device, ResourceOwnerVulkan* owner, VkImage image, int32 totalMipLevels, PixelFormat format, MSAALevel msaa, VkExtent3D extent, VkImageViewType viewType, int32 mipLevels = 1, int32 firstMipIndex = 0, int32 arraySize = 1, int32 firstArraySlice = 0, bool readOnlyDepth = false); VkImageView GetFramebufferView(); @@ -66,7 +68,6 @@ public: void Release(); public: - // [GPUResourceView] void* GetNativePtr() const override { @@ -84,7 +85,6 @@ public: class GPUTextureVulkan : public GPUResourceVulkan, public ResourceOwnerVulkan, public DescriptorOwnerResourceVulkan { private: - VkImage _image = VK_NULL_HANDLE; VmaAllocation _allocation = VK_NULL_HANDLE; GPUTextureViewVulkan _handleArray; @@ -95,7 +95,6 @@ private: Array> _handlesPerMip; // [slice][mip] public: - /// /// Initializes a new instance of the class. /// @@ -107,7 +106,6 @@ public: } public: - /// /// Gets the Vulkan image handle. /// @@ -127,11 +125,9 @@ public: VkImageAspectFlags DefaultAspectMask; private: - void initHandles(); public: - // [GPUTexture] GPUTextureView* View(int32 arrayOrDepthIndex) const override { @@ -178,7 +174,6 @@ public: void DescriptorAsStorageImage(GPUContextVulkan* context, VkImageView& imageView, VkImageLayout& layout) override; protected: - // [GPUTexture] bool OnInit() override; void OnResidentMipsChanged() override; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h index 7411d9b24..9111e3bb4 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUTimerQueryVulkan.h @@ -13,7 +13,6 @@ class GPUTimerQueryVulkan : public GPUResourceVulkan { private: - struct Query { BufferedQueryPoolVulkan* Pool; @@ -35,7 +34,6 @@ private: Array> _queries; public: - /// /// Initializes a new instance of the class. /// @@ -43,7 +41,6 @@ public: GPUTimerQueryVulkan(GPUDeviceVulkan* device); public: - /// /// Interrupts an in-progress query, allowing the command buffer to submitted. Interrupted queries must be resumed using Resume(). /// @@ -57,14 +54,12 @@ public: void Resume(CmdBufferVulkan* cmdBuffer); private: - bool GetResult(Query& query); void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const; bool TryGetResult(); bool UseQueries(); public: - // [GPUTimerQuery] void Begin() override; void End() override; @@ -72,7 +67,6 @@ public: float GetResult() override; protected: - // [GPUResourceVulkan] void OnReleaseGPU() override; }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs index a76d94ee3..663ff97a0 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs +++ b/Source/Engine/GraphicsDevice/Vulkan/GraphicsDeviceVulkan.Build.cs @@ -133,6 +133,59 @@ public sealed class VulkanSdk : Sdk } return false; } + + /// + /// Adds any runtime dependency files to the build that uses Vulkan SDK. + /// + /// Build options. + public void AddDependencyFiles(BuildOptions options) + { + switch (options.Platform.Target) + { + case TargetPlatform.Mac: + case TargetPlatform.iOS: + { + // MoltenVK + var platformName = options.Platform.Target == TargetPlatform.iOS ? "iOS" : "macOS"; + var location1 = Path.Combine(RootPath, "../MoltenVK/dylib/" + platformName); + if (Directory.Exists(location1)) + { + // Initial location + options.DependencyFiles.Add(Path.Combine(location1, "libMoltenVK.dylib")); + options.DependencyFiles.Add(Path.Combine(location1, "MoltenVK_icd.json")); + return; + } + + // New location from SDK 1.3.275 + if (options.Platform.Target == TargetPlatform.iOS) + { + var location2 = Path.Combine(RootPath, "../iOS/lib/MoltenVK.xcframework/ios-arm64/MoltenVK.framework"); + var location3 = Path.Combine(RootPath, "../iOS/share/vulkan/icd.d"); + if (Directory.Exists(location2) && Directory.Exists(location3)) + { + // iOS + options.DependencyFiles.Add(Path.Combine(location2, "MoltenVK")); + options.DependencyFiles.Add(Path.Combine(location3, "MoltenVK_icd.json")); + return; + } + } + else + { + var location2 = Path.Combine(RootPath, "lib"); + var location3 = Path.Combine(RootPath, "share/vulkan/icd.d"); + if (Directory.Exists(location2) && Directory.Exists(location3)) + { + // macOS + options.DependencyFiles.Add(Path.Combine(location2, "libMoltenVK.dylib")); + options.DependencyFiles.Add(Path.Combine(location3, "MoltenVK_icd.json")); + return; + } + } + Log.Error($"Missing MoltenVK files for {platformName} in VulkanSDK '{RootPath}'"); + break; + } + } + } } /// diff --git a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h index cab5b2d2f..e01e980d7 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h +++ b/Source/Engine/GraphicsDevice/Vulkan/IncludeVulkanHeaders.h @@ -14,7 +14,6 @@ #include #undef VK_EXT_debug_utils #undef VK_EXT_validation_cache -#define VMA_DEDICATED_ALLOCATION 0 #pragma clang diagnostic ignored "-Wpointer-bool-conversion" #pragma clang diagnostic ignored "-Wtautological-pointer-compare" diff --git a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h index e1bfd2f49..3c8ee101e 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Linux/LinuxVulkanPlatform.h @@ -6,7 +6,8 @@ #if GRAPHICS_API_VULKAN && PLATFORM_LINUX -#define VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 1 +// Support more backbuffers in case driver decides to use more (https://gitlab.freedesktop.org/apinheiro/mesa/-/issues/9) +#define VULKAN_BACK_BUFFERS_COUNT_MAX 8 /// /// The implementation for the Vulkan API support for Linux platform. diff --git a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp index 3cdd1a174..302952112 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.cpp @@ -14,17 +14,15 @@ QueueVulkan::QueueVulkan(GPUDeviceVulkan* device, uint32 familyIndex) , _device(device) , _lastSubmittedCmdBuffer(nullptr) , _lastSubmittedCmdBufferFenceCounter(0) - , _submitCounter(0) { vkGetDeviceQueue(device->Device, familyIndex, 0, &_queue); } -void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 numSignalSemaphores, VkSemaphore* signalSemaphores) +void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 signalSemaphoresCount, const VkSemaphore* signalSemaphores) { ASSERT(cmdBuffer->HasEnded()); - auto fence = cmdBuffer->GetFence(); - ASSERT(!fence->IsSignaled()); + ASSERT(!fence->IsSignaled); const VkCommandBuffer cmdBuffers[] = { cmdBuffer->GetHandle() }; @@ -32,7 +30,7 @@ void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 numSignalSemaphores, RenderToolsVulkan::ZeroStruct(submitInfo, VK_STRUCTURE_TYPE_SUBMIT_INFO); submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = cmdBuffers; - submitInfo.signalSemaphoreCount = numSignalSemaphores; + submitInfo.signalSemaphoreCount = signalSemaphoresCount; submitInfo.pSignalSemaphores = signalSemaphores; Array> waitSemaphores; @@ -48,7 +46,7 @@ void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 numSignalSemaphores, submitInfo.pWaitDstStageMask = cmdBuffer->_waitFlags.Get(); } - VALIDATE_VULKAN_RESULT(vkQueueSubmit(_queue, 1, &submitInfo, fence->GetHandle())); + VALIDATE_VULKAN_RESULT(vkQueueSubmit(_queue, 1, &submitInfo, fence->Handle)); // Mark semaphores as submitted cmdBuffer->_state = CmdBufferVulkan::State::Submitted; @@ -78,21 +76,16 @@ void QueueVulkan::Submit(CmdBufferVulkan* cmdBuffer, uint32 numSignalSemaphores, void QueueVulkan::GetLastSubmittedInfo(CmdBufferVulkan*& cmdBuffer, uint64& fenceCounter) const { _locker.Lock(); - cmdBuffer = _lastSubmittedCmdBuffer; fenceCounter = _lastSubmittedCmdBufferFenceCounter; - _locker.Unlock(); } void QueueVulkan::UpdateLastSubmittedCommandBuffer(CmdBufferVulkan* cmdBuffer) { _locker.Lock(); - _lastSubmittedCmdBuffer = cmdBuffer; _lastSubmittedCmdBufferFenceCounter = cmdBuffer->GetFenceSignaledCounter(); - _submitCounter++; - _locker.Unlock(); } diff --git a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.h index b37deb616..b28fde1a5 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/QueueVulkan.h @@ -17,7 +17,6 @@ class CmdBufferVulkan; class QueueVulkan { private: - VkQueue _queue; uint32 _familyIndex; uint32 _queueIndex; @@ -25,10 +24,8 @@ private: CriticalSection _locker; CmdBufferVulkan* _lastSubmittedCmdBuffer; uint64 _lastSubmittedCmdBufferFenceCounter; - uint64 _submitCounter; public: - QueueVulkan(GPUDeviceVulkan* device, uint32 familyIndex); inline uint32 GetFamilyIndex() const @@ -36,7 +33,7 @@ public: return _familyIndex; } - void Submit(CmdBufferVulkan* cmdBuffer, uint32 numSignalSemaphores = 0, VkSemaphore* signalSemaphores = nullptr); + void Submit(CmdBufferVulkan* cmdBuffer, uint32 signalSemaphoresCount = 0, const VkSemaphore* signalSemaphores = nullptr); inline void Submit(CmdBufferVulkan* cmdBuffer, VkSemaphore signalSemaphore) { @@ -50,13 +47,7 @@ public: void GetLastSubmittedInfo(CmdBufferVulkan*& cmdBuffer, uint64& fenceCounter) const; - inline uint64 GetSubmitCount() const - { - return _submitCounter; - } - private: - void UpdateLastSubmittedCommandBuffer(CmdBufferVulkan* cmdBuffer); }; diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp index e682068ad..cd9d49b6b 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.cpp @@ -6,8 +6,10 @@ #include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Log.h" -VkFormat RenderToolsVulkan::PixelFormatToVkFormat[static_cast(PixelFormat::MAX)] = { +// @formatter:off +VkFormat RenderToolsVulkan::PixelFormatToVkFormat[108] = +{ VK_FORMAT_UNDEFINED, VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT, @@ -27,13 +29,10 @@ VkFormat RenderToolsVulkan::PixelFormatToVkFormat[static_cast(PixelFormat VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32G32_UINT, VK_FORMAT_R32G32_SINT, - VK_FORMAT_UNDEFINED, - // TODO: R32G8X24_Typeless + VK_FORMAT_UNDEFINED, // TODO: R32G8X24_Typeless VK_FORMAT_D32_SFLOAT_S8_UINT, - VK_FORMAT_UNDEFINED, - // TODO: R32_Float_X8X24_Typeless - VK_FORMAT_UNDEFINED, - // TODO: X32_Typeless_G8X24_UInt + VK_FORMAT_UNDEFINED, // TODO: R32_Float_X8X24_Typeless + VK_FORMAT_UNDEFINED, // TODO: X32_Typeless_G8X24_UInt VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_UINT_PACK32, @@ -76,15 +75,11 @@ VkFormat RenderToolsVulkan::PixelFormatToVkFormat[static_cast(PixelFormat VK_FORMAT_R8_UINT, VK_FORMAT_R8_SNORM, VK_FORMAT_R8_SINT, - VK_FORMAT_UNDEFINED, - // TODO: A8_UNorm - VK_FORMAT_UNDEFINED, - // TODO: R1_UNorm + VK_FORMAT_UNDEFINED, // TODO: A8_UNorm + VK_FORMAT_UNDEFINED, // TODO: R1_UNorm VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, - VK_FORMAT_UNDEFINED, - // TODO: R8G8_B8G8_UNorm - VK_FORMAT_UNDEFINED, - // TODO: G8R8_G8B8_UNorm + VK_FORMAT_UNDEFINED, // TODO: R8G8_B8G8_UNorm + VK_FORMAT_UNDEFINED, // TODO: G8R8_G8B8_UNorm VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, @@ -104,8 +99,7 @@ VkFormat RenderToolsVulkan::PixelFormatToVkFormat[static_cast(PixelFormat VK_FORMAT_B5G5R5A1_UNORM_PACK16, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, - VK_FORMAT_UNDEFINED, - // TODO: R10G10B10_Xr_Bias_A2_UNorm + VK_FORMAT_UNDEFINED, // TODO: R10G10B10_Xr_Bias_A2_UNorm VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, @@ -116,83 +110,63 @@ VkFormat RenderToolsVulkan::PixelFormatToVkFormat[static_cast(PixelFormat VK_FORMAT_BC7_UNORM_BLOCK, VK_FORMAT_BC7_UNORM_BLOCK, VK_FORMAT_BC7_SRGB_BLOCK, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK, + VK_FORMAT_ASTC_8x8_UNORM_BLOCK, + VK_FORMAT_ASTC_8x8_SRGB_BLOCK, + VK_FORMAT_ASTC_10x10_UNORM_BLOCK, + VK_FORMAT_ASTC_10x10_SRGB_BLOCK, }; -VkBlendFactor RenderToolsVulkan::BlendToVkBlendFactor[static_cast(BlendingMode::Blend::MAX)] = +VkBlendFactor RenderToolsVulkan::BlendToVkBlendFactor[20] = { VK_BLEND_FACTOR_MAX_ENUM, - VK_BLEND_FACTOR_ZERO, - // Zero - VK_BLEND_FACTOR_ONE, - // One - VK_BLEND_FACTOR_SRC_COLOR, - // SrcColor - VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, - // InvSrcColor - VK_BLEND_FACTOR_SRC_ALPHA, - // SrcAlpha - VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - // InvSrcAlpha - VK_BLEND_FACTOR_DST_ALPHA, - // DestAlpha - VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, - // InvDestAlpha - VK_BLEND_FACTOR_DST_COLOR, - // DestColor, - VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, - // InvDestColor - VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, - // SrcAlphaSat - VK_BLEND_FACTOR_CONSTANT_ALPHA, - // BlendFactor - VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, - // BlendInvFactor - VK_BLEND_FACTOR_SRC1_COLOR, - // Src1Color - VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, - // InvSrc1Color - VK_BLEND_FACTOR_SRC1_ALPHA, - // Src1Alpha - VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, - // InvSrc1Alpha + VK_BLEND_FACTOR_ZERO, // Zero + VK_BLEND_FACTOR_ONE, // One + VK_BLEND_FACTOR_SRC_COLOR, // SrcColor + VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, // InvSrcColor + VK_BLEND_FACTOR_SRC_ALPHA, // SrcAlpha + VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, // InvSrcAlpha + VK_BLEND_FACTOR_DST_ALPHA, // DestAlpha + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, // InvDestAlpha + VK_BLEND_FACTOR_DST_COLOR, // DestColor, + VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, // InvDestColor + VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, // SrcAlphaSat + VK_BLEND_FACTOR_CONSTANT_ALPHA, // BlendFactor + VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, // BlendInvFactor + VK_BLEND_FACTOR_SRC1_COLOR, // Src1Color + VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, // InvSrc1Color + VK_BLEND_FACTOR_SRC1_ALPHA, // Src1Alpha + VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, // InvSrc1Alpha }; -VkBlendOp RenderToolsVulkan::OperationToVkBlendOp[static_cast(BlendingMode::Operation::MAX)] = +VkBlendOp RenderToolsVulkan::OperationToVkBlendOp[6] = { VK_BLEND_OP_MAX_ENUM, - VK_BLEND_OP_ADD, - // Add - VK_BLEND_OP_SUBTRACT, - // Subtract - VK_BLEND_OP_REVERSE_SUBTRACT, - // RevSubtract - VK_BLEND_OP_MIN, - // Min - VK_BLEND_OP_MAX, - // Max + VK_BLEND_OP_ADD, // Add + VK_BLEND_OP_SUBTRACT, // Subtract + VK_BLEND_OP_REVERSE_SUBTRACT, // RevSubtract + VK_BLEND_OP_MIN, // Min + VK_BLEND_OP_MAX, // Max }; -VkCompareOp RenderToolsVulkan::ComparisonFuncToVkCompareOp[static_cast(ComparisonFunc::MAX)] = +VkCompareOp RenderToolsVulkan::ComparisonFuncToVkCompareOp[9] = { VK_COMPARE_OP_MAX_ENUM, - VK_COMPARE_OP_NEVER, - // Never - VK_COMPARE_OP_LESS, - // Less - VK_COMPARE_OP_EQUAL, - // Equal - VK_COMPARE_OP_LESS_OR_EQUAL, - // LessEqual - VK_COMPARE_OP_GREATER, - // Grather - VK_COMPARE_OP_NOT_EQUAL, - // NotEqual - VK_COMPARE_OP_GREATER_OR_EQUAL, - // GratherEqual - VK_COMPARE_OP_ALWAYS, - // Always + VK_COMPARE_OP_NEVER, // Never + VK_COMPARE_OP_LESS, // Less + VK_COMPARE_OP_EQUAL, // Equal + VK_COMPARE_OP_LESS_OR_EQUAL, // LessEqual + VK_COMPARE_OP_GREATER, // Grather + VK_COMPARE_OP_NOT_EQUAL, // NotEqual + VK_COMPARE_OP_GREATER_OR_EQUAL, // GratherEqual + VK_COMPARE_OP_ALWAYS, // Always }; +// @formatter:on + #define VKERR(x) case x: sb.Append(TEXT(#x)); break #if GPU_ENABLE_RESOURCE_NAMING @@ -303,4 +277,15 @@ void RenderToolsVulkan::LogVkResult(VkResult result) LogVkResult(result, "", 0); } +bool RenderToolsVulkan::HasExtension(const Array& extensions, const char* name) +{ + for (int32 i = 0; i < extensions.Count(); i++) + { + const char* extension = extensions[i]; + if (extension && StringUtils::Compare(extension, name) == 0) + return true; + } + return false; +} + #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h index bb9543943..afe16a6bc 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/RenderToolsVulkan.h @@ -34,14 +34,12 @@ class RenderToolsVulkan { private: - static VkFormat PixelFormatToVkFormat[static_cast(PixelFormat::MAX)]; static VkBlendFactor BlendToVkBlendFactor[static_cast(BlendingMode::Blend::MAX)]; static VkBlendOp OperationToVkBlendOp[static_cast(BlendingMode::Operation::MAX)]; static VkCompareOp ComparisonFuncToVkCompareOp[static_cast(ComparisonFunc::MAX)]; public: - #if GPU_ENABLE_RESOURCE_NAMING static void SetObjectName(VkDevice device, uint64 objectHandle, VkObjectType objectType, const String& name); static void SetObjectName(VkDevice device, uint64 objectHandle, VkObjectType objectType, const char* name); @@ -79,14 +77,12 @@ public: case VK_ACCESS_SHADER_WRITE_BIT: stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; -#if VK_KHR_maintenance2 - case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT: - case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT: - stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - break; -#endif + case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT: + case VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT: + stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + break; default: - CRASH; + CRASH; break; } return stageFlags; @@ -110,7 +106,9 @@ public: stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; break; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: - accessFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL: + case VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL: + accessFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; break; case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: @@ -126,21 +124,22 @@ public: stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: - accessFlags = VK_ACCESS_SHADER_READ_BIT; - stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL: + case VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL: + accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + break; + case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: + case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: + accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; break; -#if VK_KHR_maintenance2 - case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR: - accessFlags = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - stageFlags = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - break; -#endif case VK_IMAGE_LAYOUT_GENERAL: accessFlags = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; stageFlags = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; default: - CRASH; + CRASH; break; } return stageFlags; @@ -152,7 +151,7 @@ public: static_assert(!TIsPointer::Value, "Don't use a pointer."); static_assert(OFFSET_OF(T, sType) == 0, "Assumes type is the first member in the Vulkan type."); data.sType = type; - Platform::MemoryClear(((uint8*)&data) + sizeof(VkStructureType), sizeof(T) - sizeof(VkStructureType)); + Platform::MemoryClear((uint8*)&data + sizeof(VkStructureType), sizeof(T) - sizeof(VkStructureType)); } /// @@ -213,7 +212,7 @@ public: result = VK_SAMPLER_MIPMAP_MODE_LINEAR; break; default: - CRASH; + CRASH; break; } return result; @@ -237,7 +236,7 @@ public: result = VK_FILTER_LINEAR; break; default: - CRASH; + CRASH; break; } return result; @@ -261,7 +260,7 @@ public: result = VK_FILTER_LINEAR; break; default: - CRASH; + CRASH; break; } return result; @@ -285,7 +284,7 @@ public: result = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; break; default: - CRASH; + CRASH; break; } return result; @@ -303,11 +302,13 @@ public: result = VK_COMPARE_OP_NEVER; break; default: - CRASH; + CRASH; break; } return result; } + + static bool HasExtension(const Array& extensions, const char* name); }; #endif diff --git a/Source/Engine/GraphicsDevice/Vulkan/ResourceOwnerVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/ResourceOwnerVulkan.h index 0a6bc126c..9f71fa171 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/ResourceOwnerVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/ResourceOwnerVulkan.h @@ -30,13 +30,11 @@ class ResourceOwnerVulkan friend GPUContextVulkan; public: - virtual ~ResourceOwnerVulkan() { } public: - /// /// The resource state tracking helper. Used for resource barriers. /// @@ -48,7 +46,6 @@ public: int32 ArraySlices; public: - /// /// Gets resource owner object as a GPUResource type or returns null if cannot perform cast. /// @@ -56,7 +53,6 @@ public: virtual GPUResource* AsGPUResource() const = 0; protected: - void initResource(VkImageLayout initialState, int32 mipLevels = 1, int32 arraySize = 1, bool usePerSubresourceTracking = false) { State.Initialize(mipLevels * arraySize, initialState, usePerSubresourceTracking); diff --git a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h index 9ffa8d465..db3ada2c7 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h +++ b/Source/Engine/GraphicsDevice/Vulkan/VulkanPlatformBase.h @@ -23,7 +23,6 @@ enum class VulkanValidationLevel class VulkanPlatformBase { public: - static void GetInstanceExtensions(Array& extensions, Array& layers) { } diff --git a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h index 73eb6b8a3..85658737f 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h +++ b/Source/Engine/GraphicsDevice/Vulkan/Win32/Win32VulkanPlatform.h @@ -8,7 +8,6 @@ #define VULKAN_USE_PLATFORM_WIN32_KHR 1 #define VULKAN_USE_PLATFORM_WIN32_KHX 1 -#define VULKAN_HAS_PHYSICAL_DEVICE_PROPERTIES2 1 #define VULKAN_USE_CREATE_WIN32_SURFACE 1 /// diff --git a/Source/Engine/Input/InputSettings.h b/Source/Engine/Input/InputSettings.h index 9f03db243..b251eaf3c 100644 --- a/Source/Engine/Input/InputSettings.h +++ b/Source/Engine/Input/InputSettings.h @@ -12,6 +12,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API InputSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(InputSettings); + public: /// /// Maps a discrete button or key press events to a "friendly name" that will later be bound to event-driven behavior. The end effect is that pressing (and/or releasing) a key, mouse button, or keypad button. diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index ca8485ffc..f7f03afff 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -390,7 +390,7 @@ namespace FlaxEngine } #if FLAX_EDITOR - internal bool ShowTransform => !(this is UIControl); + private bool ShowTransform => !(this is UIControl); #endif } } diff --git a/Source/Engine/Level/Actors/BoneSocket.cpp b/Source/Engine/Level/Actors/BoneSocket.cpp index c5743de22..a4f338b2f 100644 --- a/Source/Engine/Level/Actors/BoneSocket.cpp +++ b/Source/Engine/Level/Actors/BoneSocket.cpp @@ -45,14 +45,14 @@ void BoneSocket::UpdateTransformation() } auto& nodes = parent->GraphInstance.NodesPose; - if (nodes.HasItems() && nodes.Count() > _index) - { - Transform t; - nodes[_index].Decompose(t); - if (!_useScale) - t.Scale = _localTransform.Scale; - SetLocalTransform(t); - } + Transform t; + if (nodes.IsValidIndex(_index)) + nodes.Get()[_index].Decompose(t); + else + t = parent->SkinnedModel->Skeleton.GetNodeTransform(_index); + if (!_useScale) + t.Scale = _localTransform.Scale; + SetLocalTransform(t); } } diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index e7c7971b7..190126e54 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -77,7 +77,7 @@ public: /// /// Gets the camera's field of view (in degrees). /// - API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))") + API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective)), ValueCategory(Utils.ValueCategory.Angle)") float GetFieldOfView() const; /// @@ -99,7 +99,7 @@ public: /// /// Gets camera's near plane distance. /// - API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")") + API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)") float GetNearPlane() const; /// @@ -110,7 +110,7 @@ public: /// /// Gets camera's far plane distance. /// - API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")") + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), ValueCategory(Utils.ValueCategory.Distance)") float GetFarPlane() const; /// diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index aa6772904..07ff609bd 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -553,13 +553,11 @@ const Span StaticModel::GetMaterialSlots() const MaterialBase* StaticModel::GetMaterial(int32 entryIndex) { - if (Model) - Model->WaitForLoaded(); - else + if (!Model || Model->WaitForLoaded()) return nullptr; CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr); MaterialBase* material = Entries[entryIndex].Material.Get(); - if (!material) + if (!material && entryIndex < Model->MaterialSlots.Count()) { material = Model->MaterialSlots[entryIndex].Material.Get(); if (!material) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 31271f173..95f3d7f0f 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -12,6 +12,7 @@ #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Config/LayersTagsSettings.h" #include "Engine/Core/Types/LayersMask.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Debug/Exceptions/ArgumentException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/InvalidOperationException.h" @@ -868,13 +869,11 @@ bool Level::loadScene(rapidjson_flax::Document& document, Scene** outScene) bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene) { PROFILE_CPU_NAMED("Level.LoadScene"); - if (outScene) *outScene = nullptr; - LOG(Info, "Loading scene..."); - const DateTime startTime = DateTime::NowUTC(); - _lastSceneLoadTime = startTime; + Stopwatch stopwatch; + _lastSceneLoadTime = DateTime::Now(); // Here whole scripting backend should be loaded for current project // Later scripts will setup attached scripts and restore initial vars @@ -925,7 +924,6 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // Create scene actor // Note: the first object in the scene file data is a Scene Actor auto scene = New(ScriptingObjectSpawnParams(sceneId, Scene::TypeInitializer)); - scene->LoadTime = startTime; scene->RegisterObject(); scene->Deserialize(data[0], modifier.Value); @@ -1084,7 +1082,8 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou // Fire event CallSceneEvent(SceneEventType::OnSceneLoaded, scene, sceneId); - LOG(Info, "Scene loaded in {0} ms", (int32)(DateTime::NowUTC() - startTime).GetTotalMilliseconds()); + stopwatch.Stop(); + LOG(Info, "Scene loaded in {0}ms", stopwatch.GetMilliseconds()); if (outScene) *outScene = scene; return false; @@ -1113,8 +1112,7 @@ bool LevelImpl::saveScene(Scene* scene, const String& path) auto sceneId = scene->GetID(); LOG(Info, "Saving scene {0} to \'{1}\'", scene->GetName(), path); - const DateTime startTime = DateTime::NowUTC(); - scene->SaveTime = startTime; + Stopwatch stopwatch; // Serialize to json rapidjson_flax::StringBuffer buffer; @@ -1132,7 +1130,8 @@ bool LevelImpl::saveScene(Scene* scene, const String& path) return true; } - LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt((float)(DateTime::NowUTC() - startTime).GetTotalMilliseconds())); + stopwatch.Stop(); + LOG(Info, "Scene saved! Time {0}ms", stopwatch.GetMilliseconds()); #if USE_EDITOR // Reload asset at the target location if is loaded @@ -1212,10 +1211,8 @@ bool Level::SaveSceneToBytes(Scene* scene, rapidjson_flax::StringBuffer& outData { ASSERT(scene); ScopeLock lock(_sceneActionsLocker); - + Stopwatch stopwatch; LOG(Info, "Saving scene {0} to bytes", scene->GetName()); - const DateTime startTime = DateTime::NowUTC(); - scene->SaveTime = startTime; // Serialize to json if (saveScene(scene, outData, prettyJson)) @@ -1224,8 +1221,8 @@ bool Level::SaveSceneToBytes(Scene* scene, rapidjson_flax::StringBuffer& outData return true; } - // Info - LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt(static_cast((DateTime::NowUTC() - startTime).GetTotalMilliseconds()))); + stopwatch.Stop(); + LOG(Info, "Scene saved! Time {0}ms", stopwatch.GetMilliseconds()); // Fire event CallSceneEvent(SceneEventType::OnSceneSaved, scene, scene->GetID()); diff --git a/Source/Engine/Level/Scene/Scene.cpp b/Source/Engine/Level/Scene/Scene.cpp index 8c9836e95..0a2bdd387 100644 --- a/Source/Engine/Level/Scene/Scene.cpp +++ b/Source/Engine/Level/Scene/Scene.cpp @@ -273,9 +273,6 @@ void Scene::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_GET_OTHER_OBJ(Scene); - // Update scene info object - SaveTime = DateTime::NowUTC(); - LightmapsData.SaveLightmaps(Info.Lightmaps); Info.Serialize(stream, other ? &other->Info : nullptr); diff --git a/Source/Engine/Level/Scene/Scene.h b/Source/Engine/Level/Scene/Scene.h index a2dfd0c1f..4e0a00dcc 100644 --- a/Source/Engine/Level/Scene/Scene.h +++ b/Source/Engine/Level/Scene/Scene.h @@ -32,16 +32,6 @@ public: /// SceneInfo Info; - /// - /// The last load time. - /// - DateTime LoadTime; - - /// - /// The last save time. - /// - DateTime SaveTime; - /// /// The scene rendering manager. /// diff --git a/Source/Engine/Level/Scene/SceneCSGData.h b/Source/Engine/Level/Scene/SceneCSGData.h index 0b2f3676b..57d652451 100644 --- a/Source/Engine/Level/Scene/SceneCSGData.h +++ b/Source/Engine/Level/Scene/SceneCSGData.h @@ -2,10 +2,11 @@ #pragma once +#include "Engine/Core/ISerializable.h" +#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Math/Triangle.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Physics/CollisionData.h" -#include "Engine/Core/ISerializable.h" #include "Engine/Content/AssetReference.h" #include "Engine/Content/Assets/RawDataAsset.h" #include "Engine/Content/Assets/Model.h" diff --git a/Source/Engine/Localization/LocalizationSettings.h b/Source/Engine/Localization/LocalizationSettings.h index 0f5e1d99c..cfd391cc3 100644 --- a/Source/Engine/Localization/LocalizationSettings.h +++ b/Source/Engine/Localization/LocalizationSettings.h @@ -12,6 +12,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API LocalizationSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(LocalizationSettings); + public: /// /// The list of the string localization tables used by the game. diff --git a/Source/Engine/Main/Windows/main.cpp b/Source/Engine/Main/Windows/main.cpp index 3667efb6b..f8924a73b 100644 --- a/Source/Engine/Main/Windows/main.cpp +++ b/Source/Engine/Main/Windows/main.cpp @@ -25,8 +25,6 @@ extern "C" { __declspec(dllexport) int32 AmdPowerXpressRequestHighPerformance = 1; } -extern LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep); - #if FLAX_TESTS int main(int argc, char* argv[]) #else @@ -54,7 +52,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmd { return Engine::Main(lpCmdLine); } - __except (SehExceptionHandler(GetExceptionInformation())) + __except (Platform::SehExceptionHandler(GetExceptionInformation())) { return -1; } diff --git a/Source/Engine/Navigation/NavMeshRuntime.cpp b/Source/Engine/Navigation/NavMeshRuntime.cpp index aa4974a7e..0e6ae0a0e 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.cpp +++ b/Source/Engine/Navigation/NavMeshRuntime.cpp @@ -194,7 +194,7 @@ bool NavMeshRuntime::TestPath(const Vector3& startPosition, const Vector3& endPo return true; } -bool NavMeshRuntime::ProjectPoint(const Vector3& point, Vector3& result) const +bool NavMeshRuntime::FindClosestPoint(const Vector3& point, Vector3& result) const { ScopeLock lock(Locker); const auto query = GetNavMeshQuery(); diff --git a/Source/Engine/Navigation/NavMeshRuntime.h b/Source/Engine/Navigation/NavMeshRuntime.h index cb22b91b5..9b090b97c 100644 --- a/Source/Engine/Navigation/NavMeshRuntime.h +++ b/Source/Engine/Navigation/NavMeshRuntime.h @@ -146,12 +146,24 @@ public: API_FUNCTION() bool TestPath(const Vector3& startPosition, const Vector3& endPosition) const; /// - /// Projects the point to nav mesh surface (finds the nearest polygon). + /// Finds the nearest point on a nav mesh surface. /// /// The source point. /// The result position on the navmesh (valid only if method returns true). /// True if found valid location on the navmesh, otherwise false. - API_FUNCTION() bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const; + API_FUNCTION() bool FindClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const; + + /// + /// Projects the point to nav mesh surface (finds the nearest polygon). + /// [Deprecated in v1.8] + /// + /// The source point. + /// The result position on the navmesh (valid only if method returns true). + /// True if found valid location on the navmesh, otherwise false. + API_FUNCTION() bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const + { + return FindClosestPoint(point, result); + } /// /// Finds random location on nav mesh. diff --git a/Source/Engine/Navigation/Navigation.cpp b/Source/Engine/Navigation/Navigation.cpp index d7632d0df..a30367391 100644 --- a/Source/Engine/Navigation/Navigation.cpp +++ b/Source/Engine/Navigation/Navigation.cpp @@ -325,11 +325,11 @@ bool Navigation::TestPath(const Vector3& startPosition, const Vector3& endPositi return NavMeshes.First()->TestPath(startPosition, endPosition); } -bool Navigation::ProjectPoint(const Vector3& point, Vector3& result) +bool Navigation::FindClosestPoint(const Vector3& point, Vector3& result) { if (NavMeshes.IsEmpty()) return false; - return NavMeshes.First()->ProjectPoint(point, result); + return NavMeshes.First()->FindClosestPoint(point, result); } bool Navigation::FindRandomPoint(Vector3& result) diff --git a/Source/Engine/Navigation/Navigation.h b/Source/Engine/Navigation/Navigation.h index 921955598..7628aa59a 100644 --- a/Source/Engine/Navigation/Navigation.h +++ b/Source/Engine/Navigation/Navigation.h @@ -40,12 +40,24 @@ public: API_FUNCTION() static bool TestPath(const Vector3& startPosition, const Vector3& endPosition); /// - /// Projects the point to nav mesh surface (finds the nearest polygon). + /// Finds the nearest point on a nav mesh surface. /// /// The source point. /// The result position on the navmesh (valid only if method returns true). /// True if found valid location on the navmesh, otherwise false. - API_FUNCTION() static bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result); + API_FUNCTION() static bool FindClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result); + + /// + /// Projects the point to nav mesh surface (finds the nearest polygon). + /// [Deprecated in v1.8] + /// + /// The source point. + /// The result position on the navmesh (valid only if method returns true). + /// True if found valid location on the navmesh, otherwise false. + API_FUNCTION() DEPRECATED static bool ProjectPoint(const Vector3& point, API_PARAM(Out) Vector3& result) + { + return FindClosestPoint(point, result); + } /// /// Finds random location on nav mesh. diff --git a/Source/Engine/Navigation/NavigationSettings.h b/Source/Engine/Navigation/NavigationSettings.h index 62671433f..f4e6c26fa 100644 --- a/Source/Engine/Navigation/NavigationSettings.h +++ b/Source/Engine/Navigation/NavigationSettings.h @@ -12,6 +12,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API NavigationSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(NavigationSettings); + public: /// /// If checked, enables automatic navmesh actors spawning on a scenes that are using it during navigation building. diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.cpp b/Source/Engine/Networking/NetworkReplicationHierarchy.cpp index 03d7e61a6..224ee9f31 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.cpp +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.cpp @@ -80,7 +80,16 @@ bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj) if (index != -1) { NetworkReplicationHierarchyObject& e = Objects[index]; - e.ReplicationUpdatesLeft = 0; + if (e.ReplicationFPS < -ZeroTolerance) // < 0 + { + // Indicate for manual sync (see logic in Update) + e.ReplicationUpdatesLeft = 1; + } + else + { + // Replicate it next frame + e.ReplicationUpdatesLeft = 0; + } } return index != -1; } @@ -93,6 +102,12 @@ void NetworkReplicationNode::Update(NetworkReplicationHierarchyUpdateResult* res { if (obj.ReplicationFPS < -ZeroTolerance) // < 0 { + if (obj.ReplicationUpdatesLeft) + { + // Marked as dirty to sync manually + obj.ReplicationUpdatesLeft = 0; + result->AddObject(obj.Object); + } continue; } else if (obj.ReplicationFPS < ZeroTolerance) // == 0 @@ -167,7 +182,7 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj cell->MinCullDistance = obj.CullDistance; } cell->Node->AddObject(obj); - _objectToCell[obj.Object] = coord; + _objectToCell[obj.Object.Get()] = coord; // Cache minimum culling distance for a whole cell to skip it at once cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance); @@ -176,12 +191,10 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj) { Int3 coord; - if (!_objectToCell.TryGet(obj, coord)) { return false; } - if (_children[coord].Node->RemoveObject(obj)) { _objectToCell.Remove(obj); @@ -195,12 +208,10 @@ bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj) bool NetworkReplicationGridNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) { Int3 coord; - if (!_objectToCell.TryGet(obj, coord)) { return false; } - if (_children[coord].Node->GetObject(obj, result)) { return true; @@ -208,6 +219,16 @@ bool NetworkReplicationGridNode::GetObject(ScriptingObject* obj, NetworkReplicat return false; } +bool NetworkReplicationGridNode::DirtyObject(ScriptingObject* obj) +{ + Int3 coord; + if (_objectToCell.TryGet(obj, coord)) + { + return _children[coord].Node->DirtyObject(obj); + } + return NetworkReplicationNode::DirtyObject(obj); +} + void NetworkReplicationGridNode::Update(NetworkReplicationHierarchyUpdateResult* result) { CHECK(result); diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.h b/Source/Engine/Networking/NetworkReplicationHierarchy.h index ba2665622..78f9cad1f 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.h +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.h @@ -20,11 +20,11 @@ API_STRUCT(NoDefault, Namespace = "FlaxEngine.Networking") struct FLAXENGINE_API // The object to replicate. API_FIELD() ScriptingObjectReference Object; - // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object and less than 0 (eg. -1) for 'never relevant' objects that would only get synched on client join once. + // The target amount of the replication updates per second (frequency of the replication). Constrained by NetworkManager::NetworkFPS. Use 0 for 'always relevant' object and less than 0 (eg. -1) for 'never relevant' objects that would only get synced on client join once (or upon DirtyObject). API_FIELD() float ReplicationFPS = 60; // The minimum distance from the player to the object at which it can process replication. For example, players further away won't receive object data. Use 0 if unused. API_FIELD() float CullDistance = 15000; - // Runtime value for update frames left for the next replication of this object. Matches NetworkManager::NetworkFPS calculated from ReplicationFPS. + // Runtime value for update frames left for the next replication of this object. Matches NetworkManager::NetworkFPS calculated from ReplicationFPS. Set to 1 if ReplicationFPS less than 0 to indicate dirty object. API_FIELD(Attributes="HideInEditor") uint16 ReplicationUpdatesLeft = 0; FORCE_INLINE NetworkReplicationHierarchyObject(const ScriptingObjectReference& obj) @@ -257,6 +257,7 @@ public: void AddObject(NetworkReplicationHierarchyObject obj) override; bool RemoveObject(ScriptingObject* obj) override; bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) override; + bool DirtyObject(ScriptingObject* obj) override; void Update(NetworkReplicationHierarchyUpdateResult* result) override; }; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 85e6bcdd3..5c8979d42 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -1203,7 +1203,7 @@ void NetworkReplicator::RemoveObject(ScriptingObject* obj) return; ScopeLock lock(ObjectsLock); const auto it = Objects.Find(obj->GetID()); - if (it != Objects.End()) + if (it == Objects.End()) return; // Remove object from the list diff --git a/Source/Engine/Networking/NetworkSettings.h b/Source/Engine/Networking/NetworkSettings.h index 98340e369..c861114f9 100644 --- a/Source/Engine/Networking/NetworkSettings.h +++ b/Source/Engine/Networking/NetworkSettings.h @@ -10,8 +10,9 @@ /// API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API NetworkSettings : public SettingsBase { - API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkSettings); + API_AUTO_SERIALIZATION(); + public: /// /// Maximum amount of active network clients in a game session. Used by server or host to limit amount of players and spectators. diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 870bba867..04672552c 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -569,7 +569,7 @@ void RigidBody::OnTransformChanged() void RigidBody::OnPhysicsSceneChanged(PhysicsScene* previous) { - PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor); + PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _actor, true); void* scene = GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::AddSceneActor(scene, _actor); const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy(); diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index f005f1baa..c862a8e97 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -181,7 +181,7 @@ public: /// /// Gets the mass value measured in kilograms (use override value only if OverrideMass is checked). /// - API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\")") + API_PROPERTY(Attributes="EditorOrder(110), Limit(0), EditorDisplay(\"Rigid Body\"), ValueCategory(Utils.ValueCategory.Mass)") float GetMass() const; /// diff --git a/Source/Engine/Physics/Colliders/BoxCollider.h b/Source/Engine/Physics/Colliders/BoxCollider.h index f413fa042..58298ada8 100644 --- a/Source/Engine/Physics/Colliders/BoxCollider.h +++ b/Source/Engine/Physics/Colliders/BoxCollider.h @@ -23,7 +23,7 @@ public: /// Gets the size of the box, measured in the object's local space. /// /// The box size will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(typeof(Float3), \"100,100,100\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE Float3 GetSize() const { return _size; diff --git a/Source/Engine/Physics/Colliders/CapsuleCollider.h b/Source/Engine/Physics/Colliders/CapsuleCollider.h index 60526750c..c225ac2a5 100644 --- a/Source/Engine/Physics/Colliders/CapsuleCollider.h +++ b/Source/Engine/Physics/Colliders/CapsuleCollider.h @@ -25,7 +25,7 @@ public: /// Gets the radius of the sphere, measured in the object's local space. /// /// The sphere radius will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(20.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetRadius() const { return _radius; @@ -41,7 +41,7 @@ public: /// Gets the height of the capsule, measured in the object's local space between the centers of the hemispherical ends. /// /// The capsule height will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(100.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetHeight() const { return _height; diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 386c49eb5..545f4e9d9 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -73,7 +73,7 @@ public: /// /// Gets the radius of the sphere, measured in the object's local space. The sphere radius will be scaled by the actor's world scale. /// - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") float GetRadius() const; /// @@ -84,7 +84,7 @@ public: /// /// Gets the height of the capsule, measured in the object's local space. The capsule height will be scaled by the actor's world scale. /// - API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(150.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") float GetHeight() const; /// @@ -95,7 +95,7 @@ public: /// /// Gets the slope limit (in degrees). Limits the collider to only climb slopes that are less steep (in degrees) than the indicated value. /// - API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(210), DefaultValue(45.0f), Limit(0, 100), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Angle)") float GetSlopeLimit() const; /// @@ -117,7 +117,7 @@ public: /// /// Gets the step height. The character will step up a stair only if it is closer to the ground than the indicated value. This should not be greater than the Character Controller’s height or it will generate an error. /// - API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(220), DefaultValue(30.0f), Limit(0), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)") float GetStepOffset() const; /// @@ -139,7 +139,7 @@ public: /// /// Gets the minimum move distance of the character controller. The minimum traveled distance to consider. If traveled distance is smaller, the character doesn't move. This is used to stop the recursive motion algorithm when remaining distance to travel is small. /// - API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\")") + API_PROPERTY(Attributes="EditorOrder(230), DefaultValue(0.0f), Limit(0, 1000), EditorDisplay(\"Character Controller\"), ValueCategory(Utils.ValueCategory.Distance)") float GetMinMoveDistance() const; /// diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 1ad6b4946..d8541a222 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -438,7 +438,7 @@ void Collider::OnPhysicsSceneChanged(PhysicsScene* previous) if (_staticActor != nullptr) { - PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor); + PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _staticActor, true); void* scene = GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::AddSceneActor(scene, _staticActor); } diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index a2e115dcc..05f2a6eb2 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -52,7 +52,7 @@ public: /// /// Gets the center of the collider, measured in the object's local space. /// - API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE Vector3 GetCenter() const { return _center; @@ -66,7 +66,7 @@ public: /// /// Gets the contact offset. Colliders whose distance is less than the sum of their ContactOffset values will generate contacts. The contact offset must be positive. Contact offset allows the collision detection system to predictively enforce the contact constraint even when the objects are slightly separated. /// - API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(2.0f), Limit(0, 100), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetContactOffset() const { return _contactOffset; diff --git a/Source/Engine/Physics/Colliders/SphereCollider.h b/Source/Engine/Physics/Colliders/SphereCollider.h index 084dd0700..e3b295eda 100644 --- a/Source/Engine/Physics/Colliders/SphereCollider.h +++ b/Source/Engine/Physics/Colliders/SphereCollider.h @@ -21,7 +21,7 @@ public: /// Gets the radius of the sphere, measured in the object's local space. /// /// The sphere radius will be scaled by the actor's world scale. - API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\")") + API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(50.0f), EditorDisplay(\"Collider\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetRadius() const { return _radius; diff --git a/Source/Engine/Physics/Joints/DistanceJoint.h b/Source/Engine/Physics/Joints/DistanceJoint.h index 8cb0e5fd0..0f0f59b01 100644 --- a/Source/Engine/Physics/Joints/DistanceJoint.h +++ b/Source/Engine/Physics/Joints/DistanceJoint.h @@ -67,7 +67,7 @@ public: /// Gets the allowed minimum distance for the joint. /// /// Used only when DistanceJointFlag.MinDistance flag is set. The minimum distance must be no more than the maximum distance. Default: 0, Range: [0, float.MaxValue]. - API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(110), DefaultValue(0.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetMinDistance() const { return _minDistance; @@ -83,7 +83,7 @@ public: /// Gets the allowed maximum distance for the joint. /// /// Used only when DistanceJointFlag.MaxDistance flag is set. The maximum distance must be no less than the minimum distance. Default: 0, Range: [0, float.MaxValue]. - API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(120), DefaultValue(10.0f), Limit(0.0f), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Distance)") FORCE_INLINE float GetMaxDistance() const { return _maxDistance; diff --git a/Source/Engine/Physics/Joints/Joint.h b/Source/Engine/Physics/Joints/Joint.h index f4733f01d..a4584a07e 100644 --- a/Source/Engine/Physics/Joints/Joint.h +++ b/Source/Engine/Physics/Joints/Joint.h @@ -38,7 +38,7 @@ public: /// /// Gets the break force. Determines the maximum force the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// - API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Force)") FORCE_INLINE float GetBreakForce() const { return _breakForce; @@ -52,7 +52,7 @@ public: /// /// Gets the break torque. Determines the maximum torque the joint can apply before breaking. Broken joints no longer participate in physics simulation. /// - API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\")") + API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(float.MaxValue), EditorDisplay(\"Joint\"), ValueCategory(Utils.ValueCategory.Torque)") FORCE_INLINE float GetBreakTorque() const { return _breakTorque; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 0c77738f6..5d86ed8c9 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -1998,11 +1998,14 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor) FlushLocker.Unlock(); } -void PhysicsBackend::RemoveSceneActor(void* scene, void* actor) +void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately) { auto scenePhysX = (ScenePhysX*)scene; FlushLocker.Lock(); - scenePhysX->RemoveActors.Add((PxActor*)actor); + if (immediately) + scenePhysX->Scene->removeActor(*(PxActor*)actor); + else + scenePhysX->RemoveActors.Add((PxActor*)actor); FlushLocker.Unlock(); } diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index 518201573..8bd493267 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -113,7 +113,7 @@ public: static void SetSceneBounceThresholdVelocity(void* scene, float value); static void SetSceneOrigin(void* scene, const Vector3& oldOrigin, const Vector3& newOrigin); static void AddSceneActor(void* scene, void* actor); - static void RemoveSceneActor(void* scene, void* actor); + static void RemoveSceneActor(void* scene, void* actor, bool immediately = false); static void AddSceneActorAction(void* scene, void* actor, ActionType action); #if COMPILE_WITH_PROFILER static void GetSceneStatistics(void* scene, PhysicsStatistics& result); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 26054c342..941813b2e 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -115,7 +115,7 @@ void PhysicsBackend::AddSceneActor(void* scene, void* actor) { } -void PhysicsBackend::RemoveSceneActor(void* scene, void* actor) +void PhysicsBackend::RemoveSceneActor(void* scene, void* actor, bool immediately) { } diff --git a/Source/Engine/Physics/PhysicsSettings.h b/Source/Engine/Physics/PhysicsSettings.h index eefbacc87..fca1ea2d4 100644 --- a/Source/Engine/Physics/PhysicsSettings.h +++ b/Source/Engine/Physics/PhysicsSettings.h @@ -56,6 +56,7 @@ API_ENUM() enum class PhysicsSolverType API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API PhysicsSettings : public SettingsBase { DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicsSettings); + public: /// /// The default gravity force value (in cm^2/s). diff --git a/Source/Engine/Platform/Android/AndroidPlatformSettings.h b/Source/Engine/Platform/Android/AndroidPlatformSettings.h index 4c4e58330..390a3936e 100644 --- a/Source/Engine/Platform/Android/AndroidPlatformSettings.h +++ b/Source/Engine/Platform/Android/AndroidPlatformSettings.h @@ -48,6 +48,24 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API AutoRotation, }; + /// + /// The output textures quality (compression). + /// + API_ENUM() enum class TextureQuality + { + // Raw image data without any compression algorithm. Mostly for testing or compatibility. + Uncompressed, + // ASTC 4x4 block compression. + API_ENUM(Attributes="EditorDisplay(null, \"ASTC High\")") + ASTC_High, + // ASTC 6x6 block compression. + API_ENUM(Attributes="EditorDisplay(null, \"ASTC Medium\")") + ASTC_Medium, + // ASTC 8x8 block compression. + API_ENUM(Attributes="EditorDisplay(null, \"ASTC Low\")") + ASTC_Low, + }; + /// /// The application package name (eg. com.company.product). Custom tokens: ${PROJECT_NAME}, ${COMPANY_NAME}. /// @@ -66,6 +84,12 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API API_FIELD(Attributes = "EditorOrder(110), EditorDisplay(\"General\")") ScreenOrientation DefaultOrientation = ScreenOrientation::AutoRotation; + /// + /// The output textures quality (compression). + /// + API_FIELD(Attributes="EditorOrder(500), EditorDisplay(\"General\")") + TextureQuality TexturesQuality = TextureQuality::ASTC_Medium; + /// /// Custom icon texture to use for the application (overrides the default one). /// diff --git a/Source/Engine/Platform/Base/FileSystemBase.cpp b/Source/Engine/Platform/Base/FileSystemBase.cpp index cfd8ed9e0..759771bb9 100644 --- a/Source/Engine/Platform/Base/FileSystemBase.cpp +++ b/Source/Engine/Platform/Base/FileSystemBase.cpp @@ -240,12 +240,7 @@ bool FileSystemBase::CopyFile(const String& dst, const String& src) bool FileSystemBase::CopyDirectory(const String& dst, const String& src, bool withSubDirectories) { - // Check if source exists - if (!FileSystem::DirectoryExists(*src)) - return false; - - // Copy - return FileSystemBase::DirectoryCopyHelper(dst, src, withSubDirectories); + return !FileSystem::DirectoryExists(*src) || FileSystemBase::DirectoryCopyHelper(dst, src, withSubDirectories); } uint64 FileSystemBase::GetDirectorySize(const StringView& path) diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 6adfd2c31..8a6baf4bc 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -273,7 +273,6 @@ void PlatformBase::Fatal(const Char* msg, void* context) Globals::ExitCode = -1; // Collect crash info (platform-dependant implementation that might collect stack trace and/or create memory dump) - if (Log::Logger::LogFilePath.HasChars()) { // Log separation for crash info Log::Logger::WriteFloor(); @@ -288,24 +287,32 @@ void PlatformBase::Fatal(const Char* msg, void* context) LOG(Error, "Stack trace:"); for (const auto& frame : stackFrames) { - char chr = 0; + // Remove any path from the module name int32 num = StringUtils::Length(frame.ModuleName); - while (num > 0 && chr != '\\' && chr != '/' && chr != ':') - chr = frame.ModuleName[--num]; - StringAsUTF16 moduleName(frame.ModuleName + num + 1); + while (num > 0 && frame.ModuleName[num - 1] != '\\' && frame.ModuleName[num - 1] != '/' && frame.ModuleName[num - 1] != ':') + num--; + StringAsUTF16 moduleName(frame.ModuleName + num); + num = moduleName.Length(); + if (num != 0 && num < ARRAY_COUNT(StackFrame::ModuleName) - 2) + { + // Append separator between module name and the function name + ((Char*)moduleName.Get())[num++] = '!'; + ((Char*)moduleName.Get())[num] = 0; + } + StringAsUTF16 functionName(frame.FunctionName); if (StringUtils::Length(frame.FileName) != 0) { StringAsUTF16 fileName(frame.FileName); - LOG(Error, " at {0}!{1}() in {2}:line {3}", moduleName.Get(), functionName.Get(), fileName.Get(), frame.LineNumber); + LOG(Error, " at {0}{1}() in {2}:line {3}", moduleName.Get(), functionName.Get(), fileName.Get(), frame.LineNumber); } else if (StringUtils::Length(frame.FunctionName) != 0) { - LOG(Error, " at {0}!{1}()", moduleName.Get(), functionName.Get()); + LOG(Error, " at {0}{1}()", moduleName.Get(), functionName.Get()); } else if (StringUtils::Length(frame.ModuleName) != 0) { - LOG(Error, " at {0} 0x{1:x}", moduleName.Get(), (uint64)frame.ProgramCounter); + LOG(Error, " at {0}0x{1:x}", moduleName.Get(), (uint64)frame.ProgramCounter); } else { @@ -324,7 +331,9 @@ void PlatformBase::Fatal(const Char* msg, void* context) LOG(Error, "Process Used Physical Memory: {0}", Utilities::BytesToText(processMemoryStats.UsedPhysicalMemory)); LOG(Error, "Process Used Virtual Memory: {0}", Utilities::BytesToText(processMemoryStats.UsedVirtualMemory)); } - + } + if (Log::Logger::LogFilePath.HasChars()) + { // Create separate folder with crash info const String crashDataFolder = String(StringUtils::GetDirectoryName(Log::Logger::LogFilePath)) / TEXT("Crash_") + StringUtils::GetFileNameWithoutExtension(Log::Logger::LogFilePath).Substring(4); FileSystem::CreateDirectory(crashDataFolder); diff --git a/Source/Engine/Platform/Win32/Win32FileSystem.h b/Source/Engine/Platform/Win32/Win32FileSystem.h index 0af67e4bc..42ac5f4a1 100644 --- a/Source/Engine/Platform/Win32/Win32FileSystem.h +++ b/Source/Engine/Platform/Win32/Win32FileSystem.h @@ -16,12 +16,12 @@ class FLAXENGINE_API Win32FileSystem : public FileSystemBase public: // Creates a new directory - // @param path Drectory path + // @param path Directory path // @returns True if cannot create directory, otherwise false static bool CreateDirectory(const StringView& path); // Deletes an existing directory - // @param path Drectory path + // @param path Directory path // @param deleteSubdirectories True if delete all subdirectories and files, otherwise false // @returns True if cannot delete directory, otherwise false static bool DeleteDirectory(const String& path, bool deleteContents = true); @@ -32,15 +32,15 @@ public: static bool DirectoryExists(const StringView& path); // Finds the names of files (including their paths) that match the specified search pattern in the specified directory, using a value to determine whether to search subdirectories - // @param results When this metod completes, this list contains list of all filenames that match the specified search pattern + // @param results When this method completes, this list contains list of all filenames that match the specified search pattern // @param path Path of the directory to search in it - // @param searchPattern Custo msearch pattern to use during that operation - // @param option Addidtional search options + // @param searchPattern Custom search pattern to use during that operation + // @param option Additional search options // @returns True if an error occurred, otherwise false static bool DirectoryGetFiles(Array& results, const String& path, const Char* searchPattern, DirectorySearchOption option = DirectorySearchOption::AllDirectories); // Finds the names of directories (including their paths) that are inside the specified directory - // @param results When this metod completes, this list contains list of all filenames that match the specified search pattern + // @param results When this method completes, this list contains list of all filenames that match the specified search pattern // @param directory Path of the directory to search in it // @returns True if an error occurred, otherwise false static bool GetChildDirectories(Array& results, const String& directory); diff --git a/Source/Engine/Platform/Win32/Win32Thread.cpp b/Source/Engine/Platform/Win32/Win32Thread.cpp index 38fe00e7c..7ac9d5b60 100644 --- a/Source/Engine/Platform/Win32/Win32Thread.cpp +++ b/Source/Engine/Platform/Win32/Win32Thread.cpp @@ -88,10 +88,6 @@ bool Win32Thread::Start(uint32 stackSize) return false; } -#if PLATFORM_WINDOWS -extern LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep); -#endif - unsigned long Win32Thread::ThreadProc(void* pThis) { auto thread = (Win32Thread*)pThis; @@ -103,7 +99,7 @@ unsigned long Win32Thread::ThreadProc(void* pThis) return static_cast(exitCode); } #if PLATFORM_WINDOWS - __except (SehExceptionHandler(GetExceptionInformation())) + __except (Platform::SehExceptionHandler(GetExceptionInformation())) { return -1; } diff --git a/Source/Engine/Platform/Windows/WindowsInput.cpp b/Source/Engine/Platform/Windows/WindowsInput.cpp index c06845e68..5d4b3e12f 100644 --- a/Source/Engine/Platform/Windows/WindowsInput.cpp +++ b/Source/Engine/Platform/Windows/WindowsInput.cpp @@ -196,6 +196,12 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM switch (msg) { case WM_MOUSEMOVE: + { + OnMouseMove(mousePos, window); + result = true; + break; + } + case WM_NCMOUSEMOVE: { OnMouseMove(mousePos, window); result = true; diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index bae848d37..ba450d7ee 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -273,7 +273,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hwnd, msg, wParam, lParam); } -LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep) +long __stdcall WindowsPlatform::SehExceptionHandler(EXCEPTION_POINTERS* ep) { if (ep->ExceptionRecord->ExceptionCode == CLR_EXCEPTION) { diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.h b/Source/Engine/Platform/Windows/WindowsPlatform.h index 31e5df7ad..046b79d58 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.h +++ b/Source/Engine/Platform/Windows/WindowsPlatform.h @@ -23,6 +23,9 @@ public: /// static void* Instance; + // Native exceptions handling function. + static long __stdcall SehExceptionHandler(struct _EXCEPTION_POINTERS* ep); + public: /// diff --git a/Source/Engine/Platform/iOS/iOSPlatformSettings.h b/Source/Engine/Platform/iOS/iOSPlatformSettings.h index 12626692f..e90129955 100644 --- a/Source/Engine/Platform/iOS/iOSPlatformSettings.h +++ b/Source/Engine/Platform/iOS/iOSPlatformSettings.h @@ -46,6 +46,24 @@ API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API All = Portrait | PortraitUpsideDown | LandscapeLeft | LandscapeRight }; + /// + /// The output textures quality (compression). + /// + API_ENUM() enum class TextureQuality + { + // Raw image data without any compression algorithm. Mostly for testing or compatibility. + Uncompressed, + // ASTC 4x4 block compression. + API_ENUM(Attributes="EditorDisplay(null, \"ASTC High\")") + ASTC_High, + // ASTC 6x6 block compression. + API_ENUM(Attributes="EditorDisplay(null, \"ASTC Medium\")") + ASTC_Medium, + // ASTC 8x8 block compression. + API_ENUM(Attributes="EditorDisplay(null, \"ASTC Low\")") + ASTC_Low, + }; + /// /// The app developer name - App Store Team ID. For example: 'VG6K6HT8B'. /// @@ -64,6 +82,12 @@ API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"General\")") ExportMethods ExportMethod = ExportMethods::Development; + /// + /// The output textures quality (compression). + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"General\")") + TextureQuality TexturesQuality = TextureQuality::ASTC_Medium; + /// /// The UI interface orientation modes supported on iPhone devices. /// diff --git a/Source/Engine/Profiler/Profiler.Build.cs b/Source/Engine/Profiler/Profiler.Build.cs index 15079abf6..704612b58 100644 --- a/Source/Engine/Profiler/Profiler.Build.cs +++ b/Source/Engine/Profiler/Profiler.Build.cs @@ -34,6 +34,7 @@ public class Profiler : EngineModule case TargetPlatform.Android: case TargetPlatform.Linux: case TargetPlatform.Windows: + case TargetPlatform.Switch: options.PublicDependencies.Add("tracy"); break; } diff --git a/Source/Engine/Renderer/AntiAliasing/TAA.cpp b/Source/Engine/Renderer/AntiAliasing/TAA.cpp index f48e0955a..38e38636a 100644 --- a/Source/Engine/Renderer/AntiAliasing/TAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/TAA.cpp @@ -8,6 +8,7 @@ #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Renderer/RenderList.h" +#include "Engine/Renderer/GBufferPass.h" #include "Engine/Engine/Engine.h" PACK_STRUCT(struct Data @@ -18,6 +19,7 @@ PACK_STRUCT(struct Data float StationaryBlending; float MotionBlending; float Dummy0; + GBufferData GBuffer; }); bool TAA::Init() @@ -125,6 +127,7 @@ void TAA::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextu data.Sharpness = settings.TAA_Sharpness; data.StationaryBlending = settings.TAA_StationaryBlending * blendStrength; data.MotionBlending = settings.TAA_MotionBlending * blendStrength; + GBufferPass::SetInputs(renderContext.View, data.GBuffer); const auto cb = _shader->GetShader()->GetCB(0); context->UpdateCB(cb, &data); context->BindCB(0, cb); @@ -146,4 +149,7 @@ void TAA::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextu context->Draw(output); renderContext.Buffers->TemporalAA = outputHistory; } + + // Mark TAA jitter as resolved for future drawing + (bool&)renderContext.View.IsTaaResolved = true; } diff --git a/Source/Engine/Renderer/MotionBlurPass.cpp b/Source/Engine/Renderer/MotionBlurPass.cpp index 1b42e2afe..64f0aa80f 100644 --- a/Source/Engine/Renderer/MotionBlurPass.cpp +++ b/Source/Engine/Renderer/MotionBlurPass.cpp @@ -151,7 +151,6 @@ void MotionBlurPass::Dispose() void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext) { - // Prepare auto motionVectors = renderContext.Buffers->MotionVectors; ASSERT(motionVectors); MotionBlurSettings& settings = renderContext.List->Settings.MotionBlur; @@ -160,8 +159,6 @@ void MotionBlurPass::RenderMotionVectors(RenderContext& renderContext) const int32 screenHeight = renderContext.Buffers->GetHeight(); const int32 motionVectorsWidth = screenWidth / static_cast(settings.MotionVectorsResolution); const int32 motionVectorsHeight = screenHeight / static_cast(settings.MotionVectorsResolution); - - // Ensure to have valid data if (!renderContext.List->Setup.UseMotionVectors || checkIfSkipPass()) { // Skip pass (just clear motion vectors if texture is allocated) diff --git a/Source/Engine/Renderer/ReflectionsPass.cpp b/Source/Engine/Renderer/ReflectionsPass.cpp index 465737e87..f7e825e14 100644 --- a/Source/Engine/Renderer/ReflectionsPass.cpp +++ b/Source/Engine/Renderer/ReflectionsPass.cpp @@ -356,15 +356,11 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light { auto device = GPUDevice::Instance; auto context = device->GetMainContext(); - - // Skip pass if resources aren't ready if (checkIfSkipPass()) { + // Skip pass (just clear buffer when doing debug preview) if (renderContext.View.Mode == ViewMode::Reflections) - { context->Clear(lightBuffer, Color::Black); - } - return; } diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index af2495b67..577d58255 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -266,6 +266,8 @@ void RenderList::RunPostFxPass(GPUContext* context, RenderContext& renderContext { if (fx->Location == locationB) { + context->ResetSR(); + context->ResetUA(); if (fx->UseSingleTarget || output == nullptr) { fx->Render(context, renderContext, input, nullptr); @@ -652,6 +654,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL const auto* batchesData = list.Batches.Get(); const auto context = GPUDevice::Instance->GetMainContext(); bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing; + TaaJitterRemoveContext taaJitterRemove(renderContext.View); // Clear SR slots to prevent any resources binding issues (leftovers from the previous passes) context->ResetSR(); diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 3246b6d85..f3c4ac6d2 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -612,7 +612,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Color Grading LUT generation auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); - // Post processing + // Post-processing EyeAdaptationPass::Instance()->Render(renderContext, frameBuffer); PostProcessingPass::Instance()->Render(renderContext, frameBuffer, tempBuffer, colorGradingLUT); RenderTargetPool::Release(colorGradingLUT); diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 4dd338531..ebbdfed31 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -83,18 +83,20 @@ bool ShadowsPass::Init() // Select format for shadow maps _shadowMapFormat = PixelFormat::Unknown; +#if !PLATFORM_SWITCH // TODO: fix shadows performance issue on Switch for (const PixelFormat format : { PixelFormat::D16_UNorm, PixelFormat::D24_UNorm_S8_UInt, PixelFormat::D32_Float }) { const auto formatTexture = PixelFormatExtensions::FindShaderResourceFormat(format, false); const auto formatFeaturesDepth = GPUDevice::Instance->GetFormatFeatures(format); const auto formatFeaturesTexture = GPUDevice::Instance->GetFormatFeatures(formatTexture); - if (EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D) && + if (EnumHasAllFlags(formatFeaturesDepth.Support, FormatSupport::DepthStencil | FormatSupport::Texture2D | FormatSupport::TextureCube) && EnumHasAllFlags(formatFeaturesTexture.Support, FormatSupport::ShaderSample | FormatSupport::ShaderSampleComparison)) { _shadowMapFormat = format; break; } } +#endif if (_shadowMapFormat == PixelFormat::Unknown) LOG(Warning, "GPU doesn't support shadows rendering"); @@ -229,6 +231,9 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r #if USE_EDITOR if (IsRunningRadiancePass) blendCSM = false; +#elif PLATFORM_SWITCH || PLATFORM_IOS || PLATFORM_ANDROID + // Disable cascades blending on low-end platforms + blendCSM = false; #endif // Views with orthographic cameras cannot use cascades, we force it to 1 shadow map here diff --git a/Source/Engine/Scripting/Attributes/Editor/ValueCategoryAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/ValueCategoryAttribute.cs new file mode 100644 index 000000000..4f51efbe3 --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/ValueCategoryAttribute.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Specifies the value category of a numeric value as either as-is (a scalar), a distance (formatted as cm/m/km) or an angle (formatted with a degree sign). + /// + [Serializable] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ValueCategoryAttribute : Attribute + { + /// + /// The value category used for formatting. + /// + public Utils.ValueCategory Category; + + /// + /// Initializes a new instance of the class. + /// + private ValueCategoryAttribute() + { + Category = Utils.ValueCategory.None; + } + + /// + /// Initializes a new instance of the class. + /// + /// The value category. + public ValueCategoryAttribute(Utils.ValueCategory category) + { + Category = category; + } + } +} diff --git a/Source/Engine/Scripting/ManagedCLR/MAssembly.h b/Source/Engine/Scripting/ManagedCLR/MAssembly.h index 03887670c..169cfd7e3 100644 --- a/Source/Engine/Scripting/ManagedCLR/MAssembly.h +++ b/Source/Engine/Scripting/ManagedCLR/MAssembly.h @@ -219,7 +219,7 @@ private: bool LoadImage(const String& assemblyPath, const StringView& nativePath); bool UnloadImage(bool isReloading); void OnLoading(); - void OnLoaded(const struct DateTime& startTime); + void OnLoaded(struct Stopwatch& stopwatch); void OnLoadFailed(); bool ResolveMissingFile(String& assemblyPath) const; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 406b2f82f..0359ade87 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -10,6 +10,7 @@ #include "MProperty.h" #include "Engine/Core/Math/Math.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Profiler/ProfilerCPU.h" @@ -80,6 +81,7 @@ bool MAssembly::Load(const String& assemblyPath, const StringView& nativePath) return false; PROFILE_CPU(); ZoneText(*assemblyPath, assemblyPath.Length()); + Stopwatch stopwatch; const String* pathPtr = &assemblyPath; String path; @@ -94,7 +96,6 @@ bool MAssembly::Load(const String& assemblyPath, const StringView& nativePath) } } - const auto startTime = DateTime::NowUTC(); OnLoading(); if (LoadImage(*pathPtr, nativePath)) @@ -103,7 +104,7 @@ bool MAssembly::Load(const String& assemblyPath, const StringView& nativePath) return true; } - OnLoaded(startTime); + OnLoaded(stopwatch); return false; } @@ -173,7 +174,7 @@ void MAssembly::OnLoading() _domain = MCore::GetActiveDomain(); } -void MAssembly::OnLoaded(const DateTime& startTime) +void MAssembly::OnLoaded(Stopwatch& stopwatch) { // Register in domain _domain->_assemblies[_name] = this; @@ -181,8 +182,8 @@ void MAssembly::OnLoaded(const DateTime& startTime) _isLoaded = true; _isLoading = false; - const auto endTime = DateTime::NowUTC(); - LOG(Info, "Assembly {0} loaded in {1}ms", String(_name), (int32)(endTime - startTime).GetTotalMilliseconds()); + stopwatch.Stop(); + LOG(Info, "Assembly {0} loaded in {1}ms", String(_name), stopwatch.GetMilliseconds()); // Pre-cache classes GetClasses(); diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 3681b7228..01b43c049 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -9,6 +9,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Platform/Platform.h" #include "Engine/Platform/File.h" @@ -280,14 +281,19 @@ bool MCore::LoadEngine() { flaxLibraryPath = ::String(StringUtils::GetDirectoryName(Platform::GetExecutableFilePath())) / StringUtils::GetFileName(flaxLibraryPath); } +#endif +#if !PLATFORM_SWITCH + if (!FileSystem::FileExists(flaxLibraryPath)) + { + LOG(Error, "Flax Engine native library file is missing ({0})", flaxLibraryPath); + } #endif RegisterNativeLibrary("FlaxEngine", flaxLibraryPath.Get()); MRootDomain = New("Root"); MDomains.Add(MRootDomain); - void* GetRuntimeInformationPtr = GetStaticMethodPointer(TEXT("GetRuntimeInformation")); - char* buildInfo = CallStaticMethod(GetRuntimeInformationPtr); + char* buildInfo = CallStaticMethod(GetStaticMethodPointer(TEXT("GetRuntimeInformation"))); LOG(Info, ".NET runtime version: {0}", ::String(buildInfo)); MCore::GC::FreeMemory(buildInfo); @@ -658,7 +664,7 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const if (_hasCachedClasses || !IsLoaded()) return _classes; PROFILE_CPU(); - const auto startTime = DateTime::NowUTC(); + Stopwatch stopwatch; #if TRACY_ENABLE ZoneText(*_name, _name.Length()); @@ -693,8 +699,8 @@ const MAssembly::ClassesDictionary& MAssembly::GetClasses() const MCore::GC::FreeMemory(managedClasses); - const auto endTime = DateTime::NowUTC(); - LOG(Info, "Caching classes for assembly {0} took {1}ms", String(_name), (int32)(endTime - startTime).GetTotalMilliseconds()); + stopwatch.Stop(); + LOG(Info, "Caching classes for assembly {0} took {1}ms", String(_name), stopwatch.GetMilliseconds()); #if 0 for (auto i = _classes.Begin(); i.IsNotEnd(); ++i) @@ -763,7 +769,7 @@ bool MAssembly::LoadCorlib() Unload(); // Start - const auto startTime = DateTime::NowUTC(); + Stopwatch stopwatch; OnLoading(); // Load @@ -781,7 +787,7 @@ bool MAssembly::LoadCorlib() CachedAssemblyHandles.Add(_handle, this); // End - OnLoaded(startTime); + OnLoaded(stopwatch); return false; } @@ -1700,12 +1706,12 @@ bool InitHostfxr() // Warn user about missing .Net #if PLATFORM_DESKTOP - Platform::OpenUrl(TEXT("https://dotnet.microsoft.com/en-us/download/dotnet/7.0")); + Platform::OpenUrl(TEXT("https://dotnet.microsoft.com/en-us/download/dotnet/8.0")); #endif #if USE_EDITOR - LOG(Fatal, "Missing .NET 7 or later SDK installation required to run Flax Editor."); + LOG(Fatal, "Missing .NET 8 or later SDK installation required to run Flax Editor."); #else - LOG(Fatal, "Missing .NET 7 or later Runtime installation required to run this application."); + LOG(Fatal, "Missing .NET 8 or later Runtime installation required to run this application."); #endif return true; } @@ -1817,7 +1823,6 @@ void* GetStaticMethodPointer(const String& methodName) void OnLogCallback(const char* logDomain, const char* logLevel, const char* message, mono_bool fatal, void* userData) { - String currentDomain(logDomain); String msg(message); msg.Replace('\n', ' '); @@ -1845,19 +1850,6 @@ void OnLogCallback(const char* logDomain, const char* logLevel, const char* mess } } - if (currentDomain.IsEmpty()) - { - auto domain = MCore::GetActiveDomain(); - if (domain != nullptr) - { - currentDomain = domain->GetName().Get(); - } - else - { - currentDomain = "null"; - } - } - #if 0 // Print C# stack trace (crash may be caused by the managed code) if (mono_domain_get() && Assemblies::FlaxEngine.Assembly->IsLoaded()) @@ -1871,22 +1863,25 @@ void OnLogCallback(const char* logDomain, const char* logLevel, const char* mess } #endif - if (errorLevel == 0) + if (errorLevel <= 2) { - Log::CLRInnerException(String::Format(TEXT("Message: {0} | Domain: {1}"), msg, currentDomain)).SetLevel(LogType::Error); - } - else if (errorLevel <= 2) - { - Log::CLRInnerException(String::Format(TEXT("Message: {0} | Domain: {1}"), msg, currentDomain)).SetLevel(LogType::Error); + Log::CLRInnerException(String::Format(TEXT("[Mono] {0}"), msg)).SetLevel(LogType::Error); } else if (errorLevel <= 3) { - LOG(Warning, "Message: {0} | Domain: {1}", msg, currentDomain); + LOG(Warning, "[Mono] {0}", msg); } else { - LOG(Info, "Message: {0} | Domain: {1}", msg, currentDomain); + LOG(Info, "[Mono] {0}", msg); } +#if DOTNET_HOST_MONO && !BUILD_RELEASE + if (errorLevel <= 2) + { + // Mono backend ends with fatal assertions so capture crash info (eg. stack trace) + CRASH; + } +#endif } void OnPrintCallback(const char* string, mono_bool isStdout) @@ -2036,14 +2031,14 @@ bool InitHostfxr() #endif // Platform-specific setup -#if PLATFORM_IOS +#if PLATFORM_IOS || PLATFORM_SWITCH setenv("MONO_AOT_MODE", "aot", 1); setenv("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1", 1); #endif #ifdef USE_MONO_AOT_MODULE // Load AOT module - const DateTime aotModuleLoadStartTime = DateTime::Now(); + Stopwatch aotModuleLoadStopwatch; LOG(Info, "Loading Mono AOT module..."); void* libAotModule = Platform::LoadLibrary(TEXT(USE_MONO_AOT_MODULE)); if (libAotModule == nullptr) @@ -2068,7 +2063,8 @@ bool InitHostfxr() mono_aot_register_module((void**)modules[i]); } Allocator::Free(modules); - LOG(Info, "Mono AOT module loaded in {0}ms", (int32)(DateTime::Now() - aotModuleLoadStartTime).GetTotalMilliseconds()); + aotModuleLoadStopwatch.Stop(); + LOG(Info, "Mono AOT module loaded in {0}ms", aotModuleLoadStopwatch.GetMilliseconds()); #endif // Setup debugger diff --git a/Source/Engine/Scripting/Runtime/Mono.cpp b/Source/Engine/Scripting/Runtime/Mono.cpp index 4c9e31b3e..2e507a44a 100644 --- a/Source/Engine/Scripting/Runtime/Mono.cpp +++ b/Source/Engine/Scripting/Runtime/Mono.cpp @@ -7,6 +7,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Engine/Globals.h" @@ -1089,7 +1090,7 @@ bool MAssembly::Load(MonoImage* monoImage) Unload(); // Start - const auto startTime = DateTime::NowUTC(); + Stopwatch stopwatch; OnLoading(); // Load @@ -1103,7 +1104,7 @@ bool MAssembly::Load(MonoImage* monoImage) _hasCachedClasses = false; // End - OnLoaded(startTime); + OnLoaded(stopwatch); return false; } diff --git a/Source/Engine/Scripting/Scripting.Build.cs b/Source/Engine/Scripting/Scripting.Build.cs index ff0ec5e45..71c6191f1 100644 --- a/Source/Engine/Scripting/Scripting.Build.cs +++ b/Source/Engine/Scripting/Scripting.Build.cs @@ -41,7 +41,7 @@ public class Scripting : EngineModule if (options.Target is EngineTarget engineTarget && engineTarget.UseSeparateMainExecutable(options)) { // Build target doesn't support linking again main executable (eg. Linux) thus additional shared library is used for the engine (eg. libFlaxEditor.so) - var fileName = options.Platform.GetLinkOutputFileName(engineTarget.OutputName, LinkerOutput.SharedLibrary); + var fileName = options.Platform.GetLinkOutputFileName(EngineTarget.LibraryName, LinkerOutput.SharedLibrary); options.CompileEnv.PreprocessorDefinitions.Add("MCORE_MAIN_MODULE_NAME=" + fileName); } } diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index 31e400906..d6f2524c7 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -23,7 +23,7 @@ #include "Internal/StdTypesContainer.h" #include "Engine/Core/ObjectsRemovalService.h" #include "Engine/Core/Types/TimeSpan.h" -#include "Engine/Profiler/ProfilerCPU.h" +#include "Engine/Core/Types/Stopwatch.h" #include "Engine/Content/Asset.h" #include "Engine/Content/Content.h" #include "Engine/Engine/EngineService.h" @@ -31,6 +31,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Serialization/JsonTools.h" +#include "Engine/Profiler/ProfilerCPU.h" extern void registerFlaxEngineInternalCalls(); @@ -126,7 +127,7 @@ void onEngineUnloading(MAssembly* assembly); bool ScriptingService::Init() { - const auto startTime = DateTime::NowUTC(); + Stopwatch stopwatch; // Initialize managed runtime if (MCore::LoadEngine()) @@ -158,9 +159,8 @@ bool ScriptingService::Init() return true; } - auto endTime = DateTime::NowUTC(); - LOG(Info, "Scripting Engine initializated! (time: {0}ms)", (int32)((endTime - startTime).GetTotalMilliseconds())); - + stopwatch.Stop(); + LOG(Info, "Scripting Engine initializated! (time: {0}ms)", stopwatch.GetMilliseconds()); return false; } @@ -357,7 +357,7 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde if (!module) { // Load library - const auto startTime = DateTime::NowUTC(); + Stopwatch stopwatch; #if PLATFORM_ANDROID || PLATFORM_MAC // On some platforms all native binaries are side-by-side with the app in a different folder if (!FileSystem::FileExists(nativePath)) @@ -390,8 +390,8 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde LOG(Error, "Failed to setup library '{0}' for binary module {1}.", nativePath, name); return true; } - const auto endTime = DateTime::NowUTC(); - LOG(Info, "Module {0} loaded in {1}ms", name, (int32)(endTime - startTime).GetTotalMilliseconds()); + stopwatch.Stop(); + LOG(Info, "Module {0} loaded in {1}ms", name, stopwatch.GetMilliseconds()); // Get the binary module module = getBinaryFunc(); diff --git a/Source/Engine/Scripting/Scripting.cs b/Source/Engine/Scripting/Scripting.cs index f99aacf56..66a34faf0 100644 --- a/Source/Engine/Scripting/Scripting.cs +++ b/Source/Engine/Scripting/Scripting.cs @@ -180,8 +180,8 @@ namespace FlaxEngine private static void OnLocalizationChanged() { - // iOS uses globalization-invariant mode so ignore it -#if !PLATFORM_IOS + // Invariant-globalization only (see InitHostfxr with Mono) +#if !(PLATFORM_IOS || PLATFORM_SWITCH) var currentThread = Thread.CurrentThread; var language = Localization.CurrentLanguage; if (language != null) @@ -238,6 +238,10 @@ namespace FlaxEngine internal static ManagedHandle CultureInfoToManaged(int lcid) { +#if PLATFORM_IOS || PLATFORM_SWITCH + // Invariant-globalization only (see InitHostfxr with Mono) + lcid = 0; +#endif return ManagedHandle.Alloc(new CultureInfo(lcid)); } diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index ba880f81c..0eafa2451 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -433,7 +433,7 @@ bool ScriptingObject::CanCast(const MClass* from, const MClass* to) return true; CHECK_RETURN(from && to, false); -#if PLATFORM_LINUX || PLATFORM_MAC +#if DOTNET_HOST_MONO // Cannot enter GC unsafe region if the thread is not attached MCore::Thread::Attach(); #endif diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index b86b556dc..0e4b88e50 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -246,15 +246,7 @@ namespace FlaxEngine.Json /// The output json string. public static string Serialize(object obj, bool isManagedOnly = false) { - Type type = obj.GetType(); - var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value; - Current.Value = cache; - - cache.WriteBegin(); - cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type); - cache.WriteEnd(); - - return cache.StringBuilder.ToString(); + return Serialize(obj, obj.GetType(), isManagedOnly); } /// diff --git a/Source/Engine/ShadowsOfMordor/Builder.h b/Source/Engine/ShadowsOfMordor/Builder.h index 9d13232d2..ae5c158c8 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.h +++ b/Source/Engine/ShadowsOfMordor/Builder.h @@ -4,6 +4,7 @@ #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/Shader.h" +#include "Engine/Core/Types/DateTime.h" #include "Engine/CSG/CSGMesh.h" #include "Builder.Config.h" diff --git a/Source/Engine/Streaming/IStreamingHandler.h b/Source/Engine/Streaming/IStreamingHandler.h index 924c22eee..6e8c89886 100644 --- a/Source/Engine/Streaming/IStreamingHandler.h +++ b/Source/Engine/Streaming/IStreamingHandler.h @@ -13,19 +13,15 @@ class StreamableResource; class FLAXENGINE_API IStreamingHandler { public: - virtual ~IStreamingHandler() = default; -public: - /// /// Calculates target quality level (0-1) for the given resource. /// /// The resource. - /// The current time and date. /// The current platform time (seconds). /// Target quality (0-1). - virtual float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) = 0; + virtual float CalculateTargetQuality(StreamableResource* resource, double currentTime) = 0; /// /// Calculates the residency level for a given resource and quality level. diff --git a/Source/Engine/Streaming/StreamableResource.h b/Source/Engine/Streaming/StreamableResource.h index f9ac8b618..f88dcf90d 100644 --- a/Source/Engine/Streaming/StreamableResource.h +++ b/Source/Engine/Streaming/StreamableResource.h @@ -110,8 +110,8 @@ public: struct StreamingCache { - int64 LastUpdate = 0; - int64 TargetResidencyChange = 0; + double LastUpdateTime = 0.0; + double TargetResidencyChangeTime = 0; int32 TargetResidency = 0; bool Error = false; SamplesBuffer QualitySamples; diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp index 5d8f2835f..fa5112d70 100644 --- a/Source/Engine/Streaming/Streaming.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -81,14 +81,14 @@ StreamableResource::~StreamableResource() void StreamableResource::RequestStreamingUpdate() { - Streaming.LastUpdate = 0; + Streaming.LastUpdateTime = 0.0; } void StreamableResource::ResetStreaming(bool error) { Streaming.Error = error; Streaming.TargetResidency = 0; - Streaming.LastUpdate = DateTime::MaxValue().Ticks; + Streaming.LastUpdateTime = 3e+30f; // Very large number to skip any updates } void StreamableResource::StartStreaming(bool isDynamic) @@ -115,7 +115,7 @@ void StreamableResource::StopStreaming() } } -void UpdateResource(StreamableResource* resource, DateTime now, double currentTime) +void UpdateResource(StreamableResource* resource, double currentTime) { ASSERT(resource && resource->CanBeUpdated()); @@ -127,7 +127,7 @@ void UpdateResource(StreamableResource* resource, DateTime now, double currentTi float targetQuality = 1.0f; if (resource->IsDynamic()) { - targetQuality = handler->CalculateTargetQuality(resource, now, currentTime); + targetQuality = handler->CalculateTargetQuality(resource, currentTime); targetQuality = Math::Saturate(targetQuality); } @@ -142,14 +142,14 @@ void UpdateResource(StreamableResource* resource, DateTime now, double currentTi auto allocatedResidency = resource->GetAllocatedResidency(); auto targetResidency = handler->CalculateResidency(resource, targetQuality); ASSERT(allocatedResidency >= currentResidency && allocatedResidency >= 0); - resource->Streaming.LastUpdate = now.Ticks; + resource->Streaming.LastUpdateTime = currentTime; // Check if a target residency level has been changed if (targetResidency != resource->Streaming.TargetResidency) { // Register change resource->Streaming.TargetResidency = targetResidency; - resource->Streaming.TargetResidencyChange = now.Ticks; + resource->Streaming.TargetResidencyChangeTime = currentTime; } // Check if need to change resource current residency @@ -224,15 +224,14 @@ void StreamingSystem::Job(int32 index) PROFILE_CPU_NAMED("Streaming.Job"); // TODO: use streaming settings - TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(100); + const double ResourceUpdatesInterval = 0.1; int32 MaxResourcesPerUpdate = 50; // Start update ScopeLock lock(ResourcesLock); - auto now = DateTime::NowUTC(); const int32 resourcesCount = Resources.Count(); int32 resourcesUpdates = Math::Min(MaxResourcesPerUpdate, resourcesCount); - double currentTime = Platform::GetTimeSeconds(); + const double currentTime = Platform::GetTimeSeconds(); // Update high priority queue and then rest of the resources // Note: resources in the update queue are updated always, while others only between specified intervals @@ -248,9 +247,9 @@ void StreamingSystem::Job(int32 index) const auto resource = Resources[LastUpdateResourcesIndex]; // Try to update it - if (now - DateTime(resource->Streaming.LastUpdate) >= ResourceUpdatesInterval && resource->CanBeUpdated()) + if (currentTime - resource->Streaming.LastUpdateTime >= ResourceUpdatesInterval && resource->CanBeUpdated()) { - UpdateResource(resource, now, currentTime); + UpdateResource(resource, currentTime); resourcesUpdates--; } } diff --git a/Source/Engine/Streaming/StreamingHandlers.cpp b/Source/Engine/Streaming/StreamingHandlers.cpp index 23dd37e90..0b10fba1b 100644 --- a/Source/Engine/Streaming/StreamingHandlers.cpp +++ b/Source/Engine/Streaming/StreamingHandlers.cpp @@ -11,7 +11,7 @@ #include "Engine/Audio/Audio.h" #include "Engine/Audio/AudioSource.h" -float TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +float TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, double currentTime) { ASSERT(resource); auto& texture = *(StreamingTexture*)resource; @@ -59,7 +59,7 @@ int32 TexturesStreamingHandler::CalculateResidency(StreamableResource* resource, if (mipLevels > 0 && mipLevels < texture._minMipCountBlockCompressed && texture._isBlockCompressed) { - // Block compressed textures require minimum size of 4 + // Block compressed textures require minimum size of block size (eg. 4 for BC formats) mipLevels = texture._minMipCountBlockCompressed; } @@ -93,7 +93,7 @@ int32 TexturesStreamingHandler::CalculateRequestedResidency(StreamableResource* return residency; } -float ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +float ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, double currentTime) { // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options return 1.0f; @@ -132,7 +132,7 @@ int32 ModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* re return residency; } -float SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +float SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, double currentTime) { // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options return 1.0f; @@ -171,7 +171,7 @@ int32 SkinnedModelsStreamingHandler::CalculateRequestedResidency(StreamableResou return residency; } -float AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) +float AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, double currentTime) { // Audio clips don't use quality but only residency return 1.0f; diff --git a/Source/Engine/Streaming/StreamingHandlers.h b/Source/Engine/Streaming/StreamingHandlers.h index bb5d11421..436a9a8e5 100644 --- a/Source/Engine/Streaming/StreamingHandlers.h +++ b/Source/Engine/Streaming/StreamingHandlers.h @@ -11,7 +11,7 @@ class FLAXENGINE_API TexturesStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + float CalculateTargetQuality(StreamableResource* resource, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; }; @@ -23,7 +23,7 @@ class FLAXENGINE_API ModelsStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + float CalculateTargetQuality(StreamableResource* resource, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; }; @@ -35,7 +35,7 @@ class FLAXENGINE_API SkinnedModelsStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + float CalculateTargetQuality(StreamableResource* resource, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; }; @@ -47,7 +47,7 @@ class FLAXENGINE_API AudioStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; + float CalculateTargetQuality(StreamableResource* resource, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) override; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 053ab10e0..d2c4eaa94 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2148,7 +2148,9 @@ bool TerrainPatch::CreateHeightField() if (collisionHeader->CheckOldMagicNumber != MAX_int32 || collisionHeader->Version != TerrainCollisionDataHeader::CurrentVersion) { // Reset height map - return InitializeHeightMap(); + PROFILE_CPU_NAMED("ResetHeightMap"); + const float* data = GetHeightmapData(); + return SetupHeightMap(_cachedHeightMap.Count(), data); } // Create heightfield object from the data @@ -2580,7 +2582,7 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo void TerrainPatch::OnPhysicsSceneChanged(PhysicsScene* previous) { - PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor); + PhysicsBackend::RemoveSceneActor(previous->GetPhysicsScene(), _physicsActor, true); void* scene = _terrain->GetPhysicsScene()->GetPhysicsScene(); PhysicsBackend::AddSceneActor(scene, _physicsActor); } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 508417895..66d24d4f3 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -421,15 +421,31 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) // DDX case 30: { - const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); - value = writeLocal(inValue.Type, String::Format(TEXT("ddx({0})"), inValue.Value), node); + if (_treeType == MaterialTreeType::PixelShader) + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + value = writeLocal(inValue.Type, String::Format(TEXT("ddx({0})"), inValue.Value), node); + } + else + { + // No derivatives support in VS/DS + value = Value::Zero; + } break; } // DDY case 31: { - const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); - value = writeLocal(inValue.Type, String::Format(TEXT("ddy({0})"), inValue.Value), node); + if (_treeType == MaterialTreeType::PixelShader) + { + const auto inValue = tryGetValue(node->GetBox(0), 0, Value::Zero); + value = writeLocal(inValue.Type, String::Format(TEXT("ddy({0})"), inValue.Value), node); + } + else + { + // No derivatives support in VS/DS + value = Value::Zero; + } break; } // Sign diff --git a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs index b84377b08..b14f197d7 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.Build.cs +++ b/Source/Engine/Tools/TextureTool/TextureTool.Build.cs @@ -58,6 +58,12 @@ public class TextureTool : EngineModule options.PrivateDependencies.Add("bc7enc16"); } } + if (options.Target.IsEditor && astc.IsSupported(options)) + { + // ASTC for mobile (iOS and Android) + options.SourceFiles.Add(Path.Combine(FolderPath, "TextureTool.astc.cpp")); + options.PrivateDependencies.Add("astc"); + } options.PublicDefinitions.Add("COMPILE_WITH_TEXTURE_TOOL"); } diff --git a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp index f105a34cc..485d4ec6f 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.DirectXTex.cpp @@ -834,6 +834,16 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path bool TextureTool::ConvertDirectXTex(TextureData& dst, const TextureData& src, const PixelFormat dstFormat) { + if (PixelFormatExtensions::IsCompressedASTC(dstFormat)) + { +#if COMPILE_WITH_ASTC + return ConvertAstc(dst, src, dstFormat); +#else + LOG(Error, "Missing ASTC texture format compression lib."); + return true; +#endif + } + HRESULT result; DirectX::ScratchImage dstImage; DirectX::ScratchImage tmpImage; diff --git a/Source/Engine/Tools/TextureTool/TextureTool.astc.cpp b/Source/Engine/Tools/TextureTool/TextureTool.astc.cpp new file mode 100644 index 000000000..32fd9ecf1 --- /dev/null +++ b/Source/Engine/Tools/TextureTool/TextureTool.astc.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#if COMPILE_WITH_TEXTURE_TOOL && COMPILE_WITH_ASTC + +#include "TextureTool.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Math/Math.h" +#include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Graphics/PixelFormatExtensions.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Profiler/ProfilerCPU.h" +#include + +bool TextureTool::ConvertAstc(TextureData& dst, const TextureData& src, const PixelFormat dstFormat) +{ + PROFILE_CPU(); + ASSERT(PixelFormatExtensions::IsCompressedASTC(dstFormat)); + const int32 blockSize = PixelFormatExtensions::ComputeBlockSize(dstFormat); + const int32 bytesPerBlock = 16; // All ASTC blocks use 128 bits + + // Configure the compressor run + const bool isSRGB = PixelFormatExtensions::IsSRGB(dstFormat); + const bool isHDR = PixelFormatExtensions::IsHDR(src.Format); + astcenc_profile astcProfile = isHDR ? ASTCENC_PRF_HDR_RGB_LDR_A : (isSRGB ? ASTCENC_PRF_LDR_SRGB : ASTCENC_PRF_LDR); + float astcQuality = ASTCENC_PRE_MEDIUM; + unsigned int astcFlags = 0; // TODO: add custom flags support for converter to handle ASTCENC_FLG_MAP_NORMAL + astcenc_config astcConfig; + astcenc_error astcError = astcenc_config_init(astcProfile, blockSize, blockSize, 1, astcQuality, astcFlags, &astcConfig); + if (astcError != ASTCENC_SUCCESS) + { + LOG(Warning, "Cannot compress image. ASTC failed with error: {}", String(astcenc_get_error_string(astcError))); + return true; + } + astcenc_swizzle astcSwizzle = { ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A }; + if (!PixelFormatExtensions::HasAlpha(src.Format)) + { + // No alpha channel in use so fill with 1 + astcSwizzle.a = ASTCENC_SWZ_1; + } + + // Allocate working state given config and thread_count + astcenc_context* astcContext; + astcError = astcenc_context_alloc(&astcConfig, 1, &astcContext); + if (astcError != ASTCENC_SUCCESS) + { + LOG(Warning, "Cannot compress image. ASTC failed with error: {}", String(astcenc_get_error_string(astcError))); + return true; + } + TextureData const* textureData = &src; + TextureData converted; + + // Encoder uses full 4-component RGBA input image so convert it if needed + if (PixelFormatExtensions::ComputeComponentsCount(src.Format) != 4 || + PixelFormatExtensions::IsCompressed(textureData->Format) || + !PixelFormatExtensions::IsRgbAOrder(textureData->Format)) + { + if (textureData != &src) + converted = src; + const PixelFormat tempFormat = isHDR ? PixelFormat::R16G16B16A16_Float : (PixelFormatExtensions::IsSRGB(src.Format) ? PixelFormat::R8G8B8A8_UNorm_sRGB : PixelFormat::R8G8B8A8_UNorm); + if (!TextureTool::Convert(converted, *textureData, tempFormat)) + textureData = &converted; + } + + // When converting from non-sRGB to sRGB we need to change the color-space manually (otherwise image is dark) + if (PixelFormatExtensions::IsSRGB(src.Format) != isSRGB) + { + if (textureData != &src) + converted = src; + Function transform = [](Color& c) + { + c = Color::LinearToSrgb(c); + }; + if (!TextureTool::Transform(converted, transform)) + textureData = &converted; + } + + // Setup output + dst.Items.Resize(textureData->Items.Count()); + dst.Width = textureData->Width; + dst.Height = textureData->Height; + dst.Depth = 1; + dst.Format = dstFormat; + + // Compress all array slices + for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count() && astcError == ASTCENC_SUCCESS; arrayIndex++) + { + const auto& srcSlice = textureData->Items[arrayIndex]; + auto& dstSlice = dst.Items[arrayIndex]; + auto mipLevels = srcSlice.Mips.Count(); + dstSlice.Mips.Resize(mipLevels, false); + + // Compress all mip levels + for (int32 mipIndex = 0; mipIndex < mipLevels && astcError == ASTCENC_SUCCESS; mipIndex++) + { + const auto& srcMip = srcSlice.Mips[mipIndex]; + auto& dstMip = dstSlice.Mips[mipIndex]; + auto mipWidth = Math::Max(textureData->Width >> mipIndex, 1); + auto mipHeight = Math::Max(textureData->Height >> mipIndex, 1); + auto blocksWidth = Math::Max(Math::DivideAndRoundUp(mipWidth, blockSize), 1); + auto blocksHeight = Math::Max(Math::DivideAndRoundUp(mipHeight, blockSize), 1); + uint32 mipRowPitch, mipSlicePitch; + RenderTools::ComputePitch(textureData->Format, mipWidth, mipHeight, mipRowPitch, mipSlicePitch); + ASSERT(srcMip.RowPitch == mipRowPitch); + ASSERT(srcMip.DepthPitch == mipSlicePitch); + ASSERT(srcMip.Lines == mipHeight); + + // Allocate memory + dstMip.RowPitch = blocksWidth * bytesPerBlock; + dstMip.DepthPitch = dstMip.RowPitch * blocksHeight; + dstMip.Lines = blocksHeight; + dstMip.Data.Allocate(dstMip.DepthPitch); + + // Compress image + astcenc_image astcInput; + astcInput.dim_x = mipWidth; + astcInput.dim_y = mipHeight; + astcInput.dim_z = 1; + astcInput.data_type = isHDR ? ASTCENC_TYPE_F16 : ASTCENC_TYPE_U8; + void* srcData = (void*)srcMip.Data.Get(); + astcInput.data = &srcData; + astcError = astcenc_compress_image(astcContext, &astcInput, &astcSwizzle, dstMip.Data.Get(), dstMip.Data.Length(), 0); + if (astcError == ASTCENC_SUCCESS) + astcError = astcenc_compress_reset(astcContext); + } + } + + // Clean up + if (astcError != ASTCENC_SUCCESS) + { + LOG(Warning, "Cannot compress image. ASTC failed with error: {}", String(astcenc_get_error_string(astcError))); + return true; + } + astcenc_context_free(astcContext); + return astcError != ASTCENC_SUCCESS; +} + +#endif diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index 3099d011d..ca580ca7e 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -15,6 +15,7 @@ #include "Engine/Scripting/Enums.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" +#include "Engine/Profiler/ProfilerCPU.h" #if USE_EDITOR #include "Engine/Core/Collections/Dictionary.h" @@ -183,6 +184,7 @@ bool TextureTool::HasAlpha(const StringView& path) bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData) { + PROFILE_CPU(); LOG(Info, "Importing texture from \'{0}\'", path); const auto startTime = DateTime::NowUTC(); @@ -219,6 +221,7 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData, Options options, String& errorMsg) { + PROFILE_CPU(); LOG(Info, "Importing texture from \'{0}\'. Options: {1}", path, options.ToString()); const auto startTime = DateTime::NowUTC(); @@ -267,9 +270,9 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData bool TextureTool::ExportTexture(const StringView& path, const TextureData& textureData) { + PROFILE_CPU(); LOG(Info, "Exporting texture to \'{0}\'.", path); const auto startTime = DateTime::NowUTC(); - ImageType type; if (GetImageType(path, type)) return true; @@ -279,7 +282,6 @@ bool TextureTool::ExportTexture(const StringView& path, const TextureData& textu return true; } - // Export #if COMPILE_WITH_DIRECTXTEX const auto failed = ExportTextureDirectXTex(type, path, textureData); #elif COMPILE_WITH_STB @@ -303,7 +305,6 @@ bool TextureTool::ExportTexture(const StringView& path, const TextureData& textu bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelFormat dstFormat) { - // Validate input if (src.GetMipLevels() == 0) { LOG(Warning, "Missing source data."); @@ -319,6 +320,7 @@ bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelF LOG(Warning, "Converting volume texture data is not supported."); return true; } + PROFILE_CPU(); #if COMPILE_WITH_DIRECTXTEX return ConvertDirectXTex(dst, src, dstFormat); @@ -332,7 +334,6 @@ bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelF bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight) { - // Validate input if (src.GetMipLevels() == 0) { LOG(Warning, "Missing source data."); @@ -348,7 +349,7 @@ bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidt LOG(Warning, "Resizing volume texture data is not supported."); return true; } - + PROFILE_CPU(); #if COMPILE_WITH_DIRECTXTEX return ResizeDirectXTex(dst, src, dstWidth, dstHeight); #elif COMPILE_WITH_STB @@ -775,4 +776,31 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type) return false; } +bool TextureTool::Transform(TextureData& texture, const Function& transformation) +{ + PROFILE_CPU(); + auto sampler = TextureTool::GetSampler(texture.Format); + if (!sampler) + return true; + for (auto& slice : texture.Items) + { + for (int32 mipIndex = 0; mipIndex < slice.Mips.Count(); mipIndex++) + { + auto& mip = slice.Mips[mipIndex]; + auto mipWidth = Math::Max(texture.Width >> mipIndex, 1); + auto mipHeight = Math::Max(texture.Height >> mipIndex, 1); + for (int32 y = 0; y < mipHeight; y++) + { + for (int32 x = 0; x < mipWidth; x++) + { + Color color = TextureTool::SamplePoint(sampler, x, y, mip.Data.Get(), mip.RowPitch); + transformation(color); + TextureTool::Store(sampler, x, y, mip.Data.Get(), mip.RowPitch, color); + } + } + } + } + return false; +} + #endif diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index bb12effaa..8aec3eb32 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -256,6 +256,7 @@ private: }; static bool GetImageType(const StringView& path, ImageType& type); + static bool Transform(TextureData& texture, const Function& transformation); #if COMPILE_WITH_DIRECTXTEX static bool ExportTextureDirectXTex(ImageType type, const StringView& path, const TextureData& textureData); @@ -272,6 +273,9 @@ private: static bool ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight); static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight); #endif +#if COMPILE_WITH_ASTC + static bool ConvertAstc(TextureData& dst, const TextureData& src, const PixelFormat dstFormat); +#endif }; #endif diff --git a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp index ded29b73e..427521f2f 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.stb.cpp @@ -561,7 +561,7 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix } #if USE_EDITOR - if (PixelFormatExtensions::IsCompressed(dstFormat)) + if (PixelFormatExtensions::IsCompressedBC(dstFormat)) { int32 bytesPerBlock; switch (dstFormat) @@ -662,6 +662,17 @@ bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const Pix } } } + else if (PixelFormatExtensions::IsCompressedASTC(dstFormat)) + { +#if COMPILE_WITH_ASTC + if (ConvertAstc(dst, *textureData, dstFormat)) +#else + LOG(Error, "Missing ASTC texture format compression lib."); +#endif + { + return true; + } + } else #endif { diff --git a/Source/Engine/UI/GUI/CanvasScaler.cs b/Source/Engine/UI/GUI/CanvasScaler.cs index f0cc59b10..612de3f59 100644 --- a/Source/Engine/UI/GUI/CanvasScaler.cs +++ b/Source/Engine/UI/GUI/CanvasScaler.cs @@ -429,6 +429,13 @@ namespace FlaxEngine.GUI return ContainsPoint(ref location); } + /// + public override bool IntersectsChildContent(Control child, Float2 location, out Float2 childSpaceLocation) + { + location /= _scale; + return base.IntersectsChildContent(child, location, out childSpaceLocation); + } + /// public override bool ContainsPoint(ref Float2 location, bool precise = false) { @@ -462,97 +469,6 @@ namespace FlaxEngine.GUI return result; } - /// - public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) - { - location /= _scale; - return base.OnDragEnter(ref location, data); - } - - /// - public override DragDropEffect OnDragMove(ref Float2 location, DragData data) - { - location /= _scale; - return base.OnDragMove(ref location, data); - } - - /// - public override DragDropEffect OnDragDrop(ref Float2 location, DragData data) - { - location /= _scale; - return base.OnDragDrop(ref location, data); - } - - /// - public override void OnMouseEnter(Float2 location) - { - location /= _scale; - base.OnMouseEnter(location); - } - - /// - public override void OnMouseMove(Float2 location) - { - location /= _scale; - base.OnMouseMove(location); - } - - /// - public override bool OnMouseDown(Float2 location, MouseButton button) - { - location /= _scale; - return base.OnMouseDown(location, button); - } - - /// - public override bool OnMouseUp(Float2 location, MouseButton button) - { - location /= _scale; - return base.OnMouseUp(location, button); - } - - /// - public override bool OnMouseDoubleClick(Float2 location, MouseButton button) - { - location /= _scale; - return base.OnMouseDoubleClick(location, button); - } - - /// - public override bool OnMouseWheel(Float2 location, float delta) - { - location /= _scale; - return base.OnMouseWheel(location, delta); - } - - /// - public override void OnTouchEnter(Float2 location, int pointerId) - { - location /= _scale; - base.OnTouchEnter(location, pointerId); - } - - /// - public override void OnTouchMove(Float2 location, int pointerId) - { - location /= _scale; - base.OnTouchMove(location, pointerId); - } - - /// - public override bool OnTouchDown(Float2 location, int pointerId) - { - location /= _scale; - return base.OnTouchDown(location, pointerId); - } - - /// - public override bool OnTouchUp(Float2 location, int pointerId) - { - location /= _scale; - return base.OnTouchUp(location, pointerId); - } - #endregion } } diff --git a/Source/Engine/UI/GUI/Common/TextBox.cs b/Source/Engine/UI/GUI/Common/TextBox.cs index ec44a3ca8..2553dc439 100644 --- a/Source/Engine/UI/GUI/Common/TextBox.cs +++ b/Source/Engine/UI/GUI/Common/TextBox.cs @@ -170,14 +170,15 @@ namespace FlaxEngine.GUI { var leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout); var rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout); - float fontHeight = font.Height / DpiScale; + var fontHeight = font.Height; + var textHeight = fontHeight / DpiScale; // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha *= alpha; Color selectionColor = SelectionColor * alpha; // - int selectedLinesCount = 1 + Mathf.FloorToInt((rightEdge.Y - leftEdge.Y) / fontHeight); + int selectedLinesCount = 1 + Mathf.FloorToInt((rightEdge.Y - leftEdge.Y) / textHeight); if (selectedLinesCount == 1) { // Selected is part of single line @@ -194,7 +195,7 @@ namespace FlaxEngine.GUI // for (int i = 3; i <= selectedLinesCount; i++) { - leftEdge.Y += fontHeight; + leftEdge.Y += textHeight; Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, fontHeight); Render2D.FillRectangle(r, selectionColor); } diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index c22099011..460d6f4b1 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -474,7 +474,7 @@ namespace FlaxEngine.GUI caretPos.X - (caretWidth * 0.5f), caretPos.Y, caretWidth, - height); + height * DpiScale); } } @@ -1265,7 +1265,7 @@ namespace FlaxEngine.GUI // Multiline scroll if (IsMultiline && _text.Length != 0 && IsMultilineScrollable) { - TargetViewOffset = Float2.Clamp(_targetViewOffset - new Float2(0, delta * 10.0f), Float2.Zero, new Float2(_targetViewOffset.X, _textSize.Y)); + TargetViewOffset = Float2.Clamp(_targetViewOffset - new Float2(0, delta * 10.0f), Float2.Zero, new Float2(_targetViewOffset.X, _textSize.Y - Height)); return true; } @@ -1456,6 +1456,7 @@ namespace FlaxEngine.GUI { // Insert new line Insert('\n'); + ScrollToCaret(); } else if (!IsNavFocused) { diff --git a/Source/Engine/UI/GUI/ContainerControl.cs b/Source/Engine/UI/GUI/ContainerControl.cs index 917d200da..c53307c65 100644 --- a/Source/Engine/UI/GUI/ContainerControl.cs +++ b/Source/Engine/UI/GUI/ContainerControl.cs @@ -356,7 +356,7 @@ namespace FlaxEngine.GUI for (int i = _children.Count - 1; i >= 0; i--) { var child = _children[i]; - if (IntersectsChildContent(child, point, out var childLocation)) + if (child.Visible && IntersectsChildContent(child, point, out var childLocation)) { var containerControl = child as ContainerControl; var childAtRecursive = containerControl?.GetChildAtRecursive(childLocation); diff --git a/Source/Engine/UI/GUI/Control.Bounds.cs b/Source/Engine/UI/GUI/Control.Bounds.cs index d76f82308..cbb3ba80b 100644 --- a/Source/Engine/UI/GUI/Control.Bounds.cs +++ b/Source/Engine/UI/GUI/Control.Bounds.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; +using System.ComponentModel; namespace FlaxEngine.GUI { @@ -382,6 +383,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point. /// + [DefaultValue(typeof(Float2), "0,0")] [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")] public Float2 Shear { @@ -398,6 +400,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default). /// + [DefaultValue(0.0f)] [ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")] public float Rotation { diff --git a/Source/Engine/UI/GUI/Panels/SplitPanel.cs b/Source/Engine/UI/GUI/Panels/SplitPanel.cs index f215b1ccf..e721ee8a3 100644 --- a/Source/Engine/UI/GUI/Panels/SplitPanel.cs +++ b/Source/Engine/UI/GUI/Panels/SplitPanel.cs @@ -187,7 +187,7 @@ namespace FlaxEngine.GUI // Start moving splitter StartTracking(); Focus(); - return false; + return true; } return base.OnMouseDown(location, button); diff --git a/Source/Engine/UI/UICanvas.cpp b/Source/Engine/UI/UICanvas.cpp index 964c4a85f..d307bf7d6 100644 --- a/Source/Engine/UI/UICanvas.cpp +++ b/Source/Engine/UI/UICanvas.cpp @@ -12,7 +12,6 @@ #else // Cached methods (FlaxEngine.CSharp.dll is loaded only once) MMethod* UICanvas_Serialize = nullptr; -MMethod* UICanvas_SerializeDiff = nullptr; MMethod* UICanvas_Deserialize = nullptr; MMethod* UICanvas_PostDeserialize = nullptr; MMethod* UICanvas_Enable = nullptr; @@ -45,7 +44,6 @@ UICanvas::UICanvas(const SpawnParams& params) if (UICanvas_Serialize == nullptr) { MClass* mclass = GetClass(); - UICanvas_SerializeDiff = mclass->GetMethod("SerializeDiff", 1); UICanvas_Deserialize = mclass->GetMethod("Deserialize", 1); UICanvas_PostDeserialize = mclass->GetMethod("PostDeserialize"); UICanvas_Enable = mclass->GetMethod("Enable"); @@ -55,7 +53,7 @@ UICanvas::UICanvas(const SpawnParams& params) #endif UICanvas_EndPlay = mclass->GetMethod("EndPlay"); UICanvas_ParentChanged = mclass->GetMethod("ParentChanged"); - UICanvas_Serialize = mclass->GetMethod("Serialize"); + UICanvas_Serialize = mclass->GetMethod("Serialize", 1); Platform::MemoryBarrier(); } #endif @@ -83,8 +81,7 @@ void UICanvas::Serialize(SerializeStream& stream, const void* otherObj) void* params[1]; params[0] = other ? other->GetOrCreateManagedInstance() : nullptr; MObject* exception = nullptr; - auto method = other ? UICanvas_SerializeDiff : UICanvas_Serialize; - auto invokeResultStr = (MString*)method->Invoke(GetOrCreateManagedInstance(), params, &exception); + auto invokeResultStr = (MString*)UICanvas_Serialize->Invoke(GetOrCreateManagedInstance(), params, &exception); if (exception) { MException ex(exception); diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index 2d12dc12c..db78e428f 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -575,8 +575,9 @@ namespace FlaxEngine } } - internal string Serialize() + internal string Serialize(UICanvas other) { + bool noOther = other == null; StringBuilder sb = new StringBuilder(256); StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture); using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) @@ -587,137 +588,52 @@ namespace FlaxEngine jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("RenderMode"); - jsonWriter.WriteValue(_renderMode); - - jsonWriter.WritePropertyName("RenderLocation"); - jsonWriter.WriteValue(RenderLocation); - - jsonWriter.WritePropertyName("Order"); - jsonWriter.WriteValue(Order); - - jsonWriter.WritePropertyName("ReceivesEvents"); - jsonWriter.WriteValue(ReceivesEvents); - - jsonWriter.WritePropertyName("IgnoreDepth"); - jsonWriter.WriteValue(IgnoreDepth); - - jsonWriter.WritePropertyName("RenderCamera"); - jsonWriter.WriteValue(Json.JsonSerializer.GetStringID(RenderCamera)); - - jsonWriter.WritePropertyName("Distance"); - jsonWriter.WriteValue(Distance); - - if (RenderMode == CanvasRenderMode.WorldSpace || RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) - { - jsonWriter.WritePropertyName("Size"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("X"); - jsonWriter.WriteValue(Size.X); - jsonWriter.WritePropertyName("Y"); - jsonWriter.WriteValue(Size.Y); - jsonWriter.WriteEndObject(); - } - - jsonWriter.WritePropertyName("NavigationInputRepeatDelay"); - jsonWriter.WriteValue(NavigationInputRepeatDelay); - jsonWriter.WritePropertyName("NavigationInputRepeatRate"); - jsonWriter.WriteValue(NavigationInputRepeatRate); - - jsonWriter.WritePropertyName("NavigateUp"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("Name"); - jsonWriter.WriteValue(NavigateUp.Name); - jsonWriter.WriteEndObject(); - - jsonWriter.WritePropertyName("NavigateDown"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("Name"); - jsonWriter.WriteValue(NavigateDown.Name); - jsonWriter.WriteEndObject(); - - jsonWriter.WritePropertyName("NavigateLeft"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("Name"); - jsonWriter.WriteValue(NavigateLeft.Name); - jsonWriter.WriteEndObject(); - - jsonWriter.WritePropertyName("NavigateRight"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("Name"); - jsonWriter.WriteValue(NavigateRight.Name); - jsonWriter.WriteEndObject(); - - jsonWriter.WritePropertyName("NavigateSubmit"); - jsonWriter.WriteStartObject(); - jsonWriter.WritePropertyName("Name"); - jsonWriter.WriteValue(NavigateSubmit.Name); - jsonWriter.WriteEndObject(); - - jsonWriter.WriteEndObject(); - } - - return sw.ToString(); - } - - internal string SerializeDiff(UICanvas other) - { - StringBuilder sb = new StringBuilder(256); - StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture); - using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) - { - jsonWriter.IndentChar = '\t'; - jsonWriter.Indentation = 1; - jsonWriter.Formatting = Formatting.Indented; - - jsonWriter.WriteStartObject(); - - if (_renderMode != other._renderMode) + if (noOther || _renderMode != other._renderMode) { jsonWriter.WritePropertyName("RenderMode"); jsonWriter.WriteValue(_renderMode); } - if (RenderLocation != other.RenderLocation) + if (noOther || RenderLocation != other.RenderLocation) { jsonWriter.WritePropertyName("RenderLocation"); jsonWriter.WriteValue(RenderLocation); } - if (Order != other.Order) + if (noOther || Order != other.Order) { jsonWriter.WritePropertyName("Order"); jsonWriter.WriteValue(Order); } - if (ReceivesEvents != other.ReceivesEvents) + if (noOther || ReceivesEvents != other.ReceivesEvents) { jsonWriter.WritePropertyName("ReceivesEvents"); jsonWriter.WriteValue(ReceivesEvents); } - if (IgnoreDepth != other.IgnoreDepth) + if (noOther || IgnoreDepth != other.IgnoreDepth) { jsonWriter.WritePropertyName("IgnoreDepth"); jsonWriter.WriteValue(IgnoreDepth); } - if (RenderCamera != other.RenderCamera) + if (noOther || RenderCamera != other.RenderCamera) { jsonWriter.WritePropertyName("RenderCamera"); jsonWriter.WriteValue(Json.JsonSerializer.GetStringID(RenderCamera)); } - if (Mathf.Abs(Distance - other.Distance) > Mathf.Epsilon) + if (noOther || Mathf.Abs(Distance - other.Distance) > Mathf.Epsilon) { jsonWriter.WritePropertyName("Distance"); jsonWriter.WriteValue(Distance); } - if ((RenderMode == CanvasRenderMode.WorldSpace || - RenderMode == CanvasRenderMode.WorldSpaceFaceCamera || - other.RenderMode == CanvasRenderMode.WorldSpace || - other.RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) && Size != other.Size) + bool saveSize = RenderMode == CanvasRenderMode.WorldSpace || RenderMode == CanvasRenderMode.WorldSpaceFaceCamera; + if (!noOther) + saveSize = (saveSize || other.RenderMode == CanvasRenderMode.WorldSpace || other.RenderMode == CanvasRenderMode.WorldSpaceFaceCamera) && Size != other.Size; + if (saveSize) { jsonWriter.WritePropertyName("Size"); jsonWriter.WriteStartObject(); @@ -728,17 +644,17 @@ namespace FlaxEngine jsonWriter.WriteEndObject(); } - if (!Mathf.NearEqual(NavigationInputRepeatDelay, other.NavigationInputRepeatDelay)) + if (noOther || !Mathf.NearEqual(NavigationInputRepeatDelay, other.NavigationInputRepeatDelay)) { jsonWriter.WritePropertyName("NavigationInputRepeatDelay"); jsonWriter.WriteValue(NavigationInputRepeatDelay); } - if (!Mathf.NearEqual(NavigationInputRepeatRate, other.NavigationInputRepeatRate)) + if (noOther || !Mathf.NearEqual(NavigationInputRepeatRate, other.NavigationInputRepeatRate)) { jsonWriter.WritePropertyName("NavigationInputRepeatRate"); jsonWriter.WriteValue(NavigationInputRepeatRate); } - if (NavigateUp.Name != other.NavigateUp.Name) + if (noOther || NavigateUp.Name != other.NavigateUp.Name) { jsonWriter.WritePropertyName("NavigateUp"); jsonWriter.WriteStartObject(); @@ -746,7 +662,7 @@ namespace FlaxEngine jsonWriter.WriteValue(NavigateUp.Name); jsonWriter.WriteEndObject(); } - if (NavigateDown.Name != other.NavigateDown.Name) + if (noOther || NavigateDown.Name != other.NavigateDown.Name) { jsonWriter.WritePropertyName("NavigateDown"); jsonWriter.WriteStartObject(); @@ -754,7 +670,7 @@ namespace FlaxEngine jsonWriter.WriteValue(NavigateDown.Name); jsonWriter.WriteEndObject(); } - if (NavigateLeft.Name != other.NavigateLeft.Name) + if (noOther || NavigateLeft.Name != other.NavigateLeft.Name) { jsonWriter.WritePropertyName("NavigateLeft"); jsonWriter.WriteStartObject(); @@ -762,7 +678,7 @@ namespace FlaxEngine jsonWriter.WriteValue(NavigateLeft.Name); jsonWriter.WriteEndObject(); } - if (NavigateRight.Name != other.NavigateRight.Name) + if (noOther || NavigateRight.Name != other.NavigateRight.Name) { jsonWriter.WritePropertyName("NavigateRight"); jsonWriter.WriteStartObject(); @@ -770,7 +686,7 @@ namespace FlaxEngine jsonWriter.WriteValue(NavigateRight.Name); jsonWriter.WriteEndObject(); } - if (NavigateSubmit.Name != other.NavigateSubmit.Name) + if (noOther || NavigateSubmit.Name != other.NavigateSubmit.Name) { jsonWriter.WritePropertyName("NavigateSubmit"); jsonWriter.WriteStartObject(); diff --git a/Source/Engine/UI/UIControl.cpp b/Source/Engine/UI/UIControl.cpp index bfe820eba..72eed1f27 100644 --- a/Source/Engine/UI/UIControl.cpp +++ b/Source/Engine/UI/UIControl.cpp @@ -13,12 +13,11 @@ #else // Cached methods (FlaxEngine.CSharp.dll is loaded only once) MMethod* UIControl_Serialize = nullptr; -MMethod* UIControl_SerializeDiff = nullptr; MMethod* UIControl_Deserialize = nullptr; MMethod* UIControl_ParentChanged = nullptr; MMethod* UIControl_TransformChanged = nullptr; MMethod* UIControl_OrderInParentChanged = nullptr; -MMethod* UIControl_ActiveInTreeChanged = nullptr; +MMethod* UIControl_ActiveChanged = nullptr; MMethod* UIControl_BeginPlay = nullptr; MMethod* UIControl_EndPlay = nullptr; @@ -39,18 +38,19 @@ UIControl::UIControl(const SpawnParams& params) : Actor(params) { #if !COMPILE_WITHOUT_CSHARP + Platform::MemoryBarrier(); if (UIControl_Serialize == nullptr) { MClass* mclass = GetClass(); - UIControl_SerializeDiff = mclass->GetMethod("SerializeDiff", 2); UIControl_Deserialize = mclass->GetMethod("Deserialize", 2); UIControl_ParentChanged = mclass->GetMethod("ParentChanged"); UIControl_TransformChanged = mclass->GetMethod("TransformChanged"); UIControl_OrderInParentChanged = mclass->GetMethod("OrderInParentChanged"); - UIControl_ActiveInTreeChanged = mclass->GetMethod("ActiveInTreeChanged"); + UIControl_ActiveChanged = mclass->GetMethod("ActiveChanged"); UIControl_BeginPlay = mclass->GetMethod("BeginPlay"); UIControl_EndPlay = mclass->GetMethod("EndPlay"); - UIControl_Serialize = mclass->GetMethod("Serialize", 1); + UIControl_Serialize = mclass->GetMethod("Serialize", 2); + Platform::MemoryBarrier(); } #endif } @@ -82,8 +82,7 @@ void UIControl::Serialize(SerializeStream& stream, const void* otherObj) params[0] = &controlType; params[1] = other ? other->GetOrCreateManagedInstance() : nullptr; MObject* exception = nullptr; - const auto method = other ? UIControl_SerializeDiff : UIControl_Serialize; - const auto invokeResultStr = (MString*)method->Invoke(GetOrCreateManagedInstance(), params, &exception); + const auto invokeResultStr = (MString*)UIControl_Serialize->Invoke(GetOrCreateManagedInstance(), params, &exception); if (exception) { MException ex(exception); @@ -208,12 +207,12 @@ void UIControl::OnOrderInParentChanged() UICONTROL_INVOKE(OrderInParentChanged); } -void UIControl::OnActiveInTreeChanged() +void UIControl::OnActiveChanged() { - UICONTROL_INVOKE(ActiveInTreeChanged); + UICONTROL_INVOKE(ActiveChanged); // Base - Actor::OnActiveInTreeChanged(); + Actor::OnActiveChanged(); } #if !COMPILE_WITHOUT_CSHARP diff --git a/Source/Engine/UI/UIControl.cs b/Source/Engine/UI/UIControl.cs index 9cea0f6de..72e3f9625 100644 --- a/Source/Engine/UI/UIControl.cs +++ b/Source/Engine/UI/UIControl.cs @@ -1,12 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; -using System.Globalization; -using System.IO; -using System.Text; using FlaxEngine.GUI; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; namespace FlaxEngine { @@ -49,20 +44,21 @@ namespace FlaxEngine var containerControl = _control as ContainerControl; if (containerControl != null) containerControl.UnlockChildrenRecursive(); + _control.Visible = IsActive; _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; _control.Location = new Float2(LocalPosition); _control.LocationChanged += OnControlLocationChanged; // Link children UI controls - if (containerControl != null && IsActiveInHierarchy) + if (containerControl != null) { var children = ChildrenCount; var parent = Parent; for (int i = 0; i < children; i++) { var child = GetChild(i) as UIControl; - if (child != null && child.IsActiveInHierarchy && child.HasControl && child != parent) + if (child != null && child.HasControl && child != parent) { child.Control.Parent = containerControl; } @@ -108,7 +104,6 @@ namespace FlaxEngine p2 = c.PointToParent(ref p2); p3 = c.PointToParent(ref p3); p4 = c.PointToParent(ref p4); - c = c.Parent; } var min = Float2.Min(Float2.Min(p1, p2), Float2.Min(p3, p4)); @@ -120,7 +115,6 @@ namespace FlaxEngine { Extents = new Vector3(size * 0.5f, Mathf.Epsilon) }; - canvasRoot.Canvas.GetWorldMatrix(out Matrix world); Matrix.Translation(min.X + size.X * 0.5f, min.Y + size.Y * 0.5f, 0, out Matrix offset); Matrix.Multiply(ref offset, ref world, out var boxWorld); @@ -297,15 +291,6 @@ namespace FlaxEngine private ContainerControl GetParent() { - // Don't link disabled actors - if (!IsActiveInHierarchy) - return null; -#if FLAX_EDITOR - // Prefab editor doesn't fire BeginPlay so for disabled actors we don't unlink them so do it here - if (!IsActive) - return null; -#endif - var parent = Parent; if (parent is UIControl uiControl && uiControl.Control is ContainerControl uiContainerControl) return uiContainerControl; @@ -314,89 +299,34 @@ namespace FlaxEngine return FallbackParentGetDelegate?.Invoke(this); } - internal string Serialize(out string controlType) + internal string Serialize(out string controlType, UIControl other) { if (_control == null) { + // No control assigned controlType = null; return null; } var type = _control.GetType(); + var prefabDiff = other != null && other._control != null && other._control.GetType() == type; - JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(Json.JsonSerializer.Settings); - jsonSerializer.Formatting = Formatting.Indented; + // Serialize control type when not using prefab diff serialization + controlType = prefabDiff ? string.Empty : type.FullName; - StringBuilder sb = new StringBuilder(1024); - StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture); - using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) - { - // Prepare writer settings - jsonWriter.IndentChar = '\t'; - jsonWriter.Indentation = 1; - jsonWriter.Formatting = jsonSerializer.Formatting; - jsonWriter.DateFormatHandling = jsonSerializer.DateFormatHandling; - jsonWriter.DateTimeZoneHandling = jsonSerializer.DateTimeZoneHandling; - jsonWriter.FloatFormatHandling = jsonSerializer.FloatFormatHandling; - jsonWriter.StringEscapeHandling = jsonSerializer.StringEscapeHandling; - jsonWriter.Culture = jsonSerializer.Culture; - jsonWriter.DateFormatString = jsonSerializer.DateFormatString; - - JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(jsonSerializer); - - serializerWriter.Serialize(jsonWriter, _control, type); - } - - controlType = type.FullName; - return sw.ToString(); - } - - internal string SerializeDiff(out string controlType, UIControl other) - { - if (_control == null) - { - controlType = null; - return null; - } - var type = _control.GetType(); - if (other._control == null || other._control.GetType() != type) - { - return Serialize(out controlType); - } - - JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(Json.JsonSerializer.Settings); - jsonSerializer.Formatting = Formatting.Indented; - - StringBuilder sb = new StringBuilder(1024); - StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture); - using (JsonTextWriter jsonWriter = new JsonTextWriter(sw)) - { - // Prepare writer settings - jsonWriter.IndentChar = '\t'; - jsonWriter.Indentation = 1; - jsonWriter.Formatting = jsonSerializer.Formatting; - jsonWriter.DateFormatHandling = jsonSerializer.DateFormatHandling; - jsonWriter.DateTimeZoneHandling = jsonSerializer.DateTimeZoneHandling; - jsonWriter.FloatFormatHandling = jsonSerializer.FloatFormatHandling; - jsonWriter.StringEscapeHandling = jsonSerializer.StringEscapeHandling; - jsonWriter.Culture = jsonSerializer.Culture; - jsonWriter.DateFormatString = jsonSerializer.DateFormatString; - - JsonSerializerInternalWriter serializerWriter = new JsonSerializerInternalWriter(jsonSerializer); - - serializerWriter.SerializeDiff(jsonWriter, _control, type, other._control); - } - - controlType = string.Empty; - return sw.ToString(); + string json; + if (prefabDiff) + json = Json.JsonSerializer.SerializeDiff(_control, other._control, true); + else + json = Json.JsonSerializer.Serialize(_control, type, true); + return json; } internal void Deserialize(string json, Type controlType) { - if (_control == null || _control.GetType() != controlType) + if ((_control == null || _control.GetType() != controlType) && controlType != null) { - if (controlType != null) - Control = (Control)Activator.CreateInstance(controlType); + Control = (Control)Activator.CreateInstance(controlType); } if (_control != null) @@ -412,6 +342,7 @@ namespace FlaxEngine { if (_control != null && !_blockEvents) { + _control.Visible = IsActive; _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; } @@ -425,19 +356,11 @@ namespace FlaxEngine } } - internal void ActiveInTreeChanged() + internal void ActiveChanged() { if (_control != null && !_blockEvents) { - // Skip if this control is inactive and it's parent too (parent will unlink from hierarchy but children will stay connected while being inactive) - if (!IsActiveInHierarchy && Parent && !Parent.IsActive) - { - return; - } - - // Link or unlink control (won't modify Enable/Visible state) - _control.Parent = GetParent(); - _control.IndexInParent = OrderInParent; + _control.Visible = IsActive; } } @@ -453,6 +376,7 @@ namespace FlaxEngine { if (_control != null) { + _control.Visible = IsActive && _control.Visible; _control.Parent = GetParent(); _control.IndexInParent = OrderInParent; Internal_GetNavTargets(__unmanagedPtr, out UIControl up, out UIControl down, out UIControl left, out UIControl right); diff --git a/Source/Engine/UI/UIControl.h b/Source/Engine/UI/UIControl.h index 8df4a578d..e76aca65a 100644 --- a/Source/Engine/UI/UIControl.h +++ b/Source/Engine/UI/UIControl.h @@ -30,7 +30,7 @@ protected: void OnBeginPlay() override; void OnEndPlay() override; void OnOrderInParentChanged() override; - void OnActiveInTreeChanged() override; + void OnActiveChanged() override; private: #if !COMPILE_WITHOUT_CSHARP diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp index 94c3defbf..be392c16a 100644 --- a/Source/Engine/Utilities/Screenshot.cpp +++ b/Source/Engine/Utilities/Screenshot.cpp @@ -2,6 +2,7 @@ #include "Screenshot.h" #include "Engine/Core/Log.h" +#include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Math/Math.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Platform/FileSystem.h" diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index acfa5a1dc..0767ab8f4 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -257,6 +257,7 @@ namespace FlaxEngine { public static FieldInfo itemsField; } + internal static T[] ExtractArrayFromList(List list) { if (list == null) @@ -1038,5 +1039,66 @@ namespace FlaxEngine parameterTypes = Array.Empty(); return parameterTypes; } + + /// + /// A category of number values used for formatting and input fields. + /// + public enum ValueCategory + { + /// + /// Nothing. + /// + None, + + /// + /// Distance (eg. meters). + /// + Distance, + + /// + /// Area (eg. m^2). + /// + Area, + + /// + /// Volume (eg. m^3). + /// + Volume, + + /// + /// Mass (eg. kilograms). + /// + Mass, + + /// + /// Angle (eg. degrees). + /// + Angle, + + /// + /// Speed (distance / time). + /// + Speed, + + /// + /// Acceleration (distance^2 / time). + /// + Acceleration, + + /// + /// Time (eg. seconds). + /// + Time, + + /// + /// Force (mass * distance / time^2). + /// + Force, + + /// + /// Torque (mass * distance^2 / time^2). + /// + Torque, + } } } diff --git a/Source/FlaxEditor.Build.cs b/Source/FlaxEditor.Build.cs index c755c14e0..b6074e264 100644 --- a/Source/FlaxEditor.Build.cs +++ b/Source/FlaxEditor.Build.cs @@ -14,7 +14,6 @@ public class FlaxEditor : EngineTarget { base.Init(); - // Initialize IsEditor = true; OutputName = "FlaxEditor"; ConfigurationName = "Editor"; @@ -31,7 +30,6 @@ public class FlaxEditor : EngineTarget TargetArchitecture.ARM64, }; GlobalDefinitions.Add("USE_EDITOR"); - Win32ResourceFile = Path.Combine(Globals.EngineRoot, "Source", "FlaxEditor.rc"); Modules.Add("Editor"); Modules.Add("CSG"); diff --git a/Source/FlaxEngine.pch.h b/Source/FlaxEngine.pch.h new file mode 100644 index 000000000..2e3fc11d3 --- /dev/null +++ b/Source/FlaxEngine.pch.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +// Include common engine headers +#include "Engine/Platform/Platform.h" +#include "Engine/Platform/StringUtils.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Types/Guid.h" +#include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Vector4.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Core/Math/BoundingBox.h" +#include "Engine/Core/Math/BoundingSphere.h" +#include "Engine/Core/Math/Transform.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Log.h" +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Serialization/SerializationFwd.h" diff --git a/Source/FlaxEditor.rc b/Source/FlaxEngine.rc similarity index 91% rename from Source/FlaxEditor.rc rename to Source/FlaxEngine.rc index fc44418ba..0097e76a7 100644 --- a/Source/FlaxEditor.rc +++ b/Source/FlaxEngine.rc @@ -70,12 +70,12 @@ BEGIN BLOCK "040004b0" BEGIN VALUE "CompanyName", FLAXENGINE_COMPANY - VALUE "FileDescription", "Flax Editor" + VALUE "FileDescription", PRODUCT_NAME VALUE "FileVersion", FLAXENGINE_VERSION_TEXT - VALUE "InternalName", "FlaxEditor" + VALUE "InternalName", PRODUCT_NAME_INTERNAL VALUE "LegalCopyright", FLAXENGINE_COPYRIGHT - VALUE "OriginalFilename", "FlaxEditor.exe" - VALUE "ProductName", "Flax Editor" + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME VALUE "ProductVersion", FLAXENGINE_VERSION_TEXT END END diff --git a/Source/FlaxGame.Build.cs b/Source/FlaxGame.Build.cs index c59447414..61705924b 100644 --- a/Source/FlaxGame.Build.cs +++ b/Source/FlaxGame.Build.cs @@ -14,11 +14,10 @@ public class FlaxGame : EngineTarget { base.Init(); - // Initialize OutputName = "FlaxGame"; ConfigurationName = "Game"; IsPreBuilt = false; - Win32ResourceFile = Path.Combine(Globals.EngineRoot, "Source", "FlaxGame.rc"); + IsMonolithicExecutable = false; } /// diff --git a/Source/FlaxGame.rc b/Source/FlaxGame.rc deleted file mode 100644 index 6d1e4d480..000000000 --- a/Source/FlaxGame.rc +++ /dev/null @@ -1,102 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" -#include "FlaxEngine.Gen.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// Polish (Poland) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_PLK) -LANGUAGE LANG_POLISH, SUBLANG_DEFAULT - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -// Icon -IDR_MAINFRAME ICON "Icon.ico" - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION FLAXENGINE_VERSION_MAJOR,FLAXENGINE_VERSION_MINOR,FLAXENGINE_VERSION_BUILD - PRODUCTVERSION FLAXENGINE_VERSION_MAJOR,FLAXENGINE_VERSION_MINOR,FLAXENGINE_VERSION_BUILD - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040004b0" - BEGIN - VALUE "CompanyName", FLAXENGINE_COMPANY - VALUE "FileDescription", "Flax Engine" - VALUE "FileVersion", FLAXENGINE_VERSION_TEXT - VALUE "InternalName", "FlaxEngine" - VALUE "LegalCopyright", FLAXENGINE_COPYRIGHT - VALUE "OriginalFilename", "FlaxGame.exe" - VALUE "ProductName", "Flax Engine" - VALUE "ProductVersion", FLAXENGINE_VERSION_TEXT - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x400, 1200 - END -END - -#endif -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - diff --git a/Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll b/Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll index ef2f325da..24087dd29 100644 --- a/Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll +++ b/Source/Platforms/DotNet/AOT/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e045d6ae2a72b9b6e8922fa37537f7f14fdc9d5ced502e48a5b4ab5064a2458 -size 540672 +oid sha256:22f78856541397ffe932df4db572d6a4ea3b4f3c843202f1b2b947a925cc2649 +size 541184 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.dll b/Source/Platforms/DotNet/Newtonsoft.Json.dll index db1ecd531..b56719858 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.dll +++ b/Source/Platforms/DotNet/Newtonsoft.Json.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf0320e0e2e754a77d7cbeeaed16fc6c4e0dfac9953e258b86a09edbe93627d3 -size 604160 +oid sha256:2189e00df470e0fa7e3294a90a2faf286af51bd25f224605faf53cc42a7ff381 +size 604672 diff --git a/Source/Platforms/DotNet/Newtonsoft.Json.pdb b/Source/Platforms/DotNet/Newtonsoft.Json.pdb index b3feda587..97d99bb47 100644 --- a/Source/Platforms/DotNet/Newtonsoft.Json.pdb +++ b/Source/Platforms/DotNet/Newtonsoft.Json.pdb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca10fa9bd4f681410afd8c935930826511d98b33c729b721c0dba818239208bf -size 239532 +oid sha256:cd30824b5d1593411aa7c47816afb37f63ba4ec4f82c5777324d0411c853a912 +size 239516 diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libastcenc.a b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libastcenc.a new file mode 100644 index 000000000..7d5b9c24c --- /dev/null +++ b/Source/Platforms/Mac/Binaries/ThirdParty/ARM64/libastcenc.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43c102505dd86c26284896d3cb63f15feeec2f48b554a8b7e2b6dea7c0742e75 +size 2025552 diff --git a/Source/Platforms/Mac/Binaries/ThirdParty/x64/libastcenc.a b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libastcenc.a new file mode 100644 index 000000000..f492c80cf --- /dev/null +++ b/Source/Platforms/Mac/Binaries/ThirdParty/x64/libastcenc.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ef0defa218e369da6d680e7753be1831b8317fbc8b8ad5c709155c5a632fe74 +size 2106072 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/astcenc.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/astcenc.lib new file mode 100644 index 000000000..d89395539 --- /dev/null +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/astcenc.lib @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38500ac222bd4132dd81ea03016462add2b0c8188b7cb7101223d19bf209cd01 +size 536548 diff --git a/Source/Shaders/DebugDraw.shader b/Source/Shaders/DebugDraw.shader index 6495cb1a3..d10dbbf06 100644 --- a/Source/Shaders/DebugDraw.shader +++ b/Source/Shaders/DebugDraw.shader @@ -4,7 +4,8 @@ META_CB_BEGIN(0, Data) float4x4 ViewProjection; -float3 Padding; +float2 Padding; +float ClipPosZBias; bool EnableDepthTest; META_CB_END @@ -23,6 +24,7 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR) { VS2PS output; output.Position = mul(float4(Position, 1), ViewProjection); + output.Position.z += ClipPosZBias; output.Color = Color; return output; } diff --git a/Source/Shaders/TAA.shader b/Source/Shaders/TAA.shader index 04a5006e2..5ae3444b7 100644 --- a/Source/Shaders/TAA.shader +++ b/Source/Shaders/TAA.shader @@ -1,6 +1,10 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +#define DEBUG_HISTORY_REJECTION 0 +#define NO_GBUFFER_SAMPLING + #include "./Flax/Common.hlsl" +#include "./Flax/GBuffer.hlsl" META_CB_BEGIN(0, Data) float2 ScreenSizeInv; @@ -9,6 +13,7 @@ float Sharpness; float StationaryBlending; float MotionBlending; float Dummy0; +GBufferData GBuffer; META_CB_END Texture2D Input : register(t0); @@ -31,37 +36,42 @@ float4 ClipToAABB(float4 color, float4 minimum, float4 maximum) META_PS(true, FEATURE_LEVEL_ES2) float4 PS(Quad_VS2PS input) : SV_Target0 { + float2 tanHalfFOV = float2(GBuffer.InvProjectionMatrix[0][0], GBuffer.InvProjectionMatrix[1][1]); + + // Calculate previous frame UVs based on per-pixel velocity + float2 velocity = SAMPLE_RT_LINEAR(MotionVectors, input.TexCoord).xy; + float velocityLength = length(velocity); + float2 prevUV = input.TexCoord - velocity; + float prevDepth = LinearizeZ(GBuffer, SAMPLE_RT(Depth, prevUV).r); + // Find the closest pixel in 3x3 neighborhood - float bestDepth = 1; - float2 bestUV = float2(0, 0); + float currentDepth = 1; float4 neighborhoodMin = 100000; float4 neighborhoodMax = -10000; float4 current; float4 neighborhoodSum = 0; + float minDepthDiff = 100000; for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { float2 sampleUV = input.TexCoord + float2(x, y) * ScreenSizeInv; - float4 neighbor = SAMPLE_RT_LINEAR(Input, sampleUV); + float4 neighbor = SAMPLE_RT(Input, sampleUV); neighborhoodMin = min(neighborhoodMin, neighbor); neighborhoodMax = max(neighborhoodMax, neighbor); - if (x == 0 && y == 0) - current = neighbor; neighborhoodSum += neighbor; - float depth = SAMPLE_RT(Depth, sampleUV).r; - if (depth < bestDepth) + float neighborDepth = LinearizeZ(GBuffer, SAMPLE_RT(Depth, sampleUV).r); + float depthDiff = abs(max(neighborDepth - prevDepth, 0)); + minDepthDiff = min(minDepthDiff, depthDiff); + if (x == 0 && y == 0) { - bestDepth = depth; - bestUV = sampleUV; + current = neighbor; + currentDepth = neighborDepth; } } } - float2 velocity = SAMPLE_RT_LINEAR(MotionVectors, bestUV).xy; - float velocityLength = length(velocity); - float2 prevUV = input.TexCoord - velocity; // Apply sharpening float4 neighborhoodAvg = neighborhoodSum / 9.0; @@ -76,11 +86,23 @@ float4 PS(Quad_VS2PS input) : SV_Target0 // Calculate history blending factor float motion = saturate(velocityLength * 1000.0f); - float blendfactor = any(abs(prevUV * 2 - 1) >= 1.0f) ? 0.0f : lerp(StationaryBlending, MotionBlending, motion); + float blendfactor = lerp(StationaryBlending, MotionBlending, motion); // Perform linear accumulation of the previous samples with a current one float4 color = lerp(current, history, blendfactor); - color = clamp(color, 0, HDR_CLAMP_MAX); + // Reduce history blend in favor of neighborhood blend when sample has no valid prevous frame data + float miss = any(abs(prevUV * 2 - 1) >= 1.0f) ? 1 : 0; + float currentDepthWorld = currentDepth * GBuffer.ViewFar; + float minDepthDiffWorld = minDepthDiff * GBuffer.ViewFar; + float depthError = tanHalfFOV.x * ScreenSizeInv.x * 200.0f * (currentDepthWorld + 10.0f); + miss += minDepthDiffWorld > depthError ? 1 : 0; + float4 neighborhoodSharp = lerp(neighborhoodAvg, current, 0.5f); +#if DEBUG_HISTORY_REJECTION + neighborhoodSharp = float4(1, 0, 0, 1); +#endif + color = lerp(color, neighborhoodSharp, saturate(miss)); + + color = clamp(color, 0, HDR_CLAMP_MAX); return color; } diff --git a/Source/ThirdParty/astc/LICENSE.txt b/Source/ThirdParty/astc/LICENSE.txt new file mode 100644 index 000000000..b82735a31 --- /dev/null +++ b/Source/ThirdParty/astc/LICENSE.txt @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/Source/ThirdParty/astc/astc.Build.cs b/Source/ThirdParty/astc/astc.Build.cs new file mode 100644 index 000000000..4f3ec7d22 --- /dev/null +++ b/Source/ThirdParty/astc/astc.Build.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using Flax.Build; +using Flax.Build.NativeCpp; + +/// +/// https://github.com/ARM-software/astc-encoder +/// +public class astc : DepsModule +{ + /// + /// Returns true if can use astc lib for a given build setup. + /// + public static bool IsSupported(BuildOptions options) + { + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + return true; + case TargetPlatform.Mac: + return options.Architecture == TargetArchitecture.ARM64; + default: + return false; + } + } + + /// + public override void Init() + { + base.Init(); + + LicenseType = LicenseTypes.Apache2; + LicenseFilePath = "LICENSE.txt"; + + // Merge third-party modules into engine binary + BinaryModuleName = "FlaxEngine"; + } + + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PublicDefinitions.Add("COMPILE_WITH_ASTC"); + AddLib(options, options.DepsFolder, "astcenc"); + } +} diff --git a/Source/ThirdParty/astc/astcenc.h b/Source/ThirdParty/astc/astcenc.h new file mode 100644 index 000000000..c6c8c14a2 --- /dev/null +++ b/Source/ThirdParty/astc/astcenc.h @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: Apache-2.0 +// ---------------------------------------------------------------------------- +// Copyright 2020-2023 Arm Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// ---------------------------------------------------------------------------- + +/** + * @brief The core astcenc codec library interface. + * + * This interface is the entry point to the core astcenc codec. It aims to be easy to use for + * non-experts, but also to allow experts to have fine control over the compressor heuristics if + * needed. The core codec only handles compression and decompression, transferring all inputs and + * outputs via memory buffers. To catch obvious input/output buffer sizing issues, which can cause + * security and stability problems, all transfer buffers are explicitly sized. + * + * While the aim is that we keep this interface mostly stable, it should be viewed as a mutable + * interface tied to a specific source version. We are not trying to maintain backwards + * compatibility across codec versions. + * + * The API state management is based around an explicit context object, which is the context for all + * allocated memory resources needed to compress and decompress a single image. A context can be + * used to sequentially compress multiple images using the same configuration, allowing setup + * overheads to be amortized over multiple images, which is particularly important when images are + * small. + * + * Multi-threading can be used two ways. + * + * * An application wishing to process multiple images in parallel can allocate multiple + * contexts and assign each context to a thread. + * * An application wishing to process a single image in using multiple threads can configure + * contexts for multi-threaded use, and invoke astcenc_compress/decompress() once per thread + * for faster processing. The caller is responsible for creating the worker threads, and + * synchronizing between images. + * + * Extended instruction set support + * ================================ + * + * This library supports use of extended instruction sets, such as SSE4.1 and AVX2. These are + * enabled at compile time when building the library. There is no runtime checking in the core + * library that the instruction sets used are actually available. Checking compatibility is the + * responsibility of the calling code. + * + * Threading + * ========= + * + * In pseudo-code, the usage for manual user threading looks like this: + * + * // Configure the compressor run + * astcenc_config my_config; + * astcenc_config_init(..., &my_config); + * + * // Power users can tweak settings here ... + * + * // Allocate working state given config and thread_count + * astcenc_context* my_context; + * astcenc_context_alloc(&my_config, thread_count, &my_context); + * + * // Compress each image using these config settings + * foreach image: + * // For each thread in the thread pool + * for i in range(0, thread_count): + * astcenc_compress_image(my_context, &my_input, my_output, i); + * + * astcenc_compress_reset(my_context); + * + * // Clean up + * astcenc_context_free(my_context); + * + * Images + * ====== + * + * The codec supports compressing single images, which can be either 2D images or volumetric 3D + * images. Calling code is responsible for any handling of aggregate types, such as mipmap chains, + * texture arrays, or sliced 3D textures. + * + * Images are passed in as an astcenc_image structure. Inputs can be either 8-bit unorm, 16-bit + * half-float, or 32-bit float, as indicated by the data_type field. + * + * Images can be any dimension; there is no requirement to be a multiple of the ASTC block size. + * + * Data is always passed in as 4 color components, and accessed as an array of 2D image slices. Data + * within an image slice is always tightly packed without padding. Addressing looks like this: + * + * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 ] // Red + * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 + 1] // Green + * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 + 2] // Blue + * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 + 3] // Alpha + * + * Common compressor usage + * ======================= + * + * One of the most important things for coding image quality is to align the input data component + * count with the ASTC color endpoint mode. This avoids wasting bits encoding components you don't + * actually need in the endpoint colors. + * + * | Input data | Encoding swizzle | Sampling swizzle | + * | ------------ | ---------------- | ---------------- | + * | 1 component | RRR1 | .[rgb] | + * | 2 components | RRRG | .[rgb]a | + * | 3 components | RGB1 | .rgb | + * | 4 components | RGBA | .rgba | + * + * The 1 and 2 component modes recommend sampling from "g" to recover the luminance value as this + * provide best compatibility with other texture formats where the green component may be stored at + * higher precision than the others, such as RGB565. For ASTC any of the RGB components can be used; + * the luminance endpoint component will be returned for all three. + * + * When using the normal map compression mode ASTC will store normals as a two component X+Y map. + * Input images must contain unit-length normalized and should be passed in using a two component + * swizzle. The astcenc command line tool defaults to an RRRG swizzle, but some developers prefer + * to use GGGR for compatability with BC5n which will work just as well. The Z component can be + * recovered programmatically in shader code, using knowledge that the vector is unit length and + * that Z must be positive for a tangent-space normal map. + * + * Decompress-only usage + * ===================== + * + * For some use cases it is useful to have a cut-down context and/or library which supports + * decompression but not compression. + * + * A context can be made decompress-only using the ASTCENC_FLG_DECOMPRESS_ONLY flag when the context + * is allocated. These contexts have lower dynamic memory footprint than a full context. + * + * The entire library can be made decompress-only by building the files with the define + * ASTCENC_DECOMPRESS_ONLY set. In this build the context will be smaller, and the library will + * exclude the functionality which is only needed for compression. This reduces the binary size by + * ~180KB. For these builds contexts must be created with the ASTCENC_FLG_DECOMPRESS_ONLY flag. + * + * Note that context structures returned by a library built as decompress-only are incompatible with + * a library built with compression included, and visa versa, as they have different sizes and + * memory layout. + * + * Self-decompress-only usage + * ========================== + * + * ASTC is a complex format with a large search space. The parts of this search space that are + * searched is determined by heuristics that are, in part, tied to the quality level used when + * creating the context. + * + * A normal context is capable of decompressing any ASTC texture, including those generated by other + * compressors with unknown heuristics. This is the most flexible implementation, but forces the + * data tables used by the codec to include entries that are not needed during compression. This + * can slow down context creation by a significant amount, especially for the faster compression + * modes where few data table entries are actually used. To optimize this use case the context can + * be created with the ASTCENC_FLG_SELF_DECOMPRESS_ONLY flag. This tells the compressor that it will + * only be asked to decompress images that it compressed itself, allowing the data tables to + * exclude entries that are not needed by the current compression configuration. This reduces the + * size of the context data tables in memory and improves context creation performance. Note that, + * as of the 3.6 release, this flag no longer affects compression performance. + * + * Using this flag while attempting to decompress an valid image which was created by another + * compressor, or even another astcenc compressor version or configuration, may result in blocks + * returning as solid magenta or NaN value error blocks. + */ + +#ifndef ASTCENC_INCLUDED +#define ASTCENC_INCLUDED + +#include +#include + +#if defined(ASTCENC_DYNAMIC_LIBRARY) + #if defined(_MSC_VER) + #define ASTCENC_PUBLIC extern "C" __declspec(dllexport) + #else + #define ASTCENC_PUBLIC extern "C" __attribute__ ((visibility ("default"))) + #endif +#else + #define ASTCENC_PUBLIC +#endif + +/* ============================================================================ + Data declarations +============================================================================ */ + +/** + * @brief An opaque structure; see astcenc_internal.h for definition. + */ +struct astcenc_context; + +/** + * @brief A codec API error code. + */ +enum astcenc_error { + /** @brief The call was successful. */ + ASTCENC_SUCCESS = 0, + /** @brief The call failed due to low memory, or undersized I/O buffers. */ + ASTCENC_ERR_OUT_OF_MEM, + /** @brief The call failed due to the build using fast math. */ + ASTCENC_ERR_BAD_CPU_FLOAT, + /** @brief The call failed due to an out-of-spec parameter. */ + ASTCENC_ERR_BAD_PARAM, + /** @brief The call failed due to an out-of-spec block size. */ + ASTCENC_ERR_BAD_BLOCK_SIZE, + /** @brief The call failed due to an out-of-spec color profile. */ + ASTCENC_ERR_BAD_PROFILE, + /** @brief The call failed due to an out-of-spec quality value. */ + ASTCENC_ERR_BAD_QUALITY, + /** @brief The call failed due to an out-of-spec component swizzle. */ + ASTCENC_ERR_BAD_SWIZZLE, + /** @brief The call failed due to an out-of-spec flag set. */ + ASTCENC_ERR_BAD_FLAGS, + /** @brief The call failed due to the context not supporting the operation. */ + ASTCENC_ERR_BAD_CONTEXT, + /** @brief The call failed due to unimplemented functionality. */ + ASTCENC_ERR_NOT_IMPLEMENTED, +#if defined(ASTCENC_DIAGNOSTICS) + /** @brief The call failed due to an issue with diagnostic tracing. */ + ASTCENC_ERR_DTRACE_FAILURE, +#endif +}; + +/** + * @brief A codec color profile. + */ +enum astcenc_profile { + /** @brief The LDR sRGB color profile. */ + ASTCENC_PRF_LDR_SRGB = 0, + /** @brief The LDR linear color profile. */ + ASTCENC_PRF_LDR, + /** @brief The HDR RGB with LDR alpha color profile. */ + ASTCENC_PRF_HDR_RGB_LDR_A, + /** @brief The HDR RGBA color profile. */ + ASTCENC_PRF_HDR +}; + +/** @brief The fastest, lowest quality, search preset. */ +static const float ASTCENC_PRE_FASTEST = 0.0f; + +/** @brief The fast search preset. */ +static const float ASTCENC_PRE_FAST = 10.0f; + +/** @brief The medium quality search preset. */ +static const float ASTCENC_PRE_MEDIUM = 60.0f; + +/** @brief The thorough quality search preset. */ +static const float ASTCENC_PRE_THOROUGH = 98.0f; + +/** @brief The thorough quality search preset. */ +static const float ASTCENC_PRE_VERYTHOROUGH = 99.0f; + +/** @brief The exhaustive, highest quality, search preset. */ +static const float ASTCENC_PRE_EXHAUSTIVE = 100.0f; + +/** + * @brief A codec component swizzle selector. + */ +enum astcenc_swz +{ + /** @brief Select the red component. */ + ASTCENC_SWZ_R = 0, + /** @brief Select the green component. */ + ASTCENC_SWZ_G = 1, + /** @brief Select the blue component. */ + ASTCENC_SWZ_B = 2, + /** @brief Select the alpha component. */ + ASTCENC_SWZ_A = 3, + /** @brief Use a constant zero component. */ + ASTCENC_SWZ_0 = 4, + /** @brief Use a constant one component. */ + ASTCENC_SWZ_1 = 5, + /** @brief Use a reconstructed normal vector Z component. */ + ASTCENC_SWZ_Z = 6 +}; + +/** + * @brief A texel component swizzle. + */ +struct astcenc_swizzle +{ + /** @brief The red component selector. */ + astcenc_swz r; + /** @brief The green component selector. */ + astcenc_swz g; + /** @brief The blue component selector. */ + astcenc_swz b; + /** @brief The alpha component selector. */ + astcenc_swz a; +}; + +/** + * @brief A texel component data format. + */ +enum astcenc_type +{ + /** @brief Unorm 8-bit data per component. */ + ASTCENC_TYPE_U8 = 0, + /** @brief 16-bit float per component. */ + ASTCENC_TYPE_F16 = 1, + /** @brief 32-bit float per component. */ + ASTCENC_TYPE_F32 = 2 +}; + +/** + * @brief Enable normal map compression. + * + * Input data will be treated a two component normal map, storing X and Y, and the codec will + * optimize for angular error rather than simple linear PSNR. In this mode the input swizzle should + * be e.g. rrrg (the default ordering for ASTC normals on the command line) or gggr (the ordering + * used by BC5n). + */ +static const unsigned int ASTCENC_FLG_MAP_NORMAL = 1 << 0; + +/** + * @brief Enable alpha weighting. + * + * The input alpha value is used for transparency, so errors in the RGB components are weighted by + * the transparency level. This allows the codec to more accurately encode the alpha value in areas + * where the color value is less significant. + */ +static const unsigned int ASTCENC_FLG_USE_ALPHA_WEIGHT = 1 << 2; + +/** + * @brief Enable perceptual error metrics. + * + * This mode enables perceptual compression mode, which will optimize for perceptual error rather + * than best PSNR. Only some input modes support perceptual error metrics. + */ +static const unsigned int ASTCENC_FLG_USE_PERCEPTUAL = 1 << 3; + +/** + * @brief Create a decompression-only context. + * + * This mode disables support for compression. This enables context allocation to skip some + * transient buffer allocation, resulting in lower memory usage. + */ +static const unsigned int ASTCENC_FLG_DECOMPRESS_ONLY = 1 << 4; + +/** + * @brief Create a self-decompression context. + * + * This mode configures the compressor so that it is only guaranteed to be able to decompress images + * that were actually created using the current context. This is the common case for compression use + * cases, and setting this flag enables additional optimizations, but does mean that the context + * cannot reliably decompress arbitrary ASTC images. + */ +static const unsigned int ASTCENC_FLG_SELF_DECOMPRESS_ONLY = 1 << 5; + +/** + * @brief Enable RGBM map compression. + * + * Input data will be treated as HDR data that has been stored in an LDR RGBM-encoded wrapper + * format. Data must be preprocessed by the user to be in LDR RGBM format before calling the + * compression function, this flag is only used to control the use of RGBM-specific heuristics and + * error metrics. + * + * IMPORTANT: The ASTC format is prone to bad failure modes with unconstrained RGBM data; very small + * M values can round to zero due to quantization and result in black or white pixels. It is highly + * recommended that the minimum value of M used in the encoding is kept above a lower threshold (try + * 16 or 32). Applying this threshold reduces the number of very dark colors that can be + * represented, but is still higher precision than 8-bit LDR. + * + * When this flag is set the value of @c rgbm_m_scale in the context must be set to the RGBM scale + * factor used during reconstruction. This defaults to 5 when in RGBM mode. + * + * It is recommended that the value of @c cw_a_weight is set to twice the value of the multiplier + * scale, ensuring that the M value is accurately encoded. This defaults to 10 when in RGBM mode, + * matching the default scale factor. + */ +static const unsigned int ASTCENC_FLG_MAP_RGBM = 1 << 6; + +/** + * @brief The bit mask of all valid flags. + */ +static const unsigned int ASTCENC_ALL_FLAGS = + ASTCENC_FLG_MAP_NORMAL | + ASTCENC_FLG_MAP_RGBM | + ASTCENC_FLG_USE_ALPHA_WEIGHT | + ASTCENC_FLG_USE_PERCEPTUAL | + ASTCENC_FLG_DECOMPRESS_ONLY | + ASTCENC_FLG_SELF_DECOMPRESS_ONLY; + +/** + * @brief The config structure. + * + * This structure will initially be populated by a call to astcenc_config_init, but power users may + * modify it before calling astcenc_context_alloc. See astcenccli_toplevel_help.cpp for full user + * documentation of the power-user settings. + * + * Note for any settings which are associated with a specific color component, the value in the + * config applies to the component that exists after any compression data swizzle is applied. + */ +struct astcenc_config +{ + /** @brief The color profile. */ + astcenc_profile profile; + + /** @brief The set of set flags. */ + unsigned int flags; + + /** @brief The ASTC block size X dimension. */ + unsigned int block_x; + + /** @brief The ASTC block size Y dimension. */ + unsigned int block_y; + + /** @brief The ASTC block size Z dimension. */ + unsigned int block_z; + + /** @brief The red component weight scale for error weighting (-cw). */ + float cw_r_weight; + + /** @brief The green component weight scale for error weighting (-cw). */ + float cw_g_weight; + + /** @brief The blue component weight scale for error weighting (-cw). */ + float cw_b_weight; + + /** @brief The alpha component weight scale for error weighting (-cw). */ + float cw_a_weight; + + /** + * @brief The radius for any alpha-weight scaling (-a). + * + * It is recommended that this is set to 1 when using FLG_USE_ALPHA_WEIGHT on a texture that + * will be sampled using linear texture filtering to minimize color bleed out of transparent + * texels that are adjacent to non-transparent texels. + */ + unsigned int a_scale_radius; + + /** @brief The RGBM scale factor for the shared multiplier (-rgbm). */ + float rgbm_m_scale; + + /** + * @brief The maximum number of partitions searched (-partitioncountlimit). + * + * Valid values are between 1 and 4. + */ + unsigned int tune_partition_count_limit; + + /** + * @brief The maximum number of partitions searched (-2partitionindexlimit). + * + * Valid values are between 1 and 1024. + */ + unsigned int tune_2partition_index_limit; + + /** + * @brief The maximum number of partitions searched (-3partitionindexlimit). + * + * Valid values are between 1 and 1024. + */ + unsigned int tune_3partition_index_limit; + + /** + * @brief The maximum number of partitions searched (-4partitionindexlimit). + * + * Valid values are between 1 and 1024. + */ + unsigned int tune_4partition_index_limit; + + /** + * @brief The maximum centile for block modes searched (-blockmodelimit). + * + * Valid values are between 1 and 100. + */ + unsigned int tune_block_mode_limit; + + /** + * @brief The maximum iterative refinements applied (-refinementlimit). + * + * Valid values are between 1 and N; there is no technical upper limit + * but little benefit is expected after N=4. + */ + unsigned int tune_refinement_limit; + + /** + * @brief The number of trial candidates per mode search (-candidatelimit). + * + * Valid values are between 1 and TUNE_MAX_TRIAL_CANDIDATES. + */ + unsigned int tune_candidate_limit; + + /** + * @brief The number of trial partitionings per search (-2partitioncandidatelimit). + * + * Valid values are between 1 and TUNE_MAX_PARTITIONING_CANDIDATES. + */ + unsigned int tune_2partitioning_candidate_limit; + + /** + * @brief The number of trial partitionings per search (-3partitioncandidatelimit). + * + * Valid values are between 1 and TUNE_MAX_PARTITIONING_CANDIDATES. + */ + unsigned int tune_3partitioning_candidate_limit; + + /** + * @brief The number of trial partitionings per search (-4partitioncandidatelimit). + * + * Valid values are between 1 and TUNE_MAX_PARTITIONING_CANDIDATES. + */ + unsigned int tune_4partitioning_candidate_limit; + + /** + * @brief The dB threshold for stopping block search (-dblimit). + * + * This option is ineffective for HDR textures. + */ + float tune_db_limit; + + /** + * @brief The amount of MSE overshoot needed to early-out trials. + * + * The first early-out is for 1 partition, 1 plane trials, where we try a minimal encode using + * the high probability block modes. This can short-cut compression for simple blocks. + * + * The second early-out is for refinement trials, where we can exit refinement once quality is + * reached. + */ + float tune_mse_overshoot; + + /** + * @brief The threshold for skipping 3.1/4.1 trials (-2partitionlimitfactor). + * + * This option is further scaled for normal maps, so it skips less often. + */ + float tune_2partition_early_out_limit_factor; + + /** + * @brief The threshold for skipping 4.1 trials (-3partitionlimitfactor). + * + * This option is further scaled for normal maps, so it skips less often. + */ + float tune_3partition_early_out_limit_factor; + + /** + * @brief The threshold for skipping two weight planes (-2planelimitcorrelation). + * + * This option is ineffective for normal maps. + */ + float tune_2plane_early_out_limit_correlation; + + /** + * @brief The config enable for the mode0 fast-path search. + * + * If this is set to TUNE_MIN_TEXELS_MODE0 or higher then the early-out fast mode0 + * search is enabled. This option is ineffective for 3D block sizes. + */ + float tune_search_mode0_enable; + +#if defined(ASTCENC_DIAGNOSTICS) + /** + * @brief The path to save the diagnostic trace data to. + * + * This option is not part of the public API, and requires special builds + * of the library. + */ + const char* trace_file_path; +#endif +}; + +/** + * @brief An uncompressed 2D or 3D image. + * + * 3D image are passed in as an array of 2D slices. Each slice has identical + * size and color format. + */ +struct astcenc_image +{ + /** @brief The X dimension of the image, in texels. */ + unsigned int dim_x; + + /** @brief The Y dimension of the image, in texels. */ + unsigned int dim_y; + + /** @brief The Z dimension of the image, in texels. */ + unsigned int dim_z; + + /** @brief The data type per component. */ + astcenc_type data_type; + + /** @brief The array of 2D slices, of length @c dim_z. */ + void** data; +}; + +/** + * @brief A block encoding metadata query result. + * + * If the block is an error block or a constant color block or an error block all fields other than + * the profile, block dimensions, and error/constant indicator will be zero. + */ +struct astcenc_block_info +{ + /** @brief The block encoding color profile. */ + astcenc_profile profile; + + /** @brief The number of texels in the X dimension. */ + unsigned int block_x; + + /** @brief The number of texels in the Y dimension. */ + unsigned int block_y; + + /** @brief The number of texel in the Z dimension. */ + unsigned int block_z; + + /** @brief The number of texels in the block. */ + unsigned int texel_count; + + /** @brief True if this block is an error block. */ + bool is_error_block; + + /** @brief True if this block is a constant color block. */ + bool is_constant_block; + + /** @brief True if this block is an HDR block. */ + bool is_hdr_block; + + /** @brief True if this block uses two weight planes. */ + bool is_dual_plane_block; + + /** @brief The number of partitions if not constant color. */ + unsigned int partition_count; + + /** @brief The partition index if 2 - 4 partitions used. */ + unsigned int partition_index; + + /** @brief The component index of the second plane if dual plane. */ + unsigned int dual_plane_component; + + /** @brief The color endpoint encoding mode for each partition. */ + unsigned int color_endpoint_modes[4]; + + /** @brief The number of color endpoint quantization levels. */ + unsigned int color_level_count; + + /** @brief The number of weight quantization levels. */ + unsigned int weight_level_count; + + /** @brief The number of weights in the X dimension. */ + unsigned int weight_x; + + /** @brief The number of weights in the Y dimension. */ + unsigned int weight_y; + + /** @brief The number of weights in the Z dimension. */ + unsigned int weight_z; + + /** @brief The unpacked color endpoints for each partition. */ + float color_endpoints[4][2][4]; + + /** @brief The per-texel interpolation weights for the block. */ + float weight_values_plane1[216]; + + /** @brief The per-texel interpolation weights for the block. */ + float weight_values_plane2[216]; + + /** @brief The per-texel partition assignments for the block. */ + uint8_t partition_assignment[216]; +}; + +/** + * Populate a codec config based on default settings. + * + * Power users can edit the returned config struct to fine tune before allocating the context. + * + * @param profile Color profile. + * @param block_x ASTC block size X dimension. + * @param block_y ASTC block size Y dimension. + * @param block_z ASTC block size Z dimension. + * @param quality Search quality preset / effort level. Either an + * @c ASTCENC_PRE_* value, or a effort level between 0 + * and 100. Performance is not linear between 0 and 100. + + * @param flags A valid set of @c ASTCENC_FLG_* flag bits. + * @param[out] config Output config struct to populate. + * + * @return @c ASTCENC_SUCCESS on success, or an error if the inputs are invalid + * either individually, or in combination. + */ +ASTCENC_PUBLIC astcenc_error astcenc_config_init( + astcenc_profile profile, + unsigned int block_x, + unsigned int block_y, + unsigned int block_z, + float quality, + unsigned int flags, + astcenc_config* config); + +/** + * @brief Allocate a new codec context based on a config. + * + * This function allocates all of the memory resources and threads needed by the codec. This can be + * slow, so it is recommended that contexts are reused to serially compress or decompress multiple + * images to amortize setup cost. + * + * Contexts can be allocated to support only decompression using the @c ASTCENC_FLG_DECOMPRESS_ONLY + * flag when creating the configuration. The compression functions will fail if invoked. For a + * decompress-only library build the @c ASTCENC_FLG_DECOMPRESS_ONLY flag must be set when creating + * any context. + * + * @param[in] config Codec config. + * @param thread_count Thread count to configure for. + * @param[out] context Location to store an opaque context pointer. + * + * @return @c ASTCENC_SUCCESS on success, or an error if context creation failed. + */ +ASTCENC_PUBLIC astcenc_error astcenc_context_alloc( + const astcenc_config* config, + unsigned int thread_count, + astcenc_context** context); + +/** + * @brief Compress an image. + * + * A single context can only compress or decompress a single image at a time. + * + * For a context configured for multi-threading, any set of the N threads can call this function. + * Work will be dynamically scheduled across the threads available. Each thread must have a unique + * @c thread_index. + * + * @param context Codec context. + * @param[in,out] image An input image, in 2D slices. + * @param swizzle Compression data swizzle, applied before compression. + * @param[out] data_out Pointer to output data array. + * @param data_len Length of the output data array. + * @param thread_index Thread index [0..N-1] of calling thread. + * + * @return @c ASTCENC_SUCCESS on success, or an error if compression failed. + */ +ASTCENC_PUBLIC astcenc_error astcenc_compress_image( + astcenc_context* context, + astcenc_image* image, + const astcenc_swizzle* swizzle, + uint8_t* data_out, + size_t data_len, + unsigned int thread_index); + +/** + * @brief Reset the codec state for a new compression. + * + * The caller is responsible for synchronizing threads in the worker thread pool. This function must + * only be called when all threads have exited the @c astcenc_compress_image() function for image N, + * but before any thread enters it for image N + 1. + * + * Calling this is not required (but won't hurt), if the context is created for single threaded use. + * + * @param context Codec context. + * + * @return @c ASTCENC_SUCCESS on success, or an error if reset failed. + */ +ASTCENC_PUBLIC astcenc_error astcenc_compress_reset( + astcenc_context* context); + +/** + * @brief Decompress an image. + * + * @param context Codec context. + * @param[in] data Pointer to compressed data. + * @param data_len Length of the compressed data, in bytes. + * @param[in,out] image_out Output image. + * @param swizzle Decompression data swizzle, applied after decompression. + * @param thread_index Thread index [0..N-1] of calling thread. + * + * @return @c ASTCENC_SUCCESS on success, or an error if decompression failed. + */ +ASTCENC_PUBLIC astcenc_error astcenc_decompress_image( + astcenc_context* context, + const uint8_t* data, + size_t data_len, + astcenc_image* image_out, + const astcenc_swizzle* swizzle, + unsigned int thread_index); + +/** + * @brief Reset the codec state for a new decompression. + * + * The caller is responsible for synchronizing threads in the worker thread pool. This function must + * only be called when all threads have exited the @c astcenc_decompress_image() function for image + * N, but before any thread enters it for image N + 1. + * + * Calling this is not required (but won't hurt), if the context is created for single threaded use. + * + * @param context Codec context. + * + * @return @c ASTCENC_SUCCESS on success, or an error if reset failed. + */ +ASTCENC_PUBLIC astcenc_error astcenc_decompress_reset( + astcenc_context* context); + +/** + * Free the compressor context. + * + * @param context The codec context. + */ +ASTCENC_PUBLIC void astcenc_context_free( + astcenc_context* context); + +/** + * @brief Provide a high level summary of a block's encoding. + * + * This feature is primarily useful for codec developers but may be useful for developers building + * advanced content packaging pipelines. + * + * @param context Codec context. + * @param data One block of compressed ASTC data. + * @param info The output info structure to populate. + * + * @return @c ASTCENC_SUCCESS if the block was decoded, or an error otherwise. Note that this + * function will return success even if the block itself was an error block encoding, as the + * decode was correctly handled. + */ +ASTCENC_PUBLIC astcenc_error astcenc_get_block_info( + astcenc_context* context, + const uint8_t data[16], + astcenc_block_info* info); + +/** + * @brief Get a printable string for specific status code. + * + * @param status The status value. + * + * @return A human readable nul-terminated string. + */ +ASTCENC_PUBLIC const char* astcenc_get_error_string( + astcenc_error status); + +#endif diff --git a/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json b/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json index 134a0ef98..f34374fb7 100644 --- a/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json +++ b/Source/ThirdParty/nethost/FlaxEngine.CSharp.runtimeconfig.json @@ -1,9 +1,9 @@ { "runtimeOptions": { - "tfm": "net7.0", + "tfm": "net8.0", "framework": { "name": "Microsoft.NETCore.App", - "version": "7.0.0", + "version": "8.0.0", "rollForward": "latestMajor" } } diff --git a/Source/ThirdParty/nethost/nethost.Build.cs b/Source/ThirdParty/nethost/nethost.Build.cs index 7404083bd..7582322e1 100644 --- a/Source/ThirdParty/nethost/nethost.Build.cs +++ b/Source/ThirdParty/nethost/nethost.Build.cs @@ -74,11 +74,24 @@ public class nethost : ThirdPartyModule case TargetPlatform.Switch: case TargetPlatform.PS4: case TargetPlatform.PS5: - options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libmonosgen-2.0.a")); - options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.Native.a")); - options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.IO.Ports.Native.a")); - options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.IO.Compression.Native.a")); + { + var type = Flax.Build.Utilities.GetType($"Flax.Build.Platforms.{options.Platform.Target}.nethost"); + var onLink = type?.GetMethod("OnLink"); + if (onLink != null) + { + // Custom linking logic overriden by platform tools + onLink.Invoke(null, new object[] { options, hostRuntime.Path }); + } + else + { + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libmonosgen-2.0.a")); + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.Native.a")); + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.IO.Ports.Native.a")); + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.IO.Compression.Native.a")); + options.OutputFiles.Add(Path.Combine(hostRuntime.Path, "libSystem.Globalization.Native.a")); + } break; + } case TargetPlatform.Android: options.PublicDefinitions.Add("USE_MONO_DYNAMIC_LIB"); options.DependencyFiles.Add(Path.Combine(hostRuntime.Path, "libmonosgen-2.0.so")); diff --git a/Source/ThirdParty/tracy/client/TracyProfiler.cpp b/Source/ThirdParty/tracy/client/TracyProfiler.cpp index 59c31d6f8..6d67eabf2 100644 --- a/Source/ThirdParty/tracy/client/TracyProfiler.cpp +++ b/Source/ThirdParty/tracy/client/TracyProfiler.cpp @@ -113,6 +113,11 @@ extern "C" typedef BOOL (WINAPI *t_GetLogicalProcessorInformationEx)( LOGICAL_PR # include #endif +#if !defined _WIN32 && !defined __linux__ && !defined __APPLE__ +#include "Engine/Core/Types/String.h" +#include "Engine/Platform/MemoryStats.h" +#endif + namespace tracy { @@ -383,7 +388,7 @@ static int64_t SetupHwTimer() static const char* GetProcessName() { - const char* processName = "unknown"; + const char* processName = "FlaxEngine"; #ifdef _WIN32 static char buf[_MAX_PATH]; GetModuleFileNameA( nullptr, buf, _MAX_PATH ); @@ -518,7 +523,10 @@ static const char* GetHostInfo() #elif defined __OpenBSD__ ptr += sprintf( ptr, "OS: BSD (OpenBSD)\n" ); #else - ptr += sprintf( ptr, "OS: unknown\n" ); + String computerName = Platform::GetComputerName(); + char computerNameBuf[60]; + StringUtils::ConvertUTF162ANSI(computerName.Get(), computerNameBuf, computerName.Length()); + ptr += sprintf( ptr, "OS: %s\n", computerNameBuf ); #endif #if defined _MSC_VER @@ -690,7 +698,7 @@ static const char* GetHostInfo() sysctlbyname( "hw.physmem", &memSize, &sz, nullptr, 0 ); ptr += sprintf( ptr, "RAM: %zu MB\n", memSize / 1024 / 1024 ); #else - ptr += sprintf( ptr, "RAM: unknown\n" ); + ptr += sprintf( ptr, "RAM: %zu MB\n", (size_t)Platform::GetMemoryStats().TotalPhysicalMemory / 1024 / 1024 ); #endif return buf; diff --git a/Source/ThirdParty/tracy/common/TracyAlloc.hpp b/Source/ThirdParty/tracy/common/TracyAlloc.hpp index 4e49df84d..a352c8478 100644 --- a/Source/ThirdParty/tracy/common/TracyAlloc.hpp +++ b/Source/ThirdParty/tracy/common/TracyAlloc.hpp @@ -3,7 +3,7 @@ #include -#if defined TRACY_ENABLE && !defined __EMSCRIPTEN__ +#if defined TRACY_ENABLE && !defined __EMSCRIPTEN__ && !defined TRACY_USE_MALLOC # include "../client/tracy_rpmalloc.hpp" # define TRACY_USE_RPMALLOC #endif diff --git a/Source/ThirdParty/tracy/common/TracySocket.cpp b/Source/ThirdParty/tracy/common/TracySocket.cpp index 259678989..42e815232 100644 --- a/Source/ThirdParty/tracy/common/TracySocket.cpp +++ b/Source/ThirdParty/tracy/common/TracySocket.cpp @@ -450,6 +450,9 @@ ListenSocket::~ListenSocket() static int addrinfo_and_socket_for_family( uint16_t port, int ai_family, struct addrinfo** res ) { +#if PLATFORM_SWITCH + Platform::GetNetworkConnectionType(); // Ensure to have network service initialized before using sockets +#endif struct addrinfo hints; memset( &hints, 0, sizeof( hints ) ); hints.ai_family = ai_family; diff --git a/Source/ThirdParty/tracy/common/TracySystem.cpp b/Source/ThirdParty/tracy/common/TracySystem.cpp index 9a477aa31..4de80185d 100644 --- a/Source/ThirdParty/tracy/common/TracySystem.cpp +++ b/Source/ThirdParty/tracy/common/TracySystem.cpp @@ -47,6 +47,10 @@ extern "C" typedef HRESULT (WINAPI *t_GetThreadDescription)( HANDLE, PWSTR* ); #ifdef TRACY_ENABLE # include # include "TracyAlloc.hpp" + +#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__linux__) +#include "Engine/Platform/Platform.h" +#endif #endif namespace tracy @@ -88,7 +92,7 @@ TRACY_API uint32_t GetThreadHandleImpl() // thread identifier. It is a pointer to a library-allocated data structure instead. // Such pointers will be reused heavily, making the pthread_t non-unique. Additionally // a 64-bit pointer cannot be reliably truncated to 32 bits. - #error "Unsupported platform!" + return Platform::GetCurrentThreadID(); #endif } diff --git a/Source/ThirdParty/tracy/tracy.Build.cs b/Source/ThirdParty/tracy/tracy.Build.cs index 2d8d4ded8..c4aa69048 100644 --- a/Source/ThirdParty/tracy/tracy.Build.cs +++ b/Source/ThirdParty/tracy/tracy.Build.cs @@ -40,14 +40,20 @@ public class tracy : ThirdPartyModule options.PublicDefinitions.Add("TRACY_ENABLE"); options.PrivateDefinitions.Add("TRACY_NO_INVARIANT_CHECK"); options.PrivateDefinitions.Add("TRACY_NO_FRAME_IMAGE"); - if (options.Platform.Target == TargetPlatform.Windows) - { - options.PrivateDefinitions.Add("TRACY_DBGHELP_LOCK=DbgHelp"); - } if (OnDemand) { options.PublicDefinitions.Add("TRACY_ON_DEMAND"); } + switch (options.Platform.Target) + { + case TargetPlatform.Windows: + options.PrivateDefinitions.Add("TRACY_DBGHELP_LOCK=DbgHelp"); + break; + case TargetPlatform.Switch: + options.PrivateDefinitions.Add("TRACY_USE_MALLOC"); + options.PrivateDefinitions.Add("TRACY_ONLY_IPV4"); + break; + } } /// diff --git a/Source/ThirdParty/volk/volk.Build.cs b/Source/ThirdParty/volk/volk.Build.cs index 667ecf3af..bd6ee1f58 100644 --- a/Source/ThirdParty/volk/volk.Build.cs +++ b/Source/ThirdParty/volk/volk.Build.cs @@ -42,16 +42,13 @@ public class volk : ThirdPartyModule break; case TargetPlatform.Mac: options.PublicDefinitions.Add("VK_USE_PLATFORM_MACOS_MVK"); - options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/macOS/libMoltenVK.dylib")); - options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/macOS/MoltenVK_icd.json")); break; case TargetPlatform.iOS: options.PublicDefinitions.Add("VK_USE_PLATFORM_IOS_MVK"); - options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/iOS/libMoltenVK.dylib")); - options.DependencyFiles.Add(Path.Combine(VulkanSdk.Instance.RootPath, "../MoltenVK/dylib/iOS/MoltenVK_icd.json")); break; default: throw new InvalidPlatformException(options.Platform.Target); } + VulkanSdk.Instance.AddDependencyFiles(options); string includesFolderPath; if (VulkanSdk.Instance.TryGetIncludePath(options.Platform.Target, out includesFolderPath)) diff --git a/Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj b/Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj index df3b9851b..873f916c4 100644 --- a/Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj +++ b/Source/Tools/Flax.Build.Tests/Flax.Build.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 11.0 disable annotations diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index f026544e8..3f7dc1e3a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -635,7 +635,11 @@ namespace Flax.Build.Bindings else if (parameterInfo.Type.Type == "Array" && parameterInfo.Type.GenericArgs.Count > 0 && parameterInfo.Type.GenericArgs[0].Type == "bool") parameterMarshalType = $"MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = {(!functionInfo.IsStatic ? 1 : 0) + functionInfo.Parameters.Count + (functionInfo.Glue.CustomParameters.FindIndex(x => x.Name == $"__{parameterInfo.Name}Count"))})"; else if (parameterInfo.Type.Type == "Array" || parameterInfo.Type.Type == "Span" || parameterInfo.Type.Type == "DataContainer" || parameterInfo.Type.Type == "BytesContainer" || nativeType == "Array") + { parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = \"__{parameterInfo.Name}Count\")"; + if (!parameterInfo.IsOut && !parameterInfo.IsRef) + parameterMarshalType += ", In"; // The usage of 'LibraryImportAttribute' does not follow recommendations. It is recommended to use explicit '[In]' and '[Out]' attributes on array parameters. + } else if (parameterInfo.Type.Type == "Dictionary") parameterMarshalType = $"MarshalUsing(typeof(FlaxEngine.Interop.DictionaryMarshaller<,>), ConstantElementCount = 0)"; else if (nativeType == "bool") diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 681483cf9..3bf3a2a2a 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -24,7 +24,7 @@ namespace Flax.Build.Bindings private static readonly List CppAutoSerializeProperties = new List(); public static readonly HashSet CppIncludeFiles = new HashSet(); private static readonly List CppIncludeFilesList = new List(); - private static readonly HashSet CppVariantToTypes = new HashSet(); + private static readonly Dictionary CppVariantToTypes = new Dictionary(); private static readonly Dictionary CppVariantFromTypes = new Dictionary(); private static bool CppNonPodTypesConvertingGeneration = false; private static StringBuilder CppContentsEnd; @@ -231,13 +231,15 @@ namespace Flax.Build.Bindings throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { - CppVariantToTypes.Add(typeInfo); - return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; + var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); + CppVariantToTypes[wrapperName] = typeInfo; + return $"MoveTemp(VariantTo{wrapperName}({value}))"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) { - CppVariantToTypes.Add(typeInfo); - return $"MoveTemp(VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}({value}))"; + var wrapperName = GenerateCppWrapperNativeToVariantMethodName(typeInfo); + CppVariantToTypes[wrapperName] = typeInfo; + return $"MoveTemp(VariantTo{wrapperName}({value}))"; } if (typeInfo.Type == "Span" && typeInfo.GenericArgs != null) { @@ -2790,12 +2792,14 @@ namespace Flax.Build.Bindings var header = GetStringBuilder(); // Variant converting helper methods - foreach (var typeInfo in CppVariantToTypes) + foreach (var e in CppVariantToTypes) { + var wrapperName = e.Key; + var typeInfo = e.Value; var name = typeInfo.ToString(false); header.AppendLine(); header.AppendLine("namespace {"); - header.Append($"{name} VariantTo{GenerateCppWrapperNativeToVariantMethodName(typeInfo)}(const Variant& v)").AppendLine(); + header.Append($"{name} VariantTo{wrapperName}(const Variant& v)").AppendLine(); header.Append('{').AppendLine(); header.Append($" {name} result;").AppendLine(); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) @@ -3132,6 +3136,8 @@ namespace Flax.Build.Bindings CppIncludeFilesList.Add(fileInfo.Name); CppIncludeFilesList.AddRange(CppIncludeFiles); CppIncludeFilesList.Sort(); + if (CppIncludeFilesList.Remove("Engine/Serialization/Serialization.h")) + CppIncludeFilesList.Add("Engine/Serialization/Serialization.h"); foreach (var path in CppIncludeFilesList) header.AppendFormat("#include \"{0}\"", path).AppendLine(); contents.Insert(headerPos, header.ToString()); diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs index 7eced2b66..2b08077a6 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetAOT.cs @@ -309,7 +309,7 @@ namespace Flax.Build // Peek class library folder var coreLibPaths = Directory.GetFiles(aotAssembliesPath, "System.Private.CoreLib.dll", SearchOption.AllDirectories); if (coreLibPaths.Length != 1) - throw new Exception("Invalid C# class library setup in " + aotAssembliesPath); + throw new Exception($"Invalid C# class library setup in '{aotAssembliesPath}' (missing C# dll files)"); var dotnetLibPath = Utilities.NormalizePath(Path.GetDirectoryName(coreLibPaths[0])); Log.Info("Class library found in: " + dotnetLibPath); diff --git a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs index 581ba6139..b32529404 100644 --- a/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs +++ b/Source/Tools/Flax.Build/Build/DotNet/DotNetSdk.cs @@ -130,7 +130,7 @@ namespace Flax.Build /// /// The minimum SDK version. /// - public static Version MinimumVersion => new Version(7, 0); + public static Version MinimumVersion => new Version(8, 0); /// /// The maximum SDK version. @@ -243,19 +243,12 @@ namespace Flax.Build dotnetPath = string.Empty; } } - - bool isRunningOnArm64Targetx64 = architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64); - - // We need to support two paths here: - // 1. We are running an x64 binary and we are running on an arm64 host machine - // 2. We are running an Arm64 binary and we are targeting an x64 host machine - if (Flax.Build.Platforms.MacPlatform.GetProcessIsTranslated() || isRunningOnArm64Targetx64) + if (Flax.Build.Platforms.MacPlatform.BuildingForx64) { rid = "osx-x64"; dotnetPath = Path.Combine(dotnetPath, "x64"); architecture = TargetArchitecture.x64; } - break; } default: throw new InvalidPlatformException(platform); @@ -472,6 +465,8 @@ namespace Flax.Build { var versions = GetVersions(root); var version = GetVersion(versions); + if (version == null) + throw new Exception($"Failed to select dotnet version from '{root}' ({string.Join(", ", versions)})"); return Path.Combine(root, version); } diff --git a/Source/Tools/Flax.Build/Build/EngineModule.cs b/Source/Tools/Flax.Build/Build/EngineModule.cs index f69c20cd5..021a31f44 100644 --- a/Source/Tools/Flax.Build/Build/EngineModule.cs +++ b/Source/Tools/Flax.Build/Build/EngineModule.cs @@ -35,6 +35,10 @@ namespace Flax.Build options.ScriptingAPI.Defines.Add("FLAX_GAME"); } + // Use custom precompiled header file for the engine to boost compilation time + options.CompileEnv.PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.CreateManual; + options.CompileEnv.PrecompiledHeaderSource = Utilities.NormalizePath(Path.Combine(Globals.EngineRoot, "Source/FlaxEngine.pch.h")); + BinaryModuleName = "FlaxEngine"; options.ScriptingAPI.Defines.Add("FLAX"); options.ScriptingAPI.Defines.Add("FLAX_ASSERTIONS"); diff --git a/Source/Tools/Flax.Build/Build/EngineTarget.cs b/Source/Tools/Flax.Build/Build/EngineTarget.cs index a4837518d..e62defdfe 100644 --- a/Source/Tools/Flax.Build/Build/EngineTarget.cs +++ b/Source/Tools/Flax.Build/Build/EngineTarget.cs @@ -16,6 +16,11 @@ namespace Flax.Build { private static Version _engineVersion; + /// + /// Name of the native engine library. + /// + public const string LibraryName = "FlaxEngine"; + /// /// Gets the engine project. /// @@ -50,6 +55,12 @@ namespace Flax.Build defines.Add(string.Format("FLAX_{0}_{1}_OR_NEWER", engineVersion.Major, minor)); } + /// + /// True if target is built as monolithic executable with Main module inside, otherwise built as shared library with separate executable made of Main module only. + /// + /// Some platforms might not support modular build and enforce monolithic executable. See + public bool IsMonolithicExecutable = true; + /// public override void Init() { @@ -60,19 +71,29 @@ namespace Flax.Build Modules.Add("Main"); Modules.Add("Engine"); + Win32ResourceFile = Path.Combine(Globals.EngineRoot, "Source", "FlaxEngine.rc"); } /// public override string GetOutputFilePath(BuildOptions options, TargetOutputType? outputType) { + var asLib = UseSeparateMainExecutable(options) || BuildAsLibrary(options); + // If building engine executable for platform doesn't support referencing it when linking game shared libraries - if (outputType == null && UseSeparateMainExecutable(options)) + if (outputType == null && asLib) { // Build into shared library outputType = TargetOutputType.Library; } - return base.GetOutputFilePath(options, outputType); + // Override output name to shared library name when building library for the separate main executable + var outputName = OutputName; + if (asLib && (outputType ?? OutputType) == TargetOutputType.Library) + OutputName = LibraryName; + + var result = base.GetOutputFilePath(options, outputType); + OutputName = outputName; + return result; } /// @@ -81,7 +102,7 @@ namespace Flax.Build base.SetupTargetEnvironment(options); // If building engine executable for platform doesn't support referencing it when linking game shared libraries - if (UseSeparateMainExecutable(options)) + if (UseSeparateMainExecutable(options) || BuildAsLibrary(options)) { // Build into shared library options.LinkEnv.Output = LinkerOutput.SharedLibrary; @@ -123,9 +144,26 @@ namespace Flax.Build /// /// Returns true if this build target should use separate (aka main-only) executable file and separate runtime (in shared library). Used on platforms that don't support linking again executable file but only shared library (see HasExecutableFileReferenceSupport). /// - public bool UseSeparateMainExecutable(BuildOptions buildOptions) + public virtual bool UseSeparateMainExecutable(BuildOptions buildOptions) { - return UseSymbolsExports && OutputType == TargetOutputType.Executable && !buildOptions.Platform.HasExecutableFileReferenceSupport && !Configuration.BuildBindingsOnly; + if (OutputType == TargetOutputType.Executable && !Configuration.BuildBindingsOnly) + { + if (buildOptions.Platform.Target == TargetPlatform.Android) + return false; + if (!buildOptions.Platform.HasModularBuildSupport) + return false; + return !IsMonolithicExecutable || (!buildOptions.Platform.HasExecutableFileReferenceSupport && UseSymbolsExports); + } + return false; + } + + private bool BuildAsLibrary(BuildOptions buildOptions) + { + switch (buildOptions.Platform.Target) + { + case TargetPlatform.UWP: return true; + default: return false; + } } private void BuildMainExecutable(TaskGraph graph, BuildOptions buildOptions) @@ -171,11 +209,15 @@ namespace Flax.Build mainModuleOptions.SourcePaths.Add(mainModule.FolderPath); mainModule.Setup(mainModuleOptions); mainModuleOptions.MergeSourcePathsIntoSourceFiles(); + mainModuleOptions.CompileEnv.PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.None; mainModuleOptions.CompileEnv.PreprocessorDefinitions.Add("FLAXENGINE_API=" + buildOptions.Toolchain.DllImport); Builder.BuildModuleInner(buildData, mainModule, mainModuleOptions, false); // Link executable - exeBuildOptions.LinkEnv.InputLibraries.Add(Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(OutputName, LinkerOutput.SharedLibrary))); + var engineLibraryType = LinkerOutput.SharedLibrary; + if (buildOptions.Toolchain?.Compiler == TargetCompiler.MSVC) + engineLibraryType = LinkerOutput.ImportLibrary; // MSVC links DLL against import library + exeBuildOptions.LinkEnv.InputLibraries.Add(Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(LibraryName, engineLibraryType))); exeBuildOptions.LinkEnv.InputFiles.AddRange(mainModuleOptions.OutputFiles); exeBuildOptions.DependencyFiles.AddRange(mainModuleOptions.DependencyFiles); exeBuildOptions.OptionalDependencyFiles.AddRange(mainModuleOptions.OptionalDependencyFiles); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs index 7300ff083..fb94131a1 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/BuildOptions.cs @@ -23,6 +23,27 @@ namespace Flax.Build.NativeCpp GenerateProject = 1, } + /// + /// Precompiled Headers Files (PCH) usage modes. + /// + public enum PrecompiledHeaderFileUsage + { + /// + /// Precompiled Headers Files (PCH) feature is disabled. + /// + None, + + /// + /// Enables creation and usage of the header file. The input source PCH will be precompiled and included. + /// + CreateManual, + + /// + /// Enables usage of the header file. The input source PCH will be included in the build (assuming it exists). + /// + UseManual, + } + /// /// The nullable context type used with reference types (C#). /// diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index 9c071e758..ee61da013 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -32,6 +32,7 @@ namespace Flax.Build public IGrouping[] BinaryModules; public BuildTargetInfo BuildInfo; public Dictionary ReferenceBuilds = new Dictionary(); + public Dictionary PrecompiledHeaderFiles = new(); public BuildTargetBinaryModuleInfo FinReferenceBuildModule(string name) { @@ -163,6 +164,30 @@ namespace Flax.Build } } + public string Serialize() + { + // Null any empty fields to exclude them from serialization + if (HotReloadPostfix?.Length == 0) + HotReloadPostfix = null; + foreach (var binaryModule in BinaryModules) + { + if (binaryModule.NativePathProcessed?.Length == 0) + binaryModule.NativePathProcessed = null; + if (binaryModule.ManagedPathProcessed?.Length == 0) + binaryModule.ManagedPathProcessed = null; + } + + // Convert to Json + var options = new JsonSerializerOptions + { + WriteIndented = true, + IncludeFields = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + TypeInfoResolver = BuildTargetInfoSourceGenerationContext.Default, + }; + return JsonSerializer.Serialize(this, options); + } + public static string ProcessPath(string path, string projectPath) { if (string.IsNullOrEmpty(path)) @@ -484,6 +509,13 @@ namespace Flax.Build } } + // If the PCH was already created (eg. by other engine module) then simply reuse the same file + if (moduleOptions.CompileEnv.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual && buildData.PrecompiledHeaderFiles.TryGetValue(moduleOptions.CompileEnv.PrecompiledHeaderSource, out var pch)) + { + moduleOptions.CompileEnv.PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.UseManual; + moduleOptions.CompileEnv.PrecompiledHeaderFile = pch; + } + // Compile all source files var compilationOutput = buildData.Toolchain.CompileCppFiles(buildData.Graph, moduleOptions, cppFiles, moduleOptions.OutputFolder); foreach (var e in compilationOutput.ObjectFiles) @@ -493,6 +525,11 @@ namespace Flax.Build // TODO: find better way to add generated doc files to the target linker (module exports the output doc files?) buildData.TargetOptions.LinkEnv.DocumentationFiles.AddRange(compilationOutput.DocumentationFiles); } + if (moduleOptions.CompileEnv.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual && !string.IsNullOrEmpty(compilationOutput.PrecompiledHeaderFile)) + { + // Cache PCH file to be used by other modules that reference the same file + buildData.PrecompiledHeaderFiles.Add(moduleOptions.CompileEnv.PrecompiledHeaderSource, compilationOutput.PrecompiledHeaderFile); + } if (buildData.Target.LinkType != TargetLinkType.Monolithic) { @@ -1004,7 +1041,7 @@ namespace Flax.Build buildData.BuildInfo.AddReferencedBuilds(ref i, project.ProjectFolderPath, buildData.ReferenceBuilds); if (!buildData.Target.IsPreBuilt) - Utilities.WriteFileIfChanged(Path.Combine(outputPath, target.Name + ".Build.json"), JsonSerializer.Serialize(buildData.BuildInfo, new JsonSerializerOptions() { WriteIndented = true, IncludeFields = true, TypeInfoResolver = BuildTargetInfoSourceGenerationContext.Default })); + Utilities.WriteFileIfChanged(Path.Combine(outputPath, target.Name + ".Build.json"), buildData.BuildInfo.Serialize()); } // Deploy files @@ -1203,7 +1240,7 @@ namespace Flax.Build buildData.BuildInfo.AddReferencedBuilds(ref i, project.ProjectFolderPath, buildData.ReferenceBuilds); if (!buildData.Target.IsPreBuilt) - Utilities.WriteFileIfChanged(Path.Combine(outputPath, target.Name + ".Build.json"), JsonSerializer.Serialize(buildData.BuildInfo, new JsonSerializerOptions() { WriteIndented = true, IncludeFields = true, TypeInfoResolver = BuildTargetInfoSourceGenerationContext.Default })); + Utilities.WriteFileIfChanged(Path.Combine(outputPath, target.Name + ".Build.json"), buildData.BuildInfo.Serialize()); } // Deploy files diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs index e859ae61f..d9271e586 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileEnvironment.cs @@ -162,6 +162,21 @@ namespace Flax.Build.NativeCpp /// public readonly HashSet CustomArgs = new HashSet(); + /// + /// The Precompiled Header File (PCH) usage. + /// + public PrecompiledHeaderFileUsage PrecompiledHeaderUsage = PrecompiledHeaderFileUsage.None; + + /// + /// The Precompiled Header File (PCH) binary path. Null if not created. + /// + public string PrecompiledHeaderFile; + + /// + /// The Precompiled Header File (PCH) source path. Null if not provided. + /// + public string PrecompiledHeaderSource; + /// public object Clone() { @@ -184,7 +199,10 @@ namespace Flax.Build.NativeCpp StringPooling = StringPooling, IntrinsicFunctions = IntrinsicFunctions, BufferSecurityCheck = BufferSecurityCheck, - TreatWarningsAsErrors = TreatWarningsAsErrors + TreatWarningsAsErrors = TreatWarningsAsErrors, + PrecompiledHeaderUsage = PrecompiledHeaderUsage, + PrecompiledHeaderFile = PrecompiledHeaderFile, + PrecompiledHeaderSource = PrecompiledHeaderSource, }; clone.PreprocessorDefinitions.AddRange(PreprocessorDefinitions); clone.IncludePaths.AddRange(IncludePaths); diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs b/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs index f1f321eaf..0d98066ff 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/CompileOutput.cs @@ -23,5 +23,10 @@ namespace Flax.Build.NativeCpp /// The result documentation files. /// public readonly List DocumentationFiles = new List(); + + /// + /// The result precompiled header file (PCH) created during compilation. Can be used in other compilations (as shared). + /// + public string PrecompiledHeaderFile; } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs b/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs index adfcc2e6d..5f20f4a57 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/LinkEnvironment.cs @@ -127,8 +127,7 @@ namespace Flax.Build.NativeCpp LinkAsConsoleProgram = LinkAsConsoleProgram, GenerateDocumentation = GenerateDocumentation }; - foreach (var e in InputFiles) - clone.InputFiles.Add(e); + clone.InputFiles.AddRange(InputFiles); clone.DocumentationFiles.AddRange(DocumentationFiles); clone.InputLibraries.AddRange(InputLibraries); clone.LibraryPaths.AddRange(LibraryPaths); diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index cceed9958..5fe01a93c 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -211,7 +211,7 @@ namespace Flax.Build /// /// The target platform. /// True if return null platform if it's missing, otherwise will invoke an exception. - /// The toolchain. + /// The platform. public static Platform GetPlatform(TargetPlatform targetPlatform, bool nullIfMissing = false) { if (_platforms == null) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 5cf7bab62..8b4e7e6c9 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -839,10 +839,11 @@ namespace Flax.Build.Plugins module.GetType("System.IntPtr", out var intPtrType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); + TypeReference intPtr = module.ImportReference(intPtrType); var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType); - m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); - m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType)); + m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr)); + m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr)); TypeReference networkStream = module.ImportReference(context.NetworkStreamType); ILProcessor il = m.Body.GetILProcessor(); il.Emit(OpCodes.Nop); @@ -1645,12 +1646,13 @@ namespace Flax.Build.Plugins module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); TypeReference networkStream = module.ImportReference(networkStreamType); + TypeReference intPtr = module.ImportReference(intPtrType); // Generate static method to execute RPC locally { var m = new MethodDefinition(method.Name + "_Execute", MethodAttributes.Static | MethodAttributes.Assembly | MethodAttributes.HideBySig, voidType); - m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType)); - m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, module.ImportReference(intPtrType))); + m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtr)); + m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtr)); ILProcessor ilp = m.Body.GetILProcessor(); var il = new DotnetIlContext(ilp, method); il.Emit(OpCodes.Nop); diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index ee682576b..98a9ef530 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -345,12 +345,12 @@ namespace Flax.Deploy // Optimize package size Utilities.Run("strip", "FlaxEditor", null, dst, Utilities.RunOptions.None); - Utilities.Run("strip", "FlaxEditor.dylib", null, dst, Utilities.RunOptions.None); + Utilities.Run("strip", "FlaxEngine.dylib", null, dst, Utilities.RunOptions.None); Utilities.Run("strip", "libMoltenVK.dylib", null, dst, Utilities.RunOptions.None); // Sign binaries CodeSign(Path.Combine(dst, "FlaxEditor")); - CodeSign(Path.Combine(dst, "FlaxEditor.dylib")); + CodeSign(Path.Combine(dst, "FlaxEngine.dylib")); CodeSign(Path.Combine(dst, "libMoltenVK.dylib")); } } diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs index 3196638c1..805cfdbe9 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Platforms.cs @@ -35,9 +35,12 @@ namespace Flax.Deploy // For Linux don't deploy engine libs used by C++ scripting linking (engine source required) if (platform == TargetPlatform.Linux) { - File.Delete(Path.Combine(dst, "Binaries", "Game", "x64", "Debug", "FlaxGame.a")); - File.Delete(Path.Combine(dst, "Binaries", "Game", "x64", "Development", "FlaxGame.a")); - File.Delete(Path.Combine(dst, "Binaries", "Game", "x64", "Release", "FlaxGame.a")); + Utilities.FileDelete(Path.Combine(dst, "Binaries", "Game", "x64", "Debug", "FlaxGame.a")); + Utilities.FileDelete(Path.Combine(dst, "Binaries", "Game", "x64", "Development", "FlaxGame.a")); + Utilities.FileDelete(Path.Combine(dst, "Binaries", "Game", "x64", "Release", "FlaxGame.a")); + Utilities.FileDelete(Path.Combine(dst, "Binaries", "Game", "x64", "Debug", "FlaxEngine.a")); + Utilities.FileDelete(Path.Combine(dst, "Binaries", "Game", "x64", "Development", "FlaxEngine.a")); + Utilities.FileDelete(Path.Combine(dst, "Binaries", "Game", "x64", "Release", "FlaxEngine.a")); } // Sign binaries @@ -45,29 +48,32 @@ namespace Flax.Deploy { var binaries = Path.Combine(dst, "Binaries", "Game", "x64", "Debug"); CodeSign(Path.Combine(binaries, "FlaxGame.exe")); + CodeSign(Path.Combine(binaries, "FlaxEngine.dll")); CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); binaries = Path.Combine(dst, "Binaries", "Game", "x64", "Development"); CodeSign(Path.Combine(binaries, "FlaxGame.exe")); + CodeSign(Path.Combine(binaries, "FlaxEngine.dll")); CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); binaries = Path.Combine(dst, "Binaries", "Game", "x64", "Release"); CodeSign(Path.Combine(binaries, "FlaxGame.exe")); + CodeSign(Path.Combine(binaries, "FlaxEngine.dll")); CodeSign(Path.Combine(binaries, "FlaxEngine.CSharp.dll")); } else if (platform == TargetPlatform.Mac) { var binaries = Path.Combine(dst, "Binaries", "Game", "arm64", "Debug"); CodeSign(Path.Combine(binaries, "FlaxGame")); - CodeSign(Path.Combine(binaries, "FlaxGame.dylib")); + CodeSign(Path.Combine(binaries, "FlaxEngine.dylib")); binaries = Path.Combine(dst, "Binaries", "Game", "arm64", "Development"); CodeSign(Path.Combine(binaries, "FlaxGame")); - CodeSign(Path.Combine(binaries, "FlaxGame.dylib")); + CodeSign(Path.Combine(binaries, "FlaxEngine.dylib")); binaries = Path.Combine(dst, "Binaries", "Game", "arm64", "Release"); CodeSign(Path.Combine(binaries, "FlaxGame")); - CodeSign(Path.Combine(binaries, "FlaxGame.dylib")); + CodeSign(Path.Combine(binaries, "FlaxEngine.dylib")); } // Don't distribute engine deps diff --git a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs index e6af90f5a..c72383858 100644 --- a/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs +++ b/Source/Tools/Flax.Build/Deploy/FlaxBuild.cs @@ -15,7 +15,7 @@ namespace Flax.Deploy { var buildPlatform = Platform.BuildPlatform.Target; var flaxBuildTool = Path.Combine(Globals.EngineRoot, buildPlatform == TargetPlatform.Windows ? "Binaries/Tools/Flax.Build.exe" : "Binaries/Tools/Flax.Build"); - var format = "-build -buildtargets={0} -log -logfile= -perf -platform={1} -arch={2} -configuration={3}"; + var format = "-build -buildtargets={0} -log -logfile= -platform={1} -arch={2} -configuration={3}"; var cmdLine = string.Format(format, target, platform, architecture, configuration); Configuration.PassArgs(ref cmdLine); diff --git a/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs b/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs index f75c89c8d..4aa5cc060 100644 --- a/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs +++ b/Source/Tools/Flax.Build/Deploy/VCEnvironment.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Flax.Build; using Flax.Build.Platforms; using Flax.Build.Projects.VisualStudio; @@ -288,10 +287,25 @@ namespace Flax.Deploy var sdks = WindowsPlatformBase.GetSDKs(); if (sdks.Count == 0) throw new Exception("No Windows SDK found. Cannot sign file."); - var sdkKeys = sdks.Keys.ToList(); - sdkKeys.Sort(); - var sdk = sdks[sdkKeys.Last()]; - var signtool = Path.Combine(sdk, "bin", "x64", "signtool.exe"); + var signtool = string.Empty; + foreach (var e in sdks) + { + try + { + var sdk = e.Value; + signtool = Path.Combine(sdk, "bin", "x64", "signtool.exe"); + if (File.Exists(signtool)) + break; + var ver = WindowsPlatformBase.GetSDKVersion(e.Key); + signtool = Path.Combine(sdk, "bin", ver.ToString(4), "x64", "signtool.exe"); + if (File.Exists(signtool)) + break; + } + catch + { + // Ignore version formatting exception + } + } var cmdLine = string.Format("sign /debug /f \"{0}\" /p \"{1}\" /tr http://timestamp.comodoca.com /td sha256 /fd sha256 \"{2}\"", certificatePath, certificatePass, file); Utilities.Run(signtool, cmdLine, null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError); } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs index aa4ef578f..048fa9ea1 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NewtonsoftJson.cs @@ -50,13 +50,13 @@ namespace Flax.Deps.Dependencies "Newtonsoft.Json.pdb", "Newtonsoft.Json.xml", }; - var binFolder = Path.Combine(root, "Src", "Newtonsoft.Json", "bin", configuration, "net7.0"); + var binFolder = Path.Combine(root, "Src", "Newtonsoft.Json", "bin", configuration, "net8.0"); // Get the source CloneGitRepo(root, "https://github.com/FlaxEngine/Newtonsoft.Json.git"); // Default build - GitCheckout(root, "flax-net70"); + GitCheckout(root, "flax-net80"); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, buildPlatform); foreach (var platform in options.Platforms) { diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs index 84b1efa38..f128c0abc 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs @@ -102,7 +102,7 @@ namespace Flax.Deps.Dependencies var packagePath = Path.Combine(root, "package.zip"); File.Delete(packagePath); Downloader.DownloadFileFromUrlToPath("https://openal-soft.org/openal-releases/openal-soft-" + version + ".tar.bz2", packagePath); - Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.None); + Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.ConsoleLogOutput); // Use separate build directory root = Path.Combine(root, "openal-soft-" + version); @@ -110,8 +110,8 @@ namespace Flax.Deps.Dependencies SetupDirectory(buildDir, true); // Build for Linux - Utilities.Run("cmake", "-G \"Unix Makefiles\" -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DLIBTYPE=STATIC " + config + " ..", null, buildDir, Utilities.RunOptions.None, envVars); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None, envVars); + Utilities.Run("cmake", "-G \"Unix Makefiles\" -DCMAKE_BUILD_TYPE=Release -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DLIBTYPE=STATIC " + config + " ..", null, buildDir, Utilities.RunOptions.ConsoleLogOutput, envVars); + Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.ConsoleLogOutput, envVars); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in binariesToCopy) Utilities.FileCopy(Path.Combine(buildDir, file), Path.Combine(depsFolder, file)); @@ -137,7 +137,7 @@ namespace Flax.Deps.Dependencies } else { - Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.None); + Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.ConsoleLogOutput); } // Use separate build directory @@ -147,7 +147,7 @@ namespace Flax.Deps.Dependencies // Build RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DLIBTYPE=STATIC -DCMAKE_BUILD_TYPE=Release " + config); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); foreach (var file in binariesToCopy) Utilities.FileCopy(Path.Combine(buildDir, file), Path.Combine(depsFolder, file)); @@ -165,7 +165,7 @@ namespace Flax.Deps.Dependencies var packagePath = Path.Combine(root, "package.zip"); File.Delete(packagePath); Downloader.DownloadFileFromUrlToPath("https://openal-soft.org/openal-releases/openal-soft-" + version + ".tar.bz2", packagePath); - Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.None); + Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.ConsoleLogOutput); // Use separate build directory root = Path.Combine(root, "openal-soft-" + version); @@ -176,7 +176,7 @@ namespace Flax.Deps.Dependencies { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DLIBTYPE=STATIC -DCMAKE_BUILD_TYPE=Release " + config); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, architecture); foreach (var file in binariesToCopy) Utilities.FileCopy(Path.Combine(buildDir, file), Path.Combine(depsFolder, file)); @@ -196,7 +196,7 @@ namespace Flax.Deps.Dependencies if (!File.Exists(packagePath)) { Downloader.DownloadFileFromUrlToPath("https://openal-soft.org/openal-releases/openal-soft-" + version + ".tar.bz2", packagePath); - Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.None); + Utilities.Run("tar", "xjf " + packagePath.Replace('\\', '/'), null, root, Utilities.RunOptions.ConsoleLogOutput); } // Use separate build directory @@ -206,7 +206,7 @@ namespace Flax.Deps.Dependencies // Build for iOS SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_SYSTEM_NAME=iOS -DALSOFT_OSX_FRAMEWORK=ON -DLIBTYPE=STATIC -DCMAKE_BUILD_TYPE=Release " + config); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); foreach (var file in binariesToCopy) Utilities.FileCopy(Path.Combine(buildDir, file), Path.Combine(depsFolder, file)); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs index db4362951..734ba3929 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/PhysX.cs @@ -283,7 +283,7 @@ namespace Flax.Deps.Dependencies switch (targetPlatform) { case TargetPlatform.Android: - Utilities.Run("cmake", "--build .", null, Path.Combine(root, "physx\\compiler\\android-" + configuration), Utilities.RunOptions.None, envVars); + Utilities.Run("cmake", "--build .", null, Path.Combine(root, "physx\\compiler\\android-" + configuration), Utilities.RunOptions.ConsoleLogOutput, envVars); break; default: VCEnvironment.BuildSolution(Path.Combine(solutionFilesRoot, preset, "PhysXSDK.sln"), configuration, buildPlatform, msBuildProps, msBuild); @@ -291,10 +291,10 @@ namespace Flax.Deps.Dependencies } break; case TargetPlatform.Linux: - Utilities.Run("make", null, null, Path.Combine(projectGenDir, "compiler", "linux-" + configuration), Utilities.RunOptions.None); + Utilities.Run("make", null, null, Path.Combine(projectGenDir, "compiler", "linux-" + configuration), Utilities.RunOptions.ConsoleLogOutput); break; case TargetPlatform.Mac: - Utilities.Run("xcodebuild", "-project PhysXSDK.xcodeproj -alltargets -configuration " + configuration, null, Path.Combine(projectGenDir, "compiler", preset), Utilities.RunOptions.None); + Utilities.Run("xcodebuild", "-project PhysXSDK.xcodeproj -alltargets -configuration " + configuration, null, Path.Combine(projectGenDir, "compiler", preset), Utilities.RunOptions.ConsoleLogOutput); break; default: throw new InvalidPlatformException(BuildPlatform); } @@ -321,7 +321,7 @@ namespace Flax.Deps.Dependencies { case TargetPlatform.Mac: case TargetPlatform.Android: - Utilities.Run("strip", "\"" + filename + "\"", null, dstBinaries, Utilities.RunOptions.None); + Utilities.Run("strip", "\"" + filename + "\"", null, dstBinaries, Utilities.RunOptions.ConsoleLogOutput); break; } break; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/astc.cs b/Source/Tools/Flax.Build/Deps/Dependencies/astc.cs new file mode 100644 index 000000000..01e369d62 --- /dev/null +++ b/Source/Tools/Flax.Build/Deps/Dependencies/astc.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System.IO; +using Flax.Build; + +namespace Flax.Deps.Dependencies +{ + /// + /// ASTC texture format compression lib. + /// + /// + class astc : Dependency + { + /// + public override TargetPlatform[] Platforms + { + get + { + switch (BuildPlatform) + { + case TargetPlatform.Windows: + return new[] + { + TargetPlatform.Windows, + }; + case TargetPlatform.Mac: + return new[] + { + TargetPlatform.Mac, + }; + default: return new TargetPlatform[0]; + } + } + } + + /// + public override void Build(BuildOptions options) + { + var root = options.IntermediateFolder; + var buildDir = Path.Combine(root, "build"); + + // Get the source + var commit = "aeece2f609db959d1c5e43e4f00bd177ea130575"; // 4.6.1 + CloneGitRepo(root, "https://github.com/ARM-software/astc-encoder.git", commit); + + foreach (var platform in options.Platforms) + { + switch (platform) + { + case TargetPlatform.Windows: + foreach (var architecture in new []{ TargetArchitecture.x64 }) + { + var isa = "-DASTCENC_ISA_SSE2=ON"; + var lib = "astcenc-sse2-static.lib"; + SetupDirectory(buildDir, true); + RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release -DASTCENC_CLI=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL " + isa); + BuildCmake(buildDir); + var depsFolder = GetThirdPartyFolder(options, platform, architecture); + Utilities.FileCopy(Path.Combine(buildDir, "Source/Release", lib), Path.Combine(depsFolder, "astcenc.lib")); + } + break; + case TargetPlatform.Mac: + foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + { + var isa = architecture == TargetArchitecture.ARM64 ? "-DASTCENC_ISA_NEON=ON" : "-DASTCENC_ISA_SSE2=ON"; + var lib = architecture == TargetArchitecture.ARM64 ? "libastcenc-neon-static.a" : "libastcenc-sse2-static.a"; + SetupDirectory(buildDir, true); + RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release -DASTCENC_UNIVERSAL_BUILD=OFF -DASTCENC_UNIVERSAL_BINARY=OFF " + isa); + BuildCmake(buildDir); + var depsFolder = GetThirdPartyFolder(options, platform, architecture); + Utilities.FileCopy(Path.Combine(buildDir, "Source", lib), Path.Combine(depsFolder, "libastcenc.a")); + } + break; + } + } + + // Copy header and license + Utilities.FileCopy(Path.Combine(root, "LICENSE.txt"), Path.Combine(options.ThirdPartyFolder, "astc/LICENSE.txt")); + Utilities.FileCopy(Path.Combine(root, "Source/astcenc.h"), Path.Combine(options.ThirdPartyFolder, "astc/astcenc.h")); + } + } +} diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs index 8f2664e53..c2a69cc48 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs @@ -229,7 +229,7 @@ namespace Flax.Deps.Dependencies // Build for Android SetupDirectory(buildDir, true); RunCmake(buildDir, TargetPlatform.Android, TargetArchitecture.ARM64, ".. -DFT_WITH_BZIP2=OFF -DFT_WITH_ZLIB=OFF -DFT_WITH_PNG=OFF -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); break; @@ -239,7 +239,7 @@ namespace Flax.Deps.Dependencies // Build for Switch SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); break; @@ -251,7 +251,7 @@ namespace Flax.Deps.Dependencies { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, architecture); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); } @@ -268,7 +268,7 @@ namespace Flax.Deps.Dependencies // Build for iOS SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DIOS_PLATFORM=OS -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_BUILD_TYPE=Release -DFT_WITH_BZIP2=OFF -DFT_WITH_ZLIB=OFF -DFT_WITH_PNG=OFF"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); break; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs b/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs index b9847534e..086124867 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs @@ -53,7 +53,7 @@ namespace Flax.Deps.Dependencies CloneGitRepoFast(root, "https://github.com/FlaxEngine/glslang.git"); // Setup the external sources - Utilities.Run("python", "update_glslang_sources.py", null, root, Utilities.RunOptions.None); + Utilities.Run("python", "update_glslang_sources.py", null, root, Utilities.RunOptions.ConsoleLogOutput); foreach (var platform in options.Platforms) { @@ -77,7 +77,7 @@ namespace Flax.Deps.Dependencies // Build for Win64 File.Delete(Path.Combine(buildDir, "CMakeCache.txt")); RunCmake(buildDir, platform, TargetArchitecture.x64, cmakeArgs); - Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.None); + Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.ConsoleLogOutput); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, "x64"); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in outputFiles) @@ -103,14 +103,14 @@ namespace Flax.Deps.Dependencies // Build for Linux RunCmake(root, platform, TargetArchitecture.x64, cmakeArgs); - Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.None); - Utilities.Run("make", null, null, root, Utilities.RunOptions.None); + Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.ConsoleLogOutput); + Utilities.Run("make", null, null, root, Utilities.RunOptions.ConsoleLogOutput); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in outputFiles) { var dst = Path.Combine(depsFolder, Path.GetFileName(file)); Utilities.FileCopy(file, dst); - //Utilities.Run("strip", string.Format("-s \"{0}\"", dst), null, null, Utilities.RunOptions.None); + //Utilities.Run("strip", string.Format("-s \"{0}\"", dst), null, null, Utilities.RunOptions.ConsoleLogOutput); } break; } @@ -133,14 +133,14 @@ namespace Flax.Deps.Dependencies foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { RunCmake(root, platform, architecture, cmakeArgs); - Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.None); - Utilities.Run("make", null, null, root, Utilities.RunOptions.None); + Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.ConsoleLogOutput); + Utilities.Run("make", null, null, root, Utilities.RunOptions.ConsoleLogOutput); var depsFolder = GetThirdPartyFolder(options, platform, architecture); foreach (var file in outputFiles) { var dst = Path.Combine(depsFolder, Path.GetFileName(file)); Utilities.FileCopy(file, dst); - Utilities.Run("strip", string.Format("\"{0}\"", dst), null, null, Utilities.RunOptions.None); + Utilities.Run("strip", string.Format("\"{0}\"", dst), null, null, Utilities.RunOptions.ConsoleLogOutput); } } break; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs index 48f26d119..090edf4be 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs @@ -9,8 +9,6 @@ using Flax.Build.Platforms; using Flax.Deploy; using System.IO.Compression; -#pragma warning disable 0219 - namespace Flax.Deps.Dependencies { /// @@ -30,6 +28,10 @@ namespace Flax.Deps.Dependencies return new[] { TargetPlatform.PS4, + TargetPlatform.PS5, + TargetPlatform.Switch, + TargetPlatform.XboxOne, + TargetPlatform.XboxScarlett, }; case TargetPlatform.Linux: return new[] @@ -45,22 +47,24 @@ namespace Flax.Deps.Dependencies public override bool BuildByDefault => false; private string root; + private bool cleanArtifacts; private void Build(BuildOptions options, TargetPlatform targetPlatform, TargetArchitecture architecture) { // Build configuration (see build.cmd -help) string configuration = "Release"; - string framework = "net7.0"; + string framework = "net8.0"; // Clean output directory var artifacts = Path.Combine(root, "artifacts"); - SetupDirectory(artifacts, true); + SetupDirectory(artifacts, cleanArtifacts); + cleanArtifacts = true; // Peek options - string os, arch, runtimeFlavor, subset, hostRuntimeName = DotNetSdk.GetHostRuntimeIdentifier(targetPlatform, architecture), buildArgsBase = string.Empty; - bool setupVersion = false; - string[] hostRuntimeFiles = Array.Empty(); + string os, arch, runtimeFlavor, hostRuntimeName = DotNetSdk.GetHostRuntimeIdentifier(targetPlatform, architecture), buildArgs = string.Empty, buildMonoAotCrossArgs = string.Empty; + bool setupVersion = false, buildMonoAotCross = false; var envVars = new Dictionary(); + envVars.Add("VisualStudioVersion", null); // Unset this so 'init-vs-env.cmd' will properly init VS Environment switch (architecture) { case TargetArchitecture.x86: @@ -82,36 +86,46 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Windows: os = "windows"; runtimeFlavor = "CoreCLR"; - subset = "clr"; + break; + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: + os = "windows"; + runtimeFlavor = "Mono"; + buildMonoAotCross = true; + buildArgs = $" -subset mono+libs -cmakeargs \"-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1\" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; break; case TargetPlatform.Linux: - os = "Linux"; + os = "linux"; runtimeFlavor = "CoreCLR"; - subset = "clr"; break; case TargetPlatform.Mac: - os = "OSX"; + os = "osx"; runtimeFlavor = "CoreCLR"; - subset = "clr"; break; case TargetPlatform.Android: - os = "Android"; + os = "android"; runtimeFlavor = "Mono"; - subset = "mono+libs"; break; case TargetPlatform.PS4: - os = "PS4"; + case TargetPlatform.PS5: + case TargetPlatform.Switch: runtimeFlavor = "Mono"; - subset = "mono+libs"; setupVersion = true; - buildArgsBase = " /p:RuntimeOS=ps4 -cmakeargs \"-DCLR_CROSS_COMPONENTS_BUILD=1\""; - hostRuntimeFiles = new[] + buildMonoAotCross = true; + switch (targetPlatform) { - "coreclr_delegates.h", - "hostfxr.h", - "nethost.h", - "libnethost.a", - }; + case TargetPlatform.PS4: + os = "ps4"; + break; + case TargetPlatform.PS5: + os = "ps5"; + break; + case TargetPlatform.Switch: + os = "switch"; + break; + default: throw new InvalidPlatformException(targetPlatform); + } + buildArgs = $" /p:RuntimeOS={os} -subset mono+libs -cmakeargs \"-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1\" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; break; default: throw new InvalidPlatformException(targetPlatform); } @@ -174,74 +188,107 @@ namespace Flax.Deps.Dependencies } // Build - buildArgsBase = $"-os {os} -a {arch} -f {framework} -c {configuration} -lc {configuration} -rc {configuration} -rf {runtimeFlavor}{buildArgsBase}"; - //foreach (var buildStep in new[] { subset, "host.pkg", "packs.product" }) - /*var buildStep = "host.pkg"; + if (runtimeFlavor == "CoreCLR") + buildArgs = $"-os {os} -a {arch} -f {framework} -c {configuration} -lc {configuration} -rc {configuration} -rf {runtimeFlavor}{buildArgs}"; + else if (runtimeFlavor == "Mono") + buildArgs = $"-os {os} -a {arch} -c {configuration} -rf {runtimeFlavor}{buildArgs}"; + Utilities.Run(Path.Combine(root, buildScript), buildArgs, null, root, Utilities.RunOptions.DefaultTool, envVars); + if (buildMonoAotCross) { - var buildArgs = $"{buildArgsBase} -s {buildStep}"; - if (BuildPlatform == TargetPlatform.Windows) - { - // For some reason running from Visual Studio fails the build so use command shell - //buildArgs = $"/C {buildScript} {buildArgs}"; - //buildApp = "cmd.exe"; - // TODO: maybe write command line into bat file and run it here? - WinAPI.SetClipboard($"{buildScript} {buildArgs}"); - WinAPI.MessageBox.Show($"Open console command in folder '{root}' and run command from clipboard. Then close this dialog.", "Run command manually", WinAPI.MessageBox.Buttons.Ok); - } - else - { - //Utilities.Run(Path.Combine(root, buildScript), buildArgs, null, root, Utilities.RunOptions.ThrowExceptionOnError, envVars); - } - }*/ - Utilities.Run(Path.Combine(root, buildScript), buildArgsBase, null, root, Utilities.RunOptions.ThrowExceptionOnError, envVars); + buildMonoAotCrossArgs = $"-c {configuration} -rf {runtimeFlavor} -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true /p:TargetOS={os} /p:HostOS=windows -cmakeargs \"-DCMAKE_CROSSCOMPILING=True\"{buildMonoAotCrossArgs}"; + Utilities.Run(Path.Combine(root, buildScript), buildMonoAotCrossArgs, null, root, Utilities.RunOptions.DefaultTool, envVars); + } // Deploy build products + var privateCoreLib = "System.Private.CoreLib.dll"; var dstBinaries = GetThirdPartyFolder(options, targetPlatform, architecture); - var srcHostRuntime = Path.Combine(artifacts, "bin", $"{hostRuntimeName}.{configuration}", "corehost"); - foreach (var file in hostRuntimeFiles) - { - Utilities.FileCopy(Path.Combine(srcHostRuntime, file), Path.Combine(dstBinaries, file)); - } - var dstDotnet = Path.Combine(GetBinariesFolder(options, targetPlatform), "Dotnet"); - var dstClassLibrary = Path.Combine(dstDotnet, "shared", "Microsoft.NETCore.App", version); - SetupDirectory(dstClassLibrary, true); - foreach (var file in new[] - { - "LICENSE.TXT", - "THIRD-PARTY-NOTICES.TXT", - }) - { + var dstPlatform = Path.Combine(options.PlatformsFolder, targetPlatform.ToString()); + var dstDotnet = Path.Combine(dstPlatform, "Dotnet"); + foreach (var file in new[] { "LICENSE.TXT", "THIRD-PARTY-NOTICES.TXT" }) Utilities.FileCopy(Path.Combine(root, file), Path.Combine(dstDotnet, file)); - } - var srcDotnetLibsPkg = Path.Combine(artifacts, "packages", "Release", "Shipping", $"Microsoft.NETCore.App.Runtime.Mono.{hostRuntimeName}.{version}.nupkg"); - if (!File.Exists(srcDotnetLibsPkg)) - throw new Exception($"Missing .NET Core App class library package at '{srcDotnetLibsPkg}'"); - var unpackTemp = Path.Combine(Path.GetDirectoryName(srcDotnetLibsPkg), "UnpackTemp"); - SetupDirectory(unpackTemp, true); - using (var zip = ZipFile.Open(srcDotnetLibsPkg, ZipArchiveMode.Read)) + if (runtimeFlavor == "CoreCLR") { - zip.ExtractToDirectory(unpackTemp); - } - var privateCorelib = "System.Private.CoreLib.dll"; - Utilities.FileCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "native", privateCorelib), Path.Combine(dstClassLibrary, privateCorelib)); - Utilities.DirectoryCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "lib", "net7.0"), dstClassLibrary, false, true); - // TODO: host/fxr//hostfxr.dll - // TODO: shared/Microsoft.NETCore.App//hostpolicy.dl - // TODO: shared/Microsoft.NETCore.App//System.IO.Compression.Native.dll - if (runtimeFlavor == "Mono") - { - // TODO: implement automatic deployment based on the setup: - // PS4 outputs mono into artifacts\obj\mono\PS4.x64.Release\out - // PS4 outputs native libs into artifacts\bin\native\net7.0-PS4-Release-x64\lib - // PS4 outputs System.Private.CoreLib lib into artifacts\bin\mono\PS4.x64.Release - // PS4 outputs C# libs into artifacts\bin\runtime\net7.0-PS4.Release.x64 - // PS4 outputs AOT compiler into artifacts\bin\mono\PS4.x64.Release\cross\ps4-x64 + var srcHostRuntime = Path.Combine(artifacts, "bin", $"{hostRuntimeName}.{configuration}", "corehost"); + var dstClassLibrary = Path.Combine(dstDotnet, "shared", "Microsoft.NETCore.App", version); + SetupDirectory(dstClassLibrary, true); + var srcDotnetLibsPkg = Path.Combine(artifacts, "packages", "Release", "Shipping", $"Microsoft.NETCore.App.Runtime.Mono.{hostRuntimeName}.{version}.nupkg"); + if (!File.Exists(srcDotnetLibsPkg)) + throw new Exception($"Missing .NET Core App class library package at '{srcDotnetLibsPkg}'"); + var unpackTemp = Path.Combine(Path.GetDirectoryName(srcDotnetLibsPkg), "UnpackTemp"); + SetupDirectory(unpackTemp, true); + using (var zip = ZipFile.Open(srcDotnetLibsPkg, ZipArchiveMode.Read)) + zip.ExtractToDirectory(unpackTemp); + Utilities.FileCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "native", privateCoreLib), Path.Combine(dstClassLibrary, privateCoreLib)); + Utilities.DirectoryCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "lib", framework), dstClassLibrary, false, true); + // TODO: host/fxr//hostfxr.dll + // TODO: shared/Microsoft.NETCore.App//hostpolicy.dl + // TODO: shared/Microsoft.NETCore.App//System.IO.Compression.Native.dll Utilities.DirectoryCopy(Path.Combine(unpackTemp, "runtimes", hostRuntimeName, "native"), Path.Combine(dstDotnet, "native"), true, true); - Utilities.FileDelete(Path.Combine(dstDotnet, "native", privateCorelib)); + Utilities.FileDelete(Path.Combine(dstDotnet, "native", privateCoreLib)); + Utilities.DirectoriesDelete(unpackTemp); + } + else if (runtimeFlavor == "Mono") + { + // Native libs + var src1 = Path.Combine(artifacts, "obj", "mono", $"{os}.{arch}.{configuration}", "out"); + if (!Directory.Exists(src1)) + throw new DirectoryNotFoundException(src1); + var src2 = Path.Combine(artifacts, "bin", "native", $"{framework}-{os}-{configuration}-{arch}"); + if (!Directory.Exists(src2)) + throw new DirectoryNotFoundException(src2); + string[] libs1, libs2; + switch (targetPlatform) + { + case TargetPlatform.Windows: + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: + libs1 = new[] + { + "lib/coreclr.dll", + "lib/coreclr.import.lib", + }; + libs2 = new string[] + { + }; + break; + default: + libs1 = new[] + { + "lib/libmonosgen-2.0.a", + "lib/libmono-profiler-aot.a", + }; + libs2 = new[] + { + "lib/libSystem.Globalization.Native.a", + "lib/libSystem.IO.Compression.Native.a", + "lib/libSystem.IO.Ports.Native.a", + "lib/libSystem.Native.a", + }; + break; + } + foreach (var file in libs1) + Utilities.FileCopy(Path.Combine(src1, file), Path.Combine(dstBinaries, Path.GetFileName(file))); + foreach (var file in libs2) + Utilities.FileCopy(Path.Combine(src2, file), Path.Combine(dstBinaries, Path.GetFileName(file))); + + // Include headers + Utilities.DirectoryDelete(Path.Combine(dstBinaries, "include")); + Utilities.DirectoryCopy(Path.Combine(src1, "include"), Path.Combine(dstBinaries, "include"), true, true); + + if (buildMonoAotCross) + { + // AOT compiler + Utilities.FileCopy(Path.Combine(artifacts, "bin", "mono", $"{os}.x64.{configuration}", "cross", $"{(os == "windows" ? "win" : os)}-x64", "mono-aot-cross.exe"), Path.Combine(dstPlatform, "Binaries", "Tools", "mono-aot-cross.exe")); + } + + // Class library + var dstDotnetLib = Path.Combine(dstPlatform, "Dotnet", "lib", framework); + foreach (var subDir in Directory.GetDirectories(Path.Combine(dstPlatform, "Dotnet", "lib"))) + Utilities.DirectoryDelete(subDir); + SetupDirectory(dstDotnetLib, true); + Utilities.FileCopy(Path.Combine(artifacts, "bin", "mono", $"{os}.{arch}.{configuration}", privateCoreLib), Path.Combine(dstDotnetLib, privateCoreLib)); + Utilities.DirectoryCopy(Path.Combine(artifacts, "bin", "runtime", $"{framework}-{os}-{configuration}-{arch}"), dstDotnetLib, false, true, "*.dll"); } - else - throw new InvalidPlatformException(targetPlatform); - Utilities.DirectoriesDelete(unpackTemp); } /// @@ -254,8 +301,12 @@ namespace Flax.Deps.Dependencies Utilities.Run("cmake", "--version", null, null, Utilities.RunOptions.ThrowExceptionOnError); // Get the source - CloneGitRepo(root, "https://github.com/FlaxEngine/dotnet-runtime.git", "flax-master", null, true); - SetupDirectory(Path.Combine(root, "src", "external"), false); + if (!Directory.Exists(Path.Combine(root, ".git"))) + { + CloneGitRepo(root, "https://github.com/FlaxEngine/dotnet-runtime.git", null, null, true); + GitCheckout(root, "flax-master-8"); + SetupDirectory(Path.Combine(root, "src", "external"), false); + } /* * Mono AOT for Windows: @@ -265,10 +316,10 @@ namespace Flax.Deps.Dependencies * .\build.cmd -c release -runtimeFlavor mono -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true * * Mono AOT for PS4: - * .\build.cmd -os PS4 -a x64 /p:RuntimeOS=ps4 -c release -runtimeFlavor mono -subset mono+libs -cmakeargs "-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false + * .\build.cmd -os ps4 -a x64 /p:RuntimeOS=ps4 -c release -runtimeFlavor mono -subset mono+libs -cmakeargs "-DDISABLE_JIT=1-DENABLE_PERFTRACING=0-DDISABLE_REFLECTION_EMIT=1-DDISABLE_EVENTPIPE=1-DDISABLE_COM=1-DDISABLE_PROFILER=1-DDISABLE_COMPONENTS=1" /p:FeaturePerfTracing=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false * * Mono AOT cross-compiler for PS4: - * .\build.cmd -c release -runtimeFlavor mono -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true /p:TargetOS=PS4 /p:HostOS=windows -cmakeargs "-DCMAKE_CROSSCOMPILING=True" + * .\build.cmd -c release -runtimeFlavor mono -subset mono /p:BuildMonoAotCrossCompiler=true /p:BuildMonoAOTCrossCompilerOnly=true /p:TargetOS=ps4 /p:HostOS=windows -cmakeargs "-DCMAKE_CROSSCOMPILING=True" */ foreach (var platform in options.Platforms) @@ -280,11 +331,16 @@ namespace Flax.Deps.Dependencies { case TargetPlatform.PS4: case TargetPlatform.PS5: + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: Build(options, platform, TargetArchitecture.x64); break; case TargetPlatform.Android: Build(options, platform, TargetArchitecture.ARM64); break; + case TargetPlatform.Switch: + Build(options, platform, TargetArchitecture.ARM64); + break; } } diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs b/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs index 5d555bc36..72009cabb 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs @@ -128,8 +128,8 @@ namespace Flax.Deps.Dependencies var toolchain = UnixToolchain.GetToolchainName(platform, TargetArchitecture.x64); Utilities.Run(Path.Combine(root, "configure"), string.Format("--host={0}", toolchain), null, root, Utilities.RunOptions.Default, envVars); SetupDirectory(buildDir, true); - Utilities.Run("cmake", "-G \"Unix Makefiles\" -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..", null, buildDir, Utilities.RunOptions.None, envVars); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None, envVars); + Utilities.Run("cmake", "-G \"Unix Makefiles\" -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..", null, buildDir, Utilities.RunOptions.ConsoleLogOutput, envVars); + Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.ConsoleLogOutput, envVars); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); @@ -196,7 +196,7 @@ namespace Flax.Deps.Dependencies // Build for Android SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); break; @@ -209,7 +209,7 @@ namespace Flax.Deps.Dependencies // Build for Switch SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); break; @@ -221,7 +221,7 @@ namespace Flax.Deps.Dependencies { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, architecture); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); } @@ -231,7 +231,7 @@ namespace Flax.Deps.Dependencies { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release"); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); Utilities.FileCopy(Path.Combine(buildDir, libraryFileName), Path.Combine(depsFolder, libraryFileName)); break; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs index 4d8db65d7..b5298eb9f 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs @@ -292,8 +292,8 @@ namespace Flax.Deps.Dependencies var toolchain = UnixToolchain.GetToolchainName(platform, TargetArchitecture.x64); Utilities.Run(Path.Combine(root, "configure"), string.Format("--host={0}", toolchain), null, root, Utilities.RunOptions.Default, envVars); SetupDirectory(buildDir, true); - Utilities.Run("cmake", "-G \"Unix Makefiles\" -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..", null, buildDir, Utilities.RunOptions.None, envVars); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None, envVars); + Utilities.Run("cmake", "-G \"Unix Makefiles\" -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release ..", null, buildDir, Utilities.RunOptions.ConsoleLogOutput, envVars); + Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.ConsoleLogOutput, envVars); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.x64); foreach (var file in binariesToCopyUnix) Utilities.FileCopy(Path.Combine(buildDir, file.SrcFolder, file.Filename), Path.Combine(depsFolder, file.Filename)); @@ -328,10 +328,10 @@ namespace Flax.Deps.Dependencies // Build for Android SetupDirectory(oggBuildDir, true); RunCmake(oggBuildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"../install\""); - Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.None); + Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.ConsoleLogOutput); SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, string.Format(".. -DCMAKE_BUILD_TYPE=Release -DOGG_INCLUDE_DIR=\"{0}/install/include\" -DOGG_LIBRARY=\"{0}/install/lib\"", oggRoot)); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); foreach (var file in binariesToCopyUnix) Utilities.FileCopy(Path.Combine(buildDir, file.SrcFolder, file.Filename), Path.Combine(depsFolder, file.Filename)); @@ -354,11 +354,11 @@ namespace Flax.Deps.Dependencies // Build for Switch SetupDirectory(oggBuildDir, true); RunCmake(oggBuildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"../install\""); - Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.None); + Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.ConsoleLogOutput); Utilities.FileCopy(Path.Combine(GetBinariesFolder(options, platform), "ogg", "include", "ogg", "config_types.h"), Path.Combine(oggRoot, "install", "include", "ogg", "config_types.h")); SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, string.Format(".. -DCMAKE_BUILD_TYPE=Release -DOGG_INCLUDE_DIR=\"{0}/install/include\" -DOGG_LIBRARY=\"{0}/install/lib\"", oggRoot)); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); foreach (var file in binariesToCopyUnix) Utilities.FileCopy(Path.Combine(buildDir, file.SrcFolder, file.Filename), Path.Combine(depsFolder, file.Filename)); @@ -380,10 +380,10 @@ namespace Flax.Deps.Dependencies { SetupDirectory(oggBuildDir, true); RunCmake(oggBuildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"../install\""); - Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.None); + Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.ConsoleLogOutput); SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, string.Format(".. -DCMAKE_BUILD_TYPE=Release -DOGG_INCLUDE_DIR=\"{0}/install/include\" -DOGG_LIBRARY=\"{0}/install/lib\"", oggRoot)); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, architecture); foreach (var file in binariesToCopyUnix) Utilities.FileCopy(Path.Combine(buildDir, file.SrcFolder, file.Filename), Path.Combine(depsFolder, file.Filename)); @@ -404,10 +404,10 @@ namespace Flax.Deps.Dependencies // Build for Mac SetupDirectory(oggBuildDir, true); RunCmake(oggBuildDir, platform, TargetArchitecture.ARM64, ".. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"../install\""); - Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.None); + Utilities.Run("cmake", "--build . --target install", null, oggBuildDir, Utilities.RunOptions.ConsoleLogOutput); SetupDirectory(buildDir, true); RunCmake(buildDir, platform, TargetArchitecture.ARM64, string.Format(".. -DCMAKE_BUILD_TYPE=Release -DOGG_INCLUDE_DIR=\"{0}/install/include\" -DOGG_LIBRARY=\"{0}/install/lib\"", oggRoot)); - Utilities.Run("cmake", "--build .", null, buildDir, Utilities.RunOptions.None); + BuildCmake(buildDir); var depsFolder = GetThirdPartyFolder(options, platform, TargetArchitecture.ARM64); foreach (var file in binariesToCopyUnix) Utilities.FileCopy(Path.Combine(buildDir, file.SrcFolder, file.Filename), Path.Combine(depsFolder, file.Filename)); diff --git a/Source/Tools/Flax.Build/Deps/Dependency.cs b/Source/Tools/Flax.Build/Deps/Dependency.cs index 7e6096392..c99dd0ee4 100644 --- a/Source/Tools/Flax.Build/Deps/Dependency.cs +++ b/Source/Tools/Flax.Build/Deps/Dependency.cs @@ -136,15 +136,12 @@ namespace Flax.Deps if (submodules) cmdLine += " --recurse-submodules"; - Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None); + Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.DefaultTool); if (submodules) - Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); + Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.DefaultTool); } - if (commit != null) - { - Utilities.Run("git", string.Format("reset --hard {0}", commit), null, null, Utilities.RunOptions.None); - } + Utilities.Run("git", string.Format("reset --hard {0}", commit), null, null, Utilities.RunOptions.DefaultTool); } /// @@ -164,9 +161,9 @@ namespace Flax.Deps if (submodules) cmdLine += " --recurse-submodules"; - Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None); + Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.DefaultTool); if (submodules) - Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); + Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.DefaultTool); } } @@ -191,14 +188,14 @@ namespace Flax.Deps if (submodules) cmdLine += " --recurse-submodules"; - Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.None); + Utilities.Run("git", cmdLine, null, null, Utilities.RunOptions.DefaultTool); if (submodules) - Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); + Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.DefaultTool); } if (commit != null) { - Utilities.Run("git", string.Format("reset --hard {0}", commit), null, path, Utilities.RunOptions.None); + Utilities.Run("git", string.Format("reset --hard {0}", commit), null, path, Utilities.RunOptions.DefaultTool); } } @@ -218,13 +215,13 @@ namespace Flax.Deps if (submodules) cmdLine += " --recurse-submodules"; - Utilities.Run("git", cmdLine, null, path, Utilities.RunOptions.None); + Utilities.Run("git", cmdLine, null, path, Utilities.RunOptions.DefaultTool); if (submodules) - Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.None); + Utilities.Run("git", "submodule update --init --recursive", null, null, Utilities.RunOptions.DefaultTool); if (commit != null) { - Utilities.Run("git", string.Format("reset --hard {0}", commit), null, path, Utilities.RunOptions.None); + Utilities.Run("git", string.Format("reset --hard {0}", commit), null, path, Utilities.RunOptions.DefaultTool); } } @@ -234,7 +231,17 @@ namespace Flax.Deps /// The local path that contains git repository. public static void GitResetLocalChanges(string path) { - Utilities.Run("git", "reset --hard", null, path, Utilities.RunOptions.None); + Utilities.Run("git", "reset --hard", null, path, Utilities.RunOptions.DefaultTool); + } + + /// + /// Builds the cmake project. + /// + /// The path. + /// Custom environment variables to pass to the child process. + public static void BuildCmake(string path, Dictionary envVars = null) + { + Utilities.Run("cmake", "--build . --config Release", null, path, Utilities.RunOptions.DefaultTool, envVars); } /// @@ -313,7 +320,7 @@ namespace Flax.Deps if (customArgs != null) cmdLine += " " + customArgs; - Utilities.Run("cmake", cmdLine, null, path, Utilities.RunOptions.None, envVars); + Utilities.Run("cmake", cmdLine, null, path, Utilities.RunOptions.DefaultTool, envVars); } /// diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index 1837bf0ec..0270e7f82 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -1,7 +1,7 @@  Exe - net7.0 + net8.0 11.0 disable annotations diff --git a/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs b/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs index 0a333157c..a1268ddfc 100644 --- a/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Apple/AppleToolchain.cs @@ -150,23 +150,23 @@ namespace Flax.Build.Platforms if (compileEnvironment.TreatWarningsAsErrors) commonArgs.Add("-Wall -Werror"); - // TODO: compileEnvironment.IntrinsicFunctions - // TODO: compileEnvironment.FunctionLevelLinking - // TODO: compileEnvironment.FavorSizeOrSpeed - // TODO: compileEnvironment.RuntimeChecks - // TODO: compileEnvironment.StringPooling - // TODO: compileEnvironment.BufferSecurityCheck - if (compileEnvironment.DebugInformation) commonArgs.Add("-gdwarf-2"); commonArgs.Add("-pthread"); + if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode) + commonArgs.Add("-Ofast"); + else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode) + commonArgs.Add("-Os"); if (compileEnvironment.Optimization) commonArgs.Add("-O3"); else commonArgs.Add("-O0"); + if (compileEnvironment.BufferSecurityCheck) + commonArgs.Add("-fstack-protector"); + if (!compileEnvironment.Inlining) { commonArgs.Add("-fno-inline-functions"); @@ -396,7 +396,7 @@ namespace Flax.Build.Platforms rpathTask.DependentTasks.Add(lastTask); lastTask = rpathTask; } - // TODO: fix dylib ID: 'install_name_tool -id @rpath/FlaxGame.dylib FlaxGame.dylib' + // TODO: fix dylib ID: 'install_name_tool -id @rpath/FlaxEngine.dylib FlaxEngine.dylib' if (!options.LinkEnv.DebugInformation) { // Strip debug symbols diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs index 3a2cfeba7..62d22911a 100644 --- a/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKPlatform.cs @@ -18,13 +18,13 @@ namespace Flax.Build.Platforms protected GDKPlatform() { // Visual Studio 2017 or newer required - var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version == VisualStudioVersion.VisualStudio2017 || x.Version == VisualStudioVersion.VisualStudio2019); + var visualStudio = VisualStudioInstance.GetInstances().FirstOrDefault(x => x.Version >= VisualStudioVersion.VisualStudio2017); if (visualStudio == null) _hasRequiredSDKsInstalled = false; // Windows 10.0.19041.0 SDK or newer required var sdks = GetSDKs(); - if (!sdks.ContainsKey(WindowsPlatformSDK.v10_0_19041_0)) + if (sdks.All(x => x.Key < WindowsPlatformSDK.v10_0_19041_0)) _hasRequiredSDKsInstalled = false; // v141 toolset or newer required @@ -33,9 +33,7 @@ namespace Flax.Build.Platforms !toolsets.ContainsKey(WindowsPlatformToolset.v142) && !toolsets.ContainsKey(WindowsPlatformToolset.v143) && !toolsets.ContainsKey(WindowsPlatformToolset.v144)) - { _hasRequiredSDKsInstalled = false; - } } } } diff --git a/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs b/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs index 79fdf5f04..9da10ccc0 100644 --- a/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/GDK/GDKToolchain.cs @@ -2,7 +2,6 @@ using System; using System.IO; -using System.Linq; using Flax.Build.NativeCpp; namespace Flax.Build.Platforms @@ -19,8 +18,9 @@ namespace Flax.Build.Platforms /// /// The platform. /// The architecture. - protected GDKToolchain(GDKPlatform platform, TargetArchitecture architecture) - : base(platform, architecture, WindowsPlatformBase.GetToolsets().Keys.Where(x => x <= WindowsPlatformToolset.v142).Max(), WindowsPlatformSDK.v10_0_19041_0) + /// The Windows toolset to use. + protected GDKToolchain(GDKPlatform platform, TargetArchitecture architecture, WindowsPlatformToolset toolset = WindowsPlatformToolset.Latest) + : base(platform, architecture, toolset, WindowsPlatformSDK.Latest) { // Setup system paths SystemIncludePaths.Add(Path.Combine(GDK.Instance.RootPath, "GRDK\\GameKit\\Include")); @@ -42,7 +42,7 @@ namespace Flax.Build.Platforms options.LinkEnv.InputLibraries.Add("xgameruntime.lib"); options.LinkEnv.InputLibraries.Add("xgameplatform.lib"); - options.LinkEnv.InputLibraries.Add("Microsoft.Xbox.Services.142.GDK.C.lib"); + options.LinkEnv.InputLibraries.Add($"Microsoft.Xbox.Services.{(int)Toolset}.GDK.C.lib"); var toolsetPath = WindowsPlatformBase.GetToolsets()[Toolset]; var toolsPath = WindowsPlatformBase.GetVCToolPath64(Toolset); diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 7d800851f..decfb48de 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -73,6 +73,19 @@ namespace Flax.Build.Platforms Utilities.Run("codesign", cmdLine, null, null, Utilities.RunOptions.Default | Utilities.RunOptions.ThrowExceptionOnError); } + internal static bool BuildingForx64 + { + get + { + // We need to support two paths here: + // 1. We are running an x64 binary and we are running on an arm64 host machine + // 2. We are running an Arm64 binary and we are targeting an x64 host machine + var architecture = Platform.BuildTargetArchitecture; + bool isRunningOnArm64Targetx64 = architecture == TargetArchitecture.ARM64 && (Configuration.BuildArchitectures != null && Configuration.BuildArchitectures[0] == TargetArchitecture.x64); + return GetProcessIsTranslated() || isRunningOnArm64Targetx64; + } + } + /// /// Returns true if running an x64 binary an arm64 host machine. /// diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index 031dfaa72..b03dcbb90 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -363,28 +363,22 @@ namespace Flax.Build.Platforms if (compileEnvironment.TreatWarningsAsErrors) commonArgs.Add("-Wall -Werror"); - // TODO: compileEnvironment.IntrinsicFunctions - // TODO: compileEnvironment.FunctionLevelLinking - // TODO: compileEnvironment.FavorSizeOrSpeed - // TODO: compileEnvironment.RuntimeChecks - // TODO: compileEnvironment.StringPooling - // TODO: compileEnvironment.BufferSecurityCheck - if (compileEnvironment.DebugInformation) - { commonArgs.Add("-glldb"); - } commonArgs.Add("-pthread"); + if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.FastCode) + commonArgs.Add("-Ofast"); + else if (compileEnvironment.FavorSizeOrSpeed == FavorSizeOrSpeed.SmallCode) + commonArgs.Add("-Os"); if (compileEnvironment.Optimization) - { commonArgs.Add("-O2"); - } else - { commonArgs.Add("-O0"); - } + + if (compileEnvironment.BufferSecurityCheck) + commonArgs.Add("-fstack-protector"); if (!compileEnvironment.Inlining) { @@ -393,13 +387,9 @@ namespace Flax.Build.Platforms } if (compileEnvironment.EnableExceptions) - { commonArgs.Add("-fexceptions"); - } else - { commonArgs.Add("-fno-exceptions"); - } } // Add preprocessor definitions diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs index bfae02224..9d9a6913b 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsPlatform.cs @@ -62,10 +62,10 @@ namespace Flax.Build.Platforms case TargetPlatform.UWP: return GetSDKs().FirstOrDefault(x => x.Key != WindowsPlatformSDK.v8_1).Value != null; case TargetPlatform.PS4: return Sdk.HasValid("PS4Sdk"); case TargetPlatform.PS5: return Sdk.HasValid("PS5Sdk"); - case TargetPlatform.XboxOne: - case TargetPlatform.XboxScarlett: return GetSDKs().ContainsKey(WindowsPlatformSDK.v10_0_19041_0) && Sdk.HasValid("GDK"); case TargetPlatform.Android: return AndroidSdk.Instance.IsValid && AndroidNdk.Instance.IsValid; case TargetPlatform.Switch: return Sdk.HasValid("SwitchSdk"); + case TargetPlatform.XboxOne: + case TargetPlatform.XboxScarlett: return GetPlatform(platform, true)?.HasRequiredSDKsInstalled ?? false; default: return false; } } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs index d08fcea3d..66c39f853 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchain.cs @@ -43,12 +43,12 @@ namespace Flax.Build.Platforms } /// - public override void PreBuild(TaskGraph graph, BuildOptions options) + public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) { - base.PreBuild(graph, options); - // Compile and include resource file if need to - if (options.Target.Win32ResourceFile != null && !options.Target.IsPreBuilt && options.Target.OutputType == TargetOutputType.Executable) + if (options.Target.Win32ResourceFile != null && + !options.Target.IsPreBuilt && + (options.LinkEnv.Output == LinkerOutput.Executable || options.LinkEnv.Output == LinkerOutput.SharedLibrary)) { var task = graph.Add(); var args = new List(); @@ -62,24 +62,22 @@ namespace Flax.Build.Platforms // Add preprocessor definitions foreach (var definition in options.CompileEnv.PreprocessorDefinitions) - { args.Add(string.Format("/D \"{0}\"", definition)); - } + args.Add(string.Format("/D \"ORIGINAL_FILENAME=\\\"{0}\\\"\"", Path.GetFileName(outputFilePath))); + args.Add(string.Format("/D \"PRODUCT_NAME=\\\"{0}\\\"\"", options.Target.ProjectName + " " + options.Target.ConfigurationName)); + args.Add(string.Format("/D \"PRODUCT_NAME_INTERNAL=\\\"{0}\\\"\"", options.Target.Name)); // Add include paths foreach (var includePath in options.CompileEnv.IncludePaths) - { AddIncludePath(args, includePath); - } // Add the resource file to the produced item list - var outputFile = Path.Combine(options.IntermediateFolder, Path.GetFileNameWithoutExtension(sourceFile) + ".res"); + var outputFile = Path.Combine(options.IntermediateFolder, Path.GetFileName(outputFilePath) + ".res"); args.Add(string.Format("/Fo\"{0}\"", outputFile)); options.LinkEnv.InputFiles.Add(outputFile); // Request included files to exist - var includes = IncludesCache.FindAllIncludedFiles(sourceFile); - task.PrerequisiteFiles.AddRange(includes); + task.PrerequisiteFiles.AddRange(IncludesCache.FindAllIncludedFiles(sourceFile)); // Add the source file args.Add(string.Format("\"{0}\"", sourceFile)); @@ -91,6 +89,8 @@ namespace Flax.Build.Platforms task.PrerequisiteFiles.Add(sourceFile); task.Cost = 1; } + + base.LinkFiles(graph, options, outputFilePath); } } } diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 1c1f95afe..c31a63cca 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -439,6 +439,7 @@ namespace Flax.Build.Platforms var commonArgs = new List(); commonArgs.AddRange(options.CompileEnv.CustomArgs); SetupCompileCppFilesArgs(graph, options, commonArgs); + var useSeparatePdb = true; //compileEnvironment.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.None; { // Suppress Startup Banner commonArgs.Add("/nologo"); @@ -500,7 +501,10 @@ namespace Flax.Build.Platforms if (compileEnvironment.DebugInformation) { // Debug Information Format - commonArgs.Add("/Zi"); + if (useSeparatePdb) + commonArgs.Add("/Zi"); + else + commonArgs.Add("/Z7"); // Enhance Optimized Debugging commonArgs.Add("/Zo"); @@ -621,8 +625,66 @@ namespace Flax.Build.Platforms AddIncludePath(commonArgs, includePath); } - // Compile all C++ files var args = new List(); + + // Create precompiled header + string pchFile = null, pchSource = null; + if (compileEnvironment.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.UseManual) + { + pchFile = compileEnvironment.PrecompiledHeaderFile; + pchSource = compileEnvironment.PrecompiledHeaderSource; + } + else if (compileEnvironment.PrecompiledHeaderUsage == PrecompiledHeaderFileUsage.CreateManual) + { + // Use intermediate cpp file that includes the PCH path but also contains compiler info to properly recompile when it's modified + pchSource = compileEnvironment.PrecompiledHeaderSource; + var pchFilName = Path.GetFileName(pchSource); + var pchSourceFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "cpp")); + var contents = Bindings.BindingsGenerator.GetStringBuilder(); + contents.AppendLine("// This code was auto-generated. Do not modify it."); + // TODO: write compiler version to properly rebuild pch on Visual Studio updates + contents.Append("// Compiler: ").AppendLine(_compilerPath); + contents.Append("#include \"").Append(pchSource).AppendLine("\""); + Utilities.WriteFileIfChanged(pchSourceFile, contents.ToString()); + Bindings.BindingsGenerator.PutStringBuilder(contents); + + // Compile intermediate cpp file into actual PCH (and obj+pdb files) + pchFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "pch")); + if (pchFile.EndsWith(".pch.pch")) + pchFile = pchFile.Substring(0, pchFile.Length - 4); + var pchPdbFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "pdb")); + var pchObjFile = Path.Combine(options.IntermediateFolder, Path.ChangeExtension(pchFilName, "obj")); + var task = graph.Add(); + task.PrerequisiteFiles.Add(pchSourceFile); + task.PrerequisiteFiles.Add(pchSource); + task.PrerequisiteFiles.AddRange(IncludesCache.FindAllIncludedFiles(pchSource)); + task.ProducedFiles.Add(pchFile); + task.ProducedFiles.Add(pchObjFile); + args.AddRange(commonArgs); + args.Add(string.Format("/Yc\"{0}\"", pchSource)); + args.Add(string.Format("/Fp\"{0}\"", pchFile)); + args.Add(string.Format("/Fd\"{0}\"", pchPdbFile)); + args.Add(string.Format("/Fo\"{0}\"", pchObjFile)); + args.Add("/FS"); + args.Add(string.Format("\"{0}\"", pchSourceFile)); + task.WorkingDirectory = options.WorkingDirectory; + task.CommandPath = _compilerPath; + task.CommandArguments = string.Join(" ", args); + task.Cost = int.MaxValue; // Run it before any other tasks + + // Setup outputs + output.PrecompiledHeaderFile = pchFile; + output.ObjectFiles.Add(pchObjFile); + } + if (pchFile != null) + { + // Include PCH file + commonArgs.Add(string.Format("/FI\"{0}\"", pchSource)); + commonArgs.Add(string.Format("/Yu\"{0}\"", pchSource)); + commonArgs.Add(string.Format("/Fp\"{0}\"", pchFile)); + } + + // Compile all C++ files foreach (var sourceFile in sourceFiles) { var sourceFilename = Path.GetFileNameWithoutExtension(sourceFile); @@ -635,9 +697,25 @@ namespace Flax.Build.Platforms if (compileEnvironment.DebugInformation) { // Program Database File Name - var pdbFile = Path.Combine(outputPath, sourceFilename + ".pdb"); - args.Add(string.Format("/Fd\"{0}\"", pdbFile)); - output.DebugDataFiles.Add(pdbFile); + string pdbFile = null; + if (pchFile != null) + { + // When using PCH we need to share the same PDB file that was used when building PCH + pdbFile = pchFile + ".pdb"; + + // Turn on sync for file access to prevent issues when compiling on multiple threads at once + if (useSeparatePdb) + args.Add("/FS"); + } + else if (useSeparatePdb) + { + pdbFile = Path.Combine(outputPath, sourceFilename + ".pdb"); + } + if (pdbFile != null) + { + args.Add(string.Format("/Fd\"{0}\"", pdbFile)); + output.DebugDataFiles.Add(pdbFile); + } } if (compileEnvironment.GenerateDocumentation) @@ -660,6 +738,10 @@ namespace Flax.Build.Platforms // Request included files to exist var includes = IncludesCache.FindAllIncludedFiles(sourceFile); task.PrerequisiteFiles.AddRange(includes); + if (pchFile != null) + { + task.PrerequisiteFiles.Add(pchFile); + } // Compile task.WorkingDirectory = options.WorkingDirectory; @@ -863,6 +945,13 @@ namespace Flax.Build.Platforms task.PrerequisiteFiles.AddRange(linkEnvironment.InputFiles); foreach (var file in linkEnvironment.InputFiles) { + if (file.EndsWith(".pch", StringComparison.OrdinalIgnoreCase)) + { + // PCH file + args.Add(string.Format("/Yu:\"{0}\"", file)); + continue; + } + args.Add(string.Format("\"{0}\"", file)); } diff --git a/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs b/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs index 458cc52bf..273382d7c 100644 --- a/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/iOS/iOSToolchain.cs @@ -65,8 +65,11 @@ namespace Flax.Build.Platforms switch (options.Action) { case CSharpOptions.ActionTypes.GetPlatformTools: - options.PlatformToolsPath = Path.Combine(DotNetSdk.SelectVersionFolder(Path.Combine(DotNetSdk.Instance.RootPath, "packs/Microsoft.NETCore.App.Runtime.AOT.osx-x64.Cross.ios-arm64")), "tools"); + { + var arch = Flax.Build.Platforms.MacPlatform.BuildingForx64 ? "x64" : "arm64"; + options.PlatformToolsPath = Path.Combine(DotNetSdk.SelectVersionFolder(Path.Combine(DotNetSdk.Instance.RootPath, $"packs/Microsoft.NETCore.App.Runtime.AOT.osx-{arch}.Cross.ios-arm64")), "tools"); return false; + } case CSharpOptions.ActionTypes.MonoCompile: { var aotCompilerPath = Path.Combine(options.PlatformToolsPath, "mono-aot-cross"); @@ -81,7 +84,7 @@ namespace Flax.Build.Platforms // Setup options var monoAotMode = "full"; var monoDebugMode = options.EnableDebugSymbols ? "soft-debug" : "nodebug"; - var aotCompilerArgs = $"--aot={monoAotMode},asmonly,verbose,stats,print-skipped,{monoDebugMode} -O=all"; + var aotCompilerArgs = $"--aot={monoAotMode},asmonly,verbose,stats,print-skipped,{monoDebugMode} -O=float32"; if (options.EnableDebugSymbols || options.EnableToolDebug) aotCompilerArgs = "--debug " + aotCompilerArgs; var envVars = new Dictionary(); @@ -94,13 +97,7 @@ namespace Flax.Build.Platforms // Run cross-compiler compiler (outputs assembly code) int result = Utilities.Run(aotCompilerPath, $"{aotCompilerArgs} \"{inputFile}\"", null, inputFileFolder, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars); if (result != 0) - { - // Try without optimizations as a fallback - aotCompilerArgs = aotCompilerArgs.Replace("-O=all", ""); - result = Utilities.Run(aotCompilerPath, $"{aotCompilerArgs} \"{inputFile}\"", null, inputFileFolder, Utilities.RunOptions.AppMustExist | Utilities.RunOptions.ConsoleLogOutput, envVars); - if (result != 0) - return true; - } + return true; // Get build args for iOS var clangArgs = new List(); diff --git a/Source/Tools/Flax.Build/Utilities/Utilities.cs b/Source/Tools/Flax.Build/Utilities/Utilities.cs index 1f6aaa0a2..c4e58f629 100644 --- a/Source/Tools/Flax.Build/Utilities/Utilities.cs +++ b/Source/Tools/Flax.Build/Utilities/Utilities.cs @@ -320,10 +320,20 @@ namespace Flax.Build /// ConsoleLogOutput = 1 << 7, + /// + /// Enables to run process via Shell instead of standard process startup. + /// + Shell = 1 << 8, + /// /// The default options. /// Default = AppMustExist, + + /// + /// The default options for tools execution in build tool. + /// + DefaultTool = ConsoleLogOutput | ThrowExceptionOnError, } private static void StdLogInfo(object sender, DataReceivedEventArgs e) @@ -342,6 +352,16 @@ namespace Flax.Build } } + /// + /// Calls within Flax.Build context - can be used by build scripts to properly find a type by name. + /// + /// The full name (namespace with class name). + /// Type or null if not found. + public static Type GetType(string name) + { + return Type.GetType(name); + } + /// /// Runs the external program. /// @@ -393,11 +413,12 @@ namespace Flax.Build } bool redirectStdOut = (options & RunOptions.NoStdOutRedirect) != RunOptions.NoStdOutRedirect; + bool shell = options.HasFlag(RunOptions.Shell); Process proc = new Process(); proc.StartInfo.FileName = app; proc.StartInfo.Arguments = commandLine != null ? commandLine : ""; - proc.StartInfo.UseShellExecute = false; + proc.StartInfo.UseShellExecute = shell; proc.StartInfo.RedirectStandardInput = input != null; proc.StartInfo.CreateNoWindow = true; @@ -406,7 +427,7 @@ namespace Flax.Build proc.StartInfo.WorkingDirectory = workspace; } - if (redirectStdOut) + if (redirectStdOut && !shell) { proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.RedirectStandardError = true; @@ -422,16 +443,24 @@ namespace Flax.Build } } - if (envVars != null) + if (envVars != null && !shell) { foreach (var env in envVars) { if (env.Key == "PATH") { + // Append to path proc.StartInfo.EnvironmentVariables[env.Key] = env.Value + ';' + proc.StartInfo.EnvironmentVariables[env.Key]; } + else if (env.Value == null) + { + // Remove variable + if (proc.StartInfo.EnvironmentVariables.ContainsKey(env.Key)) + proc.StartInfo.EnvironmentVariables.Remove(env.Key); + } else { + // Set variable proc.StartInfo.EnvironmentVariables[env.Key] = env.Value; } } @@ -772,6 +801,12 @@ namespace Flax.Build Array.Sort(paths, (a, b) => { Version va, vb; + var fa = Path.GetFileName(a); + var fb = Path.GetFileName(b); + if (!string.IsNullOrEmpty(fa)) + a = fa; + if (!string.IsNullOrEmpty(fb)) + b = fb; if (Version.TryParse(a, out va)) { if (Version.TryParse(b, out vb)) diff --git a/Source/Tools/Flax.Build/global.json b/Source/Tools/Flax.Build/global.json index 3fe9e80f6..9498eeef6 100644 --- a/Source/Tools/Flax.Build/global.json +++ b/Source/Tools/Flax.Build/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.0", + "version": "8.0.0", "rollForward": "latestMajor" } } \ No newline at end of file diff --git a/global.json b/global.json index 3fe9e80f6..9498eeef6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.0", + "version": "8.0.0", "rollForward": "latestMajor" } } \ No newline at end of file