diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..0e27bbfb --- /dev/null +++ b/.clang-format @@ -0,0 +1,21 @@ +Language: Cpp +UseTab: Never +IndentWidth: 4 +ColumnLimit: 200 +PointerAlignment: Left +BreakBeforeBraces: Allman +Cpp11BracedListStyle: false +InsertBraces: true + +IndentCaseLabels: true +AlignAfterOpenBracket: DontAlign +AlignArrayOfStructures: None +AlignEscapedNewlines: DontAlign +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +PenaltyBreakAssignment: 15 +PenaltyBreakBeforeFirstCallParameter: 15 +PenaltyBreakOpenParenthesis: 15 +BinPackArguments: false +BinPackParameters: false diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index aa22844d..44519815 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -17,12 +17,6 @@ jobs: with: platform: ${{ matrix.platform }} architecture: ${{ matrix.architecture }} - - # TODO: Do a build bindings sub workflow - #build_dotnet: - # uses: ./.github/workflows/build-dotnet.yml - #name: 'build-dotnet' - #secrets: inherit run_tests: strategy: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3a51004..dc66da26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: jobs: build: runs-on: ${{ - (inputs.platform == 'osx' && 'macos-14') || + (inputs.platform == 'osx' && 'macos-latest') || (inputs.platform == 'linux' && 'ubuntu-latest') || (inputs.platform == 'win' && 'windows-latest') }} @@ -27,14 +27,11 @@ jobs: with: arch: ${{ inputs.architecture }} - # TODO: For the moment Github actions doesn't have the latest version of ubuntu - # we will re enable the linux build after - # TODO: libwayland-dev wayland-protocols libdecor-devel - name: Setup Dependencies (Linux) if: inputs.platform == 'linux' run: | sudo apt-get update - sudo apt-get install -y -V libgtk-4-dev + sudo apt-get install libwayland-dev wayland-protocols libudev-dev libdecor-0-dev - name: Setup Ninja uses: seanmiddleditch/gha-setup-ninja@master diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 53031d30..059c5760 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -22,10 +22,12 @@ jobs: with: name: tests.${{ inputs.platform }}-${{ inputs.architecture }} path: ./ - + - name: Setup Dependencies (Linux) if: inputs.platform == 'linux' - run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev + run: | + sudo apt-get update + sudo apt-get install libwayland-dev wayland-protocols libudev-dev libdecor-0-dev - name: Extract Tests Archive (Windows) if: inputs.platform == 'win' @@ -59,8 +61,7 @@ jobs: if: inputs.platform == 'osx' run: ./tests/ToolsTests.app/Contents/MacOS/ToolsTests - #- name: Run Graphics Tests - #if: inputs.platform == 'win' - #run: | -#$ErrorView = 'NormalView' -# ./Tests/GraphicsTests + #- name: Run Graphics Tests (MacOS) + #if: inputs.platform == 'osx' + #run: ./tests/GraphicsTests.app/Contents/MacOS/GraphicsTests + diff --git a/.gitignore b/.gitignore index a6d15501..4ff7342a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,16 @@ *.userosscache *.sln.docstates *.resolved +*.obj +*.mesh +*.ini +*.gltf +*.bin +*.tga +*.png +*.jpg +*.dds +*.mtl xcuserdata/ packages/ @@ -32,3 +42,4 @@ bld/ external/shader-compilers/ .DS_Store artifacts/ +*.db diff --git a/.gitmodules b/.gitmodules index 38f56fef..9ecbeb89 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,24 @@ [submodule "external/metal-cpp"] path = external/metal-cpp url = https://github.com/bkaradzic/metal-cpp.git +[submodule "external/meshoptimizer"] + path = external/meshoptimizer + url = https://github.com/zeux/meshoptimizer.git +[submodule "external/cimgui"] + path = external/cimgui + url = https://github.com/cimgui/cimgui.git +[submodule "external/fast_obj"] + path = external/fast_obj + url = https://github.com/thisistherk/fast_obj.git +[submodule "external/cgltf"] + path = external/cgltf + url = https://github.com/jkuhlmann/cgltf.git +[submodule "external/stb"] + path = external/stb + url = https://github.com/nothings/stb.git +[submodule "external/bc7enc_rdo"] + path = external/bc7enc_rdo + url = https://github.com/richgel999/bc7enc_rdo.git +[submodule "external/MikkTSpace"] + path = external/MikkTSpace + url = https://github.com/mmikk/MikkTSpace.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 02eb8d91..d8fcc6c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,9 @@ if(WIN32) elseif(LINUX) set(CMAKE_CXX_COMPILER "clang++") set(CMAKE_C_COMPILER "clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lm") + set(CMAKE_POSITION_INDEPENDENT_CODE ON) else() if(BUILD_FOR_IOS) set(CMAKE_SYSTEM_NAME iOS) @@ -49,13 +50,13 @@ add_compile_options($<$:-Wno-unused-parameter>) add_compile_options($<$:-Wno-missing-field-initializers>) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fansi-escape-codes -fcolor-diagnostics -D_CRT_SECURE_NO_WARNINGS") set(CMAKE_CXX_STANDARD "23") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") -set(CMAKE_C_STANDARD "99") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fansi-escape-codes -fcolor-diagnostics -D_CRT_SECURE_NO_WARNINGS") +set(CMAKE_C_STANDARD "23") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/LICENSE b/LICENSE index a0a4bc38..bdfc3bbe 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Double Buffer SRL +Copyright (c) 2023-2025 Double Buffer SRL Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bindings/dotnet/Directory.Build.props b/bindings/dotnet/Directory.Build.props deleted file mode 100644 index e403c4a0..00000000 --- a/bindings/dotnet/Directory.Build.props +++ /dev/null @@ -1,31 +0,0 @@ - - - - net9.0 - preview - enable - enable - true - true - - - - true - CS1668;CA5393;CA1711;CA1420 - - - - latest - All - All - All - All - All - All - All - All - All - All - - - diff --git a/bindings/dotnet/Directory.Packages.props b/bindings/dotnet/Directory.Packages.props deleted file mode 100644 index 2cbaa763..00000000 --- a/bindings/dotnet/Directory.Packages.props +++ /dev/null @@ -1,10 +0,0 @@ - - - true - - - - - - - diff --git a/bindings/dotnet/Elemental.sln b/bindings/dotnet/Elemental.sln deleted file mode 100644 index b67e6fea..00000000 --- a/bindings/dotnet/Elemental.sln +++ /dev/null @@ -1,84 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1F23B2BD-4FB1-4466-B5FA-F03D6390A8D2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elemental", "src\Elemental\Elemental.csproj", "{B68362C7-510F-4E5F-AE3B-BF5BE481B8EB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elemental.Common", "src\Elemental.Common\Elemental.Common.csproj", "{63930D32-EE2F-4E48-8AFF-FB5A90807946}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elemental.Tools", "src\Elemental.Tools\Elemental.Tools.csproj", "{88001C17-BA54-4869-8E51-A8787365C400}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elemental.SourceGenerators", "src\Elemental.SourceGenerators\Elemental.SourceGenerators.csproj", "{3A9C7DBA-AB71-47E5-855D-9223C5B4897E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorld", "..\..\samples\dotnet\01-HelloWorld\HelloWorld.csproj", "{5E78BF43-09F0-4FFD-B073-644EA9531AAA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F7455345-C90D-405C-BD55-0B9BC7BC7CDE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWorld", "samples\01-HelloWorld\HelloWorld.csproj", "{463AC234-64AB-411E-B4B1-19FC384192D8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloWindow", "samples\02-HelloWindow\HelloWindow.csproj", "{92FD8004-B341-426D-8251-782BB27AE9EE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloTriangle", "samples\03-HelloTriangle\HelloTriangle.csproj", "{4567AC5D-0B65-4C07-8F22-EE10DD553920}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloInputs", "samples\04-HelloInputs\HelloInputs.csproj", "{D30D019A-E8F2-4E2D-987B-225E6EA532E6}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B68362C7-510F-4E5F-AE3B-BF5BE481B8EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B68362C7-510F-4E5F-AE3B-BF5BE481B8EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B68362C7-510F-4E5F-AE3B-BF5BE481B8EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B68362C7-510F-4E5F-AE3B-BF5BE481B8EB}.Release|Any CPU.Build.0 = Release|Any CPU - {63930D32-EE2F-4E48-8AFF-FB5A90807946}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63930D32-EE2F-4E48-8AFF-FB5A90807946}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63930D32-EE2F-4E48-8AFF-FB5A90807946}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63930D32-EE2F-4E48-8AFF-FB5A90807946}.Release|Any CPU.Build.0 = Release|Any CPU - {88001C17-BA54-4869-8E51-A8787365C400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88001C17-BA54-4869-8E51-A8787365C400}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88001C17-BA54-4869-8E51-A8787365C400}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88001C17-BA54-4869-8E51-A8787365C400}.Release|Any CPU.Build.0 = Release|Any CPU - {3A9C7DBA-AB71-47E5-855D-9223C5B4897E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A9C7DBA-AB71-47E5-855D-9223C5B4897E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A9C7DBA-AB71-47E5-855D-9223C5B4897E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A9C7DBA-AB71-47E5-855D-9223C5B4897E}.Release|Any CPU.Build.0 = Release|Any CPU - {5E78BF43-09F0-4FFD-B073-644EA9531AAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5E78BF43-09F0-4FFD-B073-644EA9531AAA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5E78BF43-09F0-4FFD-B073-644EA9531AAA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5E78BF43-09F0-4FFD-B073-644EA9531AAA}.Release|Any CPU.Build.0 = Release|Any CPU - {463AC234-64AB-411E-B4B1-19FC384192D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {463AC234-64AB-411E-B4B1-19FC384192D8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {463AC234-64AB-411E-B4B1-19FC384192D8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {463AC234-64AB-411E-B4B1-19FC384192D8}.Release|Any CPU.Build.0 = Release|Any CPU - {92FD8004-B341-426D-8251-782BB27AE9EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92FD8004-B341-426D-8251-782BB27AE9EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92FD8004-B341-426D-8251-782BB27AE9EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92FD8004-B341-426D-8251-782BB27AE9EE}.Release|Any CPU.Build.0 = Release|Any CPU - {4567AC5D-0B65-4C07-8F22-EE10DD553920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4567AC5D-0B65-4C07-8F22-EE10DD553920}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4567AC5D-0B65-4C07-8F22-EE10DD553920}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4567AC5D-0B65-4C07-8F22-EE10DD553920}.Release|Any CPU.Build.0 = Release|Any CPU - {D30D019A-E8F2-4E2D-987B-225E6EA532E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D30D019A-E8F2-4E2D-987B-225E6EA532E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D30D019A-E8F2-4E2D-987B-225E6EA532E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D30D019A-E8F2-4E2D-987B-225E6EA532E6}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {B68362C7-510F-4E5F-AE3B-BF5BE481B8EB} = {1F23B2BD-4FB1-4466-B5FA-F03D6390A8D2} - {63930D32-EE2F-4E48-8AFF-FB5A90807946} = {1F23B2BD-4FB1-4466-B5FA-F03D6390A8D2} - {88001C17-BA54-4869-8E51-A8787365C400} = {1F23B2BD-4FB1-4466-B5FA-F03D6390A8D2} - {3A9C7DBA-AB71-47E5-855D-9223C5B4897E} = {1F23B2BD-4FB1-4466-B5FA-F03D6390A8D2} - {463AC234-64AB-411E-B4B1-19FC384192D8} = {F7455345-C90D-405C-BD55-0B9BC7BC7CDE} - {92FD8004-B341-426D-8251-782BB27AE9EE} = {F7455345-C90D-405C-BD55-0B9BC7BC7CDE} - {4567AC5D-0B65-4C07-8F22-EE10DD553920} = {F7455345-C90D-405C-BD55-0B9BC7BC7CDE} - {D30D019A-E8F2-4E2D-987B-225E6EA532E6} = {F7455345-C90D-405C-BD55-0B9BC7BC7CDE} - EndGlobalSection -EndGlobal diff --git a/bindings/dotnet/publish/Elemental.Tools/Elemental.Tools.nuspec b/bindings/dotnet/publish/Elemental.Tools/Elemental.Tools.nuspec deleted file mode 100644 index 3a44feda..00000000 --- a/bindings/dotnet/publish/Elemental.Tools/Elemental.Tools.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - Elemental.Tools - #VERSION# - Double Buffer - Elemental Tools contains tools for Elemental, a portable low-level game platform abstraction library for .NET 7+ that targets only next-gen features. - Copyright (c) Double Buffer SRL 2023 - https://github.com/double-buffer/elemental - - - MIT - gamedev;graphics;windows;macos; - - - \ No newline at end of file diff --git a/bindings/dotnet/publish/Elemental/Elemental.nuspec b/bindings/dotnet/publish/Elemental/Elemental.nuspec deleted file mode 100644 index 592eec5c..00000000 --- a/bindings/dotnet/publish/Elemental/Elemental.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - Elemental - #VERSION# - Double Buffer - Elemental is a portable low-level game platform abstraction library for .NET 7+ that targets only next-gen features. - Copyright (c) Double Buffer SRL 2023 - https://github.com/double-buffer/elemental - - - MIT - gamedev;graphics;windows;macos; - - - \ No newline at end of file diff --git a/bindings/dotnet/samples/01-HelloWorld/HelloWorld.csproj b/bindings/dotnet/samples/01-HelloWorld/HelloWorld.csproj deleted file mode 100644 index 79889a52..00000000 --- a/bindings/dotnet/samples/01-HelloWorld/HelloWorld.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - True - - - - - - - - - - diff --git a/bindings/dotnet/samples/01-HelloWorld/Program.cs b/bindings/dotnet/samples/01-HelloWorld/Program.cs deleted file mode 100644 index 20fe0009..00000000 --- a/bindings/dotnet/samples/01-HelloWorld/Program.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Elemental; - -var counter = 0; - -var applicationService = new ApplicationService(); -applicationService.ConfigureLogHandler(DefaultLogHandlers.ConsoleLogHandler); - -using var application = applicationService.CreateApplication("Hello World"u8); - -applicationService.RunApplication(application, (status) => -{ - if (counter > 10 || status != ApplicationStatus.Active) - { - return false; - } - - Console.WriteLine($"Hello World {counter}!"); - counter++; - return true; -}); diff --git a/bindings/dotnet/samples/02-HelloWindow/HelloWindow.csproj b/bindings/dotnet/samples/02-HelloWindow/HelloWindow.csproj deleted file mode 100644 index 444bd662..00000000 --- a/bindings/dotnet/samples/02-HelloWindow/HelloWindow.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - True - - - - $(AssemblyName).app/Contents/MacOS - - - - - - - diff --git a/bindings/dotnet/samples/02-HelloWindow/Program.cs b/bindings/dotnet/samples/02-HelloWindow/Program.cs deleted file mode 100644 index a52f9af4..00000000 --- a/bindings/dotnet/samples/02-HelloWindow/Program.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Elemental; - -var applicationService = new ApplicationService(); -applicationService.ConfigureLogHandler(DefaultLogHandlers.ConsoleLogHandler); - -applicationService.RunApplication(new () -{ - ApplicationName = "Hello Window"u8, - InitHandler = InitSample, - FreeHandler = FreeSample, - Payload = new TestPayload - { - Test = 28 - } -}); - -void InitSample(ref TestPayload payload) -{ - var systemInfo = applicationService.GetSystemInfo(); - - Console.WriteLine($"Path: {System.Text.Encoding.UTF8.GetString(systemInfo.ApplicationPath)}"); - - payload.Window = applicationService.CreateWindow(); - Console.WriteLine($"Test: {payload.Test}"); -} - -void FreeSample(ref TestPayload payload) -{ - payload.Window.Dispose(); - Console.WriteLine("Exit Sample"); -} - -record struct TestPayload -{ - public Window Window { get; set; } - public int Test { get; set; } -} diff --git a/bindings/dotnet/samples/03-HelloTriangle/HelloTriangle.csproj b/bindings/dotnet/samples/03-HelloTriangle/HelloTriangle.csproj deleted file mode 100644 index 7d129c49..00000000 --- a/bindings/dotnet/samples/03-HelloTriangle/HelloTriangle.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - True - - - - $(AssemblyName).app/Contents/MacOS - - - - - - - - - - Triangle.metal - PreserveNewest - - - Triangle.hlsl - PreserveNewest - - - - diff --git a/bindings/dotnet/samples/03-HelloTriangle/Program.cs b/bindings/dotnet/samples/03-HelloTriangle/Program.cs deleted file mode 100644 index ec5f1e12..00000000 --- a/bindings/dotnet/samples/03-HelloTriangle/Program.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Diagnostics; -using System.Numerics; -using Elemental; -using Elemental.Graphics; -using Elemental.Tools; - -//TODO: HelloTriangle should be minimal, do another sample to demonstrate the debug functionality - -static void LogMessageHandler(LogMessageType messageType, LogMessageCategory category, string function, string message) -{ - var mainForegroundColor = messageType switch - { - LogMessageType.Warning => ConsoleColor.Yellow, - LogMessageType.Error => ConsoleColor.Red, - _ => ConsoleColor.Gray - }; - - Console.Write("["); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write($"{category}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.Write("]"); - - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($" {function}"); - - Console.ForegroundColor = mainForegroundColor; - Console.WriteLine($" {message}"); - Console.ForegroundColor = ConsoleColor.Gray; -} - -using var applicationService = new NativeApplicationService(new() { LogMessageHandler = LogMessageHandler }); -using var graphicsService = new GraphicsService(new() { GraphicsDiagnostics = GraphicsDiagnostics.Debug }); - -using var application = applicationService.CreateApplication("Hello Window"); -using var window = applicationService.CreateWindow(application); - -var availableGraphicsDevices = graphicsService.GetAvailableGraphicsDevices(); -var selectedGraphicsDevice = availableGraphicsDevices[0]; - -// TODO: Do a separate sample to showcase the use of Native + Vulkan with 2 windows (if available) -foreach (var availableGraphicsDevice in availableGraphicsDevices) -{ - if (availableGraphicsDevice.GraphicsApi == GraphicsApi.Vulkan) - { - //selectedGraphicsDevice = availableGraphicsDevice; - } - - Console.WriteLine($"{availableGraphicsDevice}"); -} - -using var graphicsDevice = graphicsService.CreateGraphicsDevice(new() { DeviceId = selectedGraphicsDevice.DeviceId }); -using var commandQueue = graphicsService.CreateCommandQueue(graphicsDevice, CommandQueueType.Graphics); - -applicationService.SetWindowTitle(window, $"Hello Triangle! (GraphicsDevice: {graphicsService.GetGraphicsDeviceInfo(graphicsDevice)})"); - -using var swapChain = graphicsService.CreateSwapChain(window, commandQueue); -var currentRenderSize = applicationService.GetWindowRenderSize(window); - -// HACK: This is needed when we run the program from the root directory -Environment.CurrentDirectory = Path.GetDirectoryName(Environment.ProcessPath)!; - -using var shaderCompiler = new ShaderCompiler(); - -Console.WriteLine($"Can Compile Shader HLSL: {shaderCompiler.CanCompileShader(ShaderLanguage.Hlsl, selectedGraphicsDevice.GraphicsApi)}"); -Console.WriteLine($"Can Compile Shader Metal: {shaderCompiler.CanCompileShader(ShaderLanguage.Msl, selectedGraphicsDevice.GraphicsApi)}"); - -var shaderCode = File.ReadAllText("Triangle.hlsl"); - -// TODO: This is needed for Apple Metal because SPIRV-Cross doesn't yet support metal mesh shaders -var shaderCodeMetal = File.ReadAllText("Triangle.metal"); - -var meshShaderSourceType = selectedGraphicsDevice.GraphicsApi == GraphicsApi.Metal ? ShaderLanguage.Msl : ShaderLanguage.Hlsl; - -var shaderInputs = new ShaderCompilerInput[] -{ - new() { ShaderCode = selectedGraphicsDevice.GraphicsApi == GraphicsApi.Metal ? shaderCodeMetal : shaderCode, Stage = ShaderStage.MeshShader, EntryPoint = "MeshMain", ShaderLanguage = meshShaderSourceType }, - new() { ShaderCode = shaderCode, Stage = ShaderStage.PixelShader, EntryPoint = "PixelMain", ShaderLanguage = ShaderLanguage.Hlsl } -}; - -var shaderCompilationResults = shaderCompiler.CompileShaders(shaderInputs, selectedGraphicsDevice.GraphicsApi); - -// TODO: Avoid the array declaration -var shaderParts = new ShaderPart[shaderCompilationResults.Length]; - -for (var i = 0; i < shaderCompilationResults.Length; i++) -{ - var shaderCompilationResult = shaderCompilationResults[i]; - - foreach (var logEntry in shaderCompilationResult.LogEntries.Span) - { - Console.WriteLine($"{logEntry.Type}: {logEntry.Message}"); - } - - if (!shaderCompilationResult.IsSuccess) - { - return; - } - - shaderParts[i] = new() - { - Stage = shaderCompilationResult.Stage, - EntryPoint = shaderCompilationResult.EntryPoint, - Data = shaderCompilationResult.ShaderData, - MetaData = !(selectedGraphicsDevice.GraphicsApi == GraphicsApi.Metal && shaderCompilationResult.Stage == ShaderStage.MeshShader) ? shaderCompilationResult.MetaData : new ShaderMetaData[] - { - new ShaderMetaData { Type = ShaderMetaDataType.PushConstantsCount, Value = 1 }, - new ShaderMetaData { Type = ShaderMetaDataType.ThreadCountX, Value = 32 }, - new ShaderMetaData { Type = ShaderMetaDataType.ThreadCountY, Value = 1 }, - new ShaderMetaData { Type = ShaderMetaDataType.ThreadCountZ, Value = 1 } - } - }; -} - -using var shader = graphicsService.CreateShader(graphicsDevice, shaderParts); - -var stopWatch = new Stopwatch(); -var frameTimer = new Stopwatch(); -var rotationY = 0.0f; - -applicationService.RunApplication(application, (status) => -{ - if (status.IsClosing) - { - return false; - } - - var renderSize = applicationService.GetWindowRenderSize(window); - - if (renderSize != currentRenderSize) - { - Console.WriteLine($"Resize SwapChain to: {renderSize}"); - graphicsService.ResizeSwapChain(swapChain, renderSize.Width, renderSize.Height); - currentRenderSize = renderSize; - } - - stopWatch.Restart(); - graphicsService.WaitForSwapChainOnCpu(swapChain); - stopWatch.Stop(); - - //Console.WriteLine($"Wait for swapchain {stopWatch.Elapsed.TotalMilliseconds}"); - - rotationY += (float)frameTimer.Elapsed.TotalSeconds; - frameTimer.Restart(); - - using var commandList = graphicsService.CreateCommandList(commandQueue); - - using var backbufferTexture = graphicsService.GetSwapChainBackBufferTexture(swapChain); - graphicsService.BeginRenderPass(commandList, new() { RenderTarget0 = new() { Texture = backbufferTexture, ClearColor = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) } }); - - graphicsService.SetShader(commandList, shader); - graphicsService.SetShaderConstants(commandList, 0, ref rotationY); - graphicsService.DispatchMesh(commandList, 1, 1, 1); - - graphicsService.EndRenderPass(commandList); - graphicsService.CommitCommandList(commandList); - graphicsService.ExecuteCommandList(commandQueue, commandList); - - stopWatch.Restart(); - graphicsService.PresentSwapChain(swapChain); - stopWatch.Stop(); - - //Console.WriteLine($"Present swapchain {stopWatch.Elapsed.TotalMilliseconds}"); - return true; -}); diff --git a/bindings/dotnet/samples/03-HelloTriangle/Triangle.hlsl b/bindings/dotnet/samples/03-HelloTriangle/Triangle.hlsl deleted file mode 100644 index ed9afd1e..00000000 --- a/bindings/dotnet/samples/03-HelloTriangle/Triangle.hlsl +++ /dev/null @@ -1,60 +0,0 @@ -#define RootSignatureDef "RootFlags(0), RootConstants(num32BitConstants=1, b0)" - -struct ShaderParameters -{ - float RotationY; -}; - -[[vk::push_constant]] -ShaderParameters parameters : register(b0); - -struct Vertex -{ - float3 Position; - float4 Color; -}; - -struct VertexOutput -{ - float4 Position: SV_Position; - float4 Color: TEXCOORD0; -}; - -static Vertex triangleVertices[] = -{ - { float3(-0.5, 0.5, 0.0), float4(1.0, 0.0, 0.0, 1.0) }, - { float3(0.5, 0.5, 0.0), float4(0.0, 1.0, 0.0, 1.0) }, - { float3(-0.5, -0.5, 0.0), float4(0.0, 0.0, 1.0, 1.0) } -}; - -float3 Transform(float3 position, float rotationY) -{ - float cosY = cos(rotationY); - float sinY = sin(rotationY); - return float3(position.x * cosY - position.z * sinY, position.y, position.x * sinY + position.z * cosY + 0.5); -} - -[OutputTopology("triangle")] -[NumThreads(32, 1, 1)] -void MeshMain(in uint groupThreadId : SV_GroupThreadID, out vertices VertexOutput vertices[3], out indices uint3 indices[1]) -{ - const uint meshVertexCount = 3; - - SetMeshOutputCounts(meshVertexCount, 1); - - if (groupThreadId < meshVertexCount) - { - vertices[groupThreadId].Position = float4(Transform(triangleVertices[groupThreadId].Position, parameters.RotationY), 1.0); - vertices[groupThreadId].Color = triangleVertices[groupThreadId].Color; - } - - if (groupThreadId == 0) - { - indices[groupThreadId] = uint3(0, 1, 2); - } -} - -float4 PixelMain(const VertexOutput input) : SV_Target0 -{ - return input.Color; -} diff --git a/bindings/dotnet/samples/03-HelloTriangle/Triangle.metal b/bindings/dotnet/samples/03-HelloTriangle/Triangle.metal deleted file mode 100644 index 4d35542d..00000000 --- a/bindings/dotnet/samples/03-HelloTriangle/Triangle.metal +++ /dev/null @@ -1,117 +0,0 @@ -using namespace metal; - -#include -#include - -struct VertexOutput -{ - float4 Position [[position]]; - float4 Color [[user(locn0)]]; -}; - -struct PrimitiveOutput -{ -}; - -struct ShaderParameters -{ - float RotationY; -}; - -constant float3 triangleVertices[] = -{ - float3(-0.5, 0.5, 0), - float3(0.5, 0.5, 0), - float3(-0.5, -0.5, 0) -}; - -constant float4 triangleColors[] = -{ - float4(1.0, 0.0, 0, 1), - float4(0.0, 1.0, 0, 1), - float4(0.0, 0.0, 1, 1) -}; - -constant uint8_t rectangleIndices[] = -{ - 0, 1, 2 -}; - -float3 rotate(float3 position, float pitch, float roll, float yaw) -{ - float cosa = cos(yaw); - float sina = sin(yaw); - - float cosb = cos(pitch); - float sinb = sin(pitch); - - float cosc = cos(roll); - float sinc = sin(roll); - - float Axx = cosa*cosb; - float Axy = cosa*sinb*sinc - sina*cosc; - float Axz = cosa*sinb*cosc + sina*sinc; - - float Ayx = sina*cosb; - float Ayy = sina*sinb*sinc + cosa*cosc; - float Ayz = sina*sinb*cosc - cosa*sinc; - - float Azx = -sinb; - float Azy = cosb*sinc; - float Azz = cosb*cosc; - - float px = position.x; - float py = position.y; - float pz = position.z; - - float3 result; - - result.x = Axx*px + Axy*py + Axz*pz; - result.y = Ayx*px + Ayy*py + Ayz*pz; - result.z = Azx*px + Azy*py + Azz*pz; - - return result; -} - -constant uint32_t meshVertexCount = 3; -constant uint32_t meshPrimitiveCount = 1; - -using AAPLTriangleMeshType = metal::mesh; - -/// The mesh stage function that generates a triangle mesh. -[[mesh, max_total_threads_per_threadgroup(126)]] -void MeshMain(AAPLTriangleMeshType output, - uint32_t groupThreadId [[thread_index_in_threadgroup]], - uint32_t groupId [[threadgroup_position_in_grid]], - device const ShaderParameters ¶meters [[buffer(0)]]) -{ - - - output.set_primitive_count(meshPrimitiveCount); - - if (groupThreadId < meshVertexCount) - { - VertexOutput vertexOutput = {}; - - float3 position = triangleVertices[groupThreadId]; - - position = rotate(position, parameters.RotationY, 0, 0); - position.z = 0.5; - - vertexOutput.Position = float4(position, 1.0); - vertexOutput.Color = triangleColors[groupThreadId]; - - output.set_vertex(groupThreadId, vertexOutput); - } - - if (groupThreadId < meshPrimitiveCount) - { - PrimitiveOutput primitiveOutput; - output.set_primitive(groupThreadId, primitiveOutput); - - uint i = (3*groupThreadId); - output.set_index(i+0, rectangleIndices[i+0]); - output.set_index(i+1, rectangleIndices[i+1]); - output.set_index(i+2, rectangleIndices[i+2]); - } -} \ No newline at end of file diff --git a/bindings/dotnet/samples/03-HelloTriangleMin/HelloTriangleMin.csproj b/bindings/dotnet/samples/03-HelloTriangleMin/HelloTriangleMin.csproj deleted file mode 100644 index 0a7be362..00000000 --- a/bindings/dotnet/samples/03-HelloTriangleMin/HelloTriangleMin.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - True - - - - $(AssemblyName).app/Contents/MacOS - - - - - - - - diff --git a/bindings/dotnet/samples/03-HelloTriangleMin/Program.cs b/bindings/dotnet/samples/03-HelloTriangleMin/Program.cs deleted file mode 100644 index bd18ca46..00000000 --- a/bindings/dotnet/samples/03-HelloTriangleMin/Program.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Numerics; -using Elemental; -using Elemental.Graphics; -using Elemental.Tools; - -var applicationService = new NativeApplicationService(); -using var graphicsService = new GraphicsService(); - -using var application = applicationService.CreateApplication("Hello Window"); -using var window = applicationService.CreateWindow(application); - -using var graphicsDevice = graphicsService.CreateGraphicsDevice(); -using var commandQueue = graphicsService.CreateCommandQueue(graphicsDevice, CommandQueueType.Graphics); -using var swapChain = graphicsService.CreateSwapChain(window, commandQueue); -var currentRenderSize = applicationService.GetWindowRenderSize(window); - -var shaderCode = - """ - #define RootSignatureDef "RootFlags(0)" - - struct Vertex { float3 Position; float4 Color; }; - struct VertexOutput { float4 Position: SV_Position; float4 Color: TEXCOORD0; }; - - static Vertex triangleVertices[] = - { - { float3(-0.5, 0.5, 0.0), float4(1.0, 0.0, 0.0, 1.0) }, - { float3(0.5, 0.5, 0.0), float4(0.0, 1.0, 0.0, 1.0) }, - { float3(-0.5, -0.5, 0.0), float4(0.0, 0.0, 1.0, 1.0) } - }; - - [OutputTopology("triangle")] - [NumThreads(32, 1, 1)] - void MeshMain(in uint groupThreadId : SV_GroupThreadID, out vertices VertexOutput vertices[3], out indices uint3 indices[1]) - { - const uint meshVertexCount = 3; - - SetMeshOutputCounts(meshVertexCount, 1); - - if (groupThreadId < meshVertexCount) - { - vertices[groupThreadId].Position = float4(triangleVertices[groupThreadId].Position, 1.0); - vertices[groupThreadId].Color = triangleVertices[groupThreadId].Color; - } - - if (groupThreadId == 0) - { - indices[groupThreadId] = uint3(0, 1, 2); - } - } - - float4 PixelMain(const VertexOutput input) : SV_Target0 - { - return input.Color; - } - """; - -using var shaderCompiler = new ShaderCompiler(); - -var shaderInputs = new ShaderCompilerInput[] -{ - new() { ShaderCode = shaderCode, Stage = ShaderStage.MeshShader, EntryPoint = "MeshMain", ShaderLanguage = ShaderLanguage.Hlsl }, - new() { ShaderCode = shaderCode, Stage = ShaderStage.PixelShader, EntryPoint = "PixelMain", ShaderLanguage = ShaderLanguage.Hlsl } -}; - -var shaderCompilationResults = shaderCompiler.CompileShaders(shaderInputs, graphicsService.GetGraphicsDeviceInfo(graphicsDevice).GraphicsApi); -var shaderParts = new ShaderPart[shaderCompilationResults.Length]; - -for (var i = 0; i < shaderCompilationResults.Length; i++) -{ - shaderParts[i] = new() - { - Stage = shaderCompilationResults[i].Stage, - EntryPoint = shaderCompilationResults[i].EntryPoint, - Data = shaderCompilationResults[i].ShaderData, - MetaData = shaderCompilationResults[i].MetaData - }; -} - -using var shader = graphicsService.CreateShader(graphicsDevice, shaderParts); - -applicationService.RunApplication(application, (status) => -{ - if (status.IsClosing) return false; - - var renderSize = applicationService.GetWindowRenderSize(window); - - if (renderSize != currentRenderSize) - { - graphicsService.ResizeSwapChain(swapChain, renderSize.Width, renderSize.Height); - currentRenderSize = renderSize; - } - - graphicsService.WaitForSwapChainOnCpu(swapChain); - - using var commandList = graphicsService.CreateCommandList(commandQueue); - using var backbufferTexture = graphicsService.GetSwapChainBackBufferTexture(swapChain); - graphicsService.BeginRenderPass(commandList, new() { RenderTarget0 = new() { Texture = backbufferTexture, ClearColor = new Vector4(0.0f, 1.0f, 1.0f, 1.0f) } }); - graphicsService.SetShader(commandList, shader); - graphicsService.DispatchMesh(commandList, 1, 1, 1); - graphicsService.EndRenderPass(commandList); - graphicsService.CommitCommandList(commandList); - graphicsService.ExecuteCommandList(commandQueue, commandList); - - graphicsService.PresentSwapChain(swapChain); - return true; -}); \ No newline at end of file diff --git a/bindings/dotnet/samples/04-HelloInputs/HelloInputs.csproj b/bindings/dotnet/samples/04-HelloInputs/HelloInputs.csproj deleted file mode 100644 index 7d129c49..00000000 --- a/bindings/dotnet/samples/04-HelloInputs/HelloInputs.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - True - - - - $(AssemblyName).app/Contents/MacOS - - - - - - - - - - Triangle.metal - PreserveNewest - - - Triangle.hlsl - PreserveNewest - - - - diff --git a/bindings/dotnet/samples/04-HelloInputs/Program.cs b/bindings/dotnet/samples/04-HelloInputs/Program.cs deleted file mode 100644 index 45eb2ce9..00000000 --- a/bindings/dotnet/samples/04-HelloInputs/Program.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System.Diagnostics; -using System.Numerics; -using Elemental; -using Elemental.Graphics; -using Elemental.Inputs; -using Elemental.Tools; - -static void LogMessageHandler(LogMessageType messageType, LogMessageCategory category, string function, string message) -{ - var mainForegroundColor = messageType switch - { - LogMessageType.Warning => ConsoleColor.Yellow, - LogMessageType.Error => ConsoleColor.Red, - _ => ConsoleColor.Gray - }; - - Console.Write("["); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write($"{category}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.Write("]"); - - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($" {function}"); - - Console.ForegroundColor = mainForegroundColor; - Console.WriteLine($" {message}"); - Console.ForegroundColor = ConsoleColor.Gray; -} - -using var applicationService = new NativeApplicationService(new() { LogMessageHandler = LogMessageHandler }); -using var graphicsService = new GraphicsService(new() { GraphicsDiagnostics = GraphicsDiagnostics.Debug }); -using var inputsService = new InputsService(); -using var inputsQueue = inputsService.CreateInputsQueue(); - -using var application = applicationService.CreateApplication("Hello Inputs"); -using var window = applicationService.CreateWindow(application); - -var availableGraphicsDevices = graphicsService.GetAvailableGraphicsDevices(); -var selectedGraphicsDevice = availableGraphicsDevices[0]; - -// TODO: Do a separate sample to showcase the use of Native + Vulkan with 2 windows (if available) -foreach (var availableGraphicsDevice in availableGraphicsDevices) -{ - if (availableGraphicsDevice.GraphicsApi == GraphicsApi.Vulkan) - { - //selectedGraphicsDevice = availableGraphicsDevice; - } - - Console.WriteLine($"{availableGraphicsDevice}"); -} - -using var graphicsDevice = graphicsService.CreateGraphicsDevice(new() { DeviceId = selectedGraphicsDevice.DeviceId }); -using var commandQueue = graphicsService.CreateCommandQueue(graphicsDevice, CommandQueueType.Graphics); - -applicationService.SetWindowTitle(window, $"Hello Inputs! (GraphicsDevice: {graphicsService.GetGraphicsDeviceInfo(graphicsDevice)})"); - -using var swapChain = graphicsService.CreateSwapChain(window, commandQueue, new() { MaximumFrameLatency = 1 }); -var currentRenderSize = applicationService.GetWindowRenderSize(window); - -// HACK: This is needed when we run the program from the root directory -Environment.CurrentDirectory = Path.GetDirectoryName(Environment.ProcessPath)!; - -using var shaderCompiler = new ShaderCompiler(new() { LogMessageHandler = LogMessageHandler }); - -var shaderCode = File.ReadAllText("Triangle.hlsl"); - -// TODO: This is needed for Apple Metal because SPIRV-Cross doesn't yet support metal mesh shaders -var shaderCodeMetal = File.ReadAllText("Triangle.metal"); - -var meshShaderSourceType = selectedGraphicsDevice.GraphicsApi == GraphicsApi.Metal ? ShaderLanguage.Msl : ShaderLanguage.Hlsl; - -ShaderCompilerInput[] shaderInputs = -[ - new() { ShaderCode = selectedGraphicsDevice.GraphicsApi == GraphicsApi.Metal ? shaderCodeMetal : shaderCode, Stage = ShaderStage.MeshShader, EntryPoint = "MeshMain", ShaderLanguage = meshShaderSourceType }, - new() { ShaderCode = shaderCode, Stage = ShaderStage.PixelShader, EntryPoint = "PixelMain", ShaderLanguage = ShaderLanguage.Hlsl } -]; - -var shaderCompilationResults = shaderCompiler.CompileShaders(shaderInputs, selectedGraphicsDevice.GraphicsApi); - -// TODO: Avoid the array declaration -var shaderParts = new ShaderPart[shaderCompilationResults.Length]; - -for (var i = 0; i < shaderCompilationResults.Length; i++) -{ - var shaderCompilationResult = shaderCompilationResults[i]; - - foreach (var logEntry in shaderCompilationResult.LogEntries.Span) - { - Console.WriteLine($"{logEntry.Type}: {logEntry.Message}"); - } - - if (!shaderCompilationResult.IsSuccess) - { - return; - } - - shaderParts[i] = new() - { - Stage = shaderCompilationResult.Stage, - EntryPoint = shaderCompilationResult.EntryPoint, - Data = shaderCompilationResult.ShaderData, - MetaData = !(selectedGraphicsDevice.GraphicsApi == GraphicsApi.Metal && shaderCompilationResult.Stage == ShaderStage.MeshShader) ? shaderCompilationResult.MetaData : new ShaderMetaData[] - { - new ShaderMetaData { Type = ShaderMetaDataType.PushConstantsCount, Value = 1 }, - new ShaderMetaData { Type = ShaderMetaDataType.ThreadCountX, Value = 32 }, - new ShaderMetaData { Type = ShaderMetaDataType.ThreadCountY, Value = 1 }, - new ShaderMetaData { Type = ShaderMetaDataType.ThreadCountZ, Value = 1 } - } - }; -} - -using var shader = graphicsService.CreateShader(graphicsDevice, shaderParts); - -var frameTimer = new Stopwatch(); -var currentFrame = 0u; - -var shaderParameters = new ShaderParameters() -{ - AspectRatio = (float)currentRenderSize.Width / currentRenderSize.Height -}; - -applicationService.RunApplication(application, (status) => -{ - if (status.IsClosing) - { - return false; - } - - var renderSize = applicationService.GetWindowRenderSize(window); - - if (renderSize != currentRenderSize) - { - Console.WriteLine($"Resize SwapChain to: {renderSize}"); - graphicsService.ResizeSwapChain(swapChain, renderSize.Width, renderSize.Height); - currentRenderSize = renderSize; - - shaderParameters = shaderParameters with - { - AspectRatio = (float)currentRenderSize.Width / currentRenderSize.Height - }; - } - - graphicsService.WaitForSwapChainOnCpu(swapChain); - var inputState = inputsService.GetInputState(application); - - var inputsValues = inputsService.ReadInputsQueue(inputsQueue); - - if (inputsValues.Length > 0) - { - Console.WriteLine($"===== Inputs Queue {currentFrame} ====="); - - foreach (var inputsValue in inputsValues) - { - Console.WriteLine($"InputsValue: {inputsValue.Id}"); - } - - Console.WriteLine("===== End Inputs Queue ====="); - } - - shaderParameters = shaderParameters with - { - RotationX = shaderParameters.RotationX + -inputState.Gamepad.LeftStickY.Value * 5.0f * (float)frameTimer.Elapsed.TotalSeconds, - RotationY = shaderParameters.RotationY + inputState.Gamepad.LeftStickX.Value * 5.0f * (float)frameTimer.Elapsed.TotalSeconds, - RotationZ = shaderParameters.RotationZ + (inputState.Gamepad.RightShoulder.Value - inputState.Gamepad.LeftShoulder.Value) * 5.0f * (float)frameTimer.Elapsed.TotalSeconds, - TranslationX = shaderParameters.TranslationX + (inputState.Gamepad.RightStickX.Value + (inputState.Gamepad.DpadRight.Value - inputState.Gamepad.DpadLeft.Value)) * 5.0f * (float)frameTimer.Elapsed.TotalSeconds, - TranslationY = shaderParameters.TranslationY + (inputState.Gamepad.RightStickY.Value + (inputState.Gamepad.DpadUp.Value - inputState.Gamepad.DpadDown.Value)) * 5.0f * (float)frameTimer.Elapsed.TotalSeconds, - CurrentColorIndex = inputState.Gamepad.Button2.Value == 1 ? 1u : 0u - }; - - //Console.WriteLine($"Left: {inputState.Gamepad.DpadDown.Value}"); - - frameTimer.Restart(); - - using var commandList = graphicsService.CreateCommandList(commandQueue); - - using var backbufferTexture = graphicsService.GetSwapChainBackBufferTexture(swapChain); - graphicsService.BeginRenderPass(commandList, new() { RenderTarget0 = new() { Texture = backbufferTexture, ClearColor = new Vector4(0.0f, 0.0f, 0.0f, 1.0f) } }); - - graphicsService.SetShader(commandList, shader); - graphicsService.SetShaderConstants(commandList, 0, ref shaderParameters); - graphicsService.DispatchMesh(commandList, 1, 1, 1); - - graphicsService.EndRenderPass(commandList); - graphicsService.CommitCommandList(commandList); - graphicsService.ExecuteCommandList(commandQueue, commandList); - - graphicsService.PresentSwapChain(swapChain); - currentFrame++; - return true; -}); - -readonly record struct ShaderParameters -{ - public float RotationX { get; init; } - public float RotationY { get; init; } - public float RotationZ { get; init; } - public float TranslationX { get; init; } - public float TranslationY { get; init; } - public float TranslationZ { get; init; } - public float AspectRatio { get; init; } - public uint CurrentColorIndex { get; init; } -} diff --git a/bindings/dotnet/samples/04-HelloInputs/Triangle.hlsl b/bindings/dotnet/samples/04-HelloInputs/Triangle.hlsl deleted file mode 100644 index be5bc388..00000000 --- a/bindings/dotnet/samples/04-HelloInputs/Triangle.hlsl +++ /dev/null @@ -1,128 +0,0 @@ -#define RootSignatureDef "RootFlags(0), RootConstants(num32BitConstants=8, b0)" - -struct ShaderParameters -{ - float RotationX; - float RotationY; - float RotationZ; - float TranslationX; - float TranslationY; - float TranslationZ; - float AspectRatio; - uint CurrentColorIndex; -}; - -[[vk::push_constant]] -ShaderParameters parameters : register(b0); - -struct Vertex -{ - float3 Position; - float4 Color; -}; - -struct VertexOutput -{ - float4 Position: SV_Position; - float4 Color: TEXCOORD0; -}; - -static Vertex triangleVertices[] = -{ - { float3(-0.5, 0.5, 0.0), float4(1.0, 0.0, 0.0, 1.0) }, - { float3(0.5, 0.5, 0.0), float4(0.0, 1.0, 0.0, 1.0) }, - { float3(-0.5, -0.5, 0.0), float4(0.0, 0.0, 1.0, 1.0) } -}; - -static float4 Colors[] = -{ - float4(1.0, 0.0, 0.0, 1.0), float4(0.0, 1.0, 0.0, 1.0), float4(0.0, 0.0, 1.0, 1.0), - float4(1.0, 1.0, 0.0, 1.0), float4(0.0, 1.0, 1.0, 1.0), float4(1.0, 0.0, 1.0, 1.0) -}; - -float4x4 LookAtLHMatrix(float3 eyePosition, float3 targetPosition, float3 upDirection) -{ - float3 forwardDirection = normalize(targetPosition - eyePosition); - float3 rightDirection = normalize(cross(upDirection, forwardDirection)); - float3 upDirectionNew = cross(forwardDirection, rightDirection); - - float4 row1 = float4(rightDirection.x, upDirectionNew.x, forwardDirection.x, 0.0f); - float4 row2 = float4(rightDirection.y, upDirectionNew.y, forwardDirection.y, 0.0f); - float4 row3 = float4(rightDirection.z, upDirectionNew.z, forwardDirection.z, 0.0f); - float4 row4 = float4(-dot(rightDirection, eyePosition), -dot(upDirectionNew, eyePosition), -dot(forwardDirection, eyePosition), 1.0f); - - return float4x4(row1, row2, row3, row4); -} - -float4x4 PerspectiveProjectionMatrix(float fovY, float aspectRatio, float zNear) -{ - float height = 1.0 / tan(fovY * 0.5); - - float4 row1 = float4(height / aspectRatio, 0.0f, 0.0f, 0.0f); - float4 row2 = float4(0.0f, height, 0.0f, 0.0f); - float4 row3 = float4(0.0f, 0.0f, 0, 1.0f); - float4 row4 = float4(0.0f, 0.0f, zNear, 0.0f); - - return float4x4(row1, row2, row3, row4); -} - -float4x4 RotationMatrix(float rotationX, float rotationY, float rotationZ) -{ - float cosX = cos(rotationX); - float sinX = sin(rotationX); - float cosY = cos(rotationY); - float sinY = sin(rotationY); - float cosZ = cos(rotationZ); - float sinZ = sin(rotationZ); - - float4 row1 = float4(cosY * cosZ, sinX * sinY * cosZ - cosX * sinZ, cosX * sinY * cosZ + sinX * sinZ, 0.0f); - float4 row2 = float4(cosY * sinZ, sinX * sinY * sinZ + cosX * cosZ, cosX * sinY * sinZ - sinX * cosZ, 0.0f); - float4 row3 = float4(-sinY, sinX * cosY, cosX * cosY, 0.0f); - float4 row4 = float4(0.0f, 0.0f, 0.0f, 1.0f); - - return float4x4(row1, row2, row3, row4); -} - -float4x4 TranslationMatrix(float translationX, float translationY, float translationZ) -{ - float4 row1 = float4(1.0f, 0.0f, 0.0f, 0.0f); - float4 row2 = float4(0.0f, 1.0f, 0.0f, 0.0f); - float4 row3 = float4(0.0f, 0.0f, 1.0f, 0.0f); - float4 row4 = float4(translationX, translationY, translationZ, 1.0f); - - return float4x4(row1, row2, row3, row4); -} - -[OutputTopology("triangle")] -[NumThreads(32, 1, 1)] -void MeshMain(in uint groupThreadId : SV_GroupThreadID, out vertices VertexOutput vertices[3], out indices uint3 indices[1]) -{ - const uint meshVertexCount = 3; - - SetMeshOutputCounts(meshVertexCount, 1); - - if (groupThreadId < meshVertexCount) - { - float cameraZDistance = parameters.AspectRatio >= 0.75 ? -2.0 : -4.0; - - float4x4 worldMatrix = RotationMatrix(0.0, parameters.RotationY, 0.0); - float4x4 viewMatrix = LookAtLHMatrix(float3(0, 0, cameraZDistance), float3(0, 0, 0), float3(0, 1, 0)); - float4x4 projectionMatrix = PerspectiveProjectionMatrix(0.78, parameters.AspectRatio, 0.001); - - float4x4 worldViewProjectionMatrix = mul(worldMatrix, mul(viewMatrix, projectionMatrix)); - - - vertices[groupThreadId].Position = mul(float4(triangleVertices[groupThreadId].Position, 1), worldViewProjectionMatrix); - vertices[groupThreadId].Color = Colors[(parameters.CurrentColorIndex % 2) * 3 + groupThreadId]; - } - - if (groupThreadId == 0) - { - indices[groupThreadId] = uint3(0, 1, 2); - } -} - -float4 PixelMain(const VertexOutput input) : SV_Target0 -{ - return input.Color; -} diff --git a/bindings/dotnet/samples/04-HelloInputs/Triangle.metal b/bindings/dotnet/samples/04-HelloInputs/Triangle.metal deleted file mode 100644 index 29eabb1f..00000000 --- a/bindings/dotnet/samples/04-HelloInputs/Triangle.metal +++ /dev/null @@ -1,132 +0,0 @@ -using namespace metal; - -#include -#include - -struct VertexOutput -{ - float4 Position [[position]]; - float4 Color [[user(locn0)]]; -}; - -struct PrimitiveOutput -{ -}; - -struct ShaderParameters -{ - float RotationX; - float RotationY; - float RotationZ; - float TranslationX; - float TranslationY; - float TranslationZ; - float AspectRatio; - uint CurrentColorIndex; -}; - -constant float3 triangleVertices[] = -{ - float3(-0.5, 0.5, 0), - float3(0.5, 0.5, 0), - float3(-0.5, -0.5, 0) -}; - -constant float4 triangleColors[] = -{ - float4(1.0, 0.0, 0, 1), - float4(0.0, 1.0, 0, 1), - float4(0.0, 0.0, 1, 1) -}; - -constant uint8_t rectangleIndices[] = -{ - 0, 1, 2 -}; - -float4x4 RotationMatrix(float rotationX, float rotationY, float rotationZ) -{ - float cosX = cos(rotationX); - float sinX = sin(rotationX); - float cosY = cos(rotationY); - float sinY = sin(rotationY); - float cosZ = cos(rotationZ); - float sinZ = sin(rotationZ); - - float4 row1 = float4(cosY * cosZ, sinX * sinY * cosZ - cosX * sinZ, cosX * sinY * cosZ + sinX * sinZ, 0.0f); - float4 row2 = float4(cosY * sinZ, sinX * sinY * sinZ + cosX * cosZ, cosX * sinY * sinZ - sinX * cosZ, 0.0f); - float4 row3 = float4(-sinY, sinX * cosY, cosX * cosY, 0.0f); - float4 row4 = float4(0.0f, 0.0f, 0.0f, 1.0f); - - return float4x4(row1, row2, row3, row4); -} - -float4x4 LookAtLHMatrix(float3 eyePosition, float3 targetPosition, float3 upDirection) -{ - float3 forwardDirection = normalize(targetPosition - eyePosition); - float3 rightDirection = normalize(cross(upDirection, forwardDirection)); - float3 upDirectionNew = cross(forwardDirection, rightDirection); - - float4 row1 = float4(rightDirection.x, upDirectionNew.x, forwardDirection.x, 0.0f); - float4 row2 = float4(rightDirection.y, upDirectionNew.y, forwardDirection.y, 0.0f); - float4 row3 = float4(rightDirection.z, upDirectionNew.z, forwardDirection.z, 0.0f); - float4 row4 = float4(-dot(rightDirection, eyePosition), -dot(upDirectionNew, eyePosition), -dot(forwardDirection, eyePosition), 1.0f); - - return float4x4(row1, row2, row3, row4); -} - -float4x4 PerspectiveProjectionMatrix(float fovY, float aspectRatio, float zNear) -{ - float height = 1.0 / tan(fovY * 0.5); - - float4 row1 = float4(height / aspectRatio, 0.0f, 0.0f, 0.0f); - float4 row2 = float4(0.0f, height, 0.0f, 0.0f); - float4 row3 = float4(0.0f, 0.0f, 0, 1.0f); - float4 row4 = float4(0.0f, 0.0f, zNear, 0.0f); - - return float4x4(row1, row2, row3, row4); -} - -constant uint32_t meshVertexCount = 3; -constant uint32_t meshPrimitiveCount = 1; - -using AAPLTriangleMeshType = metal::mesh; - -/// The mesh stage function that generates a triangle mesh. -[[mesh, max_total_threads_per_threadgroup(126)]] -void MeshMain(AAPLTriangleMeshType output, - uint32_t groupThreadId [[thread_index_in_threadgroup]], - uint32_t groupId [[threadgroup_position_in_grid]], - device const ShaderParameters ¶meters [[buffer(0)]]) -{ - - - output.set_primitive_count(meshPrimitiveCount); - - if (groupThreadId < meshVertexCount) - { - VertexOutput vertexOutput = {}; - - float4x4 worldMatrix = RotationMatrix(parameters.RotationX, parameters.RotationY, parameters.RotationZ); - float4x4 viewMatrix = LookAtLHMatrix(float3(0, 0, -2), float3(0, 0, 0), float3(0, 1, 0)); - float4x4 projectionMatrix = PerspectiveProjectionMatrix(0.78, parameters.AspectRatio, 0.001); - - float4x4 worldViewProjectionMatrix = projectionMatrix * viewMatrix * worldMatrix; - - vertexOutput.Position = worldViewProjectionMatrix * float4(triangleVertices[groupThreadId], 1); - vertexOutput.Color = triangleColors[groupThreadId]; - - output.set_vertex(groupThreadId, vertexOutput); - } - - if (groupThreadId < meshPrimitiveCount) - { - PrimitiveOutput primitiveOutput; - output.set_primitive(groupThreadId, primitiveOutput); - - uint i = (3*groupThreadId); - output.set_index(i+0, rectangleIndices[i+0]); - output.set_index(i+1, rectangleIndices[i+1]); - output.set_index(i+2, rectangleIndices[i+2]); - } -} diff --git a/bindings/dotnet/samples/xx-MultiThread/HelloTriangle.csproj b/bindings/dotnet/samples/xx-MultiThread/HelloTriangle.csproj deleted file mode 100644 index 5c62edbe..00000000 --- a/bindings/dotnet/samples/xx-MultiThread/HelloTriangle.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - True - - - - $(AssemblyName).app/Contents/MacOS - - - - - - - diff --git a/bindings/dotnet/samples/xx-MultiThread/Program.cs b/bindings/dotnet/samples/xx-MultiThread/Program.cs deleted file mode 100644 index 4062b7c4..00000000 --- a/bindings/dotnet/samples/xx-MultiThread/Program.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Numerics; -using Elemental; -using Elemental.Graphics; - -var applicationService = new NativeApplicationService(); -using var application = applicationService.CreateApplication("Hello Window"); - -using var window = applicationService.CreateWindow(application, new NativeWindowOptions -{ - Title = "Hello Window!", - Width = 1280, - Height = 720, - WindowState = NativeWindowState.Normal -}); - -using var graphicsService = new GraphicsService(new GraphicsServiceOptions -{ - GraphicsDiagnostics = GraphicsDiagnostics.Debug -}); - -var availableGraphicsDevices = graphicsService.GetAvailableGraphicsDevices(); -var selectedGraphicsDevice = availableGraphicsDevices[0]; - -foreach (var availableGraphicsDevice in availableGraphicsDevices) -{ - if (availableGraphicsDevice.GraphicsApi == GraphicsApi.Vulkan) - { - selectedGraphicsDevice = availableGraphicsDevice; - } - - Console.WriteLine($"{availableGraphicsDevice}"); -} - -using var graphicsDevice = graphicsService.CreateGraphicsDevice(new GraphicsDeviceOptions -{ - //DeviceId = selectedGraphicsDevice.DeviceId -}); - -using var commandQueue = graphicsService.CreateCommandQueue(graphicsDevice, CommandQueueType.Graphics); -graphicsService.SetCommandQueueLabel(commandQueue, "Test Render Queue"); - -var graphicsDeviceInfos = graphicsService.GetGraphicsDeviceInfo(graphicsDevice); -applicationService.SetWindowTitle(window, $"Hello Triangle! (GraphicsDevice: {graphicsDeviceInfos})"); - -using var swapChain = graphicsService.CreateSwapChain(window, commandQueue, new SwapChainOptions -{ - Format = SwapChainFormat.Default -}); - -var currentRenderSize = applicationService.GetWindowRenderSize(window); - -var meshShaderData = new byte[10]; -var pixelShaderData = new byte[10]; - -/* -var shaderParts = stackalloc ShaderPart[2]; - -shaderParts[0] = new ShaderPart { Stage = ShaderStage.MeshShader, Data = meshShaderData }; -shaderParts[1] = new ShaderPart { Stage = ShaderStage.PixelShader, Data = pixelShaderData }; - -using var shader = graphicsService.CreateShader();*/ - -applicationService.RunApplication(application, (status) => -{ - if (status.IsClosing) - { - Console.WriteLine("Closing Application..."); - return false; - } - - var renderSize = applicationService.GetWindowRenderSize(window); - - if (renderSize != currentRenderSize) - { - Console.WriteLine($"Resize SwapChain to: {renderSize}"); - graphicsService.ResizeSwapChain(swapChain, renderSize.Width, renderSize.Height); - currentRenderSize = renderSize; - } - - graphicsService.WaitForSwapChainOnCpu(swapChain); - - var threadCount = 5; - var commandListCount = 5; - - var commandLists = new CommandList[threadCount * commandListCount]; - - using var backbufferTexture = graphicsService.GetSwapChainBackBufferTexture(swapChain); - - Parallel.For (0, threadCount, (i) => - //for (int i = 0; i < threadCount; i++) - { - for (var j = 0; j < commandListCount; j++) - { - var commandList = graphicsService.CreateCommandList(commandQueue); - graphicsService.SetCommandListLabel(commandList, $"Triangle CommandList {j} (Thread :{i})"); - - graphicsService.BeginRenderPass(commandList, new RenderPassDescriptor - { - RenderTarget0 = new RenderPassRenderTarget - { - Texture = backbufferTexture, - ClearColor = new Vector4(0.8f, 0.5f, 0.0f, 1.0f) - } - }); - - // TODO: Add a counter in the mesh dispatch push constant to test the frame latency - - graphicsService.EndRenderPass(commandList); - - graphicsService.BeginRenderPass(commandList, new RenderPassDescriptor - { - RenderTarget0 = new RenderPassRenderTarget - { - Texture = backbufferTexture, - ClearColor = new Vector4(0.0f, 1.0f, i == (threadCount - 1) && j == (commandListCount - 1) ? 1.0f : 0.0f, 1.0f) - } - }); - - graphicsService.EndRenderPass(commandList); - - graphicsService.CommitCommandList(commandList); - - commandLists[i * commandListCount + j] = commandList; - } - //} - }); - - graphicsService.ExecuteCommandLists(commandQueue, commandLists, Array.Empty()); - - for (var i = 0; i < commandLists.Length; i++) - { - commandLists[i].Dispose(); - } - - graphicsService.PresentSwapChain(swapChain); - - return true; -}); \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/Elemental.Tools.csproj b/bindings/dotnet/src/Elemental.Tools/Elemental.Tools.csproj deleted file mode 100644 index 7076d302..00000000 --- a/bindings/dotnet/src/Elemental.Tools/Elemental.Tools.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - true - true - true - true - - - - - - - - - - - - - - - Elemental.Tools.Native.dll - PreserveNewest - - - Elemental.Tools.Native.pdb - PreserveNewest - - - dxcompiler.dll - PreserveNewest - - - dxil.dll - PreserveNewest - - - - - - Elemental.Tools.Native.dylib - PreserveNewest - - - libdxcompiler.dylib - PreserveNewest - - - - diff --git a/bindings/dotnet/src/Elemental.Tools/Globals.cs b/bindings/dotnet/src/Elemental.Tools/Globals.cs deleted file mode 100644 index 928ca6cb..00000000 --- a/bindings/dotnet/src/Elemental.Tools/Globals.cs +++ /dev/null @@ -1,6 +0,0 @@ -global using System.Runtime.InteropServices; -global using System.Runtime.InteropServices.Marshalling; - -global using Elemental.Graphics; - -[assembly:System.Runtime.CompilerServices.DisableRuntimeMarshalling] \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/IShaderCompiler.cs b/bindings/dotnet/src/Elemental.Tools/IShaderCompiler.cs deleted file mode 100644 index 7c4a23f7..00000000 --- a/bindings/dotnet/src/Elemental.Tools/IShaderCompiler.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Represents a shader compiler that can be used to compile shaders for a specific graphics API. -/// -[PlatformService(InitMethod = nameof(PlatformServiceInterop.Native_InitShaderCompiler), DisposeMethod = nameof(PlatformServiceInterop.Native_FreeShaderCompiler))] -public interface IShaderCompiler -{ - /// - /// Determines whether the shader compiler can compile shaders for the specified shader language and graphics API. - /// - /// The shader language to check. - /// The graphics API to check. - /// true if the shader compiler can compile shaders for the specified shader language and graphics API; otherwise, false. - bool CanCompileShader(ShaderLanguage shaderLanguage, GraphicsApi graphicsApi); - - /// - /// Compiles the specified shader code into a shader object. - /// - /// The source code of the shader to compile. - /// The stage of the shader to compile. - /// The name of the entry point function for the shader. - /// The language of the shader to compile. - /// The graphics API to compile the shader for. - /// The compilation options to use for the shader. - /// A object representing the compiled shader. - ShaderCompilerResult CompileShader(string shaderCode, ShaderStage shaderStage, string entryPoint, ShaderLanguage shaderLanguage, GraphicsApi graphicsApi, in ShaderCompilationOptions options = default); - - /// - /// Compiles an array of shader code into shader objects. - /// - /// An array of shader compiler inputs. - /// The graphics API to compile the shaders for. - /// The compilation options to use for the shaders. - /// An array of objects representing the compiled shaders. - ReadOnlySpan CompileShaders(ReadOnlySpan inputs, GraphicsApi graphicsApi, in ShaderCompilationOptions options = default); -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/PlatformServiceInterop.cs b/bindings/dotnet/src/Elemental.Tools/PlatformServiceInterop.cs deleted file mode 100644 index 9bd8acb9..00000000 --- a/bindings/dotnet/src/Elemental.Tools/PlatformServiceInterop.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Elemental.Tools; -[assembly:DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - -namespace Elemental; - -internal static partial class PlatformServiceInterop -{ - [LibraryImport("Elemental.Tools.Native")] - internal static partial void Native_FreeNativePointer(nint pointer); - - [LibraryImport("Elemental.Tools.Native")] - internal static partial void Native_InitShaderCompiler(in ShaderCompilerOptions options = default); - - [LibraryImport("Elemental.Tools.Native")] - internal static partial void Native_FreeShaderCompiler(); - - [LibraryImport("Elemental.Tools.Native")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool Native_CanCompileShader(ShaderLanguage shaderLanguage, GraphicsApi graphicsApi); - - [LibraryImport("Elemental.Tools.Native", StringMarshalling = StringMarshalling.Utf8)] - internal static partial ShaderCompilerResult Native_CompileShader(string shaderCode, ShaderStage shaderStage, string entryPoint, ShaderLanguage shaderLanguage, GraphicsApi graphicsApi, in ShaderCompilationOptions options); - - [LibraryImport("Elemental.Tools.Native")] - internal unsafe static partial void Native_CompileShaders(ReadOnlySpan inputs, int inputCount, GraphicsApi graphicsApi, in ShaderCompilationOptions options, ShaderCompilerResultMarshaller.ShaderCompilerResultUnmanaged* results, out int resultCount); -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderCompilationOptions.cs b/bindings/dotnet/src/Elemental.Tools/ShaderCompilationOptions.cs deleted file mode 100644 index 7b3014e8..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderCompilationOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Specifies options for compiling shaders. -/// -public readonly record struct ShaderCompilationOptions -{ - /// - /// Initializes a new instance of the struct with the default options. - /// - public ShaderCompilationOptions() - { - DebugMode = false; - } - - /// - /// Gets or sets a value indicating whether the shader should be compiled in debug mode. - /// - public bool DebugMode { get; init; } - - // TODO: Add specialization constants -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerInput.cs b/bindings/dotnet/src/Elemental.Tools/ShaderCompilerInput.cs deleted file mode 100644 index 0bb906e8..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerInput.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Represents a shader compiler input that includes shader code, shader stage, and entry point. -/// -[NativeMarshalling(typeof(ShaderCompilerInputMarshaller))] -public readonly record struct ShaderCompilerInput -{ - /// - /// Gets or sets the source code of the shader to compile. - /// - public required string ShaderCode { get; init; } - - /// - /// Gets or sets the source code of the shader to compile. - /// - public required ShaderStage Stage { get; init; } - - /// - /// Gets or sets the name of the entry point function for the shader. - /// - public required string EntryPoint { get; init; } - - /// - /// Gets or sets the language of the shaders to compile. - /// - public required ShaderLanguage ShaderLanguage { get; init; } -} - -[CustomMarshaller(typeof(ShaderCompilerInput), MarshalMode.Default, typeof(ShaderCompilerInputMarshaller))] -internal static unsafe class ShaderCompilerInputMarshaller -{ - internal readonly struct ShaderCompilerInputUnmanaged - { - public byte* ShaderCodePointer { get; init; } - public ShaderStage Stage { get; init; } - public byte* EntryPoint { get; init; } - public ShaderLanguage ShaderLanguage { get; init; } - } - - public static ShaderCompilerInputUnmanaged ConvertToUnmanaged(ShaderCompilerInput managed) - { - return new ShaderCompilerInputUnmanaged() - { - ShaderCodePointer = Utf8StringMarshaller.ConvertToUnmanaged(managed.ShaderCode), - Stage = managed.Stage, - EntryPoint = Utf8StringMarshaller.ConvertToUnmanaged(managed.EntryPoint), - ShaderLanguage = managed.ShaderLanguage - }; - } - - public static ShaderCompilerInput ConvertToManaged(ShaderCompilerInputUnmanaged unmanaged) - { - throw new NotImplementedException(); - } - - public static void Free(ShaderCompilerInputUnmanaged unmanaged) - { - Utf8StringMarshaller.Free(unmanaged.ShaderCodePointer); - } -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerLogEntry.cs b/bindings/dotnet/src/Elemental.Tools/ShaderCompilerLogEntry.cs deleted file mode 100644 index 45921574..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerLogEntry.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Structure that represents a shader compiler log entry. -/// -public readonly record struct ShaderCompilerLogEntry -{ - /// - /// Gets the type of the log entry. - /// - /// Type of the log entry. - public required ShaderCompilerLogEntryType Type { get; init; } - - /// - /// Gets the message of the log entry. - /// - /// Message of the log entry. - public required string Message { get; init; } -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerLogEntryType.cs b/bindings/dotnet/src/Elemental.Tools/ShaderCompilerLogEntryType.cs deleted file mode 100644 index 5c8d0513..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerLogEntryType.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Enumerates the type of . -/// -public enum ShaderCompilerLogEntryType -{ - /// - /// Message log type. - /// - Message = 0, - - /// - /// Warning log type. - /// - Warning = 1, - - /// - /// Error log type. - /// - Error = 2 -} diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerOptions.cs b/bindings/dotnet/src/Elemental.Tools/ShaderCompilerOptions.cs deleted file mode 100644 index 120ac676..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerOptions.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Describes a to create. -/// -[NativeMarshalling(typeof(ShaderCompilerOptionsMarshaller))] -public readonly record struct ShaderCompilerOptions -{ - /// - /// Default constructor. - /// - public ShaderCompilerOptions() - { - } - - /// - /// Gets or sets an handle that will receive debug messages. - /// - /// Log message handler. - public LogMessageHandler? LogMessageHandler { get; init; } -} - -[CustomMarshaller(typeof(ShaderCompilerOptions), MarshalMode.ManagedToUnmanagedIn, typeof(ShaderCompilerOptionsMarshaller))] -internal static unsafe class ShaderCompilerOptionsMarshaller -{ - internal struct ShaderCompilerOptionsUnmanaged - { - public nint LogMessageHandler; - } - - public static ShaderCompilerOptionsUnmanaged ConvertToUnmanaged(ShaderCompilerOptions managed) - { - return new ShaderCompilerOptionsUnmanaged - { - LogMessageHandler = managed.LogMessageHandler != null ? LogMessageHandlerMarshaller.ConvertToUnmanaged(managed.LogMessageHandler) : nint.Zero - }; - } - - public static void Free(ShaderCompilerOptionsUnmanaged _) - { - } -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerResult.cs b/bindings/dotnet/src/Elemental.Tools/ShaderCompilerResult.cs deleted file mode 100644 index 096e8036..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderCompilerResult.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Structure that represents a shader compiler result. -/// -[NativeMarshalling(typeof(ShaderCompilerResultMarshaller))] -public readonly record struct ShaderCompilerResult -{ - /// - /// Gets a value indicating if the compilation was successful. - /// - /// True if the compilation was successful; Otherwise, false. - public required bool IsSuccess { get; init; } - - /// - /// Gets the stage to which the shader compiler was compiled. - /// - /// Shader stage. - public required ShaderStage Stage { get; init; } - - /// - /// Gets the entry point that was used to compile the shader. - /// - /// Entry point name. - public required string EntryPoint { get; init; } - - /// - /// Gets the compiled shader data. - /// - /// Compiled shader data. - public ReadOnlyMemory ShaderData { get; init; } - - /// - /// Gets the log entries associated with the compilation. - /// - /// Log entries of the compilation. - public ReadOnlyMemory LogEntries { get; init; } - - /// - /// Gets the meta data values associated with the compilation. - /// - /// Meta data values. - public ReadOnlyMemory MetaData { get; init; } -} - -[CustomMarshaller(typeof(ShaderCompilerResult), MarshalMode.Default, typeof(ShaderCompilerResultMarshaller))] -internal static unsafe class ShaderCompilerResultMarshaller -{ - internal readonly struct ShaderCompilerLogEntryUnmanaged - { - public ShaderCompilerLogEntryType Type { get; init; } - public byte* Message { get; init; } - } - - internal readonly struct ShaderCompilerResultUnmanaged - { - public bool IsSuccess { get; init; } - public ShaderStage Stage { get; init; } - public byte* EntryPoint { get; init; } - public void* ShaderDataPointer { get; init; } - public uint ShaderDataCount { get; init; } - public ShaderCompilerLogEntryUnmanaged* LogEntryPointer { get; init; } - public uint LogEntryCount { get; init; } - public ShaderMetaData* MetaDataPointer { get; init; } - public uint MetaDataCount { get; init; } - } - - public static ShaderCompilerResultUnmanaged ConvertToUnmanaged(ShaderCompilerResult _) - { - throw new NotImplementedException(); - } - - public static ShaderCompilerResult ConvertToManaged(ShaderCompilerResultUnmanaged unmanaged) - { - // TODO: Avoid the string conversions - - var shaderData = unmanaged.ShaderDataPointer != null ? new byte[unmanaged.ShaderDataCount] : Array.Empty(); - var sourceShaderDataSpan = new Span(unmanaged.ShaderDataPointer, (int)unmanaged.ShaderDataCount); - sourceShaderDataSpan.CopyTo(shaderData); - - var logEntries = unmanaged.LogEntryPointer != null ? new ShaderCompilerLogEntry[unmanaged.LogEntryCount] : Array.Empty(); - var sourceLogEntriesSpan = new Span(unmanaged.LogEntryPointer, (int)unmanaged.LogEntryCount); - - var shaderMetaData = unmanaged.MetaDataPointer != null ? new ShaderMetaData[unmanaged.MetaDataCount] : Array.Empty(); - var sourceMetaDataSpan = new Span(unmanaged.MetaDataPointer, (int)unmanaged.MetaDataCount); - sourceMetaDataSpan.CopyTo(shaderMetaData); - - for (var i = 0; i < unmanaged.LogEntryCount; i++) - { - var sourceLogEntry = sourceLogEntriesSpan[i]; - - logEntries[i] = new ShaderCompilerLogEntry - { - Type = sourceLogEntry.Type, - Message = Utf8StringMarshaller.ConvertToManaged(sourceLogEntry.Message) ?? string.Empty - }; - } - - return new ShaderCompilerResult - { - IsSuccess = unmanaged.IsSuccess, - Stage = unmanaged.Stage, - EntryPoint = Utf8StringMarshaller.ConvertToManaged(unmanaged.EntryPoint) ?? string.Empty, - ShaderData = shaderData, - LogEntries = logEntries, - MetaData = shaderMetaData - }; - } - - public static void Free(ShaderCompilerResultUnmanaged _) - { - } -} diff --git a/bindings/dotnet/src/Elemental.Tools/ShaderLanguage.cs b/bindings/dotnet/src/Elemental.Tools/ShaderLanguage.cs deleted file mode 100644 index a5030fd7..00000000 --- a/bindings/dotnet/src/Elemental.Tools/ShaderLanguage.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Elemental.Tools; - -/// -/// Enumerates the supported shader languages of the shader compiler. -/// -public enum ShaderLanguage -{ - /// - /// Unknown Shader Language. - /// - Unknown = 0, - - /// - /// DirectX Shader Language. - /// - Hlsl = 1, - - /// - /// Metal Shader Language. - /// - Msl = 2, - - /// - /// DirectX shader bytecode. - /// - Dxil = 3, - - /// - /// Vulkan shader bytecode. - /// - Spirv = 4, - - /// - /// Metal shader bytecode. - /// - MetalIR = 5 -} \ No newline at end of file diff --git a/bindings/dotnet/src/Elemental/ApplicationHandler.cs b/bindings/dotnet/src/Elemental/ApplicationHandler.cs deleted file mode 100644 index d010b7c5..00000000 --- a/bindings/dotnet/src/Elemental/ApplicationHandler.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Elemental; - -/// -/// Defines a function pointer type for handling application events. -/// -public delegate void ApplicationHandler(ref T payload) where T : unmanaged; diff --git a/bindings/dotnet/src/Elemental/ApplicationService.cs b/bindings/dotnet/src/Elemental/ApplicationService.cs deleted file mode 100644 index bff7657a..00000000 --- a/bindings/dotnet/src/Elemental/ApplicationService.cs +++ /dev/null @@ -1,147 +0,0 @@ -namespace Elemental; - -/// -public class ApplicationService : IApplicationService -{ - /// - /// Configures a custom log handler for processing log messages generated by the application. - /// - /// The function to call when a log message is generated. - public void ConfigureLogHandler(LogHandler logHandler) - { - ApplicationServiceInterop.ConfigureLogHandler(logHandler); - } - - /// - /// Retrieves system-related information, such as platform and application path. - /// - /// A structure containing system information. - public unsafe SystemInfo GetSystemInfo() - { - var resultUnsafe = ApplicationServiceInterop.GetSystemInfo(); - - var result = new SystemInfo(); - result.Platform = resultUnsafe.Platform; - - // TODO: New code to generate - result.ApplicationPath = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(resultUnsafe.ApplicationPath); - result.SupportMultiWindows = resultUnsafe.SupportMultiWindows; - - return result; - } - - /// - /// Starts the execution of an application with specified parameters. - /// - /// Configuration and handlers for the application lifecycle. - /// Status code indicating success or error. - public unsafe int RunApplication(in RunApplicationParameters parameters) where T : unmanaged - { - // TODO: New code to generate - fixed (byte* ApplicationNamePinned = parameters.ApplicationName) - { - var parametersUnsafe = new RunApplicationParametersUnsafe(); - parametersUnsafe.ApplicationName = ApplicationNamePinned; - - InitApplicationHandler._interceptorEntry = parameters.InitHandler; - parametersUnsafe.InitHandler = (nint)(delegate* )&InitApplicationHandler.Interceptor; - - FreeApplicationHandler._interceptorEntry = parameters.FreeHandler; - parametersUnsafe.FreeHandler = (nint)(delegate* )&FreeApplicationHandler.Interceptor; - - var test = stackalloc T[1]; - *test = parameters.Payload; - parametersUnsafe.Payload = (nint)test; - - return ApplicationServiceInterop.RunApplication(parametersUnsafe); - } - } - - /// - /// Exits the application, performing necessary cleanup. - /// - /// Exit code of the application. - public void ExitApplication(int exitCode) - { - ApplicationServiceInterop.ExitApplication(exitCode); - } - - /// - /// Creates a window with the specified options or default settings if none are provided. - /// - /// Configuration options for the window; NULL for defaults. - /// A handle to the newly created window. - public unsafe Window CreateWindow(in WindowOptions options = default) - { - fixed (byte* TitlePinned = options.Title) - { - var optionsUnsafe = new WindowOptionsUnsafe(); - optionsUnsafe.Title = TitlePinned; - optionsUnsafe.Width = options.Width; - optionsUnsafe.Height = options.Height; - optionsUnsafe.WindowState = options.WindowState; - optionsUnsafe.IsCursorHidden = options.IsCursorHidden; - - return ApplicationServiceInterop.CreateWindow(optionsUnsafe); - } - } - - /// - /// Releases resources associated with a window. - /// - /// Handle to the window to be freed. - public void FreeWindow(Window window) - { - ApplicationServiceInterop.FreeWindow(window); - } - - /// - /// Gets the render size of a window, accounting for DPI scaling. - /// - /// The window instance. - /// Render size of the window. - public WindowSize GetWindowRenderSize(Window window) - { - return ApplicationServiceInterop.GetWindowRenderSize(window); - } - - /// - /// Sets a window's title. - /// - /// The window instance. - /// New title for the window. - public void SetWindowTitle(Window window, ReadOnlySpan title) - { - ApplicationServiceInterop.SetWindowTitle(window, title); - } - - /// - /// Changes the state of a window (e.g., minimize, maximize). - /// - /// The window instance. - /// New state for the window. - public void SetWindowState(Window window, WindowState windowState) - { - ApplicationServiceInterop.SetWindowState(window, windowState); - } - - /// - /// TODO: Comments -///TODO: Make sure the coordinates are consistent accross all platforms - /// - public void ShowWindowCursor(Window window) - { - ApplicationServiceInterop.ShowWindowCursor(window); - } - - public void HideWindowCursor(Window window) - { - ApplicationServiceInterop.HideWindowCursor(window); - } - - public WindowCursorPosition GetWindowCursorPosition(Window window) - { - return ApplicationServiceInterop.GetWindowCursorPosition(window); - } - -} diff --git a/bindings/dotnet/src/Elemental/ApplicationServiceInterop.cs b/bindings/dotnet/src/Elemental/ApplicationServiceInterop.cs deleted file mode 100644 index 16755d7c..00000000 --- a/bindings/dotnet/src/Elemental/ApplicationServiceInterop.cs +++ /dev/null @@ -1,55 +0,0 @@ -[assembly:DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - -namespace Elemental; - -internal static partial class ApplicationServiceInterop -{ - [LibraryImport("Elemental.Native", EntryPoint = "ElemConfigureLogHandler")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void ConfigureLogHandler(LogHandler logHandler); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemGetSystemInfo")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial SystemInfoUnsafe GetSystemInfo(); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemRunApplication")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial int RunApplication(in RunApplicationParametersUnsafe parameters); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemExitApplication")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void ExitApplication(int exitCode); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemCreateWindow")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial Window CreateWindow(in WindowOptionsUnsafe options); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemFreeWindow")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void FreeWindow(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemGetWindowRenderSize")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial WindowSize GetWindowRenderSize(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemSetWindowTitle")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void SetWindowTitle(Window window, ReadOnlySpan title); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemSetWindowState")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void SetWindowState(Window window, WindowState windowState); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemShowWindowCursor")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void ShowWindowCursor(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemHideWindowCursor")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void HideWindowCursor(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemGetWindowCursorPosition")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial WindowCursorPosition GetWindowCursorPosition(Window window); - -} diff --git a/bindings/dotnet/src/Elemental/ConsoleLogHandler.cs b/bindings/dotnet/src/Elemental/ConsoleLogHandler.cs deleted file mode 100644 index 3f0ee50a..00000000 --- a/bindings/dotnet/src/Elemental/ConsoleLogHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Elemental; - -/// -/// Class that contains a default log handler implementation. -/// -public static class DefaultLogHandlers -{ - /// - /// Default console log handler. - /// - /// Message type - /// Category - /// Function - /// Message - public static void ConsoleLogHandler(LogMessageType messageType, LogMessageCategory category, ReadOnlySpan function, ReadOnlySpan message) - { - var mainForegroundColor = messageType switch - { - LogMessageType.Warning => ConsoleColor.Yellow, - LogMessageType.Error => ConsoleColor.Red, - _ => ConsoleColor.Gray - }; - - Console.Write("["); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write($"{category}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.Write("]"); - - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($" {System.Text.UTF8Encoding.UTF8.GetString(function)}"); - - Console.ForegroundColor = mainForegroundColor; - Console.WriteLine($" {messageType}: {System.Text.UTF8Encoding.UTF8.GetString(message)}"); - Console.ForegroundColor = ConsoleColor.Gray; - } -} diff --git a/bindings/dotnet/src/Elemental/Elemental.csproj b/bindings/dotnet/src/Elemental/Elemental.csproj deleted file mode 100644 index bdc1c8e6..00000000 --- a/bindings/dotnet/src/Elemental/Elemental.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - true - true - true - true - - - - - - - - - Elemental.Native.dll - PreserveNewest - - - Elemental.Native.pdb - PreserveNewest - - - D3D12Core.dll - PreserveNewest - - - D3D12SDKLayers.dll - PreserveNewest - - - - - - - - - - - - diff --git a/bindings/dotnet/src/Elemental/Globals.cs b/bindings/dotnet/src/Elemental/Globals.cs deleted file mode 100644 index 01008824..00000000 --- a/bindings/dotnet/src/Elemental/Globals.cs +++ /dev/null @@ -1,7 +0,0 @@ -global using System.Numerics; -global using System.Runtime.InteropServices; -global using System.Runtime.InteropServices.Marshalling; -global using System.Runtime.CompilerServices; -global using System.Text; - -[assembly:System.Runtime.CompilerServices.DisableRuntimeMarshalling] diff --git a/bindings/dotnet/src/Elemental/IApplicationService.cs b/bindings/dotnet/src/Elemental/IApplicationService.cs deleted file mode 100644 index d55305ef..00000000 --- a/bindings/dotnet/src/Elemental/IApplicationService.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace Elemental; - -/// -/// Defines an interface for Application services. -/// -public interface IApplicationService -{ - /// - /// Configures a custom log handler for processing log messages generated by the application. - /// - /// The function to call when a log message is generated. - void ConfigureLogHandler(LogHandler logHandler); - - /// - /// Retrieves system-related information, such as platform and application path. - /// - /// A structure containing system information. - SystemInfo GetSystemInfo(); - - /// - /// Starts the execution of an application with specified parameters. - /// - /// Configuration and handlers for the application lifecycle. - /// Status code indicating success or error. - int RunApplication(in RunApplicationParameters parameters) where T : unmanaged; - - /// - /// Exits the application, performing necessary cleanup. - /// - /// Exit code of the application. - void ExitApplication(int exitCode); - - /// - /// Creates a window with the specified options or default settings if none are provided. - /// - /// Configuration options for the window; NULL for defaults. - /// A handle to the newly created window. - Window CreateWindow(in WindowOptions options = default); - - /// - /// Releases resources associated with a window. - /// - /// Handle to the window to be freed. - void FreeWindow(Window window); - - /// - /// Gets the render size of a window, accounting for DPI scaling. - /// - /// The window instance. - /// Render size of the window. - WindowSize GetWindowRenderSize(Window window); - - /// - /// Sets a window's title. - /// - /// The window instance. - /// New title for the window. - void SetWindowTitle(Window window, ReadOnlySpan title); - - /// - /// Changes the state of a window (e.g., minimize, maximize). - /// - /// The window instance. - /// New state for the window. - void SetWindowState(Window window, WindowState windowState); - - /// - /// TODO: Comments -///TODO: Make sure the coordinates are consistent accross all platforms - /// - void ShowWindowCursor(Window window); - - void HideWindowCursor(Window window); - - WindowCursorPosition GetWindowCursorPosition(Window window); -} diff --git a/bindings/dotnet/src/Elemental/LogHandler.cs b/bindings/dotnet/src/Elemental/LogHandler.cs deleted file mode 100644 index 176a2329..00000000 --- a/bindings/dotnet/src/Elemental/LogHandler.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace Elemental; - -/// -/// Defines a function pointer type for log handling. -/// -/// The type of the log message. -/// The category of the log message. -/// The function where the log was triggered. -/// The log message. -[NativeMarshalling(typeof(LogHandlerMarshaller))] -public delegate void LogHandler(LogMessageType messageType, LogMessageCategory category, ReadOnlySpan function, ReadOnlySpan message); - -[CustomMarshaller(typeof(LogHandler), MarshalMode.ManagedToUnmanagedIn, typeof(LogHandlerMarshaller))] -internal static unsafe class LogHandlerMarshaller -{ - internal sealed unsafe record InterceptorEntry - { - public required LogHandler Callback { get; init; } - //public required GCHandle Handle { get; init; } - } - - private static InterceptorEntry? _interceptorEntry; - - private static unsafe void Interceptor(LogMessageType messageType, LogMessageCategory category, byte* function, byte* message) - { - if (_interceptorEntry == null || function == null || message == null) - { - return; - } - - var functionCounter = 0; - var functionPointer = (byte*)function; - - while (functionPointer[functionCounter] != 0) - { - functionCounter++; - } - - functionCounter++; - - var messageCounter = 0; - var messagePointer = (byte*)message; - - while (messagePointer[messageCounter] != 0) - { - messageCounter++; - } - - messageCounter++; - - _interceptorEntry.Callback(messageType, category, new ReadOnlySpan(function, functionCounter), new ReadOnlySpan(message, messageCounter)); - } - - public static nint ConvertToUnmanaged(LogHandler managed) - { - // TODO: Unallocate handle - //var interceptorDelegate = Interceptor; - //var handle = GCHandle.Alloc(interceptorDelegate); - //var unmanaged = Marshal.GetFunctionPointerForDelegate(interceptorDelegate); - - // TODO: Try to avoid all of that - delegate* unmanaged = &Interceptor; - - _interceptorEntry = new InterceptorEntry { Callback = managed }; - return (nint)unmanaged; - } - - public static void Free(nint _) - { - } -} diff --git a/bindings/dotnet/src/Elemental/LogMessageCategory.cs b/bindings/dotnet/src/Elemental/LogMessageCategory.cs deleted file mode 100644 index e49ad889..00000000 --- a/bindings/dotnet/src/Elemental/LogMessageCategory.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Elemental; - -/// -/// Categorizes log messages by their related system components. -/// -public enum LogMessageCategory -{ - /// - /// Assertions and checks. - /// - Assert = 0, - - /// - /// Memory allocation and management. - /// - Memory = 1, - - /// - /// General application behavior. - /// - Application = 2, - - /// - /// Graphics system-related messages. - /// - Graphics = 3, - - /// - /// Input system-related messages. - /// - Inputs = 4 -} diff --git a/bindings/dotnet/src/Elemental/LogMessageType.cs b/bindings/dotnet/src/Elemental/LogMessageType.cs deleted file mode 100644 index a8eb2eae..00000000 --- a/bindings/dotnet/src/Elemental/LogMessageType.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Elemental; - -/// -/// Defines the types of log messages that can be generated. -/// -public enum LogMessageType -{ - /// - /// Debugging message. - /// - Debug = 0, - - /// - /// Warning message. - /// - Warning = 1, - - /// - /// Error message. - /// - Error = 2 -} diff --git a/bindings/dotnet/src/Elemental/Platform.cs b/bindings/dotnet/src/Elemental/Platform.cs deleted file mode 100644 index d578e044..00000000 --- a/bindings/dotnet/src/Elemental/Platform.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Elemental; - -/// -/// Lists the platforms supported by the library. -/// -public enum Platform -{ - /// - /// Microsoft Windows platform. - /// - Windows = 0, - - /// - /// Apple macOS platform. - /// - MacOS = 1, - - /// - /// Apple iOS platform. - /// - iOS = 2, - - /// - /// Linux platform. - /// - Linux = 3 -} diff --git a/bindings/dotnet/src/Elemental/RunApplicationParameters.cs b/bindings/dotnet/src/Elemental/RunApplicationParameters.cs deleted file mode 100644 index c7179408..00000000 --- a/bindings/dotnet/src/Elemental/RunApplicationParameters.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace Elemental; - -internal class InitApplicationHandler where T : unmanaged -{ - public static ApplicationHandler? _interceptorEntry; - - public static void Interceptor(ref T payload) - { - if (_interceptorEntry == null) - { - return; - } - - _interceptorEntry(ref payload); - } -} - -internal static class FreeApplicationHandler where T : unmanaged -{ - public static ApplicationHandler? _interceptorEntry; - - public static void Interceptor(ref T payload) - { - if (_interceptorEntry == null) - { - return; - } - - _interceptorEntry(ref payload); - } -} - -/// -/// Holds parameters for running an application, including initialization and cleanup routines. -/// -public ref struct RunApplicationParameters where T: unmanaged -{ - /// - /// Name of the application. - /// - public ReadOnlySpan ApplicationName { get; set; } - - /// - /// Function called at application startup. - /// - public ApplicationHandler InitHandler { get; set; } - - /// - /// Function called at application termination. - /// - public ApplicationHandler FreeHandler { get; set; } - - /// - /// Custom user data passed to handler functions. - /// - public T Payload { get; set; } -} - -internal unsafe struct RunApplicationParametersUnsafe -{ - public byte* ApplicationName { get; set; } - - public nint InitHandler { get; set; } - - public nint FreeHandler { get; set; } - - public nint Payload { get; set; } -} - diff --git a/bindings/dotnet/src/Elemental/SpanUnsafe.cs b/bindings/dotnet/src/Elemental/SpanUnsafe.cs deleted file mode 100644 index e966f8ad..00000000 --- a/bindings/dotnet/src/Elemental/SpanUnsafe.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elemental; - -internal unsafe record struct SpanUnsafe where T : struct -{ - internal nuint Items { get; set; } - internal int Length { get; set; } -} diff --git a/bindings/dotnet/src/Elemental/SystemInfo.cs b/bindings/dotnet/src/Elemental/SystemInfo.cs deleted file mode 100644 index f3ee7822..00000000 --- a/bindings/dotnet/src/Elemental/SystemInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Elemental; - -/// -/// Contains information about the system, useful for tailoring application behavior. -/// -public ref struct SystemInfo -{ - /// - /// Operating system platform. - /// - public Platform Platform { get; set; } - - /// - /// Installation path of the application. - /// - public ReadOnlySpan ApplicationPath { get; set; } - - /// - /// Whether the application supports multiple windows. - /// - public bool SupportMultiWindows { get; set; } -} - -internal unsafe struct SystemInfoUnsafe -{ - public Platform Platform { get; set; } - - public byte* ApplicationPath { get; set; } - - public bool SupportMultiWindows { get; set; } -} - diff --git a/bindings/dotnet/src/Elemental/Window.cs b/bindings/dotnet/src/Elemental/Window.cs deleted file mode 100644 index 6b616a12..00000000 --- a/bindings/dotnet/src/Elemental/Window.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Elemental; - -/// -/// Represents a handle to an elemental window. -/// -public readonly record struct Window : IDisposable -{ - private UInt64 Value { get; } - - /// - /// Disposes the handler. - /// - public void Dispose() - { - ApplicationServiceInterop.FreeWindow(this); - } -} diff --git a/bindings/dotnet/src/Elemental/WindowCursorPosition.cs b/bindings/dotnet/src/Elemental/WindowCursorPosition.cs deleted file mode 100644 index 6a67a13a..00000000 --- a/bindings/dotnet/src/Elemental/WindowCursorPosition.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Elemental; - -/// -/// TODO: Comments -/// -public ref struct WindowCursorPosition -{ - public uint X { get; set; } - - public uint Y { get; set; } -} diff --git a/bindings/dotnet/src/Elemental/WindowOptions.cs b/bindings/dotnet/src/Elemental/WindowOptions.cs deleted file mode 100644 index 06209487..00000000 --- a/bindings/dotnet/src/Elemental/WindowOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Elemental; - -/// -/// Defines options for creating a new window. -/// -public ref struct WindowOptions -{ - /// - /// Title of the window. - /// - public ReadOnlySpan Title { get; set; } - - /// - /// Width of the window in pixels. - /// - public uint Width { get; set; } - - /// - /// Height of the window in pixels. - /// - public uint Height { get; set; } - - /// - /// Initial state of the window. - /// - public WindowState WindowState { get; set; } - - /// - /// True if the cursor should be hidden. - /// - public bool IsCursorHidden { get; set; } -} - -internal unsafe struct WindowOptionsUnsafe -{ - public byte* Title { get; set; } - - public uint Width { get; set; } - - public uint Height { get; set; } - - public WindowState WindowState { get; set; } - - public bool IsCursorHidden { get; set; } -} - diff --git a/bindings/dotnet/src/Elemental/WindowSize.cs b/bindings/dotnet/src/Elemental/WindowSize.cs deleted file mode 100644 index a9708ac8..00000000 --- a/bindings/dotnet/src/Elemental/WindowSize.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Elemental; - -/// -/// Contains detailed information about the size and scaling factors of a window's render area. -/// -public ref struct WindowSize -{ - /// - /// Width of the window's render area in pixels. - /// - public uint Width { get; set; } - - /// - /// Height of the window's render area in pixels. - /// - public uint Height { get; set; } - - /// - /// UI scale factor, typically used for DPI adjustments. - /// - public float UIScale { get; set; } - - /// - /// Current state of the window. - /// - public WindowState WindowState { get; set; } -} diff --git a/bindings/dotnet/src/Elemental/WindowState.cs b/bindings/dotnet/src/Elemental/WindowState.cs deleted file mode 100644 index 649a4749..00000000 --- a/bindings/dotnet/src/Elemental/WindowState.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Elemental; - -/// -/// Enumerates the possible states of a window. -/// -public enum WindowState -{ - /// - /// Window is in normal state. - /// - Normal = 0, - - /// - /// Window is minimized. - /// - Minimized = 1, - - /// - /// Window is maximized. - /// - Maximized = 2, - - /// - /// Window is in full screen mode. - /// - FullScreen = 3 -} diff --git a/bindings/dotnet/src/Elemental_REF/ApplicationHandler.cs b/bindings/dotnet/src/Elemental_REF/ApplicationHandler.cs deleted file mode 100644 index d010b7c5..00000000 --- a/bindings/dotnet/src/Elemental_REF/ApplicationHandler.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Elemental; - -/// -/// Defines a function pointer type for handling application events. -/// -public delegate void ApplicationHandler(ref T payload) where T : unmanaged; diff --git a/bindings/dotnet/src/Elemental_REF/ApplicationService.cs b/bindings/dotnet/src/Elemental_REF/ApplicationService.cs deleted file mode 100644 index cb55d807..00000000 --- a/bindings/dotnet/src/Elemental_REF/ApplicationService.cs +++ /dev/null @@ -1,177 +0,0 @@ -namespace Elemental; - -internal static class InitApplicationHandler where T : unmanaged -{ - public static ApplicationHandler? _interceptorEntry; - - public static void Interceptor(ref T payload) - { - if (_interceptorEntry == null) - { - return; - } - - _interceptorEntry(ref payload); - } -} - -internal static class FreeApplicationHandler where T : unmanaged -{ - public static ApplicationHandler? _interceptorEntry; - - public static void Interceptor(ref T payload) - { - if (_interceptorEntry == null) - { - return; - } - - _interceptorEntry(ref payload); - } -} - -/// -public class ApplicationService : IApplicationService -{ - /// - /// Configures a custom log handler for processing log messages generated by the application. - /// - /// The function to call when a log message is generated. - public void ConfigureLogHandler(LogHandler logHandler) - { - ApplicationServiceInterop.ConfigureLogHandler(logHandler); - } - - /// - /// Retrieves system-related information, such as platform and application path. - /// - /// A structure containing system information. - public unsafe SystemInfo GetSystemInfo() - { - var resultUnsafe = ApplicationServiceInterop.GetSystemInfo(); - - var result = new SystemInfo(); - result.Platform = resultUnsafe.Platform; - - // TODO: New code to generate - result.ApplicationPath = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(resultUnsafe.ApplicationPath); - result.SupportMultiWindows = resultUnsafe.SupportMultiWindows; - - return result; - } - - /// - /// Starts the execution of an application with specified parameters. - /// - /// Configuration and handlers for the application lifecycle. - /// Status code indicating success or error. - public unsafe int RunApplication(in RunApplicationParameters parameters) where T : unmanaged - { - // TODO: New code to generate - fixed (byte* ApplicationNamePinned = parameters.ApplicationName) - { - var parametersUnsafe = new RunApplicationParametersUnsafe(); - parametersUnsafe.ApplicationName = ApplicationNamePinned; - - InitApplicationHandler._interceptorEntry = parameters.InitHandler; - parametersUnsafe.InitHandler = (nint)(delegate* )&InitApplicationHandler.Interceptor; - - FreeApplicationHandler._interceptorEntry = parameters.FreeHandler; - parametersUnsafe.FreeHandler = (nint)(delegate* )&FreeApplicationHandler.Interceptor; - - var test = stackalloc T[1]; - *test = parameters.Payload; - parametersUnsafe.Payload = (nint)test; - - return ApplicationServiceInterop.RunApplication(parametersUnsafe); - } - } - - /// - /// Exits the application, performing necessary cleanup. - /// - /// Exit code of the application. - public void ExitApplication(int exitCode) - { - ApplicationServiceInterop.ExitApplication(exitCode); - } - - /// - /// Creates a window with the specified options or default settings if none are provided. - /// - /// Configuration options for the window; NULL for defaults. - /// A handle to the newly created window. - public unsafe Window CreateWindow(in WindowOptions options = default) - { - fixed (byte* TitlePinned = options.Title) - { - var optionsUnsafe = new WindowOptionsUnsafe(); - optionsUnsafe.Title = TitlePinned; - optionsUnsafe.Width = options.Width; - optionsUnsafe.Height = options.Height; - optionsUnsafe.WindowState = options.WindowState; - optionsUnsafe.IsCursorHidden = options.IsCursorHidden; - - return ApplicationServiceInterop.CreateWindow(optionsUnsafe); - } - } - - /// - /// Releases resources associated with a window. - /// - /// Handle to the window to be freed. - public void FreeWindow(Window window) - { - ApplicationServiceInterop.FreeWindow(window); - } - - /// - /// Gets the render size of a window, accounting for DPI scaling. - /// - /// The window instance. - /// Render size of the window. - public WindowSize GetWindowRenderSize(Window window) - { - return ApplicationServiceInterop.GetWindowRenderSize(window); - } - - /// - /// Sets a window's title. - /// - /// The window instance. - /// New title for the window. - public void SetWindowTitle(Window window, ReadOnlySpan title) - { - ApplicationServiceInterop.SetWindowTitle(window, title); - } - - /// - /// Changes the state of a window (e.g., minimize, maximize). - /// - /// The window instance. - /// New state for the window. - public void SetWindowState(Window window, WindowState windowState) - { - ApplicationServiceInterop.SetWindowState(window, windowState); - } - - /// - /// TODO: Comments -///TODO: Make sure the coordinates are consistent accross all platforms - /// - public void ShowWindowCursor(Window window) - { - ApplicationServiceInterop.ShowWindowCursor(window); - } - - public void HideWindowCursor(Window window) - { - ApplicationServiceInterop.HideWindowCursor(window); - } - - public WindowCursorPosition GetWindowCursorPosition(Window window) - { - return ApplicationServiceInterop.GetWindowCursorPosition(window); - } - -} diff --git a/bindings/dotnet/src/Elemental_REF/ApplicationServiceInterop.cs b/bindings/dotnet/src/Elemental_REF/ApplicationServiceInterop.cs deleted file mode 100644 index 16755d7c..00000000 --- a/bindings/dotnet/src/Elemental_REF/ApplicationServiceInterop.cs +++ /dev/null @@ -1,55 +0,0 @@ -[assembly:DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)] - -namespace Elemental; - -internal static partial class ApplicationServiceInterop -{ - [LibraryImport("Elemental.Native", EntryPoint = "ElemConfigureLogHandler")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void ConfigureLogHandler(LogHandler logHandler); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemGetSystemInfo")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial SystemInfoUnsafe GetSystemInfo(); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemRunApplication")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial int RunApplication(in RunApplicationParametersUnsafe parameters); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemExitApplication")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void ExitApplication(int exitCode); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemCreateWindow")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial Window CreateWindow(in WindowOptionsUnsafe options); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemFreeWindow")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void FreeWindow(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemGetWindowRenderSize")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial WindowSize GetWindowRenderSize(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemSetWindowTitle")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void SetWindowTitle(Window window, ReadOnlySpan title); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemSetWindowState")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void SetWindowState(Window window, WindowState windowState); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemShowWindowCursor")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void ShowWindowCursor(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemHideWindowCursor")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial void HideWindowCursor(Window window); - - [LibraryImport("Elemental.Native", EntryPoint = "ElemGetWindowCursorPosition")] - [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })] - internal static partial WindowCursorPosition GetWindowCursorPosition(Window window); - -} diff --git a/bindings/dotnet/src/Elemental_REF/ConsoleLogHandler.cs b/bindings/dotnet/src/Elemental_REF/ConsoleLogHandler.cs deleted file mode 100644 index 3f0ee50a..00000000 --- a/bindings/dotnet/src/Elemental_REF/ConsoleLogHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace Elemental; - -/// -/// Class that contains a default log handler implementation. -/// -public static class DefaultLogHandlers -{ - /// - /// Default console log handler. - /// - /// Message type - /// Category - /// Function - /// Message - public static void ConsoleLogHandler(LogMessageType messageType, LogMessageCategory category, ReadOnlySpan function, ReadOnlySpan message) - { - var mainForegroundColor = messageType switch - { - LogMessageType.Warning => ConsoleColor.Yellow, - LogMessageType.Error => ConsoleColor.Red, - _ => ConsoleColor.Gray - }; - - Console.Write("["); - Console.ForegroundColor = ConsoleColor.Cyan; - Console.Write($"{category}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.Write("]"); - - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($" {System.Text.UTF8Encoding.UTF8.GetString(function)}"); - - Console.ForegroundColor = mainForegroundColor; - Console.WriteLine($" {messageType}: {System.Text.UTF8Encoding.UTF8.GetString(message)}"); - Console.ForegroundColor = ConsoleColor.Gray; - } -} diff --git a/bindings/dotnet/src/Elemental_REF/Elemental.csproj b/bindings/dotnet/src/Elemental_REF/Elemental.csproj deleted file mode 100644 index e74bb91b..00000000 --- a/bindings/dotnet/src/Elemental_REF/Elemental.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - true - true - true - true - - - - - - - - - Elemental.Native.dll - PreserveNewest - - - Elemental.Native.pdb - PreserveNewest - - - D3D12Core.dll - PreserveNewest - - - D3D12SDKLayers.dll - PreserveNewest - - - - - - - - - - - - diff --git a/bindings/dotnet/src/Elemental_REF/Globals.cs b/bindings/dotnet/src/Elemental_REF/Globals.cs deleted file mode 100644 index 01008824..00000000 --- a/bindings/dotnet/src/Elemental_REF/Globals.cs +++ /dev/null @@ -1,7 +0,0 @@ -global using System.Numerics; -global using System.Runtime.InteropServices; -global using System.Runtime.InteropServices.Marshalling; -global using System.Runtime.CompilerServices; -global using System.Text; - -[assembly:System.Runtime.CompilerServices.DisableRuntimeMarshalling] diff --git a/bindings/dotnet/src/Elemental_REF/IApplicationService.cs b/bindings/dotnet/src/Elemental_REF/IApplicationService.cs deleted file mode 100644 index d55305ef..00000000 --- a/bindings/dotnet/src/Elemental_REF/IApplicationService.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace Elemental; - -/// -/// Defines an interface for Application services. -/// -public interface IApplicationService -{ - /// - /// Configures a custom log handler for processing log messages generated by the application. - /// - /// The function to call when a log message is generated. - void ConfigureLogHandler(LogHandler logHandler); - - /// - /// Retrieves system-related information, such as platform and application path. - /// - /// A structure containing system information. - SystemInfo GetSystemInfo(); - - /// - /// Starts the execution of an application with specified parameters. - /// - /// Configuration and handlers for the application lifecycle. - /// Status code indicating success or error. - int RunApplication(in RunApplicationParameters parameters) where T : unmanaged; - - /// - /// Exits the application, performing necessary cleanup. - /// - /// Exit code of the application. - void ExitApplication(int exitCode); - - /// - /// Creates a window with the specified options or default settings if none are provided. - /// - /// Configuration options for the window; NULL for defaults. - /// A handle to the newly created window. - Window CreateWindow(in WindowOptions options = default); - - /// - /// Releases resources associated with a window. - /// - /// Handle to the window to be freed. - void FreeWindow(Window window); - - /// - /// Gets the render size of a window, accounting for DPI scaling. - /// - /// The window instance. - /// Render size of the window. - WindowSize GetWindowRenderSize(Window window); - - /// - /// Sets a window's title. - /// - /// The window instance. - /// New title for the window. - void SetWindowTitle(Window window, ReadOnlySpan title); - - /// - /// Changes the state of a window (e.g., minimize, maximize). - /// - /// The window instance. - /// New state for the window. - void SetWindowState(Window window, WindowState windowState); - - /// - /// TODO: Comments -///TODO: Make sure the coordinates are consistent accross all platforms - /// - void ShowWindowCursor(Window window); - - void HideWindowCursor(Window window); - - WindowCursorPosition GetWindowCursorPosition(Window window); -} diff --git a/bindings/dotnet/src/Elemental_REF/LogHandler.cs b/bindings/dotnet/src/Elemental_REF/LogHandler.cs deleted file mode 100644 index 176a2329..00000000 --- a/bindings/dotnet/src/Elemental_REF/LogHandler.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace Elemental; - -/// -/// Defines a function pointer type for log handling. -/// -/// The type of the log message. -/// The category of the log message. -/// The function where the log was triggered. -/// The log message. -[NativeMarshalling(typeof(LogHandlerMarshaller))] -public delegate void LogHandler(LogMessageType messageType, LogMessageCategory category, ReadOnlySpan function, ReadOnlySpan message); - -[CustomMarshaller(typeof(LogHandler), MarshalMode.ManagedToUnmanagedIn, typeof(LogHandlerMarshaller))] -internal static unsafe class LogHandlerMarshaller -{ - internal sealed unsafe record InterceptorEntry - { - public required LogHandler Callback { get; init; } - //public required GCHandle Handle { get; init; } - } - - private static InterceptorEntry? _interceptorEntry; - - private static unsafe void Interceptor(LogMessageType messageType, LogMessageCategory category, byte* function, byte* message) - { - if (_interceptorEntry == null || function == null || message == null) - { - return; - } - - var functionCounter = 0; - var functionPointer = (byte*)function; - - while (functionPointer[functionCounter] != 0) - { - functionCounter++; - } - - functionCounter++; - - var messageCounter = 0; - var messagePointer = (byte*)message; - - while (messagePointer[messageCounter] != 0) - { - messageCounter++; - } - - messageCounter++; - - _interceptorEntry.Callback(messageType, category, new ReadOnlySpan(function, functionCounter), new ReadOnlySpan(message, messageCounter)); - } - - public static nint ConvertToUnmanaged(LogHandler managed) - { - // TODO: Unallocate handle - //var interceptorDelegate = Interceptor; - //var handle = GCHandle.Alloc(interceptorDelegate); - //var unmanaged = Marshal.GetFunctionPointerForDelegate(interceptorDelegate); - - // TODO: Try to avoid all of that - delegate* unmanaged = &Interceptor; - - _interceptorEntry = new InterceptorEntry { Callback = managed }; - return (nint)unmanaged; - } - - public static void Free(nint _) - { - } -} diff --git a/bindings/dotnet/src/Elemental_REF/LogMessageCategory.cs b/bindings/dotnet/src/Elemental_REF/LogMessageCategory.cs deleted file mode 100644 index e49ad889..00000000 --- a/bindings/dotnet/src/Elemental_REF/LogMessageCategory.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Elemental; - -/// -/// Categorizes log messages by their related system components. -/// -public enum LogMessageCategory -{ - /// - /// Assertions and checks. - /// - Assert = 0, - - /// - /// Memory allocation and management. - /// - Memory = 1, - - /// - /// General application behavior. - /// - Application = 2, - - /// - /// Graphics system-related messages. - /// - Graphics = 3, - - /// - /// Input system-related messages. - /// - Inputs = 4 -} diff --git a/bindings/dotnet/src/Elemental_REF/LogMessageType.cs b/bindings/dotnet/src/Elemental_REF/LogMessageType.cs deleted file mode 100644 index a8eb2eae..00000000 --- a/bindings/dotnet/src/Elemental_REF/LogMessageType.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Elemental; - -/// -/// Defines the types of log messages that can be generated. -/// -public enum LogMessageType -{ - /// - /// Debugging message. - /// - Debug = 0, - - /// - /// Warning message. - /// - Warning = 1, - - /// - /// Error message. - /// - Error = 2 -} diff --git a/bindings/dotnet/src/Elemental_REF/Platform.cs b/bindings/dotnet/src/Elemental_REF/Platform.cs deleted file mode 100644 index d578e044..00000000 --- a/bindings/dotnet/src/Elemental_REF/Platform.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Elemental; - -/// -/// Lists the platforms supported by the library. -/// -public enum Platform -{ - /// - /// Microsoft Windows platform. - /// - Windows = 0, - - /// - /// Apple macOS platform. - /// - MacOS = 1, - - /// - /// Apple iOS platform. - /// - iOS = 2, - - /// - /// Linux platform. - /// - Linux = 3 -} diff --git a/bindings/dotnet/src/Elemental_REF/RunApplicationParameters.cs b/bindings/dotnet/src/Elemental_REF/RunApplicationParameters.cs deleted file mode 100644 index 7ef786d2..00000000 --- a/bindings/dotnet/src/Elemental_REF/RunApplicationParameters.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Elemental; - -/// -/// Holds parameters for running an application, including initialization and cleanup routines. -/// -public ref struct RunApplicationParameters where T: unmanaged -{ - /// - /// Name of the application. - /// - public ReadOnlySpan ApplicationName { get; set; } - - /// - /// Function called at application startup. - /// - public ApplicationHandler InitHandler { get; set; } - - /// - /// Function called at application termination. - /// - public ApplicationHandler FreeHandler { get; set; } - - /// - /// Custom user data passed to handler functions. - /// - public T Payload { get; set; } -} - -internal unsafe struct RunApplicationParametersUnsafe -{ - public byte* ApplicationName { get; set; } - - public nint InitHandler { get; set; } - - public nint FreeHandler { get; set; } - - public nint Payload; -} - diff --git a/bindings/dotnet/src/Elemental_REF/SpanUnsafe.cs b/bindings/dotnet/src/Elemental_REF/SpanUnsafe.cs deleted file mode 100644 index e966f8ad..00000000 --- a/bindings/dotnet/src/Elemental_REF/SpanUnsafe.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elemental; - -internal unsafe record struct SpanUnsafe where T : struct -{ - internal nuint Items { get; set; } - internal int Length { get; set; } -} diff --git a/bindings/dotnet/src/Elemental_REF/SystemInfo.cs b/bindings/dotnet/src/Elemental_REF/SystemInfo.cs deleted file mode 100644 index f3ee7822..00000000 --- a/bindings/dotnet/src/Elemental_REF/SystemInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Elemental; - -/// -/// Contains information about the system, useful for tailoring application behavior. -/// -public ref struct SystemInfo -{ - /// - /// Operating system platform. - /// - public Platform Platform { get; set; } - - /// - /// Installation path of the application. - /// - public ReadOnlySpan ApplicationPath { get; set; } - - /// - /// Whether the application supports multiple windows. - /// - public bool SupportMultiWindows { get; set; } -} - -internal unsafe struct SystemInfoUnsafe -{ - public Platform Platform { get; set; } - - public byte* ApplicationPath { get; set; } - - public bool SupportMultiWindows { get; set; } -} - diff --git a/bindings/dotnet/src/Elemental_REF/Window.cs b/bindings/dotnet/src/Elemental_REF/Window.cs deleted file mode 100644 index 6b616a12..00000000 --- a/bindings/dotnet/src/Elemental_REF/Window.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Elemental; - -/// -/// Represents a handle to an elemental window. -/// -public readonly record struct Window : IDisposable -{ - private UInt64 Value { get; } - - /// - /// Disposes the handler. - /// - public void Dispose() - { - ApplicationServiceInterop.FreeWindow(this); - } -} diff --git a/bindings/dotnet/src/Elemental_REF/WindowCursorPosition.cs b/bindings/dotnet/src/Elemental_REF/WindowCursorPosition.cs deleted file mode 100644 index 6a67a13a..00000000 --- a/bindings/dotnet/src/Elemental_REF/WindowCursorPosition.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Elemental; - -/// -/// TODO: Comments -/// -public ref struct WindowCursorPosition -{ - public uint X { get; set; } - - public uint Y { get; set; } -} diff --git a/bindings/dotnet/src/Elemental_REF/WindowOptions.cs b/bindings/dotnet/src/Elemental_REF/WindowOptions.cs deleted file mode 100644 index 06209487..00000000 --- a/bindings/dotnet/src/Elemental_REF/WindowOptions.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Elemental; - -/// -/// Defines options for creating a new window. -/// -public ref struct WindowOptions -{ - /// - /// Title of the window. - /// - public ReadOnlySpan Title { get; set; } - - /// - /// Width of the window in pixels. - /// - public uint Width { get; set; } - - /// - /// Height of the window in pixels. - /// - public uint Height { get; set; } - - /// - /// Initial state of the window. - /// - public WindowState WindowState { get; set; } - - /// - /// True if the cursor should be hidden. - /// - public bool IsCursorHidden { get; set; } -} - -internal unsafe struct WindowOptionsUnsafe -{ - public byte* Title { get; set; } - - public uint Width { get; set; } - - public uint Height { get; set; } - - public WindowState WindowState { get; set; } - - public bool IsCursorHidden { get; set; } -} - diff --git a/bindings/dotnet/src/Elemental_REF/WindowSize.cs b/bindings/dotnet/src/Elemental_REF/WindowSize.cs deleted file mode 100644 index a9708ac8..00000000 --- a/bindings/dotnet/src/Elemental_REF/WindowSize.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Elemental; - -/// -/// Contains detailed information about the size and scaling factors of a window's render area. -/// -public ref struct WindowSize -{ - /// - /// Width of the window's render area in pixels. - /// - public uint Width { get; set; } - - /// - /// Height of the window's render area in pixels. - /// - public uint Height { get; set; } - - /// - /// UI scale factor, typically used for DPI adjustments. - /// - public float UIScale { get; set; } - - /// - /// Current state of the window. - /// - public WindowState WindowState { get; set; } -} diff --git a/bindings/dotnet/src/Elemental_REF/WindowState.cs b/bindings/dotnet/src/Elemental_REF/WindowState.cs deleted file mode 100644 index 649a4749..00000000 --- a/bindings/dotnet/src/Elemental_REF/WindowState.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Elemental; - -/// -/// Enumerates the possible states of a window. -/// -public enum WindowState -{ - /// - /// Window is in normal state. - /// - Normal = 0, - - /// - /// Window is minimized. - /// - Minimized = 1, - - /// - /// Window is maximized. - /// - Maximized = 2, - - /// - /// Window is in full screen mode. - /// - FullScreen = 3 -} diff --git a/doc/Barriers.md b/doc/DesignNotes/Barriers.md similarity index 100% rename from doc/Barriers.md rename to doc/DesignNotes/Barriers.md diff --git a/doc/DesignNotes/IOCommands.md b/doc/DesignNotes/IOCommands.md new file mode 100644 index 00000000..5721aefa --- /dev/null +++ b/doc/DesignNotes/IOCommands.md @@ -0,0 +1,56 @@ +# Barriers + +## Decisions to make + +- [x] Do we use the existing CommandQueue/CommandList system? +- [ ] It would be nice to declare just heaps that are always GPU. + For Upload we use copy functions. That handle the copy operation. + For readback we would have only a Readfunction that copy to CPU memory. (Provide a span) + Only problem is vulkan that disallow the creation of VkImages to a ReBar Heap. :( + +## Examples + +1. Solution 1: Separate System + +```c + ElemIOCommandQueue ioCommandQueue = ElemCreateIOCommandQueue(NULL); + + ElemEnqueueIOCommand(&(ElemIOCommandParameters) + { + .DestinationResource = buffer, + .DestinationOffset = 0, + .SourceType = ElemIOCommandSourceType_File, + .SourceFilename = "Test.mesh", + .SourceFileOffset = 500, + .SourceSizeInBytes = 233000, + }); + + ElemFence fence = ElemSubmitIOCommandQueue(); +``` + +2. Solution 2: Reuse current CommandList system + +```c + // This should work with DirectStorage when using IO of with normal queues when using otherwise + // TODO: IO or Transfer or Copy? + ElemCommandQueue ioCommandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_IO, NULL); + ElemCommandList ioCommandList = ElemGetCommandList(ioCommandQueue, NULL); + + // TODO: Simplify? + ElemCopyDataToGraphicsResource(ioCommandList, &(ElemIOCommandParameters) + { + .DestinationResource = buffer, + .DestinationOffset = 0, + .SourceType = ElemIOCommandSourceType_File, + .SourceFilename = "Test.mesh", + .SourceFileOffset = 500, + .SourceSizeInBytes = 233000, + }); + + // Direct upload with re-bar (no commandlist needed) + ElemDirectCopyDataToGraphicsBuffer(); + + ElemCommitCommandList(ioCommandList); + ElemExecuteCommandList(ioCommandQueue, ioCommandList, NULL); + +``` diff --git a/doc/LatencyQuestions.md b/doc/DesignNotes/LatencyQuestions.md similarity index 100% rename from doc/LatencyQuestions.md rename to doc/DesignNotes/LatencyQuestions.md diff --git a/doc/macos.md b/doc/macos.md index de94aee7..b5906620 100644 --- a/doc/macos.md +++ b/doc/macos.md @@ -2,3 +2,6 @@ xattr -l /path/to/application.app You'll probably find com.apple.quarantine listed. If so, you can get rid of it with xattr -dr com.apple.quarantine /path/to/application.app + +https://docs.avaloniaui.net/docs/deployment/macOS +https://github.com/Kong/insomnia/blob/c4dff179daa1895246edbbe335c58d559167f761/.github/workflows/release-build.yml#L169 diff --git a/doc/references.md b/doc/references.md new file mode 100644 index 00000000..3f73918c --- /dev/null +++ b/doc/references.md @@ -0,0 +1,8 @@ +https://guide.handmadehero.org/ +https://github.com/zeux/niagara + +https://therealmjp.github.io/posts/breaking-down-barriers-part-1-whats-a-barrier/ +https://therealmjp.github.io/posts/gpu-memory-pool/ + +# HDR +https://github.com/h3r2tic/tony-mc-mapface/blob/main/shader/tony_mc_mapface.hlsl diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 20a48d51..d684db5f 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,116 +1,15 @@ #======================================================================= # DirectX Shader Compiler #======================================================================= -set(DXC_VERSION_CUSTOM "v2024-07-27") - -add_library(dxc INTERFACE) - -if(WIN32) - get_github_release("double-buffer/shader-compilers-bin" ${DXC_VERSION_CUSTOM} "windows_dxc_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/dxc/") - target_include_directories(dxc INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/dxc/include/dxc/) - - function(copy_dxc_to_target target) - file(GLOB DXC_DLLS - "${CMAKE_BINARY_DIR}/external/shader-compilers/dxc/lib/*.dll") - foreach(DLL ${DXC_DLLS}) - get_filename_component(DLL_NAME "${DLL}" NAME) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DLL}" - "$/${DLL_NAME}") - endforeach() - endfunction() -elseif(LINUX) - get_github_release("double-buffer/shader-compilers-bin" ${DXC_VERSION_CUSTOM} "linux_dxc_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/dxc/") - target_include_directories(dxc INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/dxc/include/dxc/) - target_include_directories(dxc INTERFACE ./DirectX-Headers/include/directx/) - - function(copy_dxc_to_target target) - file(GLOB DXC_DLLS - "${CMAKE_BINARY_DIR}/external/shader-compilers/dxc/lib/*.so") - foreach(DLL ${DXC_DLLS}) - get_filename_component(DLL_NAME "${DLL}" NAME) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DLL}" - "$/${DLL_NAME}") - endforeach() - endfunction() -elseif(NOT BUILD_FOR_IOS) - get_github_release("double-buffer/shader-compilers-bin" ${DXC_VERSION_CUSTOM} "macos_dxc_*_arm64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/dxc/") - target_include_directories(dxc INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/dxc/include/dxc/) - target_include_directories(dxc INTERFACE ./DirectX-Headers/include/directx/) - - function(copy_dxc_to_target target) - file(GLOB DXC_DLLS - "${CMAKE_BINARY_DIR}/external/shader-compilers/dxc/lib/*.dylib") - foreach(DLL ${DXC_DLLS}) - get_filename_component(DLL_NAME "${DLL}" NAME) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DLL}" - "$/${DLL_NAME}") - endforeach() - endfunction() +if(NOT BUILD_FOR_IOS) + add_subdirectory(./DirectXShaderCompiler) endif() #======================================================================= # Metal Shader Converter #======================================================================= -if (WIN32 OR APPLE) - set(METAL_SHADER_CONVERTER_VERSION "v2024-06-11") - - add_library(metal-shader-converter INTERFACE) - target_include_directories(metal-shader-converter INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/MetalShaderConverter/include/) - target_link_directories(metal-shader-converter INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/MetalShaderConverter/lib/) - target_link_libraries(metal-shader-converter INTERFACE metalirconverter) - - if(WIN32) - get_github_release("double-buffer/shader-compilers-bin" ${METAL_SHADER_CONVERTER_VERSION} "windows_MetalShaderConverter_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/MetalShaderConverter/") - - function(copy_MetalShaderConverter_to_target target) - file(GLOB DXC_DLLS - "${CMAKE_BINARY_DIR}/external/shader-compilers/MetalShaderConverter/lib/*.dll") - foreach(DLL ${DXC_DLLS}) - get_filename_component(DLL_NAME "${DLL}" NAME) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DLL}" - "$/${DLL_NAME}") - endforeach() - endfunction() - elseif(NOT BUILD_FOR_IOS) - get_github_release("double-buffer/shader-compilers-bin" ${METAL_SHADER_CONVERTER_VERSION} "macos_MetalShaderConverter_*_arm64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/MetalShaderConverter/") - - function(copy_MetalShaderConverter_to_target target) - file(GLOB DXC_DLLS - "${CMAKE_BINARY_DIR}/external/shader-compilers/MetalShaderConverter/lib/*.dylib") - foreach(DLL ${DXC_DLLS}) - get_filename_component(DLL_NAME "${DLL}" NAME) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DLL}" - "$/${DLL_NAME}") - endforeach() - endfunction() - endif() -endif() - -#======================================================================= -# SPIRV-Cross Compiler -#======================================================================= -set(SPIRV_CROSS_VERSION "v2024-06-11") - -add_library(spirv-cross INTERFACE) -target_include_directories(spirv-cross INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/spirv-cross/include/) -target_link_libraries(spirv-cross INTERFACE spirv-cross-core spirv-cross-cpp spirv-cross-msl spirv-cross-glsl spirv-cross-reflect spirv-cross-util) - -if(WIN32) - get_github_release("double-buffer/shader-compilers-bin" ${SPIRV_CROSS_VERSION} "windows_spirv-cross_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/spirv-cross/") - target_link_directories(spirv-cross INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/spirv-cross/lib/lib/) -elseif(NOT BUILD_FOR_IOS) - get_github_release("double-buffer/shader-compilers-bin" ${SPIRV_CROSS_VERSION} "macos_spirv-cross_*_arm64.zip" "${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/spirv-cross/") - target_link_directories(spirv-cross INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/shader-compilers/spirv-cross/lib/) +if(NOT BUILD_FOR_IOS) + add_subdirectory(./MetalShaderConverter) endif() #======================================================================= @@ -134,6 +33,19 @@ if(LINUX) set(GENERATED_DIR "${CMAKE_BINARY_DIR}/generated") file(MAKE_DIRECTORY ${GENERATED_DIR}) + add_custom_command( + OUTPUT + ${GENERATED_DIR}/xdg-shell-client-protocol.h + ${GENERATED_DIR}/xdg-shell-protocol.c + COMMAND wayland-scanner client-header + ${STABLE_PROTOCOLS_DIR}/xdg-shell/xdg-shell.xml + ${GENERATED_DIR}/xdg-shell-client-protocol.h + COMMAND wayland-scanner private-code + ${STABLE_PROTOCOLS_DIR}/xdg-shell/xdg-shell.xml + ${GENERATED_DIR}/xdg-shell-protocol.c + COMMENT "Generating xdg-shell protocol files" + ) + # Generate relative-pointer protocol files add_custom_command( OUTPUT ${GENERATED_DIR}/relative-pointer-unstable-v1-client-protocol.h ${GENERATED_DIR}/relative-pointer-unstable-v1-client-protocol.c @@ -158,6 +70,8 @@ if(LINUX) ${GENERATED_DIR}/relative-pointer-unstable-v1-client-protocol.c ${GENERATED_DIR}/pointer-constraints-unstable-v1-client-protocol.h ${GENERATED_DIR}/pointer-constraints-unstable-v1-client-protocol.c + ${GENERATED_DIR}/xdg-shell-client-protocol.h + ${GENERATED_DIR}/xdg-shell-protocol.c ) target_include_directories(Wayland INTERFACE ${GENERATED_DIR}) @@ -168,47 +82,75 @@ endif() # Direct3D12 Runtime #======================================================================= if(WIN32) - set(DIRECT3D12_VERSION "1.614.0") - - add_library(Direct3D12 INTERFACE) - - download_and_extract_nuget_package(Direct3D12 "Microsoft.Direct3D.D3D12" ${DIRECT3D12_VERSION}) - target_link_libraries(Direct3D12 INTERFACE dxgi dxguid) - - # TODO: Do we really need that? - target_link_libraries(Direct3D12 INTERFACE d3d12) - - function(copy_direct3d12_to_target target) - # TODO: To Fix - set(DIRECT3D12_VERSION "1.614.0") - file(GLOB DIRECT3D12_DLLS - "${CMAKE_BINARY_DIR}/external/packages/Microsoft.Direct3D.D3D12_${DIRECT3D12_VERSION}/build/native/bin/x64/*.dll") - foreach(DLL ${DIRECT3D12_DLLS}) - get_filename_component(DLL_NAME "${DLL}" NAME) - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DLL}" - "$/${DLL_NAME}") - endforeach() - endfunction() + add_subdirectory(./DirectX12) endif() - #======================================================================= # Shader Compilers #======================================================================= add_library(tools_shader_compilers INTERFACE) -target_link_libraries(tools_shader_compilers INTERFACE spirv-cross dxc) +target_link_libraries(tools_shader_compilers INTERFACE dxc) if(WIN32 OR APPLE) target_link_libraries(tools_shader_compilers INTERFACE metal-shader-converter) endif() +#======================================================================= +# Mesh Optimizer +#======================================================================= +if(NOT BUILD_FOR_IOS) + add_subdirectory(./meshoptimizer) +endif() + +#======================================================================= +# Fast Obj +#======================================================================= +if(NOT BUILD_FOR_IOS) + add_subdirectory(./fast_obj) +endif() + +#======================================================================= +# cgltf +#======================================================================= +if(NOT BUILD_FOR_IOS) + add_library(cgltf INTERFACE) + target_include_directories(cgltf INTERFACE ./cgltf/) +endif() + +#======================================================================= +# stb +#======================================================================= +if(NOT BUILD_FOR_IOS) + add_library(stb INTERFACE) + target_include_directories(stb INTERFACE ./stb/) +endif() + +#======================================================================= +# MikkTSpace +#======================================================================= +if(NOT BUILD_FOR_IOS) + add_library(mikktspace INTERFACE) + target_include_directories(mikktspace INTERFACE ./MikkTSpace/) +endif() + +#======================================================================= +# bc7encoder_rdo +#======================================================================= +if(NOT BUILD_FOR_IOS) + set(SUPPORT_BC7E FALSE) # TODO: Needs to set it to true for GPU accell and ISPC + if(WIN32) + set(MSVC FALSE) + endif() + #add_subdirectory(./bc7enc_rdo) + add_library(bc7enc INTERFACE) + target_include_directories(bc7enc INTERFACE ./bc7enc_rdo/) +endif() + #======================================================================= # Tools External Dependencies #======================================================================= add_library(tools_external_dependencies INTERFACE) -target_link_libraries(tools_external_dependencies INTERFACE tools_shader_compilers) +target_link_libraries(tools_external_dependencies INTERFACE tools_shader_compilers meshoptimizer fast_obj cgltf stb mikktspace bc7enc) #======================================================================= # xxHash @@ -216,6 +158,12 @@ target_link_libraries(tools_external_dependencies INTERFACE tools_shader_compile add_library(xxHash INTERFACE) target_include_directories(xxHash INTERFACE ./xxHash/) +#======================================================================= +# ImGui +#======================================================================= +add_subdirectory(./cimgui) +#set(IMGUI_STATIC "yes") + #======================================================================= # Volk #======================================================================= diff --git a/external/DirectX-Headers b/external/DirectX-Headers index 5c45253f..48a76297 160000 --- a/external/DirectX-Headers +++ b/external/DirectX-Headers @@ -1 +1 @@ -Subproject commit 5c45253f7d5dd2f967f45f3f48088890d72640a5 +Subproject commit 48a762973271c5a75869946bf1fdbc489a628a5c diff --git a/external/DirectX12/CMakeLists.txt b/external/DirectX12/CMakeLists.txt new file mode 100644 index 00000000..56a72b57 --- /dev/null +++ b/external/DirectX12/CMakeLists.txt @@ -0,0 +1,25 @@ +if(WIN32) + set(DIRECT3D12_VERSION "1.614.1") + + add_library(Direct3D12 INTERFACE) + + download_and_extract_nuget_package(Direct3D12 "Microsoft.Direct3D.D3D12" ${DIRECT3D12_VERSION}) + target_link_libraries(Direct3D12 INTERFACE dxgi dxguid) + + # TODO: Do we really need that? + target_link_libraries(Direct3D12 INTERFACE d3d12) + + function(copy_direct3d12_to_target target) + # TODO: To Fix + set(DIRECT3D12_VERSION "1.614.1") + file(GLOB DIRECT3D12_DLLS + "${CMAKE_BINARY_DIR}/external/DirectX12/packages/Microsoft.Direct3D.D3D12_${DIRECT3D12_VERSION}/build/native/bin/x64/*.dll") + foreach(DLL ${DIRECT3D12_DLLS}) + get_filename_component(DLL_NAME "${DLL}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DLL}" + "$/${DLL_NAME}") + endforeach() + endfunction() +endif() diff --git a/external/DirectXShaderCompiler/CMakeLists.txt b/external/DirectXShaderCompiler/CMakeLists.txt new file mode 100644 index 00000000..86d01289 --- /dev/null +++ b/external/DirectXShaderCompiler/CMakeLists.txt @@ -0,0 +1,52 @@ +set(DXC_VERSION_CUSTOM "v2025-01-07") + +add_library(dxc INTERFACE) + +if(WIN32) + get_github_release("double-buffer/shader-compilers-bin" ${DXC_VERSION_CUSTOM} "windows_dxc_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/dxc/") + target_include_directories(dxc INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/dxc/include/dxc/) + + function(copy_dxc_to_target target) + file(GLOB DXC_DLLS + "${CMAKE_BINARY_DIR}/external/DirectXShaderCompiler/dxc/lib/*.dll") + foreach(DLL ${DXC_DLLS}) + get_filename_component(DLL_NAME "${DLL}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DLL}" + "$/${DLL_NAME}") + endforeach() + endfunction() +elseif(LINUX) + get_github_release("double-buffer/shader-compilers-bin" ${DXC_VERSION_CUSTOM} "linux_dxc_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/dxc/") + target_include_directories(dxc INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/dxc/include/dxc/) + target_include_directories(dxc INTERFACE ../DirectX-Headers/include/directx/) + + function(copy_dxc_to_target target) + file(GLOB DXC_DLLS + "${CMAKE_BINARY_DIR}/external/DirectXShaderCompiler/dxc/lib/*.so") + foreach(DLL ${DXC_DLLS}) + get_filename_component(DLL_NAME "${DLL}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DLL}" + "$/${DLL_NAME}") + endforeach() + endfunction() +elseif(NOT BUILD_FOR_IOS) + get_github_release("double-buffer/shader-compilers-bin" ${DXC_VERSION_CUSTOM} "macos_dxc_*_arm64.zip" "${CMAKE_CURRENT_BINARY_DIR}/dxc/") + target_include_directories(dxc INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/dxc/include/dxc/) + target_include_directories(dxc INTERFACE ../DirectX-Headers/include/directx/) + + function(copy_dxc_to_target target) + file(GLOB DXC_DLLS + "${CMAKE_BINARY_DIR}/external/DirectXShaderCompiler/dxc/lib/*.dylib") + foreach(DLL ${DXC_DLLS}) + get_filename_component(DLL_NAME "${DLL}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DLL}" + "$/${DLL_NAME}") + endforeach() + endfunction() +endif() diff --git a/external/MetalShaderConverter/CMakeLists.txt b/external/MetalShaderConverter/CMakeLists.txt new file mode 100644 index 00000000..2f9e46c4 --- /dev/null +++ b/external/MetalShaderConverter/CMakeLists.txt @@ -0,0 +1,38 @@ +if (WIN32 OR APPLE) + set(METAL_SHADER_CONVERTER_VERSION "v2024-06-11") + + add_library(metal-shader-converter INTERFACE) + target_include_directories(metal-shader-converter INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/MetalShaderConverter/include/) + target_link_directories(metal-shader-converter INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/MetalShaderConverter/lib/) + target_link_libraries(metal-shader-converter INTERFACE metalirconverter) + + if(WIN32) + get_github_release("double-buffer/shader-compilers-bin" ${METAL_SHADER_CONVERTER_VERSION} "windows_MetalShaderConverter_*_x64.zip" "${CMAKE_CURRENT_BINARY_DIR}/MetalShaderConverter/") + + function(copy_MetalShaderConverter_to_target target) + file(GLOB DXC_DLLS + "${CMAKE_BINARY_DIR}/external/MetalShaderConverter/MetalShaderConverter/lib/*.dll") + foreach(DLL ${DXC_DLLS}) + get_filename_component(DLL_NAME "${DLL}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DLL}" + "$/${DLL_NAME}") + endforeach() + endfunction() + elseif(NOT BUILD_FOR_IOS) + get_github_release("double-buffer/shader-compilers-bin" ${METAL_SHADER_CONVERTER_VERSION} "macos_MetalShaderConverter_*_arm64.zip" "${CMAKE_CURRENT_BINARY_DIR}/MetalShaderConverter/") + + function(copy_MetalShaderConverter_to_target target) + file(GLOB DXC_DLLS + "${CMAKE_BINARY_DIR}/external/MetalShaderConverter/MetalShaderConverter/lib/*.dylib") + foreach(DLL ${DXC_DLLS}) + get_filename_component(DLL_NAME "${DLL}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DLL}" + "$/${DLL_NAME}") + endforeach() + endfunction() + endif() +endif() diff --git a/external/MikkTSpace b/external/MikkTSpace new file mode 160000 index 00000000..3e895b49 --- /dev/null +++ b/external/MikkTSpace @@ -0,0 +1 @@ +Subproject commit 3e895b49d05ea07e4c2133156cfa94369e19e409 diff --git a/external/Vulkan-Headers b/external/Vulkan-Headers index 31aa7f63..d4a196d8 160000 --- a/external/Vulkan-Headers +++ b/external/Vulkan-Headers @@ -1 +1 @@ -Subproject commit 31aa7f634b052d87ede4664053e85f3f4d1d50d3 +Subproject commit d4a196d8c84e032d27f999adcea3075517c1c97f diff --git a/external/bc7enc_rdo b/external/bc7enc_rdo new file mode 160000 index 00000000..e6990bc1 --- /dev/null +++ b/external/bc7enc_rdo @@ -0,0 +1 @@ +Subproject commit e6990bc11829c072d9f9e37296f3335072aab4e4 diff --git a/external/cgltf b/external/cgltf new file mode 160000 index 00000000..7177c019 --- /dev/null +++ b/external/cgltf @@ -0,0 +1 @@ +Subproject commit 7177c019be6c105fd12bc35f249e431314f2bab1 diff --git a/external/cimgui b/external/cimgui new file mode 160000 index 00000000..e3b48a15 --- /dev/null +++ b/external/cimgui @@ -0,0 +1 @@ +Subproject commit e3b48a15f098d8f569f266abd96807094f827624 diff --git a/external/fast_obj b/external/fast_obj new file mode 160000 index 00000000..9884aa86 --- /dev/null +++ b/external/fast_obj @@ -0,0 +1 @@ +Subproject commit 9884aa86b00a74122719754006b63e6dfcba238f diff --git a/external/meshoptimizer b/external/meshoptimizer new file mode 160000 index 00000000..4affad04 --- /dev/null +++ b/external/meshoptimizer @@ -0,0 +1 @@ +Subproject commit 4affad044571506a5724c9a6f15424f43e86f731 diff --git a/external/metal-cpp b/external/metal-cpp index 89f87c73..5caea74c 160000 --- a/external/metal-cpp +++ b/external/metal-cpp @@ -1 +1 @@ -Subproject commit 89f87c73755940fb2019f874df5a8ffff81bad29 +Subproject commit 5caea74c5f77492add32b7cad109d796e342ab49 diff --git a/external/stb b/external/stb new file mode 160000 index 00000000..5c205738 --- /dev/null +++ b/external/stb @@ -0,0 +1 @@ +Subproject commit 5c205738c191bcb0abc65c4febfa9bd25ff35234 diff --git a/external/utest b/external/utest index ab627881..25c4a9d7 160000 --- a/external/utest +++ b/external/utest @@ -1 +1 @@ -Subproject commit ab62788136a5635e04675322ece84b64d938bd76 +Subproject commit 25c4a9d78df0fb12e6c5364bc811f7f675e9dcb3 diff --git a/external/volk b/external/volk index 12e006f6..0b17a763 160000 --- a/external/volk +++ b/external/volk @@ -1 +1 @@ -Subproject commit 12e006f60f6f10bc92205612d6875ee539c354ad +Subproject commit 0b17a763ba5643e32da1b2152f8140461b3b7345 diff --git a/external/xxHash b/external/xxHash index f91df681..e626a72b 160000 --- a/external/xxHash +++ b/external/xxHash @@ -1 +1 @@ -Subproject commit f91df681b034d78c7ce87de66f0f78a1e40e7bfb +Subproject commit e626a72bc2321cd320e953a0ccf1584cad60f363 diff --git a/samples/01-HelloWindow/CMakeLists.txt b/samples/01-HelloWindow/CMakeLists.txt deleted file mode 100644 index 3f17b313..00000000 --- a/samples/01-HelloWindow/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -set(SAMPLE_NAME HelloWindow) - -add_executable(${SAMPLE_NAME} main.c) -target_link_libraries(${SAMPLE_NAME} PRIVATE ElementalInterface) -add_dependencies(${SAMPLE_NAME} Elemental) - -configure_project_package(${SAMPLE_NAME} "samples" DEPENDENCIES Elemental) diff --git a/samples/01-HelloWindow/main.c b/samples/01-HelloWindow/main.c deleted file mode 100644 index 1e60c2e4..00000000 --- a/samples/01-HelloWindow/main.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "Elemental.h" - -typedef struct -{ - ElemWindow Window; -} ApplicationPayload; - -void InitSample(void* payload) -{ - ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; - - ElemWindow window = ElemCreateWindow(NULL); - applicationPayload->Window = window; -} - -void FreeSample(void* payload) -{ - ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; - ElemFreeWindow(applicationPayload->Window); - printf("Exit Sample\n"); -} - -int main(void) -{ - ElemConfigureLogHandler(ElemConsoleLogHandler); - - ApplicationPayload payload = {0}; - - ElemRunApplication(&(ElemRunApplicationParameters) - { - .ApplicationName = "Hello Window", - .InitHandler = InitSample, - .FreeHandler = FreeSample, - .Payload = &payload - }); -} diff --git a/samples/02-ShaderCompiler/CMakeLists.txt b/samples/02-ShaderCompiler/CMakeLists.txt deleted file mode 100644 index c3c77d5f..00000000 --- a/samples/02-ShaderCompiler/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -if(NOT BUILD_FOR_IOS) - add_executable(ShaderCompiler main.c) - target_link_libraries(ShaderCompiler PRIVATE ElementalToolsInterface) - - configure_project_package(ShaderCompiler "samples" DEPENDENCIES ElementalTools) -endif() diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 401868da..7e8ae236 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,3 +1,3 @@ add_library(SampleCommon INTERFACE) - target_include_directories(SampleCommon INTERFACE ./Common/) +target_link_libraries(SampleCommon INTERFACE ElementalInterface) diff --git a/samples/Common/SampleGpuMemory.h b/samples/Common/SampleGpuMemory.h new file mode 100644 index 00000000..ecaff9b5 --- /dev/null +++ b/samples/Common/SampleGpuMemory.h @@ -0,0 +1,111 @@ +#pragma once + +#include "Elemental.h" +#include "SampleUtils.h" + +typedef struct +{ + ElemGraphicsDevice GraphicsDevice; + ElemGraphicsHeap GraphicsHeap; + uint32_t CurrentHeapOffset; +} SampleGpuMemory; + +typedef struct +{ + ElemGraphicsResource Buffer; + ElemGraphicsResourceDescriptor ReadDescriptor; +} SampleGpuBuffer; + +typedef struct +{ + ElemGraphicsResource Texture; + ElemGraphicsResourceDescriptor ReadDescriptor; +} SampleGpuTexture; + +SampleGpuMemory SampleCreateGpuMemory(ElemGraphicsDevice graphicsDevice, uint32_t sizeInBytes) +{ + // TODO: For now we need to put the heap as GpuUpload but it should be Gpu when we use IOQueues + // TODO: Having GPU Upload is still annoying 😞 + ElemGraphicsHeap graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, sizeInBytes, &(ElemGraphicsHeapOptions) { .HeapType = ElemGraphicsHeapType_GpuUpload }); + + return (SampleGpuMemory) + { + .GraphicsDevice = graphicsDevice, + .GraphicsHeap = graphicsHeap, + .CurrentHeapOffset = 0u + }; +} + +void SampleFreeGpuMemory(SampleGpuMemory* gpuMemory) +{ + ElemFreeGraphicsHeap(gpuMemory->GraphicsHeap); + gpuMemory->GraphicsHeap = ELEM_HANDLE_NULL; +} + +SampleGpuBuffer SampleCreateGpuBuffer(SampleGpuMemory* gpuMemory, uint32_t sizeInBytes, const char* debugName) +{ + // TODO: Alignment should be used with the offset before adding the size of the resource! + ElemGraphicsResourceInfo bufferDescription = ElemCreateGraphicsBufferResourceInfo(gpuMemory->GraphicsDevice, sizeInBytes, ElemGraphicsResourceUsage_Read, &(ElemGraphicsResourceInfoOptions) { .DebugName = debugName }); + + gpuMemory->CurrentHeapOffset = SampleAlignValue(gpuMemory->CurrentHeapOffset, bufferDescription.Alignment); + ElemGraphicsResource buffer = ElemCreateGraphicsResource(gpuMemory->GraphicsHeap, gpuMemory->CurrentHeapOffset, &bufferDescription); + gpuMemory->CurrentHeapOffset += bufferDescription.SizeInBytes; + + ElemGraphicsResourceDescriptor readDescriptor = ElemCreateGraphicsResourceDescriptor(buffer, ElemGraphicsResourceDescriptorUsage_Read, NULL); + + return (SampleGpuBuffer) + { + .Buffer = buffer, + .ReadDescriptor = readDescriptor + }; +} + +// TODO: To remove when IOQueues +SampleGpuBuffer SampleCreateGpuBufferAndUploadData(SampleGpuMemory* gpuMemory, const void* dataPointer, uint32_t sizeInBytes, const char* debugName) +{ + SampleGpuBuffer result = SampleCreateGpuBuffer(gpuMemory, sizeInBytes, debugName); + ElemUploadGraphicsBufferData(result.Buffer, 0, (ElemDataSpan) { .Items = (uint8_t*)dataPointer, .Length = sizeInBytes }); + + return (SampleGpuBuffer) + { + .Buffer = result.Buffer, + .ReadDescriptor = result.ReadDescriptor + }; +} + +void SampleFreeGpuBuffer(SampleGpuBuffer* gpuBuffer) +{ + ElemFreeGraphicsResourceDescriptor(gpuBuffer->ReadDescriptor, NULL); + gpuBuffer->ReadDescriptor = ELEM_HANDLE_NULL; + + ElemFreeGraphicsResource(gpuBuffer->Buffer, NULL); + gpuBuffer->Buffer = ELEM_HANDLE_NULL; +} + +SampleGpuTexture SampleCreateGpuTexture(SampleGpuMemory* gpuMemory, uint32_t width, uint32_t height, uint32_t mipLevels, ElemGraphicsFormat format, const char* debugName) +{ + ElemGraphicsResourceInfo textureDescription = ElemCreateTexture2DResourceInfo(gpuMemory->GraphicsDevice, width, height, mipLevels, format, ElemGraphicsResourceUsage_Read, &(ElemGraphicsResourceInfoOptions) { .DebugName = debugName }); + + gpuMemory->CurrentHeapOffset = SampleAlignValue(gpuMemory->CurrentHeapOffset, textureDescription.Alignment); + ElemGraphicsResource texture = ElemCreateGraphicsResource(gpuMemory->GraphicsHeap, gpuMemory->CurrentHeapOffset, &textureDescription); + gpuMemory->CurrentHeapOffset += textureDescription.SizeInBytes; + + ElemGraphicsResourceDescriptor readDescriptor = ElemCreateGraphicsResourceDescriptor(texture, ElemGraphicsResourceDescriptorUsage_Read, NULL); + + return (SampleGpuTexture) + { + .Texture = texture, + .ReadDescriptor = readDescriptor + }; +} + +void SampleFreeGpuTexture(SampleGpuTexture* gpuTexture) +{ + assert(gpuTexture); + + ElemFreeGraphicsResourceDescriptor(gpuTexture->ReadDescriptor, NULL); + gpuTexture->ReadDescriptor = ELEM_HANDLE_NULL; + + ElemFreeGraphicsResource(gpuTexture->Texture, NULL); + gpuTexture->Texture = ELEM_HANDLE_NULL; +} diff --git a/samples/Common/SampleInputs.h b/samples/Common/SampleInputs.h index 60d1b82b..32d2aebd 100644 --- a/samples/Common/SampleInputs.h +++ b/samples/Common/SampleInputs.h @@ -26,6 +26,7 @@ typedef struct uint32_t Length; } SampleInputActionBindingSpan; +// TODO: Extract ModelView into a separate file typedef struct { float TranslateLeft; @@ -52,7 +53,7 @@ typedef struct float TouchRotateSide; float Action1; - float ShowCursor; + float SwitchShowCursor; float ExitApp; } SampleStandardInputActions; @@ -67,10 +68,8 @@ void SampleRegisterInputActionBinding(SampleInputActionBindingSpan* bindings, El }; } -void SampleUpdateInputActions(SampleInputActionBindingSpan* inputActionBindings) +void SampleUpdateInputActions(SampleInputActionBindingSpan* inputActionBindings, ElemInputStream inputStream) { - ElemInputStream inputStream = ElemGetInputStream(); - for (uint32_t i = 0; i < inputActionBindings->Length; i++) { SampleInputActionBinding binding = inputActionBindings->Items[i]; @@ -85,6 +84,8 @@ void SampleUpdateInputActions(SampleInputActionBindingSpan* inputActionBindings) { ElemInputEvent* inputEvent = &inputStream.Events.Items[i]; + //printf("Input Event: %d, %f\n", inputEvent->InputId, inputEvent->Value); + for (uint32_t j = 0; j < inputActionBindings->Length; j++) { SampleInputActionBinding* binding = &inputActionBindings->Items[j]; @@ -138,7 +139,7 @@ void SampleRegisterStandardInputBindings(SampleInputActionBindingSpan* inputActi SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_KeyZ, 0, SampleInputActionBindingType_Value, &inputActions->ZoomIn); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_KeyX, 0, SampleInputActionBindingType_Value, &inputActions->ZoomOut); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_KeySpacebar, 0, SampleInputActionBindingType_Value, &inputActions->Action1); - SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_KeyF1, 0, SampleInputActionBindingType_ReleasedSwitch, &inputActions->ShowCursor); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_KeyF1, 0, SampleInputActionBindingType_ReleasedSwitch, &inputActions->SwitchShowCursor); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_KeyEscape, 0, SampleInputActionBindingType_Released, &inputActions->ExitApp); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_MouseLeftButton, 0, SampleInputActionBindingType_Value, &inputActions->Touch); @@ -158,11 +159,12 @@ void SampleRegisterStandardInputBindings(SampleInputActionBindingSpan* inputActi SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadLeftStickYPositive, 0, SampleInputActionBindingType_Value, &inputActions->TranslateUp); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadLeftStickYNegative, 0, SampleInputActionBindingType_Value, &inputActions->TranslateDown); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadLeftStickButton, 0, SampleInputActionBindingType_Value, &inputActions->Action1); - SampleRegisterInputActionBinding(inputActionBindings, ElemInputID_GamepadLeftTrigger, 0, SampleInputActionBindingType_Value, &inputActions->RotateSideLeft); - SampleRegisterInputActionBinding(inputActionBindings, ElemInputID_GamepadRightTrigger, 0, SampleInputActionBindingType_Value, &inputActions->RotateSideRight); - SampleRegisterInputActionBinding(inputActionBindings, ElemInputID_GamepadLeftShoulder, 0, SampleInputActionBindingType_Value, &inputActions->ZoomOut); - SampleRegisterInputActionBinding(inputActionBindings, ElemInputID_GamepadRightShoulder, 0, SampleInputActionBindingType_Value, &inputActions->ZoomIn); - SampleRegisterInputActionBinding(inputActionBindings, ElemInputID_GamepadButtonA, 0, SampleInputActionBindingType_Value, &inputActions->Action1); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadLeftTrigger, 0, SampleInputActionBindingType_Value, &inputActions->RotateSideLeft); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadRightTrigger, 0, SampleInputActionBindingType_Value, &inputActions->RotateSideRight); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadLeftShoulder, 0, SampleInputActionBindingType_Value, &inputActions->ZoomOut); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadRightShoulder, 0, SampleInputActionBindingType_Value, &inputActions->ZoomIn); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadButtonA, 0, SampleInputActionBindingType_Value, &inputActions->Action1); + SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_GamepadButtonB, 0, SampleInputActionBindingType_Released, &inputActions->ExitApp); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_Touch, 0, SampleInputActionBindingType_Value, &inputActions->Touch); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_TouchXNegative, 0, SampleInputActionBindingType_Value, &inputActions->TouchTranslateLeft); @@ -177,3 +179,4 @@ void SampleRegisterStandardInputBindings(SampleInputActionBindingSpan* inputActi SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_Touch, 0, SampleInputActionBindingType_Released, &inputActions->TouchReleased); SampleRegisterInputActionBinding(inputActionBindings, ElemInputId_Touch, 0, SampleInputActionBindingType_DoubleReleasedSwitch, &inputActions->Action1); } + diff --git a/samples/Common/SampleInputsApplication.h b/samples/Common/SampleInputsApplication.h new file mode 100644 index 00000000..a2bb06e5 --- /dev/null +++ b/samples/Common/SampleInputsApplication.h @@ -0,0 +1,61 @@ +#pragma once + +#include "Elemental.h" +#include "SampleInputs.h" + +typedef struct +{ + float SwitchShowCursor; + float ExitApp; +} SampleInputsApplicationActions; + +typedef struct +{ + bool ExitApplication; + bool ShowCursor; + bool HideCursor; + bool IsCursorDisplayed; +} SampleInputsApplicationState; + +typedef struct +{ + SampleInputsApplicationActions InputActions; + SampleInputActionBindingSpan InputActionBindings; + SampleInputsApplicationState State; +} SampleInputsApplication; + +void SampleInputsApplicationInit(SampleInputsApplication* inputs) +{ + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyF1, 0, SampleInputActionBindingType_Released, &inputs->InputActions.SwitchShowCursor); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyEscape, 0, SampleInputActionBindingType_Released, &inputs->InputActions.ExitApp); + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadButtonB, 0, SampleInputActionBindingType_Released, &inputs->InputActions.ExitApp); + + inputs->State.IsCursorDisplayed = true; +} + +void SampleInputsApplicationUpdate(ElemInputStream inputStream, SampleInputsApplication* inputs, float deltaTimeInSeconds) +{ + SampleInputsApplicationState* state = &inputs->State; + SampleInputsApplicationActions* inputActions = &inputs->InputActions; + + SampleUpdateInputActions(&inputs->InputActionBindings, inputStream); + + state->ExitApplication = inputActions->ExitApp; + state->ShowCursor = false; + state->HideCursor = false; + + if (inputActions->SwitchShowCursor) + { + if (!state->IsCursorDisplayed) + { + state->ShowCursor = true; + } + else + { + state->HideCursor = true; + } + + state->IsCursorDisplayed = !state->IsCursorDisplayed; + } +} diff --git a/samples/Common/SampleInputsCamera.h b/samples/Common/SampleInputsCamera.h new file mode 100644 index 00000000..7e33b8d7 --- /dev/null +++ b/samples/Common/SampleInputsCamera.h @@ -0,0 +1,188 @@ +#pragma once + +#include "Elemental.h" +#include "SampleInputs.h" +#include "SampleMath.h" + +typedef struct +{ + float MoveLeft; + float MoveRight; + float MoveForward; + float MoveBackward; + + float RotateLeft; + float RotateRight; + float RotateUp; + float RotateDown; + float RotateSideLeft; + float RotateSideRight; + + float RotateMouseLeft; + float RotateMouseRight; + float RotateMouseUp; + float RotateMouseDown; + float RotateMouse; + + float Action; +} SampleInputsCameraActions; + +typedef struct +{ + ElemVector3 Position; + ElemVector3 PositionVelocity; + ElemVector3 Rotation; + ElemVector3 RotationVelocity; +} SampleCamera; + +typedef struct +{ + SampleCamera Camera; + // TODO: Instead of having a debug camera, we could "freeze" the camera like in AW2 + SampleCamera DebugCamera; + SampleMatrix4x4 ProjectionMatrix; + SampleMatrix4x4 ViewProjMatrix; + float Action; +} SampleInputsCameraState; + +typedef struct +{ + SampleInputsCameraActions InputActions; + SampleInputActionBindingSpan InputActionBindings; + SampleInputsCameraState State; +} SampleInputsCamera; + +// TODO: Allow initial camera setup +void SampleInputsCameraInit(SampleInputsCamera* inputs) +{ + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyA, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyD, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyW, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveForward); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyS, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveBackward); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyLeftArrow, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyRightArrow, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyUpArrow, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyDownArrow, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateDown); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyQ, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyE, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideRight); + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveForward); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.MoveBackward); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadRightStickXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadRightStickXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadRightStickYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadRightStickYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateDown); + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseRightButton, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateMouse); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateMouseLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateMouseRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateMouseUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateMouseDown); + + //SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyQ, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideLeft); + //SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyE, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideRight); + + // TODO: Remove that one? + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeySpacebar, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Action); + + // TODO: Temporary Init code for now + SampleCamera* camera = &(inputs->State.Camera); + camera->Position = (ElemVector3) { 0.0f, 2.0f, -1.0f }; + camera->Rotation = (ElemVector3) { 0.0f, 1.2f, 0 }; + + // TODO: Allow the reset of camera to the passed initial position when we press a button +} + +ElemVector3 ComputeDeltaAndVelocity(ElemVector3* currentVelocity, ElemVector3 directionVector, float accelerationFactor, float frictionFactor, float deltaTimeInSeconds) +{ + ElemVector3 acceleration = SampleAddV3(SampleMulScalarV3(directionVector, accelerationFactor), SampleMulScalarV3(SampleInverseV3(*currentVelocity), frictionFactor)); + + ElemVector3 delta = SampleAddV3(SampleMulScalarV3(acceleration, 0.5f * SamplePow2f(deltaTimeInSeconds)), SampleMulScalarV3(*currentVelocity, deltaTimeInSeconds)); + *currentVelocity = SampleAddV3(SampleMulScalarV3(acceleration, deltaTimeInSeconds), *currentVelocity); + + return delta; +} + +SampleMatrix4x4 UpdateCamera(SampleCamera* camera, const SampleInputsCameraActions* inputActions, const ElemSwapChainUpdateParameters* updateParameters) +{ + ElemVector3 movementDirection = (ElemVector3) + { + .X = (inputActions->MoveRight - inputActions->MoveLeft), + .Y = 0.0f, + .Z = (inputActions->MoveForward - inputActions->MoveBackward) + }; + + ElemVector3 movementDelta = ComputeDeltaAndVelocity(&camera->PositionVelocity, movementDirection, 150.0f, 40.0f, updateParameters->DeltaTimeInSeconds); + //printf("Current Pos Vel: %f %f %f\n", camera->PositionVelocity.X, camera->PositionVelocity.Y, camera->PositionVelocity.Z); + + ElemVector3 rotationDirection = (ElemVector3) + { + .X = (inputActions->RotateUp - inputActions->RotateDown), + .Y = (inputActions->RotateLeft - inputActions->RotateRight), + .Z = 0.0f, + }; + + // TODO: Prevent X Rotate above 180 deg + ElemVector3 rotationDelta = ComputeDeltaAndVelocity(&camera->RotationVelocity, rotationDirection, 80.0f, 40.0f, updateParameters->DeltaTimeInSeconds); + + if (SampleMagnitudeSquaredV3(rotationDelta)) + { + camera->Rotation = SampleAddV3(camera->Rotation, rotationDelta); + } + + if (inputActions->RotateMouse) + { + ElemVector3 rotationMouseDelta = (ElemVector3) + { + .X = (inputActions->RotateMouseUp - inputActions->RotateMouseDown), + .Y = (inputActions->RotateMouseLeft - inputActions->RotateMouseRight), + .Z = 0.0f, + }; + + if (SampleMagnitudeSquaredV3(rotationMouseDelta)) + { + camera->Rotation = SampleAddV3(camera->Rotation, SampleMulScalarV3(rotationMouseDelta, 1.0f * updateParameters->DeltaTimeInSeconds)); + } + } + + // TODO: Review this + SampleVector4 rotationQuaternion = SampleMulQuat(SampleCreateQuaternion((ElemVector3){ 1, 0, 0 }, camera->Rotation.X), + SampleMulQuat(SampleCreateQuaternion((ElemVector3){ 0, 0, 1 }, camera->Rotation.Z), + SampleCreateQuaternion((ElemVector3){ 0, 1, 0 }, camera->Rotation.Y))); + + if (SampleMagnitudeSquaredV3(movementDelta)) + { + SampleMatrix4x4 translationTransform = SampleCreateTransformMatrix(rotationQuaternion, V3Zero); + ElemVector3 rotatedMovementDelta = SampleTransformPointV3(movementDelta, translationTransform); + camera->Position = SampleAddV3(camera->Position, rotatedMovementDelta); + } + + SampleMatrix4x4 targetTransform = SampleCreateTransformMatrix(rotationQuaternion, V3Zero); + ElemVector3 cameraTarget = SampleTransformPointV3((ElemVector3) { 0.0f, 0.0f, 1.0f }, targetTransform); + cameraTarget = SampleAddV3(cameraTarget, camera->Position); + + return SampleCreateLookAtLHMatrix(camera->Position, cameraTarget, (ElemVector3) { 0.0f, 1.0f, 0.0f }); +} + +void SampleInputsCameraUpdate(ElemInputStream inputStream, SampleInputsCamera* inputs, const ElemSwapChainUpdateParameters* updateParameters) +{ + SampleInputsCameraState* state = &inputs->State; + SampleInputsCameraActions* inputActions = &inputs->InputActions; + + SampleUpdateInputActions(&inputs->InputActionBindings, inputStream); + + // TODO: Change speed with a special "run" button + // TODO: Select debug camera when needed + SampleCamera* currentCamera = &state->Camera; + SampleMatrix4x4 viewMatrix = UpdateCamera(currentCamera, inputActions, updateParameters); + + if (updateParameters->SizeChanged || state->ProjectionMatrix.m[2][3] == 0.0f) + { + state->ProjectionMatrix = SampleCreatePerspectiveProjectionMatrix(0.78f, updateParameters->SwapChainInfo.AspectRatio, 0.001f); + } + + state->ViewProjMatrix = SampleMulMatrix4x4(viewMatrix, state->ProjectionMatrix); + state->Action = inputActions->Action; +} diff --git a/samples/Common/SampleInputsModelViewer.h b/samples/Common/SampleInputsModelViewer.h new file mode 100644 index 00000000..1f768dad --- /dev/null +++ b/samples/Common/SampleInputsModelViewer.h @@ -0,0 +1,262 @@ +#pragma once + +#include "Elemental.h" +#include "SampleInputs.h" +#include "SampleMath.h" + +#define SAMPLE_MODELVIEWER_ROTATION_TOUCH_DECREASE_SPEED 0.001f +#define SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED 4.0f +#define SAMPLE_MODELVIEWER_ROTATION_TOUCH_MAX_DELTA 0.3f +#define SAMPLE_MODELVIEWER_ROTATION_MULTITOUCH_SPEED 200.0f +#define SAMPLE_MODELVIEWER_ROTATION_ACCELERATION 500.0f +#define SAMPLE_MODELVIEWER_ROTATION_FRICTION 60.0f +#define SAMPLE_MODELVIEWER_ROTATION_ZOOM_MULTITOUCH_SPEED 1000.0f +#define SAMPLE_MODELVIEWER_ROTATION_ZOOM_SPEED 5.0f + +typedef struct +{ + float RotateLeft; + float RotateRight; + float RotateUp; + float RotateDown; + float RotateSideLeft; + float RotateSideRight; + float ZoomIn; + float ZoomOut; + + float Touch; + float TouchReleased; + float TouchRotateLeft; + float TouchRotateRight; + float TouchRotateUp; + float TouchRotateDown; + float TouchPositionX; + float TouchPositionY; + + float Touch2; + float Touch2PositionX; + float Touch2PositionY; + + float TouchRotateSide; + float Action; + + float AngularVelocity; + float AngularVelocityReleased; + float AngularVelocityXNegative; + float AngularVelocityXPositive; + float AngularVelocityYNegative; + float AngularVelocityYPositive; + float AngularVelocityZNegative; + float AngularVelocityZPositive; + + float AccelerometerZNegative; + float AccelerometerZPositive; +} SampleInputsModelViewerActions; + +typedef struct +{ + ElemVector3 RotationDelta; + SampleVector2 RotationTouch; + ElemVector3 CurrentRotationSpeed; + float PreviousTouchDistance; + float PreviousTouchAngle; + float Zoom; + float Action; + float InitialAccelerometerZDelta; +} SampleInputsModelViewerState; + +typedef struct +{ + SampleInputsModelViewerActions InputActions; + SampleInputActionBindingSpan InputActionBindings; + SampleInputsModelViewerState State; +} SampleInputsModelViewer; + +void SampleInputsModelViewerInit(SampleInputsModelViewer* inputs) +{ + // TODO: Review rotation signs + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyA, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyD, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyW, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyS, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateDown); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyQ, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyE, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyZ, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomIn); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeyX, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomOut); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_KeySpacebar, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Action); + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseLeftButton, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Touch); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseLeftButton, 0, SampleInputActionBindingType_DoubleReleasedSwitch, &inputs->InputActions.Action); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseLeftButton, 0, SampleInputActionBindingType_Released, &inputs->InputActions.TouchReleased); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseRightButton, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateSide); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseAxisYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateDown); // TODO: It seems inverted + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseWheelPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomIn); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseWheelNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomOut); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_MouseMiddleButton, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Action); + + // TODO: Be careful because it can cause side issues with the touch controls + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadButtonX, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocity); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadButtonX, 0, SampleInputActionBindingType_Released, &inputs->InputActions.AngularVelocityReleased); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AngularVelocityYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocityYNegative); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AngularVelocityYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocityYPositive); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AngularVelocityXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocityXNegative); // TODO: It seems inverted + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AngularVelocityXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocityXPositive); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AngularVelocityZNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocityZNegative); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AngularVelocityZPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AngularVelocityZPositive); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AccelerometerZNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AccelerometerZNegative); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_AccelerometerZPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.AccelerometerZPositive); + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateDown); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftStickButton, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Action); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftTrigger, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadRightTrigger, 0, SampleInputActionBindingType_Value, &inputs->InputActions.RotateSideRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadLeftShoulder, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomOut); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadRightShoulder, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomIn); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadDpadDown, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomOut); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadDpadUp, 0, SampleInputActionBindingType_Value, &inputs->InputActions.ZoomIn); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_GamepadButtonA, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Action); + + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_Touch, 0, SampleInputActionBindingType_Value, &inputs->InputActions.Touch); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchXNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateLeft); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchXPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateRight); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchYNegative, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateUp); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchYPositive, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchRotateDown); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchXAbsolutePosition, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchPositionX); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchYAbsolutePosition, 0, SampleInputActionBindingType_Value, &inputs->InputActions.TouchPositionY); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_Touch, 1, SampleInputActionBindingType_Value, &inputs->InputActions.Touch2); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchXAbsolutePosition, 1, SampleInputActionBindingType_Value, &inputs->InputActions.Touch2PositionX); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_TouchYAbsolutePosition, 1, SampleInputActionBindingType_Value, &inputs->InputActions.Touch2PositionY); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_Touch, 0, SampleInputActionBindingType_Released, &inputs->InputActions.TouchReleased); + SampleRegisterInputActionBinding(&inputs->InputActionBindings, ElemInputId_Touch, 0, SampleInputActionBindingType_DoubleReleasedSwitch, &inputs->InputActions.Action); +} + +void SampleInputsModelViewerResetTouchParameters(SampleInputsModelViewerState* state) +{ + state->RotationTouch = V2Zero; + state->PreviousTouchDistance = 0.0f; + state->PreviousTouchAngle = 0.0f; +} + +void SampleInputsModelViewerUpdate(ElemInputStream inputStream, SampleInputsModelViewer* inputs, float deltaTimeInSeconds) +{ + // TODO: Review rotation order in left handed + SampleInputsModelViewerState* state = &inputs->State; + SampleInputsModelViewerActions* inputActions = &inputs->InputActions; + state->RotationDelta = V3Zero; + + SampleUpdateInputActions(&inputs->InputActionBindings, inputStream); + + if (inputActions->Touch) + { + if (inputActions->Touch2) + { + SampleVector2 touchPosition = (SampleVector2) { inputActions->TouchPositionX, inputActions->TouchPositionY }; + SampleVector2 touchPosition2 = (SampleVector2) { inputActions->Touch2PositionX, inputActions->Touch2PositionY }; + + SampleVector2 diffVector = SampleSubstractV2(touchPosition, touchPosition2); + float distance = SampleMagnitudeV2(diffVector); + float angle = atan2(diffVector.X, diffVector.Y); + + if (state->PreviousTouchDistance != 0.0f) + { + state->Zoom += (distance - state->PreviousTouchDistance) * SAMPLE_MODELVIEWER_ROTATION_ZOOM_MULTITOUCH_SPEED * deltaTimeInSeconds; + } + + if (state->PreviousTouchAngle != 0.0f) + { + state->RotationDelta.Z = -SampleNormalizeAngle(angle - state->PreviousTouchAngle) * SAMPLE_MODELVIEWER_ROTATION_MULTITOUCH_SPEED * deltaTimeInSeconds; + } + + state->PreviousTouchDistance = distance; + state->PreviousTouchAngle = angle; + } + else + { + SampleInputsModelViewerResetTouchParameters(state); + + state->RotationDelta.X = (inputActions->TouchRotateUp - inputActions->TouchRotateDown) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; + state->RotationDelta.Y = (inputActions->TouchRotateLeft - inputActions->TouchRotateRight) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; + } + } + else if (inputActions->TouchRotateSide) + { + SampleInputsModelViewerResetTouchParameters(state); + + state->RotationDelta.Z = (inputActions->TouchRotateLeft - inputActions->TouchRotateRight) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; + } + else if (inputActions->TouchReleased && !inputActions->Touch2) + { + SampleInputsModelViewerResetTouchParameters(state); + + state->RotationTouch.X = (inputActions->TouchRotateUp - inputActions->TouchRotateDown) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; + state->RotationTouch.Y = (inputActions->TouchRotateLeft - inputActions->TouchRotateRight) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; + } + else if (inputActions->AngularVelocity) + { + state->RotationDelta.X = -(inputActions->AngularVelocityXPositive - inputActions->AngularVelocityXNegative) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; // TODO: Is it inverted? + state->RotationDelta.Y = -(inputActions->AngularVelocityYPositive - inputActions->AngularVelocityYNegative) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; // TODO: Is it inverted? + state->RotationDelta.Z = (inputActions->AngularVelocityZPositive - inputActions->AngularVelocityZNegative) * SAMPLE_MODELVIEWER_ROTATION_TOUCH_SPEED * deltaTimeInSeconds; + + // TODO: Use the accelerometer + /*float accelerometerZDelta = inputActions->AccelerometerZPositive - inputActions->AccelerometerZNegative; + + if (state->InitialAccelerometerZDelta) + { + float finalDelta = accelerometerZDelta - state->InitialAccelerometerZDelta; + printf("Zoom: final=%f, AccDelta=%f, Initial=%f\n", finalDelta, accelerometerZDelta, state->InitialAccelerometerZDelta); + state->Zoom += finalDelta * 2.0; + } + + state->InitialAccelerometerZDelta = accelerometerZDelta;*/ + } + else if (inputActions->AngularVelocityReleased) + { + state->InitialAccelerometerZDelta = 0.0f; + } + else + { + ElemVector3 direction = SampleNormalizeV3((ElemVector3) + { + .X = (inputActions->RotateUp - inputActions->RotateDown), + .Y = (inputActions->RotateLeft - inputActions->RotateRight), + .Z = (inputActions->RotateSideLeft - inputActions->RotateSideRight) + }); + + if (SampleMagnitudeSquaredV3(direction)) + { + ElemVector3 acceleration = SampleAddV3(SampleMulScalarV3(direction, SAMPLE_MODELVIEWER_ROTATION_ACCELERATION), SampleMulScalarV3(SampleInverseV3(state->CurrentRotationSpeed), SAMPLE_MODELVIEWER_ROTATION_FRICTION)); + + SampleInputsModelViewerResetTouchParameters(state); + state->RotationDelta = SampleAddV3(SampleMulScalarV3(acceleration, 0.5f * SamplePow2f(deltaTimeInSeconds)), SampleMulScalarV3(state->CurrentRotationSpeed, deltaTimeInSeconds)); + state->CurrentRotationSpeed = SampleAddV3(SampleMulScalarV3(acceleration, deltaTimeInSeconds), state->CurrentRotationSpeed); + } + } + + if (SampleMagnitudeSquaredV2(state->RotationTouch) > 0) + { + if (SampleMagnitudeV2(state->RotationTouch) > SAMPLE_MODELVIEWER_ROTATION_TOUCH_MAX_DELTA) + { + state->RotationTouch = SampleMulScalarV2(SampleNormalizeV2(state->RotationTouch), SAMPLE_MODELVIEWER_ROTATION_TOUCH_MAX_DELTA); + } + + state->RotationDelta = SampleAddV3(state->RotationDelta, (ElemVector3){ state->RotationTouch.X, state->RotationTouch.Y, 0.0f }); + + SampleVector2 inverse = SampleMulScalarV2(SampleNormalizeV2(SampleInverseV2(state->RotationTouch)), SAMPLE_MODELVIEWER_ROTATION_TOUCH_DECREASE_SPEED); + state->RotationTouch = SampleAddV2(state->RotationTouch, inverse); + + if (SampleMagnitudeV2(state->RotationTouch) < 0.001f) + { + SampleInputsModelViewerResetTouchParameters(state); + } + } + + state->Zoom += (inputActions->ZoomIn - inputActions->ZoomOut) * SAMPLE_MODELVIEWER_ROTATION_ZOOM_SPEED * deltaTimeInSeconds; + state->Action = inputActions->Action; +} diff --git a/samples/Common/SampleMath.h b/samples/Common/SampleMath.h index 5f1b51cf..7a0bf144 100644 --- a/samples/Common/SampleMath.h +++ b/samples/Common/SampleMath.h @@ -1,13 +1,25 @@ #pragma once +#include + #define _USE_MATH_DEFINES #include +#ifdef ElemToolsAPI +typedef ElemToolsVector3 ElemVector3; +typedef ElemToolsVector4 ElemVector4; +#else +#include "../Elemental/Elemental.h" +#endif + /** * WARNING: This math code is just for demonstration purpose only. All the math functions here focus * on readability and not performance. Don't use it in your production code! */ +// TODO: Review the angle functions, we are using left handed coordinate system. So positive rotation should +// be in clockwise order + float SamplePow2f(float value) { return value * value; @@ -30,11 +42,6 @@ typedef struct float X, Y; } SampleVector2; -typedef struct -{ - float X, Y, Z; -} SampleVector3; - typedef union { struct @@ -44,7 +51,7 @@ typedef union struct { - SampleVector3 XYZ; + ElemVector3 XYZ; }; struct @@ -54,7 +61,7 @@ typedef union } SampleVector4; #define V2Zero (SampleVector2) { .X = 0.0f, .Y = 0.0f } -#define V3Zero (SampleVector3) { .X = 0.0f, .Y = 0.0f, .Z = 0.0f } +#define V3Zero (ElemVector3) { .X = 0.0f, .Y = 0.0f, .Z = 0.0f } SampleVector2 SampleInverseV2(SampleVector2 v) { @@ -134,9 +141,9 @@ SampleVector2 SampleNormalizeV2(SampleVector2 v) return v; } -SampleVector3 SampleInverseV3(SampleVector3 v) +ElemVector3 SampleInverseV3(ElemVector3 v) { - SampleVector3 result; + ElemVector3 result; result.X = -v.X; result.Y = -v.Y; @@ -145,9 +152,9 @@ SampleVector3 SampleInverseV3(SampleVector3 v) return result; } -SampleVector3 SampleAddV3(SampleVector3 v1, SampleVector3 v2) +ElemVector3 SampleAddV3(ElemVector3 v1, ElemVector3 v2) { - SampleVector3 result; + ElemVector3 result; result.X = v1.X + v2.X; result.Y = v1.Y + v2.Y; @@ -156,9 +163,20 @@ SampleVector3 SampleAddV3(SampleVector3 v1, SampleVector3 v2) return result; } -SampleVector3 SampleMulScalarV3(SampleVector3 v, float scalar) +ElemVector3 SampleMinV3(ElemVector3 v1, ElemVector3 v2) { - SampleVector3 result; + ElemVector3 result; + + result.X = v1.X - v2.X; + result.Y = v1.Y - v2.Y; + result.Z = v1.Z - v2.Z; + + return result; +} + +ElemVector3 SampleMulScalarV3(ElemVector3 v, float scalar) +{ + ElemVector3 result; result.X = v.X * scalar; result.Y = v.Y * scalar; @@ -167,9 +185,9 @@ SampleVector3 SampleMulScalarV3(SampleVector3 v, float scalar) return result; } -SampleVector3 SampleDivideScalarV3(SampleVector3 v, float scalar) +ElemVector3 SampleDivideScalarV3(ElemVector3 v, float scalar) { - SampleVector3 result; + ElemVector3 result; result.X = v.X / scalar; result.Y = v.Y / scalar; @@ -178,9 +196,9 @@ SampleVector3 SampleDivideScalarV3(SampleVector3 v, float scalar) return result; } -SampleVector3 SampleMulV3(SampleVector3 v1, SampleVector3 v2) +ElemVector3 SampleMulV3(ElemVector3 v1, ElemVector3 v2) { - SampleVector3 result; + ElemVector3 result; result.X = v1.X * v2.X; result.Y = v1.Y * v2.Y; @@ -189,44 +207,44 @@ SampleVector3 SampleMulV3(SampleVector3 v1, SampleVector3 v2) return result; } -float SampleDotProductV3(SampleVector3 v1, SampleVector3 v2) +float SampleDotProductV3(ElemVector3 v1, ElemVector3 v2) { return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; } -float SampleMagnitudeSquaredV3(SampleVector3 v) +float SampleMagnitudeSquaredV3(ElemVector3 v) { return SampleDotProductV3(v, v); } -float SampleMagnitudeV3(SampleVector3 v) +float SampleMagnitudeV3(ElemVector3 v) { return sqrtf(SampleMagnitudeSquaredV3(v)); } -SampleVector3 SampleNormalizeV3(SampleVector3 v) +ElemVector3 SampleNormalizeV3(ElemVector3 v) { float magnitude = SampleMagnitudeV3(v); if (magnitude > 0.0f) { - SampleVector3 result = SampleMulScalarV3(v, (1.0f / SampleMagnitudeV3(v))); + ElemVector3 result = SampleMulScalarV3(v, (1.0f / SampleMagnitudeV3(v))); return result; } return v; } -SampleVector3 SampleCrossProductV3(SampleVector3 v1, SampleVector3 v2) +ElemVector3 SampleCrossProductV3(ElemVector3 v1, ElemVector3 v2) { - SampleVector3 result; + ElemVector3 result; result.X = v1.Y * v2.Z - v1.Z * v2.Y; result.Y = v1.Z * v2.X - v1.X * v2.Z; result.Z = v1.X * v2.Y - v1.Y * v2.X; return result; } -SampleVector4 SampleCreateQuaternion(SampleVector3 v, float w) +SampleVector4 SampleCreateQuaternion(ElemVector3 v, float w) { SampleVector4 result; @@ -254,15 +272,16 @@ SampleVector4 SampleMulQuat(SampleVector4 q1, SampleVector4 q2) return (SampleVector4) { .X = x, .Y = y, .Z = z, .W = w }; } -// TODO: Get rid of the 4x4 here and write a function that convert it to constant buffer format -typedef struct +// TODO: Get rid of this +typedef union { float m[4][4]; -} SampleMatrix3x3; + SampleVector4 Rows[4]; +} SampleMatrix4x4; -SampleMatrix3x3 SampleCreateIdentityMatrix() +SampleMatrix4x4 SampleCreateIdentityMatrix() { - SampleMatrix3x3 result; + SampleMatrix4x4 result; result.m[0][0] = 1.0f; result.m[0][1] = 0.0f; @@ -279,12 +298,62 @@ SampleMatrix3x3 SampleCreateIdentityMatrix() result.m[2][2] = 1.0f; result.m[2][3] = 0.0f; + result.m[3][0] = 0.0f; + result.m[3][1] = 0.0f; + result.m[3][2] = 0.0f; + result.m[3][3] = 1.0f; + return result; } -SampleMatrix3x3 SampleCreateRotationMatrix(float angle) +// TODO: To review +SampleMatrix4x4 SampleCreateTransformMatrix(SampleVector4 quaternion, ElemVector3 translation) { - SampleMatrix3x3 result; + float xw = quaternion.X *quaternion.W , xx = quaternion.X *quaternion.X , yy = quaternion.Y *quaternion.Y , + yw = quaternion.Y *quaternion.W , xy = quaternion.X *quaternion.Y , yz = quaternion.Y *quaternion.Z , + zw = quaternion.Z *quaternion.W , xz = quaternion.X *quaternion.Z , zz = quaternion.Z *quaternion.Z ; + + SampleMatrix4x4 result; + + result.Rows[0] = (SampleVector4) { .X = 1-2*(yy+zz), .Y = 2*(xy+zw), .Z = 2*(xz-yw), .W = 0.0f }; + result.Rows[1] = (SampleVector4) { .X = 2*(xy-zw), .Y = 1-2*(xx+zz), .Z = 2*(yz+xw), .W = 0.0f }; + result.Rows[2] = (SampleVector4) { .X = 2*(xz+yw), .Y = 2*(yz-xw), .Z = 1-2*(xx+yy), .W = 0.0f }; + result.Rows[3] = (SampleVector4) { .X = 0.0f, .Y = 0.0f, .Z = 0.0f, .W = 1.0f }; + + return result; +} + +SampleMatrix4x4 SampleTransposeMatrix(SampleMatrix4x4 matrix) +{ + SampleMatrix4x4 result; + + result.m[0][0] = matrix.m[0][0]; + result.m[0][1] = matrix.m[1][0]; + result.m[0][2] = matrix.m[2][0]; + result.m[0][3] = matrix.m[3][0]; + + result.m[1][0] = matrix.m[0][1]; + result.m[1][1] = matrix.m[1][1]; + result.m[1][2] = matrix.m[2][1]; + result.m[1][3] = matrix.m[3][1]; + + result.m[2][0] = matrix.m[0][2]; + result.m[2][1] = matrix.m[1][2]; + result.m[2][2] = matrix.m[2][2]; + result.m[2][3] = matrix.m[3][2]; + + result.m[3][0] = matrix.m[0][3]; + result.m[3][1] = matrix.m[1][3]; + result.m[3][2] = matrix.m[2][3]; + result.m[3][3] = matrix.m[3][3]; + + return result; +} + +SampleMatrix4x4 SampleCreateRotationMatrix(float angle) +{ + // BUG: Uninit values! + SampleMatrix4x4 result = {}; float c = cosf(angle); float s = sinf(angle); @@ -300,12 +369,15 @@ SampleMatrix3x3 SampleCreateRotationMatrix(float angle) result.m[2][1] = 0.0f; result.m[2][2] = 1.0f; + result.m[3][3] = 1.0f; + return result; } -SampleMatrix3x3 SampleCreateScaleMatrix(float scale) +SampleMatrix4x4 SampleCreateScaleMatrix(float scale) { - SampleMatrix3x3 result; + // BUG: Uninit values! + SampleMatrix4x4 result = {}; result.m[0][0] = scale; result.m[0][1] = 0.0f; @@ -317,46 +389,68 @@ SampleMatrix3x3 SampleCreateScaleMatrix(float scale) result.m[2][0] = 0.0f; result.m[2][1] = 0.0f; - result.m[2][2] = 1.0f; + result.m[2][2] = scale; + + result.m[3][3] = 1.0f; return result; } -SampleMatrix3x3 SampleCreateTranslationMatrix(float tx, float ty) +SampleMatrix4x4 SampleCreateTranslationMatrix(float tx, float ty) { - SampleMatrix3x3 result; + // BUG: Uninit values! + SampleMatrix4x4 result = SampleCreateIdentityMatrix(); + result.Rows[3].XY = (SampleVector2) { tx, ty }; - result.m[0][0] = 1.0f; - result.m[0][1] = 0.0f; - result.m[0][2] = tx; + return result; +} - result.m[1][0] = 0.0f; - result.m[1][1] = 1.0f; - result.m[1][2] = ty; +SampleMatrix4x4 SampleCreateLookAtLHMatrix(ElemVector3 eyePosition, ElemVector3 targetPosition, ElemVector3 upDirection) +{ + ElemVector3 forwardDirection = SampleNormalizeV3(SampleMinV3(targetPosition, eyePosition)); + ElemVector3 rightDirection = SampleNormalizeV3(SampleCrossProductV3(upDirection, forwardDirection)); + ElemVector3 upDirectionNew = SampleCrossProductV3(forwardDirection, rightDirection); - result.m[2][0] = 0.0f; - result.m[2][1] = 0.0f; - result.m[2][2] = 1.0f; + SampleMatrix4x4 result; + + result.Rows[0] = (SampleVector4) { .X = rightDirection.X, .Y = upDirectionNew.X, .Z = forwardDirection.X, .W = 0.0f }; + result.Rows[1] = (SampleVector4) { .X = rightDirection.Y, .Y = upDirectionNew.Y, .Z = forwardDirection.Y, .W = 0.0f }; + result.Rows[2] = (SampleVector4) { .X = rightDirection.Z, .Y = upDirectionNew.Z, .Z = forwardDirection.Z, .W = 0.0f }; + result.Rows[3] = (SampleVector4) { .X = -SampleDotProductV3(rightDirection, eyePosition), .Y = -SampleDotProductV3(upDirectionNew, eyePosition), .Z = -SampleDotProductV3(forwardDirection, eyePosition), .W = 1.0f }; return result; } -SampleMatrix3x3 SampleMulMatrix3x3(SampleMatrix3x3 a, SampleMatrix3x3 b) +SampleMatrix4x4 SampleCreatePerspectiveProjectionMatrix(float fovY, float aspectRatio, float zNear) { - SampleMatrix3x3 result; + float height = 1.0f / tanf(fovY * 0.5f); - for (int i = 0; i < 3; i++) + SampleMatrix4x4 result; + + result.Rows[0] = (SampleVector4) { .X = height / aspectRatio, .Y = 0.0f, .Z = 0.0f, .W = 0.0f }; + result.Rows[1] = (SampleVector4) { .X = 0.0f, .Y = height, .Z = 0.0f, .W = 0.0f }; + result.Rows[2] = (SampleVector4) { .X = 0.0f, .Y = 0.0f, .Z = 0.0f, .W = 1.0f }; + result.Rows[3] = (SampleVector4) { .X = 0.0f, .Y = 0.0f, .Z = zNear, .W = 0.0f }; + + return result; +} + +SampleMatrix4x4 SampleMulMatrix4x4(SampleMatrix4x4 a, SampleMatrix4x4 b) +{ + SampleMatrix4x4 result; + + for (uint32_t i = 0; i < 4; i++) { - for (int j = 0; j < 3; j++) + for (uint32_t j = 0; j < 4; j++) { - result.m[i][j] = a.m[i][0] * b.m[0][j] + a.m[i][1] * b.m[1][j] + a.m[i][2] * b.m[2][j]; + result.m[i][j] = a.m[i][0] * b.m[0][j] + a.m[i][1] * b.m[1][j] + a.m[i][2] * b.m[2][j] + a.m[i][3] * b.m[3][j]; } } return result; } -SampleVector2 SampleTransformPoint(SampleVector2 point, SampleMatrix3x3 m) +SampleVector2 SampleTransformPointV2(SampleVector2 point, SampleMatrix4x4 m) { SampleVector2 result; @@ -365,3 +459,15 @@ SampleVector2 SampleTransformPoint(SampleVector2 point, SampleMatrix3x3 m) return result; } + +ElemVector3 SampleTransformPointV3(ElemVector3 point, SampleMatrix4x4 m) +{ + ElemVector3 result; + + result.X = m.m[0][0] * point.X + m.m[0][1] * point.Y + m.m[0][2] * point.Z + m.m[0][3]; + result.Y = m.m[1][0] * point.X + m.m[1][1] * point.Y + m.m[1][2] * point.Z + m.m[1][3]; + result.Z = m.m[2][0] * point.X + m.m[2][1] * point.Y + m.m[2][2] * point.Z + m.m[2][3]; + + return result; +} + diff --git a/samples/Common/SampleScene.h b/samples/Common/SampleScene.h new file mode 100644 index 00000000..5b411e91 --- /dev/null +++ b/samples/Common/SampleScene.h @@ -0,0 +1,63 @@ +#pragma once + +#include "SampleMath.h" +#include + +typedef enum +{ + SampleSceneNodeType_Unknown = 0, + SampleSceneNodeType_Mesh = 1 +} SampleSceneNodeType; + +typedef struct +{ + char FileId[5]; + uint32_t MeshCount; + uint32_t NodeCount; + uint32_t MaterialCount; +} SampleSceneHeader; + +typedef struct +{ + char Name[50]; + char AlbedoTexturePath[255]; + char NormalTexturePath[255]; + ElemVector4 AlbedoFactor; +} SampleSceneMaterialHeader; + +typedef struct +{ + char Name[50]; + SampleSceneNodeType NodeType; + int32_t ReferenceIndex; + ElemVector4 Rotation; + float Scale; + ElemVector3 Translation; + // TODO: Children +} SampleSceneNodeHeader; + +typedef struct +{ + char Name[50]; + // TODO: Vertex size, etc. + uint32_t MeshPrimitiveCount; + uint32_t MeshBufferOffset; + uint32_t MeshBufferSizeInBytes; +} SampleMeshHeader; + +// TODO: Rename to mesh primitive? +typedef struct +{ + uint32_t MeshletCount; + uint32_t VertexBufferOffset; + uint32_t MeshletOffset; + uint32_t MeshletVertexIndexOffset; + uint32_t MeshletTriangleIndexOffset; + int32_t MaterialId; +} SampleMeshPrimitiveHeader; + +typedef struct +{ + uint32_t Offset; + uint32_t SizeInBytes; +} SampleDataBlockEntry; diff --git a/samples/Common/SampleSceneLoader.h b/samples/Common/SampleSceneLoader.h new file mode 100644 index 00000000..5b1ebf46 --- /dev/null +++ b/samples/Common/SampleSceneLoader.h @@ -0,0 +1,350 @@ +#pragma once + +#include "Elemental.h" +#include "SampleUtils.h" +#include "SampleScene.h" +#include "SampleTexture.h" +#include "SampleGpuMemory.h" + +// TODO: The struct here are temporary. The final data will be placed in buffer +typedef struct +{ + const char* Path; + SampleMeshHeader MeshHeader; + SampleMeshPrimitiveHeader* MeshPrimitives; + SampleGpuBuffer MeshBuffer; +} SampleMeshData; + +typedef struct +{ + const char* Path; + SampleTextureHeader TextureHeader; + SampleTextureDataBlockEntry* MipDataEntries; + SampleGpuTexture GpuTexture; + bool IsLoaded; +} SampleTextureData; + +typedef struct +{ + SampleSceneMaterialHeader MaterialHeader; + SampleTextureData* AlbedoTexture; + SampleTextureData* NormalTexture; +} SampleMaterialData; + +// TODO: Do otherwise +typedef struct +{ + int32_t AlbedoTextureId; + int32_t NormalTextureId; + ElemVector4 AlbedoFactor; +} ShaderMaterial; + +typedef struct +{ + uint32_t MeshCount; + SampleMeshData* Meshes; + uint32_t MaterialCount; + SampleMaterialData* Materials; + uint32_t NodeCount; + SampleSceneNodeHeader* Nodes; + SampleGpuBuffer MaterialBuffer; +} SampleSceneData; + +// TODO: Change that. For now we use that simple implementation +#define MAX_TEXTURE_COUNT 2048 +SampleTextureData TextureCache[MAX_TEXTURE_COUNT]; +uint32_t CurrentTextureCacheIndex = 0u; + +void SampleLoadMesh(const char* path, uint32_t offset, SampleMeshData* meshData) +{ + *meshData = (SampleMeshData){}; + meshData->Path = path; + + FILE* file = SampleOpenFile(path, true); + assert(file); + + if (offset > 0) + { + fseek(file, offset, SEEK_SET); + } + + fread(&meshData->MeshHeader, sizeof(SampleMeshHeader), 1, file); + + // TODO: Can we get rid of the malloc? + + meshData->MeshPrimitives = (SampleMeshPrimitiveHeader*)malloc(sizeof(SampleMeshPrimitiveHeader) * meshData->MeshHeader.MeshPrimitiveCount); + fread(meshData->MeshPrimitives, sizeof(SampleMeshPrimitiveHeader), meshData->MeshHeader.MeshPrimitiveCount, file); + + fclose(file); +} + +// TODO: We should be able to replace this whole function with IO Queues +void SampleLoadMeshData(ElemCommandList commandList, SampleMeshData* meshData, SampleGpuMemory* gpuMemory) +{ + // TODO: Construct debug name + meshData->MeshBuffer = SampleCreateGpuBuffer(gpuMemory, meshData->MeshHeader.MeshBufferSizeInBytes, meshData->MeshHeader.Name); + + // TODO: To replace with file IO + assert(meshData->Path); + uint8_t* meshBufferData = (uint8_t*)malloc(sizeof(uint8_t) * meshData->MeshHeader.MeshBufferSizeInBytes); + + FILE* file = SampleOpenFile(meshData->Path, true); + assert(file); + + fseek(file, meshData->MeshHeader.MeshBufferOffset, SEEK_SET); + fread(meshBufferData, sizeof(uint8_t), meshData->MeshHeader.MeshBufferSizeInBytes, file); + fclose(file); + + ElemCopyDataToGraphicsResourceParameters copyParameters = + { + .Resource = meshData->MeshBuffer.Buffer, + .SourceType = ElemCopyDataSourceType_Memory, + .SourceMemoryData = { .Items = (uint8_t*)meshBufferData, .Length = meshData->MeshHeader.MeshBufferSizeInBytes } + }; + + ElemCopyDataToGraphicsResource(commandList, ©Parameters); + free(meshBufferData); +} + +void SampleFreeMesh(SampleMeshData* meshData) +{ + if(meshData->MeshBuffer.Buffer != ELEM_HANDLE_NULL) + { + SampleFreeGpuBuffer(&meshData->MeshBuffer); + } + + // TODO: Free mallocs +} + +void SampleLoadTexture(const char* path, SampleTextureData** textureDataPointer, SampleGpuMemory* gpuMemory, bool isSrgb) +{ + // TODO: Here we need to use a global list of texture that are unique based on the path + // For now the same texture is loaded multiple time which is really bad + + // TODO: Don't do that, we can pre compute the list of unique textures in the compiler + // and replace the reference in each materials + for (uint32_t i = 0; i < CurrentTextureCacheIndex; i++) + { + SampleTextureData* cacheTexture = &TextureCache[i]; + + if (strstr(cacheTexture->Path, path)) + { + printf("Found texture in cache!\n"); + *textureDataPointer = cacheTexture; + return; + } + } + + + if (CurrentTextureCacheIndex + 1 > MAX_TEXTURE_COUNT - 1) + { + printf("Texture cache at max!!!\n"); + } + + *textureDataPointer = &TextureCache[CurrentTextureCacheIndex++]; + SampleTextureData* textureData = *textureDataPointer; + *textureData = (SampleTextureData){}; + + printf("Loading Texture: %s\n", path); + // TODO: Do better here + char* tmp = malloc(strlen(path) + 1); + strcpy(tmp, path); + textureData->Path = tmp; + + FILE* file = SampleOpenFile(path, true); + + if (!file) + { + printf("NO TEXT: %s\n", path); + return; + } + assert(file); + + fread(&textureData->TextureHeader, sizeof(SampleTextureHeader), 1, file); + + textureData->MipDataEntries = (SampleTextureDataBlockEntry*)malloc(sizeof(SampleTextureDataBlockEntry) * textureData->TextureHeader.MipCount); + fread(textureData->MipDataEntries, sizeof(SampleTextureDataBlockEntry), textureData->TextureHeader.MipCount, file); + + fclose(file); + + // TODO: For now we create the texture here but later, we should do it on the fly and update the material buffer + // TODO: Get texture name without folder and extension + // TODO: Get the correct format + + // TODO: We need to take into account the format of the texture + ElemGraphicsFormat format = isSrgb ? ElemGraphicsFormat_BC7_SRGB : ElemGraphicsFormat_BC7; + textureData->GpuTexture = SampleCreateGpuTexture(gpuMemory, textureData->TextureHeader.Width, textureData->TextureHeader.Height, textureData->TextureHeader.MipCount, format, textureData->Path); +} + +// TODO: In the future we will need to load individual mips from disk +void SampleLoadTextureData(ElemCommandList commandList, SampleTextureData* textureData, SampleGpuMemory* gpuMemory) +{ + assert(textureData->Path); + + // TODO: Replace that with file logic + for (uint32_t i = 0; i < textureData->TextureHeader.MipCount; i++) + { + SampleTextureDataBlockEntry mipEntry = textureData->MipDataEntries[i]; + uint8_t* mipData = (uint8_t*)malloc(sizeof(uint8_t) * mipEntry.SizeInBytes); + + FILE* file = SampleOpenFile(textureData->Path, true); + assert(file); + + fseek(file, mipEntry.Offset, SEEK_SET); + fread(mipData, sizeof(uint8_t), mipEntry.SizeInBytes, file); + fclose(file); + + ElemCopyDataToGraphicsResourceParameters copyParameters = + { + .Resource = textureData->GpuTexture.Texture, + .TextureMipLevel = i, + .SourceType = ElemCopyDataSourceType_Memory, + .SourceMemoryData = { .Items = (uint8_t*)mipData, .Length = mipEntry.SizeInBytes } + }; + + ElemCopyDataToGraphicsResource(commandList, ©Parameters); + free(mipData); + } + + // TODO: This is not true, the data will be loaded when the command list is executed + textureData->IsLoaded = true; +} + +void SampleFreeTexture(SampleTextureData* textureData) +{ + if (textureData->GpuTexture.Texture != ELEM_HANDLE_NULL) + { + SampleFreeGpuTexture(&textureData->GpuTexture); + } + // TODO: Free mallocs +} + +void SampleLoadMaterial(const SampleSceneMaterialHeader* materialHeader, SampleMaterialData* materialData, ShaderMaterial* shaderMaterial, SampleGpuMemory* gpuMemory, const char* directoryPath) +{ + *materialData = (SampleMaterialData){}; + materialData->MaterialHeader = *materialHeader; + + shaderMaterial->AlbedoFactor = materialData->MaterialHeader.AlbedoFactor; + shaderMaterial->AlbedoTextureId = -1; + shaderMaterial->NormalTextureId = -1; + + printf("Loading Material: %s\n", materialData->MaterialHeader.Name); + + // TODO: Some materials can use the same texture so we need to load it only once + if (strlen(materialHeader->AlbedoTexturePath) > 0) + { + // TODO: Refactor that + char fullTexturePath[MAX_PATH]; + memset(fullTexturePath, 0, MAX_PATH); + strcpy(fullTexturePath, directoryPath); + strcat(fullTexturePath, materialHeader->AlbedoTexturePath); + + SampleLoadTexture(fullTexturePath, &materialData->AlbedoTexture, gpuMemory, true); + shaderMaterial->AlbedoTextureId = materialData->AlbedoTexture->GpuTexture.ReadDescriptor; + } + + if (strlen(materialHeader->NormalTexturePath) > 0) + { + char fullTexturePath[MAX_PATH]; + memset(fullTexturePath, 0, MAX_PATH); + strcpy(fullTexturePath, directoryPath); + strcat(fullTexturePath, materialHeader->NormalTexturePath); + + SampleLoadTexture(fullTexturePath, &materialData->NormalTexture, gpuMemory, false); + shaderMaterial->NormalTextureId = materialData->NormalTexture->GpuTexture.ReadDescriptor; + } +} + +void SampleFreeMaterial(SampleMaterialData* materialData) +{ + if (materialData->AlbedoTexture && materialData->AlbedoTexture->IsLoaded) + { + SampleFreeTexture(materialData->AlbedoTexture); + materialData->AlbedoTexture->IsLoaded = false; + } + + if (materialData->NormalTexture && materialData->NormalTexture->IsLoaded) + { + SampleFreeTexture(materialData->NormalTexture); + materialData->NormalTexture->IsLoaded = false; + } +} + +void SampleLoadScene(const char* path, SampleSceneData* sceneData, SampleGpuMemory* gpuMemory) +{ + // TODO: When IOQueues are implemented, we only need to read the header, not the whole file! + + FILE* file = SampleOpenFile(path, true); + assert(file); + + SampleSceneHeader sceneHeader; + fread(&sceneHeader, sizeof(sceneHeader), 1, file); + + if (!(sceneHeader.FileId[0] == 'S' && sceneHeader.FileId[1] == 'C' && sceneHeader.FileId[2] == 'E' && sceneHeader.FileId[3] == 'N' && sceneHeader.FileId[4] == 'E')) + { + printf("ERROR: Wrong scene format\n"); + } + + printf("Scene Loaded: Meshes Count=%d, Material Count=%d, Nodes Count=%d\n", sceneHeader.MeshCount, sceneHeader.MaterialCount, sceneHeader.NodeCount); + + sceneData->MeshCount = sceneHeader.MeshCount; + sceneData->MaterialCount = sceneHeader.MaterialCount; + sceneData->NodeCount = sceneHeader.NodeCount; + + // TODO: Replace malloc with an utility function that we will replace + sceneData->Meshes = (SampleMeshData*)malloc(sizeof(SampleMeshData) * sceneHeader.MeshCount); + + SampleDataBlockEntry* meshDataBlocks = (SampleDataBlockEntry*)malloc(sizeof(SampleDataBlockEntry) * sceneHeader.MeshCount); + fread(meshDataBlocks, sizeof(SampleDataBlockEntry), sceneHeader.MeshCount, file); + + sceneData->Materials = (SampleMaterialData*)malloc(sizeof(SampleMaterialData) * sceneHeader.MaterialCount); + SampleSceneMaterialHeader* materialHeaders = (SampleSceneMaterialHeader*)malloc(sizeof(SampleSceneMaterialHeader) * sceneHeader.MaterialCount); + fread(materialHeaders, sizeof(SampleSceneMaterialHeader), sceneHeader.MaterialCount, file); + + sceneData->Nodes = (SampleSceneNodeHeader*)malloc(sizeof(SampleSceneNodeHeader) * sceneHeader.NodeCount); + fread(sceneData->Nodes, sizeof(SampleSceneNodeHeader), sceneHeader.NodeCount, file); + + fclose(file); + + for (uint32_t i = 0; i < sceneData->MeshCount; i++) + { + SampleLoadMesh(path, meshDataBlocks[i].Offset, &sceneData->Meshes[i]); + } + + char directoryPath[MAX_PATH]; + GetFileDirectory(path, directoryPath, MAX_PATH); + + if (sceneHeader.MaterialCount > 0) + { + ShaderMaterial* shaderMaterials = (ShaderMaterial*)malloc(sizeof(ShaderMaterial) * sceneHeader.MaterialCount); + + for (uint32_t i = 0; i < sceneData->MaterialCount; i++) + { + SampleLoadMaterial(&materialHeaders[i], &sceneData->Materials[i], &shaderMaterials[i], gpuMemory, directoryPath); + } + + sceneData->MaterialBuffer = SampleCreateGpuBufferAndUploadData(gpuMemory, shaderMaterials, sceneHeader.MaterialCount * sizeof(ShaderMaterial), "MaterialBuffer"); + free(shaderMaterials); + } + + free(materialHeaders); + free(meshDataBlocks); +} + +void SampleFreeScene(SampleSceneData* sceneData) +{ + for (uint32_t i = 0; i < sceneData->MeshCount; i++) + { + SampleFreeMesh(&sceneData->Meshes[i]); + } + + for (uint32_t i = 0; i < sceneData->MaterialCount; i++) + { + SampleFreeMaterial(&sceneData->Materials[i]); + } + + if (sceneData->MaterialBuffer.Buffer != ELEM_HANDLE_NULL) + { + SampleFreeGpuBuffer(&sceneData->MaterialBuffer); + } +} diff --git a/samples/Common/SampleTexture.h b/samples/Common/SampleTexture.h new file mode 100644 index 00000000..e9ec7d0b --- /dev/null +++ b/samples/Common/SampleTexture.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef enum +{ + SampleTextureFormat_Unknown = 0, + SampleTextureFormat_BC7 = 1 +} SampleTextureFormat; + +typedef struct +{ + char FileId[7]; + SampleTextureFormat Format; + uint32_t Width; + uint32_t Height; + uint32_t MipCount; +} SampleTextureHeader; + +typedef struct +{ + uint32_t Offset; + uint32_t SizeInBytes; +} SampleTextureDataBlockEntry; diff --git a/samples/Common/SampleUtils.h b/samples/Common/SampleUtils.h index 184b33b0..2aeab7c4 100644 --- a/samples/Common/SampleUtils.h +++ b/samples/Common/SampleUtils.h @@ -1,8 +1,16 @@ #pragma once +#include #include #include -#include "Elemental.h" +#include +#include + +#ifdef ElemToolsAPI +typedef ElemToolsDataSpan ElemDataSpan; +#else +#include "../Elemental/Elemental.h" +#endif #ifndef _WIN32 #define MAX_PATH 255 @@ -21,82 +29,81 @@ uint64_t SampleMegaBytesToBytes(uint64_t value) return value * 1024 * 1024; } -uint64_t SampleGygaBytesToBytes(uint64_t value) +uint64_t SampleGigaBytesToBytes(uint64_t value) { return SampleMegaBytesToBytes(value) * 1024; } -// ----------------------------------------------------------------------------- -// I/O Functions -// ----------------------------------------------------------------------------- -void CopyString(char* destination, uint32_t destinationLength, const char* source, uint32_t sourceLength) +uint32_t SampleAlignValue(uint32_t value, uint32_t alignment) { - #ifdef _WIN32 - strncpy_s(destination, destinationLength, source, sourceLength); - #else - strncpy(destination, source, sourceLength); - #endif + return (value + alignment - 1) & ~(alignment - 1); } -void SampleGetFullPath(char* destination, const char* path) +// ----------------------------------------------------------------------------- +// I/O Functions +// ----------------------------------------------------------------------------- +void SampleGetFullPath(char* destination, const char* path, bool prefixData) { + // TODO: Pass destination length memset(destination, 0, MAX_PATH); - char* pointer = NULL; + char* pointer = destination; + + #ifndef ElemToolsAPI ElemSystemInfo systemInfo = ElemGetSystemInfo(); - CopyString(destination, MAX_PATH, systemInfo.ApplicationPath, strlen(systemInfo.ApplicationPath)); + strncpy(destination, systemInfo.ApplicationPath, strlen(systemInfo.ApplicationPath)); pointer = destination + strlen(systemInfo.ApplicationPath); - const char* folderPrefix = "Data/"; + const char* folderPrefix = "./"; - if (systemInfo.Platform == ElemPlatform_MacOS) + if (prefixData) { - folderPrefix = "../Resources/"; - } - else if (systemInfo.Platform == ElemPlatform_iOS) + if (systemInfo.Platform == ElemPlatform_MacOS) + { + folderPrefix = "../Resources/"; + } + else if (systemInfo.Platform == ElemPlatform_iOS) + { + folderPrefix = "./"; + } + else { - folderPrefix = "./"; + folderPrefix = "Data/"; + } } - CopyString(pointer, pointer - destination, folderPrefix, strlen(folderPrefix)); + strncpy(pointer, folderPrefix, strlen(folderPrefix)); pointer = pointer + strlen(folderPrefix); + #endif - CopyString(pointer, pointer - destination, path, strlen(path)); + strncpy(pointer, path, strlen(path)); } -ElemDataSpan SampleReadFile(const char* filename) +FILE* SampleOpenFile(const char* filename, bool prefixData) { char absolutePath[MAX_PATH]; - SampleGetFullPath(absolutePath, filename); + SampleGetFullPath(absolutePath, filename, prefixData); - printf("Read path: %s\n", absolutePath); + //printf("Read path: '%s'\n", absolutePath); - #ifdef _WIN32 - FILE* file; - fopen_s(&file, absolutePath, "rb"); - #else - FILE* file = fopen(absolutePath, "rb"); - #endif + return fopen(absolutePath, "rb"); +} +// TODO: To remove? +ElemDataSpan SampleReadFile(const char* filename, bool prefixData) +{ + FILE* file = SampleOpenFile(filename, prefixData); + if (file == NULL) { - return (ElemDataSpan) - { - .Items = NULL, - .Length = 0 - }; + return (ElemDataSpan) {}; } if (fseek(file, 0, SEEK_END) != 0) { fclose(file); - - return (ElemDataSpan) - { - .Items = NULL, - .Length = 0 - }; + return (ElemDataSpan) {}; } long fileSize = ftell(file); @@ -104,27 +111,18 @@ ElemDataSpan SampleReadFile(const char* filename) if (fileSize == -1) { fclose(file); - - return (ElemDataSpan) - { - .Items = NULL, - .Length = 0 - }; + return (ElemDataSpan) {}; } rewind(file); uint8_t* buffer = (uint8_t*)malloc(fileSize + 1); + memset(buffer, 0, fileSize + 1); if (buffer == NULL) { fclose(file); - - return (ElemDataSpan) - { - .Items = NULL, - .Length = 0 - }; + return (ElemDataSpan) {}; } size_t bytesRead = fread(buffer, 1, fileSize, file); @@ -133,12 +131,7 @@ ElemDataSpan SampleReadFile(const char* filename) { free(buffer); fclose(file); - - return (ElemDataSpan) - { - .Items = NULL, - .Length = 0 - }; + return (ElemDataSpan) {}; } fclose(file); @@ -150,10 +143,105 @@ ElemDataSpan SampleReadFile(const char* filename) }; } +// TODO: Remove those? +int SampleWriteDataToFile(const char* filename, ElemDataSpan data, bool append) +{ + const char* fileMode = append ? "ab" : "wb"; + + FILE* file = fopen(filename, fileMode); + + if (file == NULL) + { + printf("ERROR 2\n"); + return -1; + } + + size_t bytesWritten = fwrite(data.Items, 1, data.Length, file); + fclose(file); + + if (bytesWritten < data.Length) + { + printf("ERROR 3: %zu\n", bytesWritten); + return -1; // Return -1 if not all bytes were written + } + + return 0; // Success +} + +int SampleWriteDataToApplicationFile(const char* filename, ElemDataSpan data, bool append) +{ + char absolutePath[MAX_PATH]; + SampleGetFullPath(absolutePath, filename, false); + + return SampleWriteDataToFile(absolutePath, data, append); +} + +// TODO: Add Sample prefix +void ReplaceFileExtension(const char* path, const char* extension, char* destination, uint32_t destinationSize) +{ + assert(destinationSize >= strlen(path)); + memset(destination, 0, destinationSize); + + const char* extensionSeparator = strrchr(path, '.'); + uint32_t prefixLength = extensionSeparator ? (extensionSeparator - path) : strlen(path); + + for (uint32_t i = 0; i < prefixLength; i++) + { + destination[i] = path[i] == '\\' ? '/' : path[i]; + } + + strncat(destination, extension, strlen(path)); +} + +void GetFileDirectory(const char* path, char* destination, uint32_t destinationSize) +{ + assert(destinationSize >= strlen(path)); + memset(destination, 0, destinationSize); + + for (uint32_t i = 0; i < strlen(path); i++) + { + destination[i] = path[i] == '\\' ? '/' : path[i]; + } + + char* lastSeparator = strrchr(destination, '/'); + + if (lastSeparator) + { + *(lastSeparator + 1) = '\0'; + } + else + { + destination[0] = '\0'; + } +} + +void GetRelativeResourcePath(const char* mainPath, const char* path, const char* extension, char* destination, uint32_t destinationSize) +{ + assert(destinationSize >= strlen(path)); + memset(destination, 0, destinationSize); + + char mainDirectory[destinationSize]; + GetFileDirectory(mainPath, mainDirectory, destinationSize); + + char resourcePath[MAX_PATH]; + ReplaceFileExtension(path, extension, resourcePath, sizeof(resourcePath)); + + size_t mainDirectoryLength = strlen(mainDirectory); + const char* startPath = resourcePath; + + if (strncmp(resourcePath, mainDirectory, mainDirectoryLength) == 0) + { + startPath = resourcePath + mainDirectoryLength; + } + + strncpy(destination, startPath, strlen(resourcePath) - (startPath - resourcePath)); +} + // ----------------------------------------------------------------------------- // UI Functions // ----------------------------------------------------------------------------- +#ifdef ElemAPI const char* SampleGetPlatformLabel(ElemPlatform platform) { switch (platform) @@ -231,6 +319,7 @@ void SampleSetWindowTitle(ElemWindow window, const char* applicationName, ElemGr memoryFormatted); ElemSetWindowTitle(window, titleFormatted); } +#endif // ----------------------------------------------------------------------------- // Timing Functions @@ -313,3 +402,70 @@ SampleFrameMeasurement SampleEndFrameMeasurement(void) .HasNewData = newData }; } + + +// ----------------------------------------------------------------------------- +// Settings Functions +// ----------------------------------------------------------------------------- + +typedef struct +{ + bool PreferVulkan; + bool PreferFullScreen; + bool DisableDiagnostics; +} SampleAppSettings; + +SampleAppSettings SampleParseAppSettings(int argc, const char* argv[]) +{ + SampleAppSettings appSettings = {}; + + for (int32_t i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--vulkan") == 0) + { + appSettings.PreferVulkan = true; + } + + if (strcmp(argv[i], "--fullscreen") == 0) + { + appSettings.PreferFullScreen = true; + } + + if (strcmp(argv[i], "--disable-diagnostics") == 0) + { + appSettings.DisableDiagnostics = true; + } + } + + return appSettings; +} + +#ifdef ElemToolsAPI +void DisplayOutputMessages(const char* prefix, ElemToolsMessageSpan messages) +{ + for (uint32_t i = 0; i < messages.Length; i++) + { + ElemToolsMessage* message = &messages.Items[i]; + + printf("["); + printf("\033[36m%s\033[0m]", prefix); + + switch (message->Type) + { + case ElemToolsMessageType_Error: + printf("\033[31m Error:"); + break; + + case ElemToolsMessageType_Warning: + printf("\033[33m Warning:"); + break; + + default: + printf("\033[0m"); + } + + printf(" %s\033[0m\n", message->Message); + fflush(stdout); + } +} +#endif diff --git a/samples/Demos/01-Renderer/CMakeLists.txt b/samples/Demos/01-Renderer/CMakeLists.txt new file mode 100644 index 00000000..aa9a059e --- /dev/null +++ b/samples/Demos/01-Renderer/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SAMPLE_NAME Renderer) + +# TODO: Download assets etc. + +add_executable(${SAMPLE_NAME} main.c Data) +target_link_libraries(${SAMPLE_NAME} PRIVATE SampleCommon) +target_link_libraries(${SAMPLE_NAME} PRIVATE ElementalInterface) +add_dependencies(${SAMPLE_NAME} SampleSharedData) + +# TODO: Allow passing parameters per data file + +configure_resource_compilation(${SAMPLE_NAME} resource_list) +configure_project_package(${SAMPLE_NAME} "samples" DEPENDENCIES Elemental RESOURCES ${resource_list}) diff --git a/samples/Demos/01-Renderer/Data/RenderMesh.hlsl b/samples/Demos/01-Renderer/Data/RenderMesh.hlsl new file mode 100644 index 00000000..92ba082f --- /dev/null +++ b/samples/Demos/01-Renderer/Data/RenderMesh.hlsl @@ -0,0 +1,204 @@ + +// TODO: Shader parameters here are temporary +struct ShaderParameters +{ + uint32_t FrameDataBufferIndex; + uint32_t MeshBuffer; + uint32_t MaterialBuffer; + uint32_t VertexBufferOffset; + uint32_t MeshletOffset; + uint32_t MeshletVertexIndexOffset; + uint32_t MeshletTriangleIndexOffset; + float Scale; + float3 Translation; + uint32_t Reserved; + float4 Rotation; + uint32_t MaterialId; + uint32_t TextureSampler; +}; + +[[vk::push_constant]] +ShaderParameters parameters : register(b0); + +struct FrameData +{ + float4x4 ViewProjMatrix; + uint32_t ShowMeshlets; +}; + +typedef struct +{ + int32_t AlbedoTextureId; + int32_t NormalTextureId; + float4 AlbedoFactor; +} ShaderMaterial; + +struct ElemMeshlet +{ + uint32_t VertexIndexOffset; + uint32_t VertexIndexCount; + uint32_t TriangleOffset; + uint32_t TriangleCount; +}; + +// Compress Data +struct Vertex +{ + float3 Position; + float3 Normal; + float4 Tangent; + float2 TextureCoordinates; +}; + +struct VertexOutput +{ + float4 Position: SV_Position; + float3 WorldNormal: Attribute0; + float4 Tangent: Attribute5; + float2 TextureCoordinates: Attribute1; + nointerpolation uint MeshletIndex: Attribute2; + nointerpolation uint MaterialId: Attribute3; +}; + +#define IDENTITY_MATRIX float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) + +float3 RotateQuaternion(float3 v, float4 q) +{ + return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); +} + +[shader("mesh")] +[OutputTopology("triangle")] +[NumThreads(126, 1, 1)] +void MeshMain(in uint groupId: SV_GroupID, + in uint groupThreadId: SV_GroupThreadID, + out vertices VertexOutput vertices[64], + out indices uint3 indices[126]) +{ + uint meshletIndex = groupId; + + ByteAddressBuffer meshBuffer = ResourceDescriptorHeap[parameters.MeshBuffer]; + + ElemMeshlet meshlet = meshBuffer.Load(parameters.MeshletOffset + meshletIndex * sizeof(ElemMeshlet)); + + SetMeshOutputCounts(meshlet.VertexIndexCount, meshlet.TriangleCount); + + if (groupThreadId < meshlet.VertexIndexCount) + { + ByteAddressBuffer frameDataBuffer = ResourceDescriptorHeap[parameters.FrameDataBufferIndex]; + FrameData frameData = frameDataBuffer.Load(0); + + uint vertexIndex = meshBuffer.Load(parameters.MeshletVertexIndexOffset + (meshlet.VertexIndexOffset + groupThreadId) * sizeof(uint)); + Vertex vertex = meshBuffer.Load(parameters.VertexBufferOffset + vertexIndex * sizeof(Vertex)); + + float3 worldPosition = RotateQuaternion(vertex.Position, parameters.Rotation) * parameters.Scale + parameters.Translation; + float3 worldNormal = RotateQuaternion(vertex.Normal, parameters.Rotation); + float3 worldTangent = RotateQuaternion(vertex.Tangent.xyz, parameters.Rotation); + + //vertices[groupThreadId].Position = mul(float4(vertex.Position, 1.0), mul(worldMatrix, frameData.ViewProjMatrix)); + // NOTE: This calculation is faster because v * M is faster than M * M + vertices[groupThreadId].Position = mul(float4(worldPosition, 1.0), frameData.ViewProjMatrix); + vertices[groupThreadId].WorldNormal = worldNormal; + vertices[groupThreadId].Tangent = float4(worldTangent, vertex.Tangent.w); + vertices[groupThreadId].TextureCoordinates = vertex.TextureCoordinates; + vertices[groupThreadId].MeshletIndex = groupId; + vertices[groupThreadId].MaterialId = parameters.MaterialId; + } + + if (groupThreadId < meshlet.TriangleCount) + { + uint triangleIndex = meshBuffer.Load(parameters.MeshletTriangleIndexOffset + (meshlet.TriangleOffset + groupThreadId) * sizeof(uint)); + + indices[groupThreadId] = unpack_u8u32(triangleIndex).xyz; + } +} + +uint hash(uint a) +{ + a = (a+0x7ed55d16) + (a<<12); + a = (a^0xc761c23c) ^ (a>>19); + a = (a+0x165667b1) + (a<<5); + a = (a+0xd3a2646c) ^ (a<<9); + a = (a+0xfd7046c5) + (a<<3); + a = (a^0xb55a4f09) ^ (a>>16); + + return a; +} + +[shader("pixel")] +float4 PixelMain(const VertexOutput input) : SV_Target0 +{ + // TODO: Get the framedata only in the mesh shader? + ByteAddressBuffer frameDataBuffer = ResourceDescriptorHeap[parameters.FrameDataBufferIndex]; + FrameData frameData = frameDataBuffer.Load(0); + + ByteAddressBuffer materialBuffer = ResourceDescriptorHeap[parameters.MaterialBuffer]; + ShaderMaterial material = materialBuffer.Load(parameters.MaterialId * sizeof(ShaderMaterial)); + + SamplerState textureSampler = SamplerDescriptorHeap[parameters.TextureSampler]; + + if (frameData.ShowMeshlets == 0 && parameters.MaterialId >= 0) + { + float3 worldNormal = input.WorldNormal; + float4 albedo = float4(1, 1, 1, 1); + + if (material.AlbedoTextureId >= 0) + { + // TODO: Should we use non uniform index here? We know we have the same material for each meshlet. + // But we sometimes may group some meshlets together + Texture2D albedoTexture = ResourceDescriptorHeap[NonUniformResourceIndex(material.AlbedoTextureId)]; + albedo = albedoTexture.Sample(textureSampler, input.TextureCoordinates) * material.AlbedoFactor; + + // TODO: Doing discard on the main pass is really bad for performance. + // Doing it disable the early depth test in the shader, so all pixel shader code has to run for + // occluded pixels. + // TODO: We will need to process transparent objects in another path in another shader + if (albedo.a < 0.5) + { + discard; + } + + // return albedo; + } + + if (material.NormalTextureId >= 0) + { + Texture2D normalTexture = ResourceDescriptorHeap[NonUniformResourceIndex(material.NormalTextureId)]; + float3 normalMap = normalTexture.Sample(textureSampler, input.TextureCoordinates).rgb * 2.0 - 1.0; + //return float4(normalMap, 1); + + float3 bitangent = cross(worldNormal, input.Tangent.xyz) * input.Tangent.w; + worldNormal = normalize(normalMap.x * input.Tangent.xyz + normalMap.y * bitangent + normalMap.z * worldNormal); + + //float3 surfaceGradient = float3(TspaceNormalToDerivative(normalMap), 0); + //worldNormal = ResolveNormalFromSurfaceGradient(worldNormal, surfaceGradient); + } + + float nDotL = max(dot(worldNormal, normalize(float3(-1.0, 1.0, 1.0))), 0.0); + float ambient = 0.05; + + return albedo * (nDotL + ambient); + return float4(worldNormal * 0.5 + 0.5, 1.0); + + if (input.Tangent.w > 0) + { + return float4(1, 0, 0, 1); + } + else + { + return float4(0, 1, 0, 1); + } + + //return float4(0, 0, 1, 1);; + //return float4(input.Tangent * 0.5 + 0.5, 1.0); + } + else + { + //uint hashResult = hash(input.MeshletIndex); + uint hashResult = hash(input.MaterialId); + float3 meshletColor = float3(float(hashResult & 255), float((hashResult >> 8) & 255), float((hashResult >> 16) & 255)) / 255.0; + + return float4(input.WorldNormal * 0.5 + 0.5, 1.0); + return float4(meshletColor, 1.0); + } +} diff --git a/samples/Demos/01-Renderer/main.c b/samples/Demos/01-Renderer/main.c new file mode 100644 index 00000000..4129d818 --- /dev/null +++ b/samples/Demos/01-Renderer/main.c @@ -0,0 +1,420 @@ +#include "Elemental.h" +#include "SampleUtils.h" +#include "SampleMath.h" +#include "SampleInputsApplication.h" +#include "SampleInputsCamera.h" +#include "SampleSceneLoader.h" +#include "SampleGpuMemory.h" + +// TODO: Share data between shader and C code + +typedef struct +{ + uint32_t FrameDataBuffer; + // TODO: Embed that into a buffer + uint32_t MeshBuffer; + uint32_t MaterialBuffer; + uint32_t VertexBufferOffset; + uint32_t MeshletOffset; + uint32_t MeshletVertexIndexOffset; + uint32_t MeshletTriangleIndexOffset; + float Scale; + ElemVector3 Translation; + uint32_t Reserved; + ElemVector4 Rotation; + uint32_t MaterialId; + uint32_t TextureSampler; +} ShaderParameters; + +typedef struct +{ + SampleMatrix4x4 ViewProjMatrix; + uint32_t ShowMeshlets; +} ShaderFrameData; + +// TODO: Group common variables into separate structs +typedef struct +{ + SampleAppSettings AppSettings; + const char* ScenePath; + + ElemWindow Window; + ElemGraphicsDevice GraphicsDevice; + ElemCommandQueue CommandQueue; + + SampleGpuMemory GpuMemory; + + ElemFence LastExecutionFence; + ElemSwapChain SwapChain; + ElemGraphicsHeap DepthBufferHeap; + ElemGraphicsResource DepthBuffer; + ElemPipelineState GraphicsPipeline; + ShaderParameters ShaderParameters; + SampleInputsApplication InputsApplication; + SampleInputsCamera InputsCamera; + SampleSceneData TestSceneData; // TODO: Do we keep that structure here? + + ShaderFrameData FrameData; + SampleGpuBuffer FrameDataBuffer; +} ApplicationPayload; + +typedef struct +{ + SampleInputsCameraState CameraState; +} SavedState; + +void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void* payload); + +void CreateDepthBuffer(ApplicationPayload* applicationPayload, uint32_t width, uint32_t height) +{ + if (applicationPayload->DepthBuffer != ELEM_HANDLE_NULL) + { + ElemFreeGraphicsResource(applicationPayload->DepthBuffer, NULL); + } + + printf("Creating DepthBuffer...\n"); + + ElemGraphicsResourceInfo resourceInfo = ElemCreateTexture2DResourceInfo(applicationPayload->GraphicsDevice, width, height, 1, ElemGraphicsFormat_D32_FLOAT, ElemGraphicsResourceUsage_DepthStencil, + &(ElemGraphicsResourceInfoOptions) { + .DebugName = "DepthBuffer" + }); + + applicationPayload->DepthBuffer = ElemCreateGraphicsResource(applicationPayload->DepthBufferHeap, 0, &resourceInfo); +} + +void UpdateFrameData(ApplicationPayload* applicationPayload, SampleMatrix4x4 viewProjMatrix, bool showMeshlets) +{ + applicationPayload->FrameData.ViewProjMatrix = viewProjMatrix; + applicationPayload->FrameData.ShowMeshlets = showMeshlets; + + ElemUploadGraphicsBufferData(applicationPayload->FrameDataBuffer.Buffer, 0, (ElemDataSpan) { .Items = (uint8_t*)&applicationPayload->FrameData, .Length = sizeof(ShaderFrameData) }); +} + +void InitSample(void* payload) +{ + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; + applicationPayload->Window = ElemCreateWindow(&(ElemWindowOptions) { .WindowState = applicationPayload->AppSettings.PreferFullScreen ? ElemWindowState_FullScreen : ElemWindowState_Normal }); + + ElemSetGraphicsOptions(&(ElemGraphicsOptions) { .EnableDebugLayer = !applicationPayload->AppSettings.DisableDiagnostics, .EnableGpuValidation = false, .EnableDebugBarrierInfo = false, .PreferVulkan = applicationPayload->AppSettings.PreferVulkan }); + + applicationPayload->GraphicsDevice = ElemCreateGraphicsDevice(NULL); + + applicationPayload->CommandQueue= ElemCreateCommandQueue(applicationPayload->GraphicsDevice, ElemCommandQueueType_Graphics, NULL); + applicationPayload->SwapChain= ElemCreateSwapChain(applicationPayload->CommandQueue, applicationPayload->Window, UpdateSwapChain, &(ElemSwapChainOptions) { .FrameLatency = 1, .UpdatePayload = payload }); + ElemSwapChainInfo swapChainInfo = ElemGetSwapChainInfo(applicationPayload->SwapChain); + + // TODO: For now we create a separate heap to avoid memory management + applicationPayload->DepthBufferHeap = ElemCreateGraphicsHeap(applicationPayload->GraphicsDevice, SampleMegaBytesToBytes(64), &(ElemGraphicsHeapOptions) { .HeapType = ElemGraphicsHeapType_Gpu }); + + // TODO: For now we need to put the heap as GpuUpload but it should be Gpu when we use IOQueues + // TODO: Having GPU Upload is still annoying 😞 + // TODO: For the moment we implement all textures of the scene in GPU memory. That is why we need 2GB for bistro scene + // We will implement virtual texturing/texture streaming in the future + applicationPayload->GpuMemory = SampleCreateGpuMemory(applicationPayload->GraphicsDevice, SampleMegaBytesToBytes(2048)); + + applicationPayload->FrameDataBuffer = SampleCreateGpuBufferAndUploadData(&applicationPayload->GpuMemory, &applicationPayload->FrameData, sizeof(ShaderFrameData), "FrameData"); + applicationPayload->ShaderParameters.FrameDataBuffer = applicationPayload->FrameDataBuffer.ReadDescriptor; + + CreateDepthBuffer(applicationPayload, swapChainInfo.Width, swapChainInfo.Height); + SampleLoadScene(applicationPayload->ScenePath, &applicationPayload->TestSceneData, &applicationPayload->GpuMemory); + + ElemGraphicsSamplerInfo samplerInfo = + { + .MinFilter = ElemGraphicsSamplerFilter_Linear, + .MagFilter = ElemGraphicsSamplerFilter_Linear, + .MipFilter = ElemGraphicsSamplerFilter_Linear, + .MaxAnisotropy = 16, + }; + + applicationPayload->ShaderParameters.TextureSampler = ElemCreateGraphicsSampler(applicationPayload->GraphicsDevice, &samplerInfo); + + ElemDataSpan shaderData = SampleReadFile(!applicationPayload->AppSettings.PreferVulkan ? "RenderMesh.shader": "RenderMesh_vulkan.shader", true); + ElemShaderLibrary shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, shaderData); + + applicationPayload->GraphicsPipeline = ElemCompileGraphicsPipelineState(applicationPayload->GraphicsDevice, &(ElemGraphicsPipelineStateParameters) { + .DebugName = "RenderMesh PSO", + .ShaderLibrary = shaderLibrary, + .MeshShaderFunction = "MeshMain", + .PixelShaderFunction = "PixelMain", + //.CullMode = ElemGraphicsCullMode_None, // TODO: We need to deactivate cull only for transparent objects! + .RenderTargets = { .Items = (ElemGraphicsPipelineStateRenderTarget[]) {{ .Format = swapChainInfo.Format }}, .Length = 1 }, + .DepthStencil = + { + .Format = ElemGraphicsFormat_D32_FLOAT, + .DepthCompareFunction = ElemGraphicsCompareFunction_Greater + } + }); + + ElemFreeShaderLibrary(shaderLibrary); + + SampleInputsApplicationInit(&applicationPayload->InputsApplication); + SampleInputsCameraInit(&applicationPayload->InputsCamera); + + if (applicationPayload->AppSettings.PreferFullScreen) + { + ElemHideWindowCursor(applicationPayload->Window); + applicationPayload->InputsApplication.State.IsCursorDisplayed = false; + } + + SampleStartFrameMeasurement(); + + ElemDataSpan savedStateData = SampleReadFile("SavedState.bin", false); + + if (savedStateData.Length > 0) + { + applicationPayload->InputsCamera.State = ((SavedState*)savedStateData.Items)->CameraState; + applicationPayload->InputsCamera.State.ProjectionMatrix = (SampleMatrix4x4){}; + } +} + +void FreeSample(void* payload) +{ + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; + + ElemWaitForFenceOnCpu(applicationPayload->LastExecutionFence); + + SampleFreeScene(&applicationPayload->TestSceneData); + SampleFreeGpuBuffer(&applicationPayload->FrameDataBuffer); + + ElemFreePipelineState(applicationPayload->GraphicsPipeline); + ElemFreeSwapChain(applicationPayload->SwapChain); + ElemFreeCommandQueue(applicationPayload->CommandQueue); + + ElemFreeGraphicsSampler(applicationPayload->ShaderParameters.TextureSampler, NULL); + ElemFreeGraphicsResource(applicationPayload->DepthBuffer, NULL); + ElemFreeGraphicsHeap(applicationPayload->DepthBufferHeap); + + SampleFreeGpuMemory(&applicationPayload->GpuMemory); + ElemFreeGraphicsDevice(applicationPayload->GraphicsDevice); + + SavedState savedState = { .CameraState = applicationPayload->InputsCamera.State }; + SampleWriteDataToApplicationFile("SavedState.bin", (ElemDataSpan) { .Items = (uint8_t*)&savedState, .Length = sizeof(SavedState) }, false); + + printf("Exit application...\n"); +} + +void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void* payload) +{ + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; + + if (updateParameters->SizeChanged) + { + CreateDepthBuffer(applicationPayload, updateParameters->SwapChainInfo.Width, updateParameters->SwapChainInfo.Height); + } + + ElemInputStream inputStream = ElemGetInputStream(); + + SampleInputsApplicationUpdate(inputStream, &applicationPayload->InputsApplication, updateParameters->DeltaTimeInSeconds); + SampleInputsCameraUpdate(inputStream, &applicationPayload->InputsCamera, updateParameters); + + // TODO: We shold move this into the application update function (we can pass the window if needed) + if (applicationPayload->InputsApplication.State.ExitApplication) + { + ElemExitApplication(0); + } + + if (applicationPayload->InputsApplication.State.ShowCursor) + { + printf("Show cursor\n"); + ElemShowWindowCursor(applicationPayload->Window); + } + else if (applicationPayload->InputsApplication.State.HideCursor) + { + printf("Hide cursor\n"); + ElemHideWindowCursor(applicationPayload->Window); + } + + SampleInputsCameraState* inputsCameraState = &applicationPayload->InputsCamera.State; + + UpdateFrameData(applicationPayload, inputsCameraState->ViewProjMatrix, inputsCameraState->Action); + + // TODO: We need to have a kind of queue system. The problem here is that if we don't have any + // data to load we will create empty lists + + ElemFence loadDataFence = {}; + + // TODO: Fow now, we load all the material textures in one shot + // We do it in the main loop now because later it will be a streaming system. + // Doing it in the main loop force us to decouple the loading + uint32_t loadResourceCounter = 0; + + for (uint32_t i = 0; i < applicationPayload->TestSceneData.MaterialCount; i++) + { + SampleMaterialData* material = &applicationPayload->TestSceneData.Materials[i]; + + if (material->AlbedoTexture && !material->AlbedoTexture->IsLoaded) + { + loadResourceCounter++; + } + + if (material->NormalTexture && !material->NormalTexture->IsLoaded) + { + loadResourceCounter++; + } + } + + for (uint32_t i = 0; i < applicationPayload->TestSceneData.MeshCount; i++) + { + SampleMeshData* meshData = &applicationPayload->TestSceneData.Meshes[i]; + + if (meshData->MeshBuffer.Buffer == ELEM_HANDLE_NULL) + { + loadResourceCounter++; + } + } + + if (loadResourceCounter > 0) + { + // TODO: We should have a queue system instead and load only what is needed otherwise + // each frames we will have empty lists + // This system will allow to split by batches the loading + ElemCommandList loadDataCommandList = ElemGetCommandList(applicationPayload->CommandQueue, NULL); + + for (uint32_t i = 0; i < applicationPayload->TestSceneData.MaterialCount; i++) + { + SampleMaterialData* material = &applicationPayload->TestSceneData.Materials[i]; + + if (material->AlbedoTexture && !material->AlbedoTexture->IsLoaded) + { + SampleLoadTextureData(loadDataCommandList, material->AlbedoTexture, &applicationPayload->GpuMemory); + loadResourceCounter++; + } + + if (material->NormalTexture && !material->NormalTexture->IsLoaded) + { + SampleLoadTextureData(loadDataCommandList, material->NormalTexture, &applicationPayload->GpuMemory); + loadResourceCounter++; + } + } + + for (uint32_t i = 0; i < applicationPayload->TestSceneData.NodeCount; i++) + { + SampleSceneNodeHeader* sceneNode = &applicationPayload->TestSceneData.Nodes[i]; + + if (sceneNode->NodeType == SampleSceneNodeType_Mesh) + { + SampleMeshData* meshData = &applicationPayload->TestSceneData.Meshes[sceneNode->ReferenceIndex]; + + if (meshData->MeshBuffer.Buffer == ELEM_HANDLE_NULL) + { + // TODO: This is an attempt to dissociate the loading of the mesh metadata and the buffer data + // Later, this will be done via streaming in parrallel. The gpu shader that will handle the mesh will need to check + // if the data was loaded. + // For now, we block to load the mesh if not already done which is equavalent at loading the full scene during the init. + SampleLoadMeshData(loadDataCommandList, meshData, &applicationPayload->GpuMemory); + loadResourceCounter++; + } + } + } + + ElemCommitCommandList(loadDataCommandList); // TODO: Measure scene loading time + + printf("ResourceLoadCount: %d\n", loadResourceCounter); + loadDataFence = ElemExecuteCommandList(applicationPayload->CommandQueue, loadDataCommandList, NULL); + } + + ElemCommandList commandList = ElemGetCommandList(applicationPayload->CommandQueue, NULL); + + ElemBeginRenderPass(commandList, &(ElemBeginRenderPassParameters) { + .RenderTargets = + { + .Items = (ElemRenderPassRenderTarget[]) { + { + .RenderTarget = updateParameters->BackBufferRenderTarget, + .ClearColor = { 0.0f, 0.01f, 0.02f, 1.0f }, + }}, + .Length = 1 + }, + .DepthStencil = + { + .DepthStencil = applicationPayload->DepthBuffer + } + }); + + ElemBindPipelineState(commandList, applicationPayload->GraphicsPipeline); + + applicationPayload->ShaderParameters.MaterialBuffer = applicationPayload->TestSceneData.MaterialBuffer.ReadDescriptor; + + // TODO: Construct a list of tasks on the cpu for now and do only one dispatch mesh with the total of tasks + // Be carreful with the limit per dimension of 65000 + for (uint32_t i = 0; i < applicationPayload->TestSceneData.NodeCount; i++) + { + SampleSceneNodeHeader* sceneNode = &applicationPayload->TestSceneData.Nodes[i]; + + if (sceneNode->NodeType == SampleSceneNodeType_Mesh) + { + SampleMeshData* meshData = &applicationPayload->TestSceneData.Meshes[sceneNode->ReferenceIndex]; + + // TODO: We need to check if the data is loaded into the gpu + + applicationPayload->ShaderParameters.MeshBuffer = meshData->MeshBuffer.ReadDescriptor; + + for (uint32_t j = 0; j < meshData->MeshHeader.MeshPrimitiveCount; j++) + { + SampleMeshPrimitiveHeader* meshPrimitive = &meshData->MeshPrimitives[j]; + applicationPayload->ShaderParameters.VertexBufferOffset = meshPrimitive->VertexBufferOffset; + applicationPayload->ShaderParameters.MeshletOffset = meshPrimitive->MeshletOffset; + applicationPayload->ShaderParameters.MeshletVertexIndexOffset = meshPrimitive->MeshletVertexIndexOffset; + applicationPayload->ShaderParameters.MeshletTriangleIndexOffset = meshPrimitive->MeshletTriangleIndexOffset; + applicationPayload->ShaderParameters.Scale = sceneNode->Scale; + applicationPayload->ShaderParameters.Translation = sceneNode->Translation; + applicationPayload->ShaderParameters.MaterialId = meshPrimitive->MaterialId; + applicationPayload->ShaderParameters.Rotation = sceneNode->Rotation; + + ElemPushPipelineStateConstants(commandList, 0, (ElemDataSpan) { .Items = (uint8_t*)&applicationPayload->ShaderParameters, .Length = sizeof(ShaderParameters) }); + ElemDispatchMesh(commandList, meshPrimitive->MeshletCount, 1, 1); + } + } + } + + ElemEndRenderPass(commandList); + + ElemCommitCommandList(commandList); + + ElemExecuteCommandListOptions executeOptions = {}; + + if (loadResourceCounter > 0) + { + executeOptions.FencesToWait = (ElemFenceSpan){ .Items = &loadDataFence, .Length = 1 }; + } + + applicationPayload->LastExecutionFence = ElemExecuteCommandList(applicationPayload->CommandQueue, commandList, &executeOptions); + + ElemPresentSwapChain(applicationPayload->SwapChain); + SampleFrameMeasurement frameMeasurement = SampleEndFrameMeasurement(); + + if (frameMeasurement.HasNewData) + { + SampleSetWindowTitle(applicationPayload->Window, "Renderer", applicationPayload->GraphicsDevice, frameMeasurement.FrameTimeInSeconds, frameMeasurement.Fps); + } + + SampleStartFrameMeasurement(); +} + + +int main(int argc, const char* argv[]) +{ + ApplicationPayload payload = + { + .AppSettings = SampleParseAppSettings(argc, argv), + .ScenePath = "Sponza/sponza.scene" + }; + + int32_t scenePathIndex = argc - 1; + + if (strstr(argv[scenePathIndex], ".scene")) + { + payload.ScenePath = argv[scenePathIndex]; + } + + ElemConfigureLogHandler(ElemConsoleLogHandler); + + ElemRunApplication(&(ElemRunApplicationParameters) + { + .ApplicationName = "Renderer", + .InitHandler = InitSample, + .FreeHandler = FreeSample, + .Payload = &payload + }); + +} diff --git a/samples/03-HelloTriangle/CMakeLists.txt b/samples/Elemental/01-HelloTriangle/CMakeLists.txt similarity index 100% rename from samples/03-HelloTriangle/CMakeLists.txt rename to samples/Elemental/01-HelloTriangle/CMakeLists.txt diff --git a/samples/03-HelloTriangle/Data/Triangle.hlsl b/samples/Elemental/01-HelloTriangle/Data/Triangle.hlsl similarity index 100% rename from samples/03-HelloTriangle/Data/Triangle.hlsl rename to samples/Elemental/01-HelloTriangle/Data/Triangle.hlsl diff --git a/samples/03-HelloTriangle/main.c b/samples/Elemental/01-HelloTriangle/main.c similarity index 95% rename from samples/03-HelloTriangle/main.c rename to samples/Elemental/01-HelloTriangle/main.c index a12aefe6..b8022d48 100644 --- a/samples/03-HelloTriangle/main.c +++ b/samples/Elemental/01-HelloTriangle/main.c @@ -33,7 +33,7 @@ void InitSample(void* payload) applicationPayload->SwapChain= ElemCreateSwapChain(applicationPayload->CommandQueue, applicationPayload->Window, UpdateSwapChain, &(ElemSwapChainOptions) { .UpdatePayload = payload }); ElemSwapChainInfo swapChainInfo = ElemGetSwapChainInfo(applicationPayload->SwapChain); - ElemDataSpan shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Triangle.shader": "Triangle_vulkan.shader"); + ElemDataSpan shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Triangle.shader": "Triangle_vulkan.shader", true); ElemShaderLibrary shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, shaderData); applicationPayload->GraphicsPipeline = ElemCompileGraphicsPipelineState(applicationPayload->GraphicsDevice, &(ElemGraphicsPipelineStateParameters) { @@ -41,7 +41,8 @@ void InitSample(void* payload) .ShaderLibrary = shaderLibrary, .MeshShaderFunction = "MeshMain", .PixelShaderFunction = "PixelMain", - .TextureFormats = { .Items = (ElemGraphicsFormat[]) { swapChainInfo.Format }, .Length = 1 } + .CullMode = ElemGraphicsCullMode_None, + .RenderTargets = { .Items = (ElemGraphicsPipelineStateRenderTarget[]) {{ .Format = swapChainInfo.Format }}, .Length = 1 }, }); ElemFreeShaderLibrary(shaderLibrary); @@ -89,7 +90,6 @@ void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void ElemCommitCommandList(commandList); ElemExecuteCommandList(applicationPayload->CommandQueue, commandList, NULL); - ElemPresentSwapChain(applicationPayload->SwapChain); // NOTE: This is the update part of the loop. If render fast this will create a gap and add latency because we need to wait for the next available diff --git a/samples/04-HelloInputs/CMakeLists.txt b/samples/Elemental/02-HelloInputs/CMakeLists.txt similarity index 100% rename from samples/04-HelloInputs/CMakeLists.txt rename to samples/Elemental/02-HelloInputs/CMakeLists.txt diff --git a/samples/04-HelloInputs/Data/Triangle.hlsl b/samples/Elemental/02-HelloInputs/Data/Triangle.hlsl similarity index 100% rename from samples/04-HelloInputs/Data/Triangle.hlsl rename to samples/Elemental/02-HelloInputs/Data/Triangle.hlsl diff --git a/samples/04-HelloInputs/main.c b/samples/Elemental/02-HelloInputs/main.c similarity index 94% rename from samples/04-HelloInputs/main.c rename to samples/Elemental/02-HelloInputs/main.c index 3881816b..4386b02f 100644 --- a/samples/04-HelloInputs/main.c +++ b/samples/Elemental/02-HelloInputs/main.c @@ -3,6 +3,8 @@ #include "SampleUtils.h" #include "SampleMath.h" +// TODO: Align sample with the Sample inputs header + #define ROTATION_TOUCH_DECREASE_SPEED 0.001f #define ROTATION_TOUCH_SPEED 4.0f #define ROTATION_TOUCH_MAX_DELTA 0.3f @@ -76,9 +78,9 @@ typedef struct typedef struct { - SampleVector3 RotationDelta; + ElemVector3 RotationDelta; SampleVector2 RotationTouch; - SampleVector3 CurrentRotationSpeed; + ElemVector3 CurrentRotationSpeed; float PreviousTouchDistance; float PreviousTouchAngle; float Zoom; @@ -142,11 +144,11 @@ void RegisterInputBindings(ApplicationPayload* applicationPayload) RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadLeftStickYPositive, 0, InputActionBindingType_Value, &applicationPayload->InputActions.RotateUp); RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadLeftStickYNegative, 0, InputActionBindingType_Value, &applicationPayload->InputActions.RotateDown); RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadLeftStickButton, 0, InputActionBindingType_Value, &applicationPayload->InputActions.TriangleColor); - RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputID_GamepadLeftTrigger, 0, InputActionBindingType_Value, &applicationPayload->InputActions.RotateSideLeft); - RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputID_GamepadRightTrigger, 0, InputActionBindingType_Value, &applicationPayload->InputActions.RotateSideRight); - RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputID_GamepadLeftShoulder, 0, InputActionBindingType_Value, &applicationPayload->InputActions.ZoomOut); - RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputID_GamepadRightShoulder, 0, InputActionBindingType_Value, &applicationPayload->InputActions.ZoomIn); - RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputID_GamepadButtonA, 0, InputActionBindingType_Value, &applicationPayload->InputActions.TriangleColor); + RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadLeftTrigger, 0, InputActionBindingType_Value, &applicationPayload->InputActions.RotateSideLeft); + RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadRightTrigger, 0, InputActionBindingType_Value, &applicationPayload->InputActions.RotateSideRight); + RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadLeftShoulder, 0, InputActionBindingType_Value, &applicationPayload->InputActions.ZoomOut); + RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadRightShoulder, 0, InputActionBindingType_Value, &applicationPayload->InputActions.ZoomIn); + RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_GamepadButtonA, 0, InputActionBindingType_Value, &applicationPayload->InputActions.TriangleColor); RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_Touch, 0, InputActionBindingType_Value, &applicationPayload->InputActions.Touch); RegisterInputActionBinding(&applicationPayload->InputActionBindings, ElemInputId_TouchXNegative, 0, InputActionBindingType_Value, &applicationPayload->InputActions.TouchRotateLeft); @@ -174,7 +176,7 @@ void InitSample(void* payload) applicationPayload->SwapChain= ElemCreateSwapChain(applicationPayload->CommandQueue, applicationPayload->Window, UpdateSwapChain, &(ElemSwapChainOptions) { .UpdatePayload = payload }); ElemSwapChainInfo swapChainInfo = ElemGetSwapChainInfo(applicationPayload->SwapChain); - ElemDataSpan shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Triangle.shader": "Triangle_vulkan.shader"); + ElemDataSpan shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Triangle.shader": "Triangle_vulkan.shader", true); ElemShaderLibrary shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, shaderData); applicationPayload->GraphicsPipeline = ElemCompileGraphicsPipelineState(applicationPayload->GraphicsDevice, &(ElemGraphicsPipelineStateParameters) { @@ -182,7 +184,8 @@ void InitSample(void* payload) .ShaderLibrary = shaderLibrary, .MeshShaderFunction = "MeshMain", .PixelShaderFunction = "PixelMain", - .TextureFormats = { .Items = (ElemGraphicsFormat[]) { swapChainInfo.Format }, .Length = 1 } + .CullMode = ElemGraphicsCullMode_None, + .RenderTargets = { .Items = (ElemGraphicsPipelineStateRenderTarget[]) {{ .Format = swapChainInfo.Format }}, .Length = 1 }, }); applicationPayload->ShaderParameters.RotationQuaternion = (SampleVector4){ .X = 0, .Y = 0, .Z = 0, .W = 1 }; @@ -321,7 +324,7 @@ void UpdateGameState(GameState* gameState, InputActions* inputActions, float del } else { - SampleVector3 direction = SampleNormalizeV3((SampleVector3) + ElemVector3 direction = SampleNormalizeV3((ElemVector3) { .X = (inputActions->RotateUp - inputActions->RotateDown), .Y = (inputActions->RotateLeft - inputActions->RotateRight), @@ -330,7 +333,7 @@ void UpdateGameState(GameState* gameState, InputActions* inputActions, float del if (SampleMagnitudeSquaredV3(direction)) { - SampleVector3 acceleration = SampleAddV3(SampleMulScalarV3(direction, ROTATION_ACCELERATION), SampleMulScalarV3(SampleInverseV3(gameState->CurrentRotationSpeed), ROTATION_FRICTION)); + ElemVector3 acceleration = SampleAddV3(SampleMulScalarV3(direction, ROTATION_ACCELERATION), SampleMulScalarV3(SampleInverseV3(gameState->CurrentRotationSpeed), ROTATION_FRICTION)); ResetTouchParameters(gameState); gameState->RotationDelta = SampleAddV3(SampleMulScalarV3(acceleration, 0.5f * SamplePow2f(deltaTimeInSeconds)), SampleMulScalarV3(gameState->CurrentRotationSpeed, deltaTimeInSeconds)); @@ -345,7 +348,7 @@ void UpdateGameState(GameState* gameState, InputActions* inputActions, float del gameState->RotationTouch = SampleMulScalarV2(SampleNormalizeV2(gameState->RotationTouch), ROTATION_TOUCH_MAX_DELTA); } - gameState->RotationDelta = SampleAddV3(gameState->RotationDelta, (SampleVector3){ gameState->RotationTouch.X, gameState->RotationTouch.Y, 0.0f }); + gameState->RotationDelta = SampleAddV3(gameState->RotationDelta, (ElemVector3){ gameState->RotationTouch.X, gameState->RotationTouch.Y, 0.0f }); SampleVector2 inverse = SampleMulScalarV2(SampleNormalizeV2(SampleInverseV2(gameState->RotationTouch)), ROTATION_TOUCH_DECREASE_SPEED); gameState->RotationTouch = SampleAddV2(gameState->RotationTouch, inverse); @@ -362,7 +365,6 @@ void UpdateGameState(GameState* gameState, InputActions* inputActions, float del void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void* payload) { ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; - applicationPayload->ShaderParameters.AspectRatio = updateParameters->SwapChainInfo.AspectRatio; InputActions* inputActions = &applicationPayload->InputActions; UpdateInputActions(&applicationPayload->InputActionBindings); @@ -386,13 +388,14 @@ void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void if (SampleMagnitudeSquaredV3(gameState->RotationDelta)) { - SampleVector4 rotationQuaternion = SampleMulQuat(SampleCreateQuaternion((SampleVector3){ 1, 0, 0 }, gameState->RotationDelta.X), - SampleMulQuat(SampleCreateQuaternion((SampleVector3){ 0, 0, 1 }, gameState->RotationDelta.Z), - SampleCreateQuaternion((SampleVector3){ 0, 1, 0 }, gameState->RotationDelta.Y))); + SampleVector4 rotationQuaternion = SampleMulQuat(SampleCreateQuaternion((ElemVector3){ 1, 0, 0 }, gameState->RotationDelta.X), + SampleMulQuat(SampleCreateQuaternion((ElemVector3){ 0, 0, 1 }, gameState->RotationDelta.Z), + SampleCreateQuaternion((ElemVector3){ 0, 1, 0 }, gameState->RotationDelta.Y))); applicationPayload->ShaderParameters.RotationQuaternion = SampleMulQuat(rotationQuaternion, applicationPayload->ShaderParameters.RotationQuaternion); } + applicationPayload->ShaderParameters.AspectRatio = updateParameters->SwapChainInfo.AspectRatio; float maxZoom = (applicationPayload->ShaderParameters.AspectRatio >= 0.75 ? 1.5f : 3.5f); applicationPayload->ShaderParameters.Zoom = fminf(maxZoom, gameState->Zoom); applicationPayload->ShaderParameters.TriangeColor = inputActions->TriangleColor; diff --git a/samples/05-HelloCompute/CMakeLists.txt b/samples/Elemental/03-HelloCompute/CMakeLists.txt similarity index 100% rename from samples/05-HelloCompute/CMakeLists.txt rename to samples/Elemental/03-HelloCompute/CMakeLists.txt diff --git a/samples/05-HelloCompute/Data/Fractal.hlsl b/samples/Elemental/03-HelloCompute/Data/Fractal.hlsl similarity index 95% rename from samples/05-HelloCompute/Data/Fractal.hlsl rename to samples/Elemental/03-HelloCompute/Data/Fractal.hlsl index 903cce78..817782ed 100644 --- a/samples/05-HelloCompute/Data/Fractal.hlsl +++ b/samples/Elemental/03-HelloCompute/Data/Fractal.hlsl @@ -2,7 +2,8 @@ struct ShaderParameters { uint32_t RenderTextureIndex; float Zoom; - float3x3 Transform; + float2 Reserved; + float4x4 Transform; }; [[vk::push_constant]] @@ -92,7 +93,7 @@ void Fractal(uint3 threadId: SV_DispatchThreadID) uv.x *= aspectRatio; uv *= parameters.Zoom; - uv = mul(float3(uv, 1.0), parameters.Transform); + uv = mul(float4(uv, 0.0, 1.0), parameters.Transform); float gradient = ComputeJuliaSet(uv); //float gradient = ComputeMandelbrotSet(uv); diff --git a/samples/05-HelloCompute/Data/Tonemap.hlsl b/samples/Elemental/03-HelloCompute/Data/Tonemap.hlsl similarity index 100% rename from samples/05-HelloCompute/Data/Tonemap.hlsl rename to samples/Elemental/03-HelloCompute/Data/Tonemap.hlsl diff --git a/samples/05-HelloCompute/main.c b/samples/Elemental/03-HelloCompute/main.c similarity index 95% rename from samples/05-HelloCompute/main.c rename to samples/Elemental/03-HelloCompute/main.c index 8b45f367..dd48df92 100644 --- a/samples/05-HelloCompute/main.c +++ b/samples/Elemental/03-HelloCompute/main.c @@ -18,7 +18,7 @@ typedef struct uint32_t RenderTextureIndex; float Zoom; uint32_t Padding[2]; - SampleMatrix3x3 Transform; + SampleMatrix4x4 Transform; } ShaderParameters; typedef struct @@ -30,6 +30,7 @@ typedef struct float PreviousTouchDistance; float PreviousTouchAngle; float Zoom; + bool IsCursorDisplayed; } GameState; // TODO: Group common variables into separate structs @@ -99,7 +100,7 @@ void InitSample(void* payload) CreateRenderTexture(applicationPayload, swapChainInfo.Width, swapChainInfo.Height); - ElemDataSpan shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Fractal.shader": "Fractal_vulkan.shader"); + ElemDataSpan shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Fractal.shader": "Fractal_vulkan.shader", true); ElemShaderLibrary shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, shaderData); applicationPayload->ComputePipeline = ElemCompileComputePipelineState(applicationPayload->GraphicsDevice, &(ElemComputePipelineStateParameters) { @@ -110,7 +111,7 @@ void InitSample(void* payload) ElemFreeShaderLibrary(shaderLibrary); - shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Tonemap.shader": "Tonemap_vulkan.shader"); + shaderData = SampleReadFile(!applicationPayload->PreferVulkan ? "Tonemap.shader": "Tonemap_vulkan.shader", true); shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, shaderData); applicationPayload->GraphicsPipeline = ElemCompileGraphicsPipelineState(applicationPayload->GraphicsDevice, &(ElemGraphicsPipelineStateParameters) { @@ -118,13 +119,12 @@ void InitSample(void* payload) .ShaderLibrary = shaderLibrary, .MeshShaderFunction = "MeshMain", .PixelShaderFunction = "PixelMain", - .TextureFormats = { .Items = (ElemGraphicsFormat[]) { swapChainInfo.Format }, .Length = 1 } + .RenderTargets = { .Items = (ElemGraphicsPipelineStateRenderTarget[]) {{ .Format = swapChainInfo.Format }}, .Length = 1 }, }); ElemFreeShaderLibrary(shaderLibrary); applicationPayload->ShaderParameters.Transform = SampleCreateIdentityMatrix(); - applicationPayload->InputActions.ShowCursor = true; applicationPayload->GameState.Zoom = 1.0f; applicationPayload->GameState.RotationDelta = 0.0f; @@ -257,29 +257,34 @@ void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void applicationPayload->ShaderParameters.RenderTextureIndex = applicationPayload->RenderTextureWriteDescriptor; + ElemInputStream inputStream = ElemGetInputStream(); SampleStandardInputActions* inputActions = &applicationPayload->InputActions; - SampleUpdateInputActions(&applicationPayload->InputActionBindings); + SampleUpdateInputActions(&applicationPayload->InputActionBindings, inputStream); if (inputActions->ExitApp) { ElemExitApplication(0); } - if (inputActions->ShowCursor) - { - ElemShowWindowCursor(applicationPayload->Window); - } - else + GameState* gameState = &applicationPayload->GameState; + + if (inputActions->SwitchShowCursor) { - ElemHideWindowCursor(applicationPayload->Window); + if (!gameState->IsCursorDisplayed) + { + ElemShowWindowCursor(applicationPayload->Window); + } + else + { + ElemHideWindowCursor(applicationPayload->Window); + } } - GameState* gameState = &applicationPayload->GameState; UpdateGameState(gameState, inputActions, updateParameters->DeltaTimeInSeconds); - SampleMatrix3x3 transformMatrix = SampleMulMatrix3x3(SampleCreateTranslationMatrix(gameState->TranslationDelta.X, gameState->TranslationDelta.Y), SampleCreateRotationMatrix(gameState->RotationDelta)); + SampleMatrix4x4 transformMatrix = SampleMulMatrix4x4(SampleCreateTranslationMatrix(gameState->TranslationDelta.X, gameState->TranslationDelta.Y), SampleCreateRotationMatrix(gameState->RotationDelta)); - applicationPayload->ShaderParameters.Transform = SampleMulMatrix3x3(applicationPayload->ShaderParameters.Transform, transformMatrix); + applicationPayload->ShaderParameters.Transform = SampleMulMatrix4x4(applicationPayload->ShaderParameters.Transform, transformMatrix); applicationPayload->ShaderParameters.Zoom = gameState->Zoom; // TODO: Do an example with another compute queue to demonstrate fences? diff --git a/samples/Elemental/04-HelloMesh/CMakeLists.txt b/samples/Elemental/04-HelloMesh/CMakeLists.txt new file mode 100644 index 00000000..a670411f --- /dev/null +++ b/samples/Elemental/04-HelloMesh/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SAMPLE_NAME HelloMesh) + +#"https://casual-effects.com/g3d/data10/research/model/buddha/buddha.zip" + +add_executable(${SAMPLE_NAME} main.c Data) +target_link_libraries(${SAMPLE_NAME} PRIVATE SampleCommon) +target_link_libraries(${SAMPLE_NAME} PRIVATE ElementalInterface) +add_dependencies(${SAMPLE_NAME} SampleSharedData) + +configure_resource_compilation(${SAMPLE_NAME} resource_list) +configure_project_package(${SAMPLE_NAME} "samples" DEPENDENCIES Elemental RESOURCES ${resource_list}) diff --git a/samples/Elemental/04-HelloMesh/Data/RenderMesh.hlsl b/samples/Elemental/04-HelloMesh/Data/RenderMesh.hlsl new file mode 100644 index 00000000..ef91d4e3 --- /dev/null +++ b/samples/Elemental/04-HelloMesh/Data/RenderMesh.hlsl @@ -0,0 +1,155 @@ +struct ShaderParameters +{ + uint32_t MeshBuffer; + uint32_t VertexBufferOffset; + uint32_t MeshletOffset; + uint32_t MeshletVertexIndexOffset; + uint32_t MeshletTriangleIndexOffset; + uint32_t Reserved1; + uint32_t Reserved2; + uint32_t Reserved3; + float4 RotationQuaternion; + float Zoom; + float AspectRatio; + uint32_t ShowMeshlets; + uint32_t MeshletCount; +}; + +[[vk::push_constant]] +ShaderParameters parameters : register(b0); + +struct ElemMeshlet +{ + uint32_t VertexIndexOffset; + uint32_t VertexIndexCount; + uint32_t TriangleOffset; + uint32_t TriangleCount; +}; + +// NOTE: The vertex layout is not optimized to not add complexity to the sample. +// Do not use this on production code. +struct Vertex +{ + float3 Position; + float3 Normal; + float4 Tangent; + float2 TextureCoordinates; +}; + +struct VertexOutput +{ + float4 Position: SV_Position; + float3 WorldNormal: NORMAL0; + uint MeshletIndex : COLOR0; +}; + +// TODO: Put that in an include file +float4x4 TransformMatrix(float4 quaternion, float3 translation) +{ + float xw = quaternion.x*quaternion.w, xx = quaternion.x*quaternion.x, yy = quaternion.y*quaternion.y, + yw = quaternion.y*quaternion.w, xy = quaternion.x*quaternion.y, yz = quaternion.y*quaternion.z, + zw = quaternion.z*quaternion.w, xz = quaternion.x*quaternion.z, zz = quaternion.z*quaternion.z; + + float4 row1 = float4(1-2*(yy+zz), 2*(xy+zw), 2*(xz-yw), 0.0); + float4 row2 = float4( 2*(xy-zw),1-2*(xx+zz), 2*(yz+xw), 0.0); + float4 row3 = float4( 2*(xz+yw), 2*(yz-xw),1-2*(xx+yy), 0.0); + float4 row4 = float4(0.0, 0.0, 0.0, 1.0); + + return float4x4(row1, row2, row3, row4); +} + +float4x4 LookAtLHMatrix(float3 eyePosition, float3 targetPosition, float3 upDirection) +{ + float3 forwardDirection = normalize(targetPosition - eyePosition); + float3 rightDirection = normalize(cross(upDirection, forwardDirection)); + float3 upDirectionNew = cross(forwardDirection, rightDirection); + + float4 row1 = float4(rightDirection.x, upDirectionNew.x, forwardDirection.x, 0.0); + float4 row2 = float4(rightDirection.y, upDirectionNew.y, forwardDirection.y, 0.0); + float4 row3 = float4(rightDirection.z, upDirectionNew.z, forwardDirection.z, 0.0); + float4 row4 = float4(-dot(rightDirection, eyePosition), -dot(upDirectionNew, eyePosition), -dot(forwardDirection, eyePosition), 1.0); + + return float4x4(row1, row2, row3, row4); +} + +float4x4 PerspectiveProjectionMatrix(float fovY, float aspectRatio, float zNear) +{ + float height = 1.0 / tan(fovY * 0.5); + + float4 row1 = float4(height / aspectRatio, 0.0f, 0.0f, 0.0f); + float4 row2 = float4(0.0f, height, 0.0f, 0.0f); + float4 row3 = float4(0.0f, 0.0f, 0, 1.0f); + float4 row4 = float4(0.0f, 0.0f, zNear, 0.0f); + + return float4x4(row1, row2, row3, row4); +} + +[shader("mesh")] +[OutputTopology("triangle")] +[NumThreads(126, 1, 1)] +void MeshMain(in uint groupId: SV_GroupID, + in uint groupThreadId : SV_GroupThreadID, + out vertices VertexOutput vertices[64], + out indices uint3 indices[126]) +{ + uint meshletIndex = groupId; + + ByteAddressBuffer meshBuffer = ResourceDescriptorHeap[parameters.MeshBuffer]; + ElemMeshlet meshlet = meshBuffer.Load(parameters.MeshletOffset + meshletIndex * sizeof(ElemMeshlet)); + + SetMeshOutputCounts(meshlet.VertexIndexCount, meshlet.TriangleCount); + + if (groupThreadId < meshlet.VertexIndexCount) + { + // TODO: Compute matrix in task shader + float cameraZDistance = (parameters.AspectRatio >= 0.75 ? -2.0 : -4.0) + parameters.Zoom; + + float4x4 worldMatrix = TransformMatrix(parameters.RotationQuaternion, float3(0.0, 0.0, 0.0)); + float4x4 viewMatrix = LookAtLHMatrix(float3(0, 0, cameraZDistance), float3(0, 0, 0), float3(0, 1, 0)); + float4x4 projectionMatrix = PerspectiveProjectionMatrix(0.78, parameters.AspectRatio, 0.001); + + float4x4 worldViewProjectionMatrix = mul(worldMatrix, mul(viewMatrix, projectionMatrix)); + float4x4 inverseTransposeWorldMatrix = worldMatrix; + + uint vertexIndex = meshBuffer.Load(parameters.MeshletVertexIndexOffset + (meshlet.VertexIndexOffset + groupThreadId) * sizeof(uint)); + Vertex vertex = meshBuffer.Load(parameters.VertexBufferOffset + vertexIndex * sizeof(Vertex)); + + vertices[groupThreadId].Position = mul(float4(vertex.Position, 1.0), worldViewProjectionMatrix); + vertices[groupThreadId].WorldNormal = mul(float4(vertex.Normal, 0.0), inverseTransposeWorldMatrix).xyz; // TODO: Compute inverse transpose + vertices[groupThreadId].MeshletIndex = groupId; + } + + if (groupThreadId < meshlet.TriangleCount) + { + uint triangleIndex = meshBuffer.Load(parameters.MeshletTriangleIndexOffset + (meshlet.TriangleOffset + groupThreadId) * sizeof(uint)); + indices[groupThreadId] = unpack_u8u32(triangleIndex).xyz; + } +} + +uint hash(uint a) +{ + a = (a+0x7ed55d16) + (a<<12); + a = (a^0xc761c23c) ^ (a>>19); + a = (a+0x165667b1) + (a<<5); + a = (a+0xd3a2646c) ^ (a<<9); + a = (a+0xfd7046c5) + (a<<3); + a = (a^0xb55a4f09) ^ (a>>16); + + return a; +} + +[shader("pixel")] +float4 PixelMain(const VertexOutput input) : SV_Target0 +{ + if (parameters.ShowMeshlets == 0) + { + return float4(normalize(input.WorldNormal) * 0.5 + 0.5, 1.0); + } + else + { + uint hashResult = hash(input.MeshletIndex); + float3 meshletColor = float3(float(hashResult & 255), float((hashResult >> 8) & 255), float((hashResult >> 16) & 255)) / 255.0; + + return float4(meshletColor, 1.0); + } +} diff --git a/samples/Elemental/04-HelloMesh/main.c b/samples/Elemental/04-HelloMesh/main.c new file mode 100644 index 00000000..f225f286 --- /dev/null +++ b/samples/Elemental/04-HelloMesh/main.c @@ -0,0 +1,261 @@ +#include "Elemental.h" +#include "SampleUtils.h" +#include "SampleMath.h" +#include "SampleInputsApplication.h" +#include "SampleInputsModelViewer.h" +#include "SampleSceneLoader.h" + +// TODO: Take all the code from the common headers and integrate it here +// SampleSceneLoaderNeeds to disappear + +typedef struct +{ + // TODO: Embed that into a buffer + uint32_t MeshBuffer; + uint32_t VertexBufferOffset; + uint32_t MeshletOffset; + uint32_t MeshletVertexIndexOffset; + uint32_t MeshletTriangleIndexOffset; + uint32_t Reserved1; + uint32_t Reserved2; + uint32_t Reserved3; + SampleVector4 RotationQuaternion; + float Zoom; + float AspectRatio; + uint32_t ShowMeshlets; + uint32_t MeshletCount; +} ShaderParameters; + + +// TODO: Group common variables into separate structs +typedef struct +{ + SampleAppSettings AppSettings; + ElemWindow Window; + ElemGraphicsDevice GraphicsDevice; + ElemCommandQueue CommandQueue; + uint32_t CurrentHeapOffset; + ElemFence LastExecutionFence; + ElemSwapChain SwapChain; + ElemGraphicsHeap DepthBufferHeap; + ElemGraphicsResource DepthBuffer; + ElemPipelineState GraphicsPipeline; + ShaderParameters ShaderParameters; + SampleInputsApplication InputsApplication; + SampleInputsModelViewer InputsModelViewer; + SampleSceneData TestSceneData; + SampleGpuMemory GpuMemory; // TODO: To remove +} ApplicationPayload; + +void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void* payload); + +void CreateDepthBuffer(ApplicationPayload* applicationPayload, uint32_t width, uint32_t height) +{ + if (applicationPayload->DepthBuffer != ELEM_HANDLE_NULL) + { + ElemFreeGraphicsResource(applicationPayload->DepthBuffer, NULL); + } + + printf("Creating DepthBuffer...\n"); + + ElemGraphicsResourceInfo resourceInfo = ElemCreateTexture2DResourceInfo(applicationPayload->GraphicsDevice, width, height, 1, ElemGraphicsFormat_D32_FLOAT, ElemGraphicsResourceUsage_DepthStencil, + &(ElemGraphicsResourceInfoOptions) { + .DebugName = "DepthBuffer" + }); + + applicationPayload->DepthBuffer = ElemCreateGraphicsResource(applicationPayload->DepthBufferHeap, 0, &resourceInfo); +} + +void InitSample(void* payload) +{ + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; + applicationPayload->Window = ElemCreateWindow(&(ElemWindowOptions) { .WindowState = applicationPayload->AppSettings.PreferFullScreen ? ElemWindowState_FullScreen : ElemWindowState_Normal }); + + ElemSetGraphicsOptions(&(ElemGraphicsOptions) { .EnableDebugLayer = true, .EnableGpuValidation = false, .EnableDebugBarrierInfo = false, .PreferVulkan = applicationPayload->AppSettings.PreferVulkan }); + + // TODO: Debug why the AMD integrated GPU is not create at all + ElemGraphicsDeviceInfoSpan devices = ElemGetAvailableGraphicsDevices(); + + for (uint32_t i = 0; i < devices.Length; i++) + { + printf("Device: %s\n", devices.Items[i].DeviceName); + } + + applicationPayload->GraphicsDevice = ElemCreateGraphicsDevice(NULL); + + applicationPayload->CommandQueue= ElemCreateCommandQueue(applicationPayload->GraphicsDevice, ElemCommandQueueType_Graphics, NULL); + applicationPayload->SwapChain= ElemCreateSwapChain(applicationPayload->CommandQueue, applicationPayload->Window, UpdateSwapChain, &(ElemSwapChainOptions) { .FrameLatency = 1, .UpdatePayload = payload }); + ElemSwapChainInfo swapChainInfo = ElemGetSwapChainInfo(applicationPayload->SwapChain); + + // TODO: For now we create a separate heap to avoid memory management + applicationPayload->DepthBufferHeap = ElemCreateGraphicsHeap(applicationPayload->GraphicsDevice, SampleMegaBytesToBytes(64), &(ElemGraphicsHeapOptions) { .HeapType = ElemGraphicsHeapType_Gpu }); + applicationPayload->GpuMemory = SampleCreateGpuMemory(applicationPayload->GraphicsDevice, SampleMegaBytesToBytes(256)); + + CreateDepthBuffer(applicationPayload, swapChainInfo.Width, swapChainInfo.Height); + SampleLoadScene("kitten.scene", &applicationPayload->TestSceneData, &applicationPayload->GpuMemory); + //SampleLoadScene("buddha.scene", &applicationPayload->TestSceneData); + + ElemCommandList loadDataCommandList = ElemGetCommandList(applicationPayload->CommandQueue, NULL); + SampleLoadMeshData(loadDataCommandList, &applicationPayload->TestSceneData.Meshes[0], &applicationPayload->GpuMemory); + ElemCommitCommandList(loadDataCommandList); + ElemExecuteCommandList(applicationPayload->CommandQueue, loadDataCommandList, NULL); + + ElemDataSpan shaderData = SampleReadFile(!applicationPayload->AppSettings.PreferVulkan ? "RenderMesh.shader": "RenderMesh_vulkan.shader", true); + ElemShaderLibrary shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, shaderData); + + applicationPayload->GraphicsPipeline = ElemCompileGraphicsPipelineState(applicationPayload->GraphicsDevice, &(ElemGraphicsPipelineStateParameters) { + .DebugName = "RenderMesh PSO", + .ShaderLibrary = shaderLibrary, + .MeshShaderFunction = "MeshMain", + .PixelShaderFunction = "PixelMain", + .RenderTargets = { .Items = (ElemGraphicsPipelineStateRenderTarget[]) {{ .Format = swapChainInfo.Format }}, .Length = 1 }, + .DepthStencil = + { + .Format = ElemGraphicsFormat_D32_FLOAT, + .DepthCompareFunction = ElemGraphicsCompareFunction_Greater + } + }); + + ElemFreeShaderLibrary(shaderLibrary); + + applicationPayload->ShaderParameters.RotationQuaternion = (SampleVector4){ .X = 0, .Y = 0, .Z = 0, .W = 1 }; + + SampleInputsApplicationInit(&applicationPayload->InputsApplication); + SampleInputsModelViewerInit(&applicationPayload->InputsModelViewer); + + if (applicationPayload->AppSettings.PreferFullScreen) + { + ElemHideWindowCursor(applicationPayload->Window); + applicationPayload->InputsApplication.State.IsCursorDisplayed = false; + } + + SampleStartFrameMeasurement(); +} + +void FreeSample(void* payload) +{ + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; + + ElemWaitForFenceOnCpu(applicationPayload->LastExecutionFence); + + SampleFreeScene(&applicationPayload->TestSceneData); + + ElemFreePipelineState(applicationPayload->GraphicsPipeline); + ElemFreeSwapChain(applicationPayload->SwapChain); + ElemFreeCommandQueue(applicationPayload->CommandQueue); + + ElemFreeGraphicsResource(applicationPayload->DepthBuffer, NULL); + ElemFreeGraphicsHeap(applicationPayload->DepthBufferHeap); + + SampleFreeGpuMemory(&applicationPayload->GpuMemory); + + ElemFreeGraphicsDevice(applicationPayload->GraphicsDevice); +} + +void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void* payload) +{ + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; + + if (updateParameters->SizeChanged) + { + CreateDepthBuffer(applicationPayload, updateParameters->SwapChainInfo.Width, updateParameters->SwapChainInfo.Height); + } + + ElemInputStream inputStream = ElemGetInputStream(); + + SampleInputsApplicationUpdate(inputStream, &applicationPayload->InputsApplication, updateParameters->DeltaTimeInSeconds); + SampleInputsModelViewerUpdate(inputStream, &applicationPayload->InputsModelViewer, updateParameters->DeltaTimeInSeconds); + + if (applicationPayload->InputsApplication.State.ExitApplication) + { + ElemExitApplication(0); + } + + if (applicationPayload->InputsApplication.State.ShowCursor) + { + printf("Show cursor\n"); + ElemShowWindowCursor(applicationPayload->Window); + } + else if (applicationPayload->InputsApplication.State.HideCursor) + { + printf("Hide cursor\n"); + ElemHideWindowCursor(applicationPayload->Window); + } + + SampleInputsModelViewerState* modelViewerState = &applicationPayload->InputsModelViewer.State; + + if (SampleMagnitudeSquaredV3(modelViewerState->RotationDelta)) + { + SampleVector4 rotationQuaternion = SampleMulQuat(SampleCreateQuaternion((ElemVector3){ 1, 0, 0 }, modelViewerState->RotationDelta.X), + SampleMulQuat(SampleCreateQuaternion((ElemVector3){ 0, 0, 1 }, modelViewerState->RotationDelta.Z), + SampleCreateQuaternion((ElemVector3){ 0, 1, 0 }, modelViewerState->RotationDelta.Y))); + + applicationPayload->ShaderParameters.RotationQuaternion = SampleMulQuat(rotationQuaternion, applicationPayload->ShaderParameters.RotationQuaternion); + } + + applicationPayload->ShaderParameters.AspectRatio = updateParameters->SwapChainInfo.AspectRatio; + float maxZoom = (applicationPayload->ShaderParameters.AspectRatio >= 0.75 ? 1.5f : 3.5f); + applicationPayload->ShaderParameters.Zoom = fminf(maxZoom, modelViewerState->Zoom); + applicationPayload->ShaderParameters.ShowMeshlets = modelViewerState->Action; + + ElemCommandList commandList = ElemGetCommandList(applicationPayload->CommandQueue, NULL); + + ElemBeginRenderPass(commandList, &(ElemBeginRenderPassParameters) { + .RenderTargets = + { + .Items = (ElemRenderPassRenderTarget[]) { + { + .RenderTarget = updateParameters->BackBufferRenderTarget, + .ClearColor = { 0.0f, 0.01f, 0.02f, 1.0f }, + }}, + .Length = 1 + }, + .DepthStencil = + { + .DepthStencil = applicationPayload->DepthBuffer + } + }); + + ElemBindPipelineState(commandList, applicationPayload->GraphicsPipeline); + ElemPushPipelineStateConstants(commandList, 0, (ElemDataSpan) { .Items = (uint8_t*)&applicationPayload->ShaderParameters, .Length = sizeof(ShaderParameters) }); + + SampleMeshPrimitiveHeader* meshPrimitive = &applicationPayload->TestSceneData.Meshes[0].MeshPrimitives[0]; + applicationPayload->ShaderParameters.VertexBufferOffset = meshPrimitive->VertexBufferOffset; + applicationPayload->ShaderParameters.MeshletOffset = meshPrimitive->MeshletOffset; + applicationPayload->ShaderParameters.MeshletVertexIndexOffset = meshPrimitive->MeshletVertexIndexOffset; + applicationPayload->ShaderParameters.MeshletTriangleIndexOffset = meshPrimitive->MeshletTriangleIndexOffset; + ElemDispatchMesh(commandList, meshPrimitive->MeshletCount, 1, 1); + + ElemEndRenderPass(commandList); + + ElemCommitCommandList(commandList); + applicationPayload->LastExecutionFence = ElemExecuteCommandList(applicationPayload->CommandQueue, commandList, NULL); + + ElemPresentSwapChain(applicationPayload->SwapChain); + SampleFrameMeasurement frameMeasurement = SampleEndFrameMeasurement(); + + if (frameMeasurement.HasNewData) + { + SampleSetWindowTitle(applicationPayload->Window, "HelloMesh", applicationPayload->GraphicsDevice, frameMeasurement.FrameTimeInSeconds, frameMeasurement.Fps); + } + + SampleStartFrameMeasurement(); +} + +int main(int argc, const char* argv[]) +{ + ApplicationPayload payload = + { + .AppSettings = SampleParseAppSettings(argc, argv) + }; + + ElemConfigureLogHandler(ElemConsoleLogHandler); + + ElemRunApplication(&(ElemRunApplicationParameters) + { + .ApplicationName = "Hello Mesh", + .InitHandler = InitSample, + .FreeHandler = FreeSample, + .Payload = &payload + }); +} diff --git a/samples/ElementalTools/01-ShaderCompiler/CMakeLists.txt b/samples/ElementalTools/01-ShaderCompiler/CMakeLists.txt new file mode 100644 index 00000000..1df1e895 --- /dev/null +++ b/samples/ElementalTools/01-ShaderCompiler/CMakeLists.txt @@ -0,0 +1,9 @@ +if(NOT BUILD_FOR_IOS) + set(SAMPLE_NAME ShaderCompiler) + + add_executable(${SAMPLE_NAME} main.c) + target_link_libraries(${SAMPLE_NAME} PRIVATE SampleCommon) + target_link_libraries(${SAMPLE_NAME} PRIVATE ElementalToolsInterface) + + configure_project_package(${SAMPLE_NAME} "samples" DEPENDENCIES ElementalTools) +endif() diff --git a/samples/02-ShaderCompiler/main.c b/samples/ElementalTools/01-ShaderCompiler/main.c similarity index 51% rename from samples/02-ShaderCompiler/main.c rename to samples/ElementalTools/01-ShaderCompiler/main.c index aeedbb47..5d76ff42 100644 --- a/samples/02-ShaderCompiler/main.c +++ b/samples/ElementalTools/01-ShaderCompiler/main.c @@ -1,99 +1,5 @@ #include "ElementalTools.h" -#include - -#ifndef _WIN32 -#define MAX_PATH 255 -#include -#else -#include -#endif - -char* ReadFileToString(const char* filename) -{ - #ifdef _WIN32 - FILE* file; - fopen_s(&file, filename, "rb"); - #else - FILE* file = fopen(filename, "rb"); - #endif - - if (file == NULL) - { - return NULL; - } - - if (fseek(file, 0, SEEK_END) != 0) - { - fclose(file); - return NULL; - } - - long fileSize = ftell(file); - - if (fileSize == -1) - { - fclose(file); - return NULL; - } - - rewind(file); - - char* buffer = (char*)malloc(fileSize + 1); - - if (buffer == NULL) - { - fclose(file); - return NULL; - } - - size_t bytesRead = fread(buffer, 1, fileSize, file); - - if (bytesRead < (size_t)fileSize) - { - free(buffer); - fclose(file); - return NULL; - } - - buffer[fileSize] = '\0'; - fclose(file); - - return buffer; -} - -int WriteDataToFile(const char* filename, ElemToolsDataSpan data) -{ - printf("Length:%s %d\n", filename, data.Length); - if (filename == NULL || data.Length == 0) - { - printf("ERROR 1\n"); - return -1; - } - - #ifdef _WIN32 - FILE* file; - fopen_s(&file, filename, "wb"); - #else - FILE* file = fopen(filename, "wb"); - #endif - - if (file == NULL) - { - printf("ERROR 2\n"); - return -1; - } - - size_t bytesWritten = fwrite(data.Items, 1, data.Length, file); - fclose(file); - - if (bytesWritten < data.Length) - { - printf("ERROR 3\n"); - return -1; // Return -1 if not all bytes were written - } - - return 0; // Success -} +#include "SampleUtils.h" int main(int argc, const char* argv[]) { @@ -164,21 +70,16 @@ int main(int argc, const char* argv[]) printf("Compiling shader: %s (DebugMode=%d)\n", inputPath, debugMode); - char* shaderSource = ReadFileToString(inputPath); - ElemShaderSourceData shaderSourceData = { .ShaderLanguage = ElemShaderLanguage_Hlsl, .Data = { .Items = (uint8_t*)shaderSource, .Length = strlen(shaderSource) } }; - ElemShaderCompilationResult compilationResult = ElemCompileShaderLibrary(targetApi, targetPlatform, &shaderSourceData, &(ElemCompileShaderOptions) { .DebugMode = debugMode }); - - for (uint32_t i = 0; i < compilationResult.Messages.Length; i++) - { - printf("Compil msg (%d): %s\n", compilationResult.Messages.Items[i].Type, compilationResult.Messages.Items[i].Message); - } + ElemShaderCompilationResult compilationResult = ElemCompileShaderLibrary(targetApi, targetPlatform, inputPath, &(ElemCompileShaderOptions) { .DebugMode = debugMode }); + + DisplayOutputMessages("ShaderCompiler", compilationResult.Messages); if (compilationResult.HasErrors) { - printf("Error while compiling shader!\n"); return 1; } + // TODO: Add timing informations printf("Writing shader data to: %s\n", outputPath); - return WriteDataToFile(outputPath, compilationResult.Data); + return SampleWriteDataToFile(outputPath, compilationResult.Data, false); } diff --git a/samples/ElementalTools/02-SceneCompiler/CMakeLists.txt b/samples/ElementalTools/02-SceneCompiler/CMakeLists.txt new file mode 100644 index 00000000..25ba4d14 --- /dev/null +++ b/samples/ElementalTools/02-SceneCompiler/CMakeLists.txt @@ -0,0 +1,9 @@ +if(NOT BUILD_FOR_IOS) + set(SAMPLE_NAME SceneCompiler) + + add_executable(${SAMPLE_NAME} main.c) + target_link_libraries(${SAMPLE_NAME} PRIVATE SampleCommon) + target_link_libraries(${SAMPLE_NAME} PRIVATE ElementalToolsInterface) + + configure_project_package(${SAMPLE_NAME} "samples" DEPENDENCIES ElementalTools) +endif() diff --git a/samples/ElementalTools/02-SceneCompiler/main.c b/samples/ElementalTools/02-SceneCompiler/main.c new file mode 100644 index 00000000..6e5e62b7 --- /dev/null +++ b/samples/ElementalTools/02-SceneCompiler/main.c @@ -0,0 +1,243 @@ +#include "ElementalTools.h" +#include "SampleUtils.h" +#include "SampleScene.h" + +// TODO: Restore MeshBuilder to use for hello mesh? + +bool WriteMeshData(FILE* file, ElemSceneMesh mesh) +{ + assert(file); + uint32_t meshHeaderOffset = ftell(file); + + SampleMeshHeader meshHeader = + { + .MeshPrimitiveCount = mesh.MeshPrimitives.Length + }; + + strncpy(meshHeader.Name, mesh.Name, 50); + + fwrite(&meshHeader, sizeof(SampleMeshHeader), 1, file); + uint32_t meshPrimitiveHeadersOffset = ftell(file); + + SampleMeshPrimitiveHeader* meshPrimitiveHeaders = (SampleMeshPrimitiveHeader*)malloc(sizeof(SampleMeshPrimitiveHeader) * mesh.MeshPrimitives.Length); + fwrite(meshPrimitiveHeaders, sizeof(SampleMeshPrimitiveHeader), mesh.MeshPrimitives.Length, file); + + meshHeader.MeshBufferOffset = ftell(file); + + for (uint32_t i = 0; i < mesh.MeshPrimitives.Length; i++) + { + ElemSceneMeshPrimitive* meshPrimitive = &mesh.MeshPrimitives.Items[i]; + + // TODO: LOD! + ElemBuildMeshletResult result = ElemBuildMeshlets(meshPrimitive->VertexBuffer, meshPrimitive->IndexBuffer, NULL); + + DisplayOutputMessages("BuildMeshlets", result.Messages); + + if (result.HasErrors) + { + return false; + } + + SampleMeshPrimitiveHeader* meshPrimitiveHeader = &meshPrimitiveHeaders[i]; + meshPrimitiveHeader->MaterialId = meshPrimitive->MaterialId; + meshPrimitiveHeader->MeshletCount = result.Meshlets.Length; + + meshPrimitiveHeader->VertexBufferOffset = ftell(file) - meshHeader.MeshBufferOffset; + fwrite(result.VertexBuffer.Data.Items, sizeof(uint8_t), result.VertexBuffer.Data.Length, file); + + meshPrimitiveHeader->MeshletOffset = ftell(file) - meshHeader.MeshBufferOffset; + fwrite(result.Meshlets.Items, sizeof(ElemMeshlet), result.Meshlets.Length, file); + + meshPrimitiveHeader->MeshletVertexIndexOffset = ftell(file) - meshHeader.MeshBufferOffset; + fwrite(result.MeshletVertexIndexBuffer.Items, sizeof(uint32_t), result.MeshletVertexIndexBuffer.Length, file); + + meshPrimitiveHeader->MeshletTriangleIndexOffset = ftell(file) - meshHeader.MeshBufferOffset; + fwrite(result.MeshletTriangleIndexBuffer.Items, sizeof(uint32_t), result.MeshletTriangleIndexBuffer.Length, file); + } + + meshHeader.MeshBufferSizeInBytes = ftell(file) - meshHeader.MeshBufferOffset; + + fseek(file, meshHeaderOffset, SEEK_SET); + fwrite(&meshHeader, sizeof(SampleMeshHeader), 1, file); + + fseek(file, meshPrimitiveHeadersOffset, SEEK_SET); + fwrite(meshPrimitiveHeaders, sizeof(SampleMeshPrimitiveHeader), mesh.MeshPrimitives.Length, file); + + fseek(file, 0, SEEK_END); + + return true; +} + +bool WriteSceneData(FILE* file, ElemLoadSceneResult scene, const char* sceneInputPath) +{ + assert(file); + + SampleSceneHeader sceneHeader = + { + .FileId = { 'S', 'C', 'E', 'N', 'E' }, + .MeshCount = scene.Meshes.Length, + .MaterialCount = scene.Materials.Length, + .NodeCount = scene.Nodes.Length + }; + + fwrite(&sceneHeader, sizeof(SampleSceneHeader), 1, file); + + // TODO: Get rid of malloc? + SampleDataBlockEntry* meshDataOffsets = (SampleDataBlockEntry*)malloc(sizeof(SampleDataBlockEntry) * scene.Meshes.Length); + fwrite(meshDataOffsets, sizeof(SampleDataBlockEntry), scene.Meshes.Length, file); + + for (uint32_t i = 0; i < scene.Materials.Length; i++) + { + ElemSceneMaterial* material = &scene.Materials.Items[i]; + + SampleSceneMaterialHeader materialHeader = + { + .AlbedoFactor = material->AlbedoFactor + }; + + strncpy(materialHeader.Name, material->Name, 50); + + // TODO: Here we need to compute a list of unique textures and add them to a list + // so we don't need to compute that unique list at runtime + if (material->AlbedoTexturePath) + { + GetRelativeResourcePath(sceneInputPath, material->AlbedoTexturePath, ".texture", materialHeader.AlbedoTexturePath, 255); + } + + if (material->NormalTexturePath) + { + GetRelativeResourcePath(sceneInputPath, material->NormalTexturePath, ".texture", materialHeader.NormalTexturePath, 255); + } + + fwrite(&materialHeader, sizeof(SampleSceneMaterialHeader), 1, file); + } + + for (uint32_t i = 0; i < scene.Nodes.Length; i++) + { + ElemSceneNode* node = &scene.Nodes.Items[i]; + + SampleSceneNodeHeader fileNode = + { + .NodeType = (SampleSceneNodeType)node->NodeType, + .ReferenceIndex = node->ReferenceIndex, + .Rotation = node->Rotation, + .Scale = node->Scale, + .Translation = node->Translation + }; + + strncpy(fileNode.Name, node->Name, 50); + fwrite(&fileNode, sizeof(SampleSceneNodeHeader), 1, file); + + //printf("Node: '%s' MeshId=%d (T=%f,%f,%f)\n", node->Name, node->ReferenceIndex, node->Translation.X, node->Translation.Y, node->Translation.Z); + } + + double beforeMeshlets = SampleGetTimerValueInMS(); + + for (uint32_t i = 0; i < scene.Meshes.Length; i++) + { + uint32_t dataOffset = ftell(file); + + bool result = WriteMeshData(file, scene.Meshes.Items[i]); + assert(result); + + meshDataOffsets[i] = (SampleDataBlockEntry) { .Offset = dataOffset, .SizeInBytes = ftell(file) - dataOffset }; + } + + printf("Built meshlets in %.2fs\n", (SampleGetTimerValueInMS() - beforeMeshlets) / 1000.0); + + fseek(file, sizeof(sceneHeader), SEEK_SET); + fwrite(meshDataOffsets, sizeof(SampleDataBlockEntry), scene.Meshes.Length, file); + + return true; +} + +int main(int argc, const char* argv[]) +{ + // TODO: Add an option to handle handness change + if (argc < 3) + { + printf("USAGE: SceneCompiler [options] inputfile outputfile\n"); + printf("\n"); + printf("OPTIONS:\n"); + printf(" --meshlet-triangle-count\tTBD: TBD. Default: TBD.\n"); + printf("\n"); + return 0; + } + + int32_t inputPathIndex = argc - 2; + const char* inputPath = argv[inputPathIndex]; + + int32_t outputPathIndex = argc - 1; + const char* outputPath = argv[outputPathIndex]; + + // TODO: Add more checks + for (uint32_t i = 1; i < (uint32_t)(argc - 2); i++) + { + printf("Options: %s\n", argv[i]); + + /*if (strcmp(argv[i], "--target-platform") == 0) + { + const char* targetPlatformString = argv[i + 1]; + + if (strcmp(targetPlatformString, "iOS") == 0) + { + targetPlatform = ElemToolsPlatform_iOS; + printf("iOS platform\n"); + } + } + else if (strcmp(argv[i], "--target-api") == 0) + { + const char* targetPlatformString = argv[i + 1]; + + if (strcmp(targetPlatformString, "vulkan") == 0) + { + targetApi = ElemToolsGraphicsApi_Vulkan; + printf("Vulkan api\n"); + } + } + else if (strcmp(argv[i], "--debug") == 0) + { + debugMode = true; + }*/ + } + + printf("Compiling mesh: %s\n", inputPath); + + SampleInitTimer(); + double initialTimer = SampleGetTimerValueInMS(); + + ElemLoadSceneOptions loadSceneOptions = {}; + + // HACK: For now we hardcode options + if (strstr(inputPath, "sponza")) + { + loadSceneOptions.Scaling = 0.01f; + } + else if (strstr(inputPath, "Sponza.gltf")) + { + //loadSceneOptions.CoordinateSystem = ElemSceneCoordinateSystem_RightHanded; + } + else if (strstr(inputPath, "bistro")) + { + //loadSceneOptions.CoordinateSystem = ElemSceneCoordinateSystem_RightHanded; + } + + // TODO: Scaling should be passed as a parameter + ElemLoadSceneResult scene = ElemLoadScene(inputPath, &loadSceneOptions); + + DisplayOutputMessages("LoadScene", scene.Messages); + + if (scene.HasErrors) + { + return 1; + } + + printf("Loaded mesh in %.2fs\n", (SampleGetTimerValueInMS() - initialTimer) / 1000.0); + + printf("Writing Scene data to: %s\n", outputPath); + + FILE* file = fopen(outputPath, "wb"); + WriteSceneData(file, scene, inputPath); + fclose(file); + printf("Scene compiled in %.2fs\n", (SampleGetTimerValueInMS() - initialTimer) / 1000.0); +} diff --git a/samples/ElementalTools/03-TextureCompiler/CMakeLists.txt b/samples/ElementalTools/03-TextureCompiler/CMakeLists.txt new file mode 100644 index 00000000..69c2c34f --- /dev/null +++ b/samples/ElementalTools/03-TextureCompiler/CMakeLists.txt @@ -0,0 +1,9 @@ +if(NOT BUILD_FOR_IOS) + set(SAMPLE_NAME TextureCompiler) + + add_executable(${SAMPLE_NAME} main.c) + target_link_libraries(${SAMPLE_NAME} PRIVATE SampleCommon) + target_link_libraries(${SAMPLE_NAME} PRIVATE ElementalToolsInterface) + + configure_project_package(${SAMPLE_NAME} "samples" DEPENDENCIES ElementalTools) +endif() diff --git a/samples/ElementalTools/03-TextureCompiler/main.c b/samples/ElementalTools/03-TextureCompiler/main.c new file mode 100644 index 00000000..49264a05 --- /dev/null +++ b/samples/ElementalTools/03-TextureCompiler/main.c @@ -0,0 +1,142 @@ +#include "ElementalTools.h" +#include "SampleUtils.h" +#include "SampleTexture.h" + +int main(int argc, const char* argv[]) +{ + // TODO: Refactor options parsing and put it in a header common + // to tools sample and normal samples + if (argc < 3) + { + printf("USAGE: TextureCompiler [options] inputfile outputfile\n"); + printf("\n"); + printf("OPTIONS:\n"); + printf("\n"); + return 0; + } + + int32_t inputPathIndex = argc - 2; + const char* inputPath = argv[inputPathIndex]; + + int32_t outputPathIndex = argc - 1; + const char* outputPath = argv[outputPathIndex]; + + // TODO: Add more checks + for (uint32_t i = 1; i < (uint32_t)(argc - 2); i++) + { + printf("Options: %s\n", argv[i]); + } + + printf("Compiling texture: %s\n", inputPath); + + SampleInitTimer(); + double initialTimer = SampleGetTimerValueInMS(); + + ElemLoadTextureResult loadTextureResult = ElemLoadTexture(inputPath, NULL); + + DisplayOutputMessages("LoadTexture", loadTextureResult.Messages); + + if (loadTextureResult.HasErrors) + { + return 1; + } + + printf("Loaded texture in %.2fs\n", (SampleGetTimerValueInMS() - initialTimer) / 1000.0); + + ElemTextureMipDataSpan mipData = loadTextureResult.MipData; + + if (mipData.Length == 0) + { + printf("No mip data in the image..."); + return 1; + } + + if (mipData.Length == 1) + { + double beforeMipGenerationTimer = SampleGetTimerValueInMS(); + printf("Generating MipData...\n"); + + ElemGenerateTextureMipDataResult generateMipDataResult = ElemGenerateTextureMipData(loadTextureResult.Format, &loadTextureResult.MipData.Items[0], NULL); + + DisplayOutputMessages("GenerateTextureMipData", generateMipDataResult.Messages); + + if (generateMipDataResult.HasErrors) + { + return 1; + } + + printf("GeneratedMips Count: %d\n", generateMipDataResult.MipData.Length); + + mipData = generateMipDataResult.MipData; + printf("Generated MipData in %.2fs\n", (SampleGetTimerValueInMS() - beforeMipGenerationTimer) / 1000.0); + } + + printf("Writing texture data to: %s\n", outputPath); + + // TODO: Create the directory if it doesn't exist + + FILE* file = fopen(outputPath, "wb"); + + SampleTextureHeader textureHeader = + { + .FileId = { 'T', 'E', 'X', 'T', 'U', 'R', 'E' }, + .Format = SampleTextureFormat_BC7, // TODO: Don't hardcode this + .Width = loadTextureResult.Width, + .Height = loadTextureResult.Height, + .MipCount = mipData.Length + }; + + fwrite(&textureHeader, sizeof(SampleTextureHeader), 1, file); + + // TODO: Get rid of malloc? + SampleTextureDataBlockEntry* mipDataOffsets = (SampleTextureDataBlockEntry*)malloc(sizeof(SampleTextureDataBlockEntry) * mipData.Length); + fwrite(mipDataOffsets, sizeof(SampleTextureDataBlockEntry), mipData.Length, file); + + if (loadTextureResult.Format != ElemToolsGraphicsFormat_BC7) + { + double beforeEncodeTimer = SampleGetTimerValueInMS(); + // TODO: Add format in the logging message + printf("Compressing data...\n"); + + for (uint32_t i = 0; i < mipData.Length; i++) + { + ElemTextureMipData* mipLevelData = &mipData.Items[i]; + printf("Mip level %d Size: %dx%d\n", i, mipLevelData->Width, mipLevelData->Height); + + ElemCompressTextureMipDataResult compressedTextureData = ElemCompressTextureMipData(ElemToolsGraphicsFormat_BC7, mipLevelData, NULL); + + DisplayOutputMessages("CompressTextureMipData", compressedTextureData.Messages); + + if (compressedTextureData.HasErrors) + { + return 1; + } + + uint32_t dataOffset = ftell(file); + fwrite(compressedTextureData.MipData.Data.Items, sizeof(uint8_t), compressedTextureData.MipData.Data.Length, file); + + mipDataOffsets[i] = (SampleTextureDataBlockEntry) { .Offset = dataOffset, .SizeInBytes = ftell(file) - dataOffset }; + } + + printf("Compressed data in %.2fs\n", (SampleGetTimerValueInMS() - beforeEncodeTimer) / 1000.0); + } + else + { + for (uint32_t i = 0; i < mipData.Length; i++) + { + ElemTextureMipData* mipLevelData = &mipData.Items[i]; + printf("Mip level %d Size: %dx%d\n", i, mipLevelData->Width, mipLevelData->Height); + + uint32_t dataOffset = ftell(file); + fwrite(mipLevelData->Data.Items, sizeof(uint8_t), mipLevelData->Data.Length, file); + + mipDataOffsets[i] = (SampleTextureDataBlockEntry) { .Offset = dataOffset, .SizeInBytes = ftell(file) - dataOffset }; + } + } + + fseek(file, sizeof(SampleTextureHeader), SEEK_SET); + fwrite(mipDataOffsets, sizeof(SampleTextureDataBlockEntry), mipData.Length, file); + + fclose(file); + printf("Texture compiled in %.2fs\n", (SampleGetTimerValueInMS() - initialTimer) / 1000.0); +} diff --git a/samples/SamplesTODO.md b/samples/SamplesTODO.md index d94863a3..168a5b7c 100644 --- a/samples/SamplesTODO.md +++ b/samples/SamplesTODO.md @@ -5,6 +5,7 @@ - Multi viewports sample (like a 3D software) - Frame pacing - Occlusion culling with work graphs + Check https://github.com/zeux/niagara/commit/bf90aa8c78e352d3b753b35553a3bcc8c65ef7a0 for a potential bug if we take code from CoreEngine - Hello Raytracing (Cornel Box) With dispayRays and RayInline - Texture streaming - Upscaling diff --git a/samples/SharedData/CMakeLists.txt b/samples/SharedData/CMakeLists.txt new file mode 100644 index 00000000..bf622930 --- /dev/null +++ b/samples/SharedData/CMakeLists.txt @@ -0,0 +1,3 @@ +add_custom_target(SampleSharedData ALL) + +message("Sample Shared Data") diff --git a/samples/XX-MinimalTriangle/main.c b/samples/XX-MinimalTriangle/main.c index 5bf33408..3bb8ab62 100644 --- a/samples/XX-MinimalTriangle/main.c +++ b/samples/XX-MinimalTriangle/main.c @@ -49,8 +49,15 @@ typedef struct void UpdateSwapChain(const ElemSwapChainUpdateParameters* updateParameters, void* payload); +ElemToolsDataSpan LoadShaderDataHandler(const char* path) +{ + return (ElemToolsDataSpan) { .Items = (uint8_t*)shaderSource, .Length = strlen(shaderSource) }; +} + void InitSample(void* payload) { + ElemToolsConfigureFileIO(LoadShaderDataHandler); + ApplicationPayload* applicationPayload = (ApplicationPayload*)payload; applicationPayload->Window = ElemCreateWindow(NULL); @@ -63,8 +70,7 @@ void InitSample(void* payload) ElemSystemInfo systemInfo = ElemGetSystemInfo(); ElemGraphicsDeviceInfo graphicsDeviceInfo = ElemGetGraphicsDeviceInfo(applicationPayload->GraphicsDevice); - ElemShaderSourceData shaderSourceData = { .ShaderLanguage = ElemShaderLanguage_Hlsl, .Data = { .Items = (uint8_t*)shaderSource, .Length = strlen(shaderSource) } }; - ElemShaderCompilationResult compilationResult = ElemCompileShaderLibrary((ElemToolsGraphicsApi)graphicsDeviceInfo.GraphicsApi, (ElemToolsPlatform)systemInfo.Platform, &shaderSourceData, NULL); + ElemShaderCompilationResult compilationResult = ElemCompileShaderLibrary((ElemToolsGraphicsApi)graphicsDeviceInfo.GraphicsApi, (ElemToolsPlatform)systemInfo.Platform, "Triangle.hlsl", NULL); ElemShaderLibrary shaderLibrary = ElemCreateShaderLibrary(applicationPayload->GraphicsDevice, (ElemDataSpan) { .Items = compilationResult.Data.Items, .Length = compilationResult.Data.Length }); @@ -72,7 +78,7 @@ void InitSample(void* payload) .ShaderLibrary = shaderLibrary, .MeshShaderFunction = "MeshMain", .PixelShaderFunction = "PixelMain", - .TextureFormats = { .Items = (ElemGraphicsFormat[]) { swapChainInfo.Format }, .Length = 1 } + .RenderTargets = { .Items = (ElemGraphicsPipelineStateRenderTarget[]) {{ .Format = swapChainInfo.Format }}, .Length = 1 } }); ElemFreeShaderLibrary(shaderLibrary); diff --git a/samples/screenshots_old/01-Foundations/01-HelloWorld.png b/samples/XX-screenshots_old/01-Foundations/01-HelloWorld.png similarity index 100% rename from samples/screenshots_old/01-Foundations/01-HelloWorld.png rename to samples/XX-screenshots_old/01-Foundations/01-HelloWorld.png diff --git a/samples/screenshots_old/01-Foundations/02-HelloWindow.png b/samples/XX-screenshots_old/01-Foundations/02-HelloWindow.png similarity index 100% rename from samples/screenshots_old/01-Foundations/02-HelloWindow.png rename to samples/XX-screenshots_old/01-Foundations/02-HelloWindow.png diff --git a/samples/screenshots_old/02-Graphics/01-HelloTriangle.png b/samples/XX-screenshots_old/02-Graphics/01-HelloTriangle.png similarity index 100% rename from samples/screenshots_old/02-Graphics/01-HelloTriangle.png rename to samples/XX-screenshots_old/02-Graphics/01-HelloTriangle.png diff --git a/samples/screenshots_old/GettingStarted.png b/samples/XX-screenshots_old/GettingStarted.png similarity index 100% rename from samples/screenshots_old/GettingStarted.png rename to samples/XX-screenshots_old/GettingStarted.png diff --git a/src/Elemental/Apple/Frameworks/GameController/GCKeyboardInput.hpp b/src/Elemental/Apple/Frameworks/GameController/GCKeyboardInput.hpp index 550795a6..caebf310 100644 --- a/src/Elemental/Apple/Frameworks/GameController/GCKeyboardInput.hpp +++ b/src/Elemental/Apple/Frameworks/GameController/GCKeyboardInput.hpp @@ -8,6 +8,7 @@ namespace GC { + // BUG: The list is wrong! _NS_ENUM(NS::UInteger, KeyCode) { KeyA = 4, @@ -85,15 +86,15 @@ namespace GC F19, F20, PrintScreen, - ScrollLock, + /*ScrollLock, Pause, Insert, Home, PageUp, DeleteForward, End, - PageDown, - RightArrow, + PageDown,*/ + RightArrow = 79, LeftArrow, DownArrow, UpArrow, @@ -144,6 +145,14 @@ namespace GC RightShift, RightAlt, RightGui, + ScrollLock, + Pause, + Insert, + Home, + PageUp, + DeleteForward, + End, + PageDown, }; class KeyboardInput; diff --git a/src/Elemental/Apple/Graphics/MetalCommandList.cpp b/src/Elemental/Apple/Graphics/MetalCommandList.cpp index b0349095..7bd44710 100644 --- a/src/Elemental/Apple/Graphics/MetalCommandList.cpp +++ b/src/Elemental/Apple/Graphics/MetalCommandList.cpp @@ -1,12 +1,10 @@ #include "MetalCommandList.h" +#include "MetalConfig.h" #include "MetalGraphicsDevice.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define METAL_MAX_COMMANDQUEUES 10u -#define METAL_MAX_COMMANDLISTS 64u - SystemDataPool metalCommandQueuePool; SystemDataPool metalCommandListPool; @@ -75,6 +73,7 @@ void ResetMetalCommandEncoder(ElemCommandList commandList) commandListData->CommandEncoder->endEncoding(); commandListData->CommandEncoder.reset(); commandListData->CommandEncoderType = MetalCommandEncoderType_None; + commandListData->ArgumentBufferBound = false; } } @@ -177,7 +176,7 @@ ElemCommandList MetalGetCommandList(ElemCommandQueue commandQueue, const ElemCom { metalCommandBuffer->setLabel(NS::String::string(options->DebugName, NS::UTF8StringEncoding)); } - + auto resourceBarrierPool = CreateResourceBarrierPool(MetalGraphicsMemoryArena); auto handle = SystemAddDataPoolItem(metalCommandListPool, { @@ -203,6 +202,9 @@ void MetalCommitCommandList(ElemCommandList commandList) auto commandListData = GetMetalCommandListData(commandList); SystemAssert(commandListData); + + FreeResourceBarrierPool(commandListData->ResourceBarrierPool); + commandListData->IsCommitted = true; metalThreadCommandBufferCommitted = true; } @@ -213,6 +215,29 @@ ElemFence MetalExecuteCommandLists(ElemCommandQueue commandQueue, ElemCommandLis auto commandQueueData = GetMetalCommandQueueData(commandQueue); SystemAssert(commandQueueData); + + if (options && options->FencesToWait.Length > 0) + { + for (uint32_t i = 0; i < options->FencesToWait.Length; i++) + { + auto fenceToWait = options->FencesToWait.Items[i]; + + auto commandQueueToWaitData = GetMetalCommandQueueData(fenceToWait.CommandQueue); + SystemAssert(commandQueueToWaitData); + + auto commandQueueToWaitDataFull = GetMetalCommandQueueDataFull(fenceToWait.CommandQueue); + SystemAssert(commandQueueToWaitDataFull); + + auto commandBuffer = NS::TransferPtr(commandQueueToWaitData->DeviceObject->commandBufferWithUnretainedReferences()); + commandBuffer->encodeWait(commandQueueToWaitData->QueueEvent.get(), fenceToWait.FenceValue); + commandBuffer->commit(); + + if (MetalDebugBarrierInfoEnabled) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Waiting for fence before ExecuteCommandLists. (CommandQueue=%d, Value=%d)", fenceToWait.CommandQueue, fenceToWait.FenceValue); + } + } + } ElemFence fence = {}; @@ -234,11 +259,15 @@ ElemFence MetalExecuteCommandLists(ElemCommandQueue commandQueue, ElemCommandLis { fence = CreateMetalCommandQueueFence(commandQueue, commandListData->DeviceObject.get()); } + + for (uint32_t j = 0; j < commandListData->UploadBufferCount; j++) + { + UpdateUploadBufferPoolItemFence(commandListData->UploadBufferPoolItems[j], fence); + } commandListData->DeviceObject->commit(); commandListData->DeviceObject.reset(); - FreeResourceBarrierPool(commandListData->ResourceBarrierPool); SystemRemoveDataPoolItem(metalCommandListPool, commandLists.Items[i]); } @@ -266,7 +295,8 @@ void MetalWaitForFenceOnCpu(ElemFence fence) if (fence.FenceValue > commandQueueToWaitDataFull->LastCompletedFenceValue) { - SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Wait for Fence on CPU..."); + // TODO: Log this with a special option? + //SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Wait for Fence on CPU..."); auto dispatchGroup = dispatch_group_create(); dispatch_group_enter(dispatchGroup); diff --git a/src/Elemental/Apple/Graphics/MetalCommandList.h b/src/Elemental/Apple/Graphics/MetalCommandList.h index 43e801c6..7ca5a030 100644 --- a/src/Elemental/Apple/Graphics/MetalCommandList.h +++ b/src/Elemental/Apple/Graphics/MetalCommandList.h @@ -7,7 +7,8 @@ enum MetalCommandEncoderType { MetalCommandEncoderType_None = 0, MetalCommandEncoderType_Render = 1, - MetalCommandEncoderType_Compute = 2 + MetalCommandEncoderType_Compute = 2, + MetalCommandEncoderType_Copy = 3 }; enum MetalResourceBarrierType @@ -44,6 +45,9 @@ struct MetalCommandListData ElemPipelineState PipelineState; bool IsCommitted; ResourceBarrierPool ResourceBarrierPool; + bool ArgumentBufferBound; + UploadBufferPoolItem>* UploadBufferPoolItems[MAX_UPLOAD_BUFFERS]; + uint32_t UploadBufferCount; }; struct MetalCommandListDataFull diff --git a/src/Elemental/Apple/Graphics/MetalConfig.h b/src/Elemental/Apple/Graphics/MetalConfig.h new file mode 100644 index 00000000..2acf7192 --- /dev/null +++ b/src/Elemental/Apple/Graphics/MetalConfig.h @@ -0,0 +1,19 @@ +#pragma once + +#define METAL_MEMORY_ARENA 128 * 1024 * 1024 +#define METAL_READBACK_MEMORY_ARENA 32 * 1024 * 1024 + +#define METAL_MAX_DEVICES 10u + +#define METAL_MAX_COMMANDQUEUES 10u +#define METAL_MAX_COMMANDLISTS 64u + +#define METAL_MAX_GRAPHICSHEAP 32 +#define METAL_MAX_RESOURCES 1000000 +#define METAL_MAX_SAMPLERS 2048 + +#define METAL_MAX_SWAPCHAINS 10u + +#define METAL_MAX_LIBRARIES UINT16_MAX +#define METAL_MAX_PIPELINESTATES UINT16_MAX + diff --git a/src/Elemental/Apple/Graphics/MetalGraphicsDevice.cpp b/src/Elemental/Apple/Graphics/MetalGraphicsDevice.cpp index 951b5e12..82e78a16 100644 --- a/src/Elemental/Apple/Graphics/MetalGraphicsDevice.cpp +++ b/src/Elemental/Apple/Graphics/MetalGraphicsDevice.cpp @@ -1,15 +1,14 @@ #include "MetalGraphicsDevice.h" +#include "MetalConfig.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemLogging.h" #include "SystemMemory.h" -#define METAL_MAXDEVICES 10u - struct MetalArgumentBufferDescriptor { uint64_t BufferAddress; - uint64_t TextureResourceId; + uint64_t ResourceId; uint64_t Metadata; }; @@ -44,15 +43,23 @@ void* MetalDebugReportCallback(void* arg) while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) { buffer[bytesRead - 1] = '\0'; + //SystemLogDebugMessage(ElemLogMessageCategory_Graphics, buffer); auto splittedStrings = SystemSplitString(stackMemoryArena, ReadOnlySpan(buffer), ']'); if (splittedStrings.Length == 2) { auto message = ((ReadOnlySpan)splittedStrings[1]).Slice(1); + // TODO: Refactor if (SystemFindSubString(message, "Metal API Validation Enabled") == -1) { - SystemLogErrorMessage(ElemLogMessageCategory_Graphics, message); + if (SystemFindSubString(message, "unused binding in encoder") == -1) + { + if (SystemFindSubString(message, "redundant setting") == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, message); + } + } } } } @@ -65,6 +72,7 @@ void InitMetal() auto stackMemoryArena = SystemGetStackMemoryArena(); // TODO: There is nothing in code to enable that 😢 + return; if (MetalDebugLayerEnabled) { @@ -106,8 +114,9 @@ void InitMetalGraphicsDeviceMemory() { if (!MetalGraphicsMemoryArena.Storage) { - MetalGraphicsMemoryArena = SystemAllocateMemoryArena(); - metalGraphicsDevicePool = SystemCreateDataPool(MetalGraphicsMemoryArena, METAL_MAXDEVICES); + // TODO: To Review + MetalGraphicsMemoryArena = SystemAllocateMemoryArena(METAL_MEMORY_ARENA); + metalGraphicsDevicePool = SystemCreateDataPool(MetalGraphicsMemoryArena, METAL_MAX_DEVICES); InitMetal(); } @@ -130,6 +139,37 @@ ElemGraphicsDeviceInfo MetalConstructGraphicsDeviceInfo(const MTL::Device* devic }; } +MTL::CompareFunction ConvertToMetalCompareFunction(ElemGraphicsCompareFunction compareFunction) +{ + switch (compareFunction) + { + case ElemGraphicsCompareFunction_Never: + return MTL::CompareFunctionNever; + + case ElemGraphicsCompareFunction_Less: + return MTL::CompareFunctionLess; + + case ElemGraphicsCompareFunction_Equal: + return MTL::CompareFunctionEqual; + + case ElemGraphicsCompareFunction_LessEqual: + return MTL::CompareFunctionLessEqual; + + case ElemGraphicsCompareFunction_Greater: + return MTL::CompareFunctionGreater; + + case ElemGraphicsCompareFunction_NotEqual: + return MTL::CompareFunctionNotEqual; + + case ElemGraphicsCompareFunction_GreaterEqual: + return MTL::CompareFunctionGreaterEqual; + + case ElemGraphicsCompareFunction_Always: + return MTL::CompareFunctionAlways; + } +} + + bool MetalCheckGraphicsDeviceCompatibility(const MTL::Device* device) { // TODO: This crash on ios simulator @@ -197,7 +237,36 @@ uint32_t CreateMetalArgumentBufferHandleForTexture(MetalArgumentBuffer argumentB } auto argumentBufferData = (MetalArgumentBufferDescriptor*)storage->ArgumentBuffer->contents(); - argumentBufferData[argumentIndex].TextureResourceId = (uint64_t)texture->gpuResourceID()._impl; + argumentBufferData[argumentIndex].ResourceId = (uint64_t)texture->gpuResourceID()._impl; + + return argumentIndex; +} + +uint32_t CreateMetalArgumentBufferHandleForSamplerState(MetalArgumentBuffer argumentBuffer, MTL::SamplerState* sampler) +{ + SystemAssert(argumentBuffer.Storage); + + auto storage = argumentBuffer.Storage; + auto argumentIndex = UINT32_MAX; + + do + { + if (storage->FreeListIndex == UINT32_MAX) + { + argumentIndex = UINT32_MAX; + break; + } + + argumentIndex = storage->FreeListIndex; + } while (!SystemAtomicCompareExchange(storage->FreeListIndex, argumentIndex, storage->Items[storage->FreeListIndex].Next)); + + if (argumentIndex == UINT32_MAX) + { + argumentIndex = SystemAtomicAdd(storage->CurrentIndex, 1); + } + + auto argumentBufferData = (MetalArgumentBufferDescriptor*)storage->ArgumentBuffer->contents(); + argumentBufferData[argumentIndex].BufferAddress = (uint64_t)sampler->gpuResourceID()._impl; return argumentIndex; } @@ -277,7 +346,7 @@ ElemGraphicsDeviceInfoSpan MetalGetAvailableGraphicsDevices() InitMetalGraphicsDeviceMemory(); auto stackMemoryArena = SystemGetStackMemoryArena(); - auto deviceInfos = SystemPushArray(stackMemoryArena, METAL_MAXDEVICES); + auto deviceInfos = SystemPushArray(stackMemoryArena, METAL_MAX_DEVICES); auto currentDeviceInfoIndex = 0u; auto device = NS::TransferPtr(MTL::CreateSystemDefaultDevice()); @@ -310,6 +379,7 @@ ElemGraphicsDevice MetalCreateGraphicsDevice(const ElemGraphicsDeviceOptions* op auto memoryArena = SystemAllocateMemoryArena(); auto resourceArgumentBuffer = CreateMetalArgumentBuffer(device, memoryArena, METAL_MAX_RESOURCES); + auto samplerArgumentBuffer = CreateMetalArgumentBuffer(device, memoryArena, METAL_MAX_SAMPLERS); auto residencySetDescriptor = NS::TransferPtr(MTL::ResidencySetDescriptor::alloc()->init()); residencySetDescriptor->setInitialCapacity(8); @@ -319,12 +389,18 @@ ElemGraphicsDevice MetalCreateGraphicsDevice(const ElemGraphicsDeviceOptions* op SystemAssert(error == nullptr); residencySet->addAllocation(resourceArgumentBuffer.Storage->ArgumentBuffer.get()); + residencySet->addAllocation(samplerArgumentBuffer.Storage->ArgumentBuffer.get()); residencySet->commit(); + + // TODO: This need to be checked. We don't know how many max threads will use this. Maybe we can allocate for MAX_CONC_THREADS variable of param (that can be overriden) + auto uploadBuffers = SystemPushArray>*>(MetalGraphicsMemoryArena, MAX_UPLOAD_BUFFERS); auto handle = SystemAddDataPoolItem(metalGraphicsDevicePool, { .Device = device, .ResourceArgumentBuffer = resourceArgumentBuffer, - .MemoryArena = memoryArena + .SamplerArgumentBuffer = samplerArgumentBuffer, + .MemoryArena = memoryArena, + .UploadBufferPools = uploadBuffers }); SystemAddDataPoolItemFull(metalGraphicsDevicePool, handle, { @@ -341,6 +417,27 @@ void MetalFreeGraphicsDevice(ElemGraphicsDevice graphicsDevice) auto graphicsDeviceData = GetMetalGraphicsDeviceData(graphicsDevice); SystemAssert(graphicsDeviceData); + for (uint32_t i = 0; i < graphicsDeviceData->UploadBufferPools.Length; i++) + { + auto bufferPool = graphicsDeviceData->UploadBufferPools[i]; + + if (bufferPool) + { + for (uint32_t j = 0; j < MAX_UPLOAD_BUFFERS; j++) + { + auto uploadBuffer = &bufferPool->UploadBuffers[j]; + + if (uploadBuffer && uploadBuffer->Buffer) + { + uploadBuffer->Buffer.reset(); + *uploadBuffer = {}; + } + } + + *bufferPool = {}; + } + } + FreeMetalArgumentBuffer(graphicsDeviceData->ResourceArgumentBuffer); SystemFreeMemoryArena(graphicsDeviceData->MemoryArena); diff --git a/src/Elemental/Apple/Graphics/MetalGraphicsDevice.h b/src/Elemental/Apple/Graphics/MetalGraphicsDevice.h index 3cb38543..4bcde811 100644 --- a/src/Elemental/Apple/Graphics/MetalGraphicsDevice.h +++ b/src/Elemental/Apple/Graphics/MetalGraphicsDevice.h @@ -1,11 +1,9 @@ #pragma once +#include "Graphics/UploadBufferPool.h" #include "Elemental.h" #include "SystemMemory.h" -// TODO: Increase to 1.000.000? -#define METAL_MAX_RESOURCES 500000 - struct MetalArgumentBufferStorage; struct MetalArgumentBuffer @@ -17,7 +15,11 @@ struct MetalGraphicsDeviceData { NS::SharedPtr Device; MetalArgumentBuffer ResourceArgumentBuffer; + MetalArgumentBuffer SamplerArgumentBuffer; MemoryArena MemoryArena; + uint64_t UploadBufferGeneration; + Span>*> UploadBufferPools; + uint32_t CurrentUploadBufferPoolIndex; }; struct MetalGraphicsDeviceDataFull @@ -32,8 +34,11 @@ extern bool MetalDebugBarrierInfoEnabled; MetalGraphicsDeviceData* GetMetalGraphicsDeviceData(ElemGraphicsDevice graphicsDevice); MetalGraphicsDeviceDataFull* GetMetalGraphicsDeviceDataFull(ElemGraphicsDevice graphicsDevice); +MTL::CompareFunction ConvertToMetalCompareFunction(ElemGraphicsCompareFunction compareFunction); + uint32_t CreateMetalArgumentBufferHandleForTexture(MetalArgumentBuffer argumentBuffer, MTL::Texture* texture); uint32_t CreateMetalArgumentBufferHandleForBuffer(MetalArgumentBuffer argumentBuffer, MTL::Buffer* buffer, uint32_t length); +uint32_t CreateMetalArgumentBufferHandleForSamplerState(MetalArgumentBuffer argumentBuffer, MTL::SamplerState* sampler); void FreeMetalArgumentBufferHandle(MetalArgumentBuffer argumentBuffer, uint64_t handle); void MetalSetGraphicsOptions(const ElemGraphicsOptions* options); diff --git a/src/Elemental/Apple/Graphics/MetalRendering.cpp b/src/Elemental/Apple/Graphics/MetalRendering.cpp index 97ec5157..01ba8918 100644 --- a/src/Elemental/Apple/Graphics/MetalRendering.cpp +++ b/src/Elemental/Apple/Graphics/MetalRendering.cpp @@ -6,6 +6,46 @@ #include "SystemFunctions.h" #include "SystemLogging.h" +MTL::LoadAction ConvertToMetalLoadAction(ElemRenderPassLoadAction loadAction) +{ + MTL::LoadAction metalLoadAction; + + switch (loadAction) + { + case ElemRenderPassLoadAction_Load: + metalLoadAction = MTL::LoadActionLoad; + break; + + case ElemRenderPassLoadAction_Clear: + metalLoadAction = MTL::LoadActionClear; + break; + + default: + metalLoadAction = MTL::LoadActionDontCare; + break; + } + + return metalLoadAction; +} + +MTL::StoreAction ConvertToMetalStoreAction(ElemRenderPassStoreAction storeAction) +{ + MTL::StoreAction metalStoreAction; + + switch (storeAction) + { + case ElemRenderPassStoreAction_Store: + metalStoreAction = MTL::StoreActionStore; + break; + + default: + metalStoreAction = MTL::StoreActionDontCare; + break; + } + + return metalStoreAction; + +} void MetalBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPassParameters* parameters) { // TODO: Check command list type != COMPUTE @@ -34,35 +74,8 @@ void MetalBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPass auto renderTargetDescriptor = renderPassDescriptor->colorAttachments()->object(i); - MTL::LoadAction loadAction; - - switch (renderTargetParameters.LoadAction) - { - case ElemRenderPassLoadAction_Load: - loadAction = MTL::LoadActionLoad; - break; - - case ElemRenderPassLoadAction_Clear: - loadAction = MTL::LoadActionClear; - break; - - default: - loadAction = MTL::LoadActionDontCare; - break; - } - - MTL::StoreAction storeAction; - - switch (renderTargetParameters.StoreAction) - { - case ElemRenderPassStoreAction_Store: - storeAction = MTL::StoreActionStore; - break; - - default: - storeAction = MTL::StoreActionDontCare; - break; - } + auto loadAction = ConvertToMetalLoadAction(renderTargetParameters.LoadAction); + auto storeAction = ConvertToMetalStoreAction(renderTargetParameters.StoreAction); if (textureData->DeviceObject.get() == nullptr) { @@ -80,6 +93,24 @@ void MetalBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPass } } + if (parameters->DepthStencil.DepthStencil != ELEM_HANDLE_NULL) + { + auto depthStencilParameters = parameters->DepthStencil; + + auto textureData = GetMetalResourceData(depthStencilParameters.DepthStencil); + SystemAssert(textureData); + + auto depthDescriptor = renderPassDescriptor->depthAttachment(); + // TODO: Validate usage + auto depthLoadAction = ConvertToMetalLoadAction(depthStencilParameters.DepthLoadAction); + auto depthStoreAction = ConvertToMetalStoreAction(depthStencilParameters.DepthStoreAction); + + depthDescriptor->setTexture((MTL::Texture*)textureData->DeviceObject.get()); + depthDescriptor->setLoadAction(depthLoadAction); + depthDescriptor->setStoreAction(depthStoreAction); + depthDescriptor->setClearDepth(depthStencilParameters.DepthClearValue); + } + auto renderCommandEncoder = NS::RetainPtr(commandListData->DeviceObject->renderCommandEncoder(renderPassDescriptor.get())); if (!renderCommandEncoder.get()) @@ -106,17 +137,39 @@ void MetalBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPass auto textureData = GetMetalResourceData(renderTargetParameters.RenderTarget); SystemAssert(textureData); - ElemViewport viewport = + ElemViewport viewport = { - .X = 0, - .Y = 0, + .X = 0, + .Y = 0, .Width = (float)textureData->Width, .Height = (float)textureData->Height, - .MinDepth = 0.0f, + .MinDepth = 0.0f, .MaxDepth = 1.0f }; - ElemSetViewport(commandList, &viewport); + ElemSetViewport(commandList, &viewport); + } + + if (parameters->ScissorRectangles.Length > 0) + { + ElemSetScissorRectangles(commandList, parameters->ScissorRectangles); + } + else if (parameters->RenderTargets.Length > 0) + { + auto renderTargetParameters = parameters->RenderTargets.Items[0]; + + auto textureData = GetMetalResourceData(renderTargetParameters.RenderTarget); + SystemAssert(textureData); + + ElemRectangle rectangle = + { + .X = 0, + .Y = 0, + .Width = (float)textureData->Width, + .Height = (float)textureData->Height + }; + + ElemSetScissorRectangle(commandList, &rectangle); } } @@ -143,7 +196,6 @@ void MetalSetViewports(ElemCommandList commandList, ElemViewportSpan viewports) auto stackMemoryArena = SystemGetStackMemoryArena(); auto metalViewports = SystemPushArray(stackMemoryArena, viewports.Length); - auto metalScissorRects = SystemPushArray(stackMemoryArena, viewports.Length); for (uint32_t i = 0; i < viewports.Length; i++) { @@ -156,18 +208,40 @@ void MetalSetViewports(ElemCommandList commandList, ElemViewportSpan viewports) .znear = viewports.Items[i].MinDepth, .zfar = viewports.Items[i].MaxDepth }; + } + + renderCommandEncoder->setViewports(metalViewports.Pointer, viewports.Length); +} +void MetalSetScissorRectangles(ElemCommandList commandList, ElemRectangleSpan rectangles) +{ + // TODO: Check command list type != COMPUTE + + SystemAssert(commandList != ELEM_HANDLE_NULL); + + auto commandListData = GetMetalCommandListData(commandList); + SystemAssert(commandListData); + + SystemAssert(commandListData->CommandEncoderType == MetalCommandEncoderType_Render); + SystemAssert(commandListData->CommandEncoder); + + auto renderCommandEncoder = (MTL::RenderCommandEncoder*)commandListData->CommandEncoder.get(); + + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto metalScissorRects = SystemPushArray(stackMemoryArena, rectangles.Length); + + for (uint32_t i = 0; i < rectangles.Length; i++) + { metalScissorRects[i] = { - .x = (uint32_t)viewports.Items[i].X, - .y = (uint32_t)viewports.Items[i].Y, - .width = (uint32_t)viewports.Items[i].Width, - .height = (uint32_t)viewports.Items[i].Height, + .x = (uint32_t)rectangles.Items[i].X, + .y = (uint32_t)rectangles.Items[i].Y, + .width = (uint32_t)rectangles.Items[i].Width, + .height = (uint32_t)rectangles.Items[i].Height, }; } - renderCommandEncoder->setViewports(metalViewports.Pointer, viewports.Length); - renderCommandEncoder->setScissorRects(metalScissorRects.Pointer, viewports.Length); + renderCommandEncoder->setScissorRects(metalScissorRects.Pointer, rectangles.Length); } void MetalDispatchMesh(ElemCommandList commandList, uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) @@ -186,9 +260,8 @@ void MetalDispatchMesh(ElemCommandList commandList, uint32_t threadGroupCountX, auto pipelineStateData = GetMetalPipelineStateData(commandListData->PipelineState); SystemAssert(pipelineStateData); - // TODO: Get the correct threads config for amplification shader auto renderCommandEncoder = (MTL::RenderCommandEncoder*)commandListData->CommandEncoder.get(); renderCommandEncoder->drawMeshThreadgroups(MTL::Size(threadGroupCountX, threadGroupCountY, threadGroupCountZ), - MTL::Size(32, 1, 1), + MTL::Size(1, 1, 1), MTL::Size(pipelineStateData->MeshShaderMetaData.ThreadSizeX, pipelineStateData->MeshShaderMetaData.ThreadSizeY, pipelineStateData->MeshShaderMetaData.ThreadSizeZ)); } diff --git a/src/Elemental/Apple/Graphics/MetalResource.cpp b/src/Elemental/Apple/Graphics/MetalResource.cpp index e3caa77d..4f8d6a5d 100644 --- a/src/Elemental/Apple/Graphics/MetalResource.cpp +++ b/src/Elemental/Apple/Graphics/MetalResource.cpp @@ -1,16 +1,21 @@ #include "MetalResource.h" +#include "MetalConfig.h" #include "MetalGraphicsDevice.h" #include "MetalCommandList.h" +#include "Graphics/UploadBufferPool.h" #include "Graphics/ResourceDeleteQueue.h" +#include "Graphics/Resource.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define METAL_MAX_GRAPHICSHEAP 32 - SystemDataPool metalGraphicsHeapPool; SystemDataPool metalResourcePool; Span metalResourceDescriptorInfos; +Span metalSamplerInfos; +MemoryArena metalReadBackMemoryArena; + +thread_local UploadBufferDevicePool> threadDirectX12UploadBufferPools[METAL_MAX_DEVICES]; void InitMetalResourceMemory() { @@ -20,7 +25,11 @@ void InitMetalResourceMemory() metalResourcePool = SystemCreateDataPool(MetalGraphicsMemoryArena, METAL_MAX_RESOURCES); // TODO: This should be part of the graphics device data - metalResourceDescriptorInfos = SystemPushArray(MetalGraphicsMemoryArena, METAL_MAX_RESOURCES); + metalResourceDescriptorInfos = SystemPushArray(MetalGraphicsMemoryArena, METAL_MAX_RESOURCES, AllocationState_Reserved); + metalSamplerInfos = SystemPushArray(MetalGraphicsMemoryArena, METAL_MAX_SAMPLERS, AllocationState_Reserved); + + // TODO: Allow to increase the size as a parameter + metalReadBackMemoryArena = SystemAllocateMemoryArena(METAL_READBACK_MEMORY_ARENA); } } @@ -51,7 +60,7 @@ MTL::PixelFormat ConvertToMetalResourceFormat(ElemGraphicsFormat format) case ElemGraphicsFormat_B8G8R8A8_SRGB: return MTL::PixelFormatBGRA8Unorm_sRGB; - case ElemGraphicsFormat_B8G8R8A8_UNORM: + case ElemGraphicsFormat_B8G8R8A8: return MTL::PixelFormatBGRA8Unorm; case ElemGraphicsFormat_R16G16B16A16_FLOAT: @@ -60,6 +69,15 @@ MTL::PixelFormat ConvertToMetalResourceFormat(ElemGraphicsFormat format) case ElemGraphicsFormat_R32G32B32A32_FLOAT: return MTL::PixelFormatRGBA32Float; + case ElemGraphicsFormat_D32_FLOAT: + return MTL::PixelFormatDepth32Float; + + case ElemGraphicsFormat_BC7: + return MTL::PixelFormatBC7_RGBAUnorm; + + case ElemGraphicsFormat_BC7_SRGB: + return MTL::PixelFormatBC7_RGBAUnorm_sRGB; + default: return MTL::PixelFormatR8Unorm; } @@ -73,7 +91,7 @@ ElemGraphicsFormat ConvertFromMetalResourceFormat(MTL::PixelFormat format) return ElemGraphicsFormat_B8G8R8A8_SRGB; case MTL::PixelFormatBGRA8Unorm: - return ElemGraphicsFormat_B8G8R8A8_UNORM; + return ElemGraphicsFormat_B8G8R8A8; case MTL::PixelFormatRGBA16Float: return ElemGraphicsFormat_R16G16B16A16_FLOAT; @@ -81,6 +99,15 @@ ElemGraphicsFormat ConvertFromMetalResourceFormat(MTL::PixelFormat format) case MTL::PixelFormatRGBA32Float: return ElemGraphicsFormat_R32G32B32A32_FLOAT; + case MTL::PixelFormatDepth32Float: + return ElemGraphicsFormat_D32_FLOAT; + + case MTL::PixelFormatBC7_RGBAUnorm: + return ElemGraphicsFormat_BC7; + + case MTL::PixelFormatBC7_RGBAUnorm_sRGB: + return ElemGraphicsFormat_BC7_SRGB; + default: return ElemGraphicsFormat_Raw; } @@ -95,7 +122,8 @@ MTL::TextureUsage ConvertToMetalResourceUsage(ElemGraphicsResourceUsage usage) result |= MTL::TextureUsageShaderWrite; } - if (usage & ElemGraphicsResourceUsage_RenderTarget) + if (usage & ElemGraphicsResourceUsage_RenderTarget || + usage & ElemGraphicsResourceUsage_DepthStencil) { result |= MTL::TextureUsageRenderTarget; } @@ -103,7 +131,7 @@ MTL::TextureUsage ConvertToMetalResourceUsage(ElemGraphicsResourceUsage usage) return result; } -ElemGraphicsResource CreateMetalGraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ElemGraphicsResourceUsage usage, NS::SharedPtr resource, bool isPresentTexture) +ElemGraphicsResource CreateMetalGraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ElemGraphicsHeap graphicsHeap, ElemGraphicsResourceUsage usage, NS::SharedPtr resource, bool isPresentTexture) { InitMetalResourceMemory(); @@ -128,6 +156,7 @@ ElemGraphicsResource CreateMetalGraphicsResourceFromResource(ElemGraphicsDevice auto handle = SystemAddDataPoolItem(metalResourcePool, { .DeviceObject = resource, + .GraphicsHeap = graphicsHeap, .Type = type, .Width = width, .Height = height, @@ -169,6 +198,13 @@ NS::SharedPtr CreateMetalTextureDescriptor(const ElemGra ElemGraphicsHeap MetalCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uint64_t sizeInBytes, const ElemGraphicsHeapOptions* options) { InitMetalResourceMemory(); + + auto heapType = ElemGraphicsHeapType_Gpu; + + if (options) + { + heapType = options->HeapType; + } SystemAssert(graphicsDevice != ELEM_HANDLE_NULL); @@ -200,6 +236,7 @@ ElemGraphicsHeap MetalCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uint .DeviceObject = graphicsHeap, .SizeInBytes = sizeInBytes, .GraphicsDevice = graphicsDevice, + .HeapType = heapType }); SystemAddDataPoolItemFull(metalGraphicsHeapPool, handle, { @@ -318,6 +355,18 @@ ElemGraphicsResource MetalCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, return ELEM_HANDLE_NULL; } + if ((resourceInfo->Usage & ElemGraphicsResourceUsage_RenderTarget) && (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil)) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Texture2D with usage RenderTarget and DepthStencil should not be used together."); + return ELEM_HANDLE_NULL; + } + + if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil && !CheckDepthStencilFormat(resourceInfo->Format)) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Texture2D with usage DepthStencil should use a compatible format."); + return ELEM_HANDLE_NULL; + } + auto textureDescriptor = CreateMetalTextureDescriptor(resourceInfo); resource = NS::TransferPtr(graphicsHeapData->DeviceObject->newTexture(textureDescriptor.get(), graphicsHeapOffset)); } @@ -334,6 +383,12 @@ ElemGraphicsResource MetalCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GraphicsBuffer usage should not be equals to RenderTarget."); return ELEM_HANDLE_NULL; } + + if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GraphicsBuffer usage should not be equals to DepthStencil."); + return ELEM_HANDLE_NULL; + } resource = NS::TransferPtr(graphicsHeapData->DeviceObject->newBuffer(resourceInfo->Width, MTL::ResourceHazardTrackingModeUntracked, graphicsHeapOffset)); } @@ -345,7 +400,7 @@ ElemGraphicsResource MetalCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, resource->setLabel(NS::String::string(resourceInfo->DebugName, NS::UTF8StringEncoding)); } - return CreateMetalGraphicsResourceFromResource(graphicsHeapData->GraphicsDevice, resourceInfo->Type, resourceInfo->Usage, resource, false); + return CreateMetalGraphicsResourceFromResource(graphicsHeapData->GraphicsDevice, resourceInfo->Type, graphicsHeap, resourceInfo->Usage, resource, false); } void MetalFreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGraphicsResourceOptions* options) @@ -389,26 +444,237 @@ ElemGraphicsResourceInfo MetalGetGraphicsResourceInfo(ElemGraphicsResource resou }; } -ElemDataSpan MetalGetGraphicsResourceDataSpan(ElemGraphicsResource resource) +void MetalUploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data) +{ + SystemAssert(resource != ELEM_HANDLE_NULL); + + auto resourceData = GetMetalResourceData(resource); + SystemAssert(resourceData); + + auto heapData = GetMetalGraphicsHeapData(resourceData->GraphicsHeap); + SystemAssert(heapData); + + if (resourceData->Type != ElemGraphicsResourceType_Buffer) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemUploadGraphicsBufferData only works with graphics buffers."); + return; + } + + if (heapData->HeapType != ElemGraphicsHeapType_GpuUpload) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemUploadGraphicsBufferData only works with graphics buffers allocated in a GpuUpload heap."); + return; + } + + auto dataPointer = ((MTL::Buffer*)resourceData->DeviceObject.get())->contents(); + + auto destinationPointer = (uint8_t*)dataPointer + offset; + memcpy(destinationPointer, data.Items, data.Length); +} + +ElemDataSpan MetalDownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options) { SystemAssert(resource != ELEM_HANDLE_NULL); auto resourceData = GetMetalResourceData(resource); SystemAssert(resourceData); + auto heapData = GetMetalGraphicsHeapData(resourceData->GraphicsHeap); + SystemAssert(heapData); + if (resourceData->Type != ElemGraphicsResourceType_Buffer) { - SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GetGraphicsResourceDataSpan only works with graphics buffers."); + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData only works with graphics buffers."); + return {}; + } + + if (heapData->HeapType != ElemGraphicsHeapType_Readback && heapData->HeapType != ElemGraphicsHeapType_GpuUpload) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData only works with graphics buffers allocated in a Readback heap or GpuUpload heap. (%d)", heapData->HeapType); return {}; } + if (heapData->HeapType != ElemGraphicsHeapType_Readback) + { + SystemLogWarningMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData works faster with graphics buffers allocated in a Readback heap."); + } + auto dataPointer = ((MTL::Buffer*)resourceData->DeviceObject.get())->contents(); - return + auto offset = 0u; + auto sizeInBytes = resourceData->Width; + + if (options) + { + offset = options->Offset; + + if (options->SizeInBytes != 0) + { + sizeInBytes = options->SizeInBytes; + } + } + + auto downloadedData = SystemPushArray(metalReadBackMemoryArena, sizeInBytes); + memcpy(downloadedData.Pointer, (uint8_t*)dataPointer + offset, sizeInBytes); + + return { .Items = downloadedData.Pointer, .Length = (uint32_t)downloadedData.Length }; +} + +NS::SharedPtr CreateMetalUploadBuffer(MTL::Device* graphicsDevice, uint64_t sizeInBytes) +{ + auto resource = NS::TransferPtr(graphicsDevice->newBuffer(sizeInBytes, MTL::ResourceHazardTrackingModeUntracked)); + + if (MetalDebugLayerEnabled) + { + resource->setLabel(NS::String::string("ElementalUploadBuffer", NS::UTF8StringEncoding)); + } + + return resource; +} + +UploadBufferMemory> GetMetalUploadBuffer(ElemGraphicsDevice graphicsDevice, uint64_t alignment, uint64_t sizeInBytes) +{ + auto graphicsDeviceData = GetMetalGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto graphicsIdUnpacked = UnpackSystemDataPoolHandle(graphicsDevice); + auto uploadBufferPool = &threadDirectX12UploadBufferPools[graphicsIdUnpacked.Index]; + + // TODO: Review this + if (!uploadBufferPool->IsInited) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Init pool"); + + auto poolIndex = SystemAtomicAdd(graphicsDeviceData->CurrentUploadBufferPoolIndex, 1); + graphicsDeviceData->UploadBufferPools[poolIndex] = uploadBufferPool; + uploadBufferPool->IsInited = true; + } + + auto uploadBuffer = GetUploadBufferPoolItem(uploadBufferPool, graphicsDeviceData->UploadBufferGeneration, alignment, sizeInBytes); + + if (uploadBuffer.PoolItem->IsResetNeeded) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to create upload buffer with size: %d...", uploadBuffer.PoolItem->SizeInBytes); + + if (uploadBuffer.PoolItem->Fence.FenceValue > 0) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Fence: %d", uploadBuffer.PoolItem->Fence.FenceValue); + MetalWaitForFenceOnCpu(uploadBuffer.PoolItem->Fence); + } + + // TODO: We need another flag to know if we need to delete + if (uploadBuffer.PoolItem->Buffer.get() != nullptr) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to delete buffer"); + uploadBuffer.PoolItem->Buffer.reset(); + } + + // TODO: Need to update the fence with the command list like in DirectX12 + + uploadBuffer.PoolItem->Buffer = CreateMetalUploadBuffer(graphicsDeviceData->Device.get(), uploadBuffer.PoolItem->SizeInBytes); + + uploadBuffer.PoolItem->CpuPointer = (uint8_t*)uploadBuffer.PoolItem->Buffer->contents(); + uploadBuffer.PoolItem->IsResetNeeded = false; + } + + return uploadBuffer; +} + +void MetalCopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters) +{ + // TODO: Implement file source + auto stackMemoryArena = SystemGetStackMemoryArena(); + + SystemAssert(commandList != ELEM_HANDLE_NULL); + + SystemAssert(parameters); + SystemAssert(parameters->Resource != ELEM_HANDLE_NULL); + + auto commandListData = GetMetalCommandListData(commandList); + SystemAssert(commandListData); + + auto graphicsDeviceData = GetMetalGraphicsDeviceData(commandListData->GraphicsDevice); + SystemAssert(graphicsDeviceData); + + auto resourceData = GetMetalResourceData(parameters->Resource); + SystemAssert(resourceData); + + ReadOnlySpan sourceData; + + // TODO: File Source + if (parameters->SourceType == ElemCopyDataSourceType_Memory) + { + sourceData = ReadOnlySpan(parameters->SourceMemoryData.Items, parameters->SourceMemoryData.Length); + } + + // TODO: Unit test first !!!!! + + auto uploadBufferAlignment = 4u; + auto uploadBufferSizeInBytes = sourceData.Length; + + if (resourceData->Type == ElemGraphicsResourceType_Texture2D) + { + if (resourceData->Format == ElemGraphicsFormat_BC7 || + resourceData->Format == ElemGraphicsFormat_BC7_SRGB) + { + uploadBufferAlignment = 16u; + uploadBufferSizeInBytes = SystemAlign(uploadBufferSizeInBytes, 16u); + } + } + + auto uploadBuffer = GetMetalUploadBuffer(commandListData->GraphicsDevice, uploadBufferAlignment, uploadBufferSizeInBytes); + SystemAssert(uploadBuffer.Offset + sourceData.Length <= uploadBuffer.PoolItem->SizeInBytes); + + // TODO: Can we do better here? + auto uploadBufferAlreadyInCommandList = false; + + for (uint32_t i = 0; i < MAX_UPLOAD_BUFFERS; i++) + { + if (commandListData->UploadBufferPoolItems[i] == uploadBuffer.PoolItem) + { + uploadBufferAlreadyInCommandList = true; + break; + } + } + + if (!uploadBufferAlreadyInCommandList) + { + commandListData->UploadBufferPoolItems[commandListData->UploadBufferCount++] = uploadBuffer.PoolItem; + } + + if (commandListData->CommandEncoderType != MetalCommandEncoderType_Copy) + { + ResetMetalCommandEncoder(commandList); + + commandListData->CommandEncoder = NS::RetainPtr(commandListData->DeviceObject->blitCommandEncoder()); + commandListData->CommandEncoderType = MetalCommandEncoderType_Copy; + } + + auto copyCommandEncoder = (MTL::BlitCommandEncoder*)commandListData->CommandEncoder.get(); + memcpy(uploadBuffer.PoolItem->CpuPointer + uploadBuffer.Offset, sourceData.Pointer, sourceData.Length); + + if (resourceData->Type == ElemGraphicsResourceType_Buffer) { - .Items = (uint8_t*)dataPointer, - .Length = resourceData->Width - }; + copyCommandEncoder->copyFromBuffer(uploadBuffer.PoolItem->Buffer.get(), uploadBuffer.Offset, (MTL::Buffer*)resourceData->DeviceObject.get(), parameters->BufferOffset, sourceData.Length); + } + else if (resourceData->Type == ElemGraphicsResourceType_Texture2D) + { + auto mipLevel = parameters->TextureMipLevel; + + auto mipWidth = SystemMax(1u, resourceData->Width >> mipLevel); + auto mipHeight = SystemMax(1u, resourceData->Height >> mipLevel); + + auto sourceBytesPerRow = mipWidth * 4; + + if (resourceData->Format == ElemGraphicsFormat_BC7 || + resourceData->Format == ElemGraphicsFormat_BC7_SRGB) + { + sourceBytesPerRow = ((mipWidth + 3) / 4) * 16; + } + + copyCommandEncoder->copyFromBuffer(uploadBuffer.PoolItem->Buffer.get(), uploadBuffer.Offset, sourceBytesPerRow, 0, MTL::Size(mipWidth, mipHeight, 1), + (MTL::Texture*)resourceData->DeviceObject.get(), 0, parameters->TextureMipLevel, MTL::Origin(0, 0, 0)); + } } ElemGraphicsResourceDescriptor MetalCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options) @@ -448,8 +714,17 @@ ElemGraphicsResourceDescriptor MetalCreateGraphicsResourceDescriptor(ElemGraphic handle = CreateMetalArgumentBufferHandleForBuffer(graphicsDeviceData->ResourceArgumentBuffer, (MTL::Buffer*)resourceData->DeviceObject.get(), resourceData->Width); } - metalResourceDescriptorInfos[handle].Resource = resource; - metalResourceDescriptorInfos[handle].Usage = usage; + if (handle != -1) + { + if ((handle % 1000) == 0) + { + SystemCommitMemory(MetalGraphicsMemoryArena, &metalResourceDescriptorInfos[handle], 1000 * sizeof(ElemGraphicsResourceDescriptorInfo)); + } + + metalResourceDescriptorInfos[handle].Resource = resource; + metalResourceDescriptorInfos[handle].Usage = usage; + } + return handle; } @@ -481,8 +756,164 @@ void MetalFreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor descript metalResourceDescriptorInfos[descriptor].Resource = ELEM_HANDLE_NULL; } -void MetalProcessGraphicsResourceDeleteQueue() +void MetalProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice) { + auto stackMemoryArena = SystemGetStackMemoryArena(); + ProcessResourceDeleteQueue(); + SystemClearMemoryArena(metalReadBackMemoryArena); + + auto graphicsDeviceData = GetMetalGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + graphicsDeviceData->UploadBufferGeneration++; + + for (uint32_t i = 0; i < graphicsDeviceData->UploadBufferPools.Length; i++) + { + auto bufferPool = graphicsDeviceData->UploadBufferPools[i]; + + if (bufferPool) + { + auto uploadBuffersToDelete = GetUploadBufferPoolItemsToDelete(stackMemoryArena, bufferPool, graphicsDeviceData->UploadBufferGeneration); + + for (uint32_t j = 0; j < uploadBuffersToDelete.Length; j++) + { + auto uploadBufferToDelete = uploadBuffersToDelete[j]; + + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to purge upload buffer: Size=%d", uploadBufferToDelete->SizeInBytes); + + uploadBufferToDelete->Buffer.reset(); + uploadBufferToDelete->CurrentOffset = 0; + uploadBufferToDelete->SizeInBytes = 0; + } + } + } +} + +MTL::SamplerMinMagFilter ConvertToMetalMinMagFilter(ElemGraphicsSamplerFilter filter) +{ + switch (filter) + { + case ElemGraphicsSamplerFilter_Nearest: + return MTL::SamplerMinMagFilterNearest; + + case ElemGraphicsSamplerFilter_Linear: + return MTL::SamplerMinMagFilterLinear; + } } +MTL::SamplerMipFilter ConvertToMetalMipFilter(ElemGraphicsSamplerFilter filter) +{ + switch (filter) + { + case ElemGraphicsSamplerFilter_Nearest: + return MTL::SamplerMipFilterNearest; + + case ElemGraphicsSamplerFilter_Linear: + return MTL::SamplerMipFilterLinear; + } +} + +MTL::SamplerAddressMode ConvertToMetalSamplerAddressMode(ElemGraphicsSamplerAddressMode addressMode) +{ + switch (addressMode) + { + case ElemGraphicsSamplerAddressMode_Repeat: + return MTL::SamplerAddressModeRepeat; + + case ElemGraphicsSamplerAddressMode_RepeatMirror: + return MTL::SamplerAddressModeMirrorRepeat; + + case ElemGraphicsSamplerAddressMode_ClampToEdge: + return MTL::SamplerAddressModeClampToEdge; + + case ElemGraphicsSamplerAddressMode_ClampToEdgeMirror: + return MTL::SamplerAddressModeMirrorClampToEdge; + + case ElemGraphicsSamplerAddressMode_ClampToBorderColor: + return MTL::SamplerAddressModeClampToBorderColor; + } +} + +ElemGraphicsSampler MetalCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo) +{ + InitMetalResourceMemory(); + + auto graphicsDeviceData = GetMetalGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto localSamplerInfo = *samplerInfo; + + if (localSamplerInfo.MaxAnisotropy == 0) + { + localSamplerInfo.MaxAnisotropy = 1; + } + + auto borderColor = MTL::SamplerBorderColorOpaqueBlack; + + if (localSamplerInfo.BorderColor.Red == 1.0f && localSamplerInfo.BorderColor.Green == 1.0f && localSamplerInfo.BorderColor.Blue == 1.0f && localSamplerInfo.BorderColor.Alpha == 1.0f) + { + borderColor = MTL::SamplerBorderColorOpaqueWhite; + } + + auto samplerDescriptor = NS::TransferPtr(MTL::SamplerDescriptor::alloc()->init()); + samplerDescriptor->setSupportArgumentBuffers(true); + samplerDescriptor->setMinFilter(ConvertToMetalMinMagFilter(localSamplerInfo.MinFilter)); + samplerDescriptor->setMagFilter(ConvertToMetalMinMagFilter(localSamplerInfo.MagFilter)); + samplerDescriptor->setMipFilter(ConvertToMetalMipFilter(localSamplerInfo.MipFilter)); + samplerDescriptor->setSAddressMode(ConvertToMetalSamplerAddressMode(localSamplerInfo.AddressU)); + samplerDescriptor->setTAddressMode(ConvertToMetalSamplerAddressMode(localSamplerInfo.AddressV)); + samplerDescriptor->setRAddressMode(ConvertToMetalSamplerAddressMode(localSamplerInfo.AddressW)); + samplerDescriptor->setMaxAnisotropy(localSamplerInfo.MaxAnisotropy); + samplerDescriptor->setCompareFunction(ConvertToMetalCompareFunction(localSamplerInfo.CompareFunction)); + samplerDescriptor->setBorderColor(borderColor); + samplerDescriptor->setLodMinClamp(localSamplerInfo.MinLod); + samplerDescriptor->setLodMaxClamp(localSamplerInfo.MaxLod == 0 ? 1000 : localSamplerInfo.MaxLod); + + auto sampler = NS::TransferPtr(graphicsDeviceData->Device->newSamplerState(samplerDescriptor.get())); + + auto handle = CreateMetalArgumentBufferHandleForSamplerState(graphicsDeviceData->SamplerArgumentBuffer, sampler.get()); + + if ((handle % 1024) == 0) + { + SystemCommitMemory(MetalGraphicsMemoryArena, &metalSamplerInfos[handle], 1024 * sizeof(MetalGraphicsSamplerInfo)); + } + + metalSamplerInfos[handle].MetalSampler = sampler; + metalSamplerInfos[handle].SamplerInfo = localSamplerInfo; + + return handle; +} + +ElemGraphicsSamplerInfo MetalGetGraphicsSamplerInfo(ElemGraphicsSampler sampler) +{ + InitMetalResourceMemory(); + + if (sampler == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Sampler is invalid."); + return {}; + } + + return metalSamplerInfos[sampler].SamplerInfo; +} + +void MetalFreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options) +{ + InitMetalResourceMemory(); + + if (sampler == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Sampler is invalid."); + return; + } + + if (options && options->FencesToWait.Length > 0) + { + EnqueueResourceDeleteEntry(MetalGraphicsMemoryArena, sampler, ResourceDeleteType_Sampler, options->FencesToWait); + return; + } + + metalSamplerInfos[sampler].MetalSampler.reset(); + metalSamplerInfos[sampler] = {}; +} diff --git a/src/Elemental/Apple/Graphics/MetalResource.h b/src/Elemental/Apple/Graphics/MetalResource.h index 6bb0b45a..671d78fe 100644 --- a/src/Elemental/Apple/Graphics/MetalResource.h +++ b/src/Elemental/Apple/Graphics/MetalResource.h @@ -7,6 +7,7 @@ struct MetalGraphicsHeapData NS::SharedPtr DeviceObject; uint64_t SizeInBytes; ElemGraphicsDevice GraphicsDevice; + ElemGraphicsHeapType HeapType; }; struct MetalGraphicsHeapDataFull @@ -17,6 +18,7 @@ struct MetalGraphicsHeapDataFull struct MetalResourceData { NS::SharedPtr DeviceObject; + ElemGraphicsHeap GraphicsHeap; ElemGraphicsResourceType Type; uint32_t Width; uint32_t Height; @@ -31,6 +33,12 @@ struct MetalResourceDataFull ElemGraphicsDevice GraphicsDevice; }; +struct MetalGraphicsSamplerInfo +{ + NS::SharedPtr MetalSampler; + ElemGraphicsSamplerInfo SamplerInfo; +}; + MetalGraphicsHeapData* GetMetalGraphicsHeapData(ElemGraphicsHeap graphicsHeap); MetalGraphicsHeapDataFull* GetMetalGraphicsHeapDataFull(ElemGraphicsHeap graphicsHeap); @@ -48,12 +56,19 @@ ElemGraphicsResourceInfo MetalCreateTexture2DResourceInfo(ElemGraphicsDevice gra ElemGraphicsResource MetalCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, uint64_t graphicsHeapOffset, const ElemGraphicsResourceInfo* resourceInfo); void MetalFreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGraphicsResourceOptions* options); ElemGraphicsResourceInfo MetalGetGraphicsResourceInfo(ElemGraphicsResource resource); -ElemDataSpan MetalGetGraphicsResourceDataSpan(ElemGraphicsResource resource); + +void MetalUploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data); +ElemDataSpan MetalDownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options); +void MetalCopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters); ElemGraphicsResourceDescriptor MetalCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options); ElemGraphicsResourceDescriptorInfo MetalGetGraphicsResourceDescriptorInfo(ElemGraphicsResourceDescriptor descriptor); void MetalFreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor descriptor, const ElemFreeGraphicsResourceDescriptorOptions* options); -void MetalProcessGraphicsResourceDeleteQueue(); +void MetalProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice); + +ElemGraphicsResource CreateMetalGraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ElemGraphicsHeap graphicsHeap, ElemGraphicsResourceUsage usage, NS::SharedPtr resource, bool isPresentTexture); -ElemGraphicsResource CreateMetalGraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ElemGraphicsResourceUsage usage, NS::SharedPtr resource, bool isPresentTexture); +ElemGraphicsSampler MetalCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo); +ElemGraphicsSamplerInfo MetalGetGraphicsSamplerInfo(ElemGraphicsSampler sampler); +void MetalFreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options); diff --git a/src/Elemental/Apple/Graphics/MetalShader.cpp b/src/Elemental/Apple/Graphics/MetalShader.cpp index 47fd453c..c5eb96c6 100644 --- a/src/Elemental/Apple/Graphics/MetalShader.cpp +++ b/src/Elemental/Apple/Graphics/MetalShader.cpp @@ -1,16 +1,16 @@ #include "MetalShader.h" +#include "MetalConfig.h" #include "MetalGraphicsDevice.h" #include "MetalCommandList.h" #include "MetalResource.h" #include "MetalResourceBarrier.h" +#include "Graphics/Resource.h" +#include "Graphics/Shader.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemLogging.h" #include "SystemMemory.h" -#define METAL_MAX_LIBRARIES UINT16_MAX -#define METAL_MAX_PIPELINESTATES UINT16_MAX - struct MetalShaderFunctionData { NS::SharedPtr Function; @@ -100,6 +100,8 @@ MetalShaderFunctionData GetMetalShaderFunction(MetalShaderLibraryData* shaderLib result.MetaData.ThreadSizeZ = metaData.Value[2]; } } + + break; } } @@ -111,6 +113,81 @@ MetalShaderFunctionData GetMetalShaderFunction(MetalShaderLibraryData* shaderLib return result; } +MTL::CullMode ConvertToMetalCullMode(ElemGraphicsCullMode cullMode) +{ + switch (cullMode) + { + case ElemGraphicsCullMode_BackFace: + return MTL::CullModeBack; + + case ElemGraphicsCullMode_FrontFace: + return MTL::CullModeFront; + + case ElemGraphicsCullMode_None: + return MTL::CullModeNone; + } +} + +MTL::BlendOperation ConvertToMetalBlendOperation(ElemGraphicsBlendOperation blendOperation) +{ + switch (blendOperation) + { + case ElemGraphicsBlendOperation_Add: + return MTL::BlendOperationAdd; + + case ElemGraphicsBlendOperation_Subtract: + return MTL::BlendOperationSubtract; + + case ElemGraphicsBlendOperation_ReverseSubtract: + return MTL::BlendOperationReverseSubtract; + + case ElemGraphicsBlendOperation_Min: + return MTL::BlendOperationMin; + + case ElemGraphicsBlendOperation_Max: + return MTL::BlendOperationMax; + } +} + +MTL::BlendFactor ConvertToMetalBlendFactor(ElemGraphicsBlendFactor blendFactor) +{ + switch (blendFactor) + { + case ElemGraphicsBlendFactor_Zero: + return MTL::BlendFactorZero; + + case ElemGraphicsBlendFactor_One: + return MTL::BlendFactorOne; + + case ElemGraphicsBlendFactor_SourceColor: + return MTL::BlendFactorSourceColor; + + case ElemGraphicsBlendFactor_InverseSourceColor: + return MTL::BlendFactorOneMinusSourceColor; + + case ElemGraphicsBlendFactor_SourceAlpha: + return MTL::BlendFactorSourceAlpha; + + case ElemGraphicsBlendFactor_InverseSourceAlpha: + return MTL::BlendFactorOneMinusSourceAlpha; + + case ElemGraphicsBlendFactor_DestinationColor: + return MTL::BlendFactorDestinationColor; + + case ElemGraphicsBlendFactor_InverseDestinationColor: + return MTL::BlendFactorOneMinusDestinationColor; + + case ElemGraphicsBlendFactor_DestinationAlpha: + return MTL::BlendFactorDestinationAlpha; + + case ElemGraphicsBlendFactor_InverseDestinationAlpha: + return MTL::BlendFactorOneMinusDestinationAlpha; + + case ElemGraphicsBlendFactor_SourceAlphaSaturated: + return MTL::BlendFactorSourceAlphaSaturated; + } +} + ElemShaderLibrary MetalCreateShaderLibrary(ElemGraphicsDevice graphicsDevice, ElemDataSpan shaderLibraryData) { InitMetalShaderLibraryMemory(); @@ -203,7 +280,44 @@ ElemPipelineState MetalCompileGraphicsPipelineState(ElemGraphicsDevice graphicsD SystemAssert(shaderLibraryData); auto pipelineStateDescriptor = NS::TransferPtr(MTL::MeshRenderPipelineDescriptor::alloc()->init()); + + for (uint32_t i = 0; i < parameters->RenderTargets.Length; i++) + { + auto renderTargetParameters = parameters->RenderTargets.Items[i]; + auto metalColorAttachment = pipelineStateDescriptor->colorAttachments()->object(i); + metalColorAttachment->setPixelFormat(ConvertToMetalResourceFormat(renderTargetParameters.Format)); + + metalColorAttachment->setBlendingEnabled(IsBlendEnabled(renderTargetParameters)); + metalColorAttachment->setSourceRGBBlendFactor(ConvertToMetalBlendFactor(renderTargetParameters.SourceBlendFactor)); + metalColorAttachment->setDestinationRGBBlendFactor(ConvertToMetalBlendFactor(renderTargetParameters.DestinationBlendFactor)); + metalColorAttachment->setRgbBlendOperation(ConvertToMetalBlendOperation(renderTargetParameters.BlendOperation)); + metalColorAttachment->setSourceAlphaBlendFactor(ConvertToMetalBlendFactor(renderTargetParameters.SourceBlendFactorAlpha)); + metalColorAttachment->setDestinationAlphaBlendFactor(ConvertToMetalBlendFactor(renderTargetParameters.DestinationBlendFactorAlpha)); + metalColorAttachment->setAlphaBlendOperation(ConvertToMetalBlendOperation(renderTargetParameters.BlendOperationAlpha)); + metalColorAttachment->setWriteMask(MTL::ColorWriteMaskAll); + } + + NS::SharedPtr depthStencilState = {}; + + if (CheckDepthStencilFormat(parameters->DepthStencil.Format)) + { + pipelineStateDescriptor->setDepthAttachmentPixelFormat(ConvertToMetalResourceFormat(parameters->DepthStencil.Format)); + + auto depthStencilDescriptor = NS::TransferPtr(MTL::DepthStencilDescriptor::alloc()->init()); + + if (!parameters->DepthStencil.DepthDisableWrite) + { + depthStencilDescriptor->setDepthWriteEnabled(true); + } + + depthStencilDescriptor->setDepthCompareFunction(ConvertToMetalCompareFunction(parameters->DepthStencil.DepthCompareFunction)); + depthStencilState = NS::TransferPtr(graphicsDeviceData->Device->newDepthStencilState(depthStencilDescriptor.get())); + } + + auto cullMode = ConvertToMetalCullMode(parameters->CullMode); + auto fillMode = parameters->FillMode == ElemGraphicsFillMode_Solid ? MTL::TriangleFillModeFill : MTL::TriangleFillModeLines; + MetalShaderMetaData meshShaderMetaData = {}; if (parameters->MeshShaderFunction) @@ -232,53 +346,6 @@ ElemPipelineState MetalCompileGraphicsPipelineState(ElemGraphicsDevice graphicsD pipelineStateDescriptor->setFragmentFunction(functionData.Function.get()); } - - for (uint32_t i = 0; i < parameters->TextureFormats.Length; i++) - { - pipelineStateDescriptor->colorAttachments()->object(0)->setPixelFormat(ConvertToMetalResourceFormat(parameters->TextureFormats.Items[i])); - } - - // TODO: Why is the triangle not back face culled by default? - //pipelineStateDescriptor.supportIndirectCommandBuffers = true - - // TODO: Use the correct render target format - /*if (renderPassDescriptor->RenderTarget0.HasValue) - { - pipelineStateDescriptor->colorAttachments()->object(0)->setPixelFormat(MTL::PixelFormatBGRA8Unorm_sRGB); - }*/ - - /* - if (metalRenderPassDescriptor.RenderTarget2TextureFormat.HasValue == 1) { - pipelineStateDescriptor.colorAttachments[1].pixelFormat = convertTextureFormat(metalRenderPassDescriptor.RenderTarget2TextureFormat.Value) - } - - if (metalRenderPassDescriptor.RenderTarget3TextureFormat.HasValue == 1) { - pipelineStateDescriptor.colorAttachments[2].pixelFormat = convertTextureFormat(metalRenderPassDescriptor.RenderTarget3TextureFormat.Value) - } - - if (metalRenderPassDescriptor.RenderTarget4TextureFormat.HasValue == 1) { - pipelineStateDescriptor.colorAttachments[3].pixelFormat = convertTextureFormat(metalRenderPassDescriptor.RenderTarget4TextureFormat.Value) - } - - if (metalRenderPassDescriptor.DepthTexturePointer.HasValue == 1) { - pipelineStateDescriptor.depthAttachmentPixelFormat = .depth32Float - } -) - if (metalRenderPassDescriptor.RenderTarget1BlendOperation.HasValue == 1) { - initBlendState(pipelineStateDescriptor.colorAttachments[0]!, metalRenderPassDescriptor.RenderTarget1BlendOperation.Value) - } - - if (metalRenderPassDescriptor.RenderTarget2BlendOperation.HasValue == 1) { - initBlendState(pipelineStateDescriptor.colorAttachments[1]!, metalRenderPassDescriptor.RenderTarget2BlendOperation.Value) - } - - if (metalRenderPassDescriptor.RenderTarget3BlendOperation.HasValue == 1) { - initBlendState(pipelineStateDescriptor.colorAttachments[2]!, metalRenderPassDescriptor.RenderTarget3BlendOperation.Value) - } - - if (metalRenderPassDescriptor.RenderTarget4BlendOperation.HasValue == 1) { - initBlendState(pipelineStateDescriptor.colorAttachments[3]!, metalRenderPassDescriptor.RenderTarget4BlendOperation.Value) - }*/ // TODO: Review this! auto dispatchGroup = dispatch_group_create(); @@ -305,6 +372,9 @@ ElemPipelineState MetalCompileGraphicsPipelineState(ElemGraphicsDevice graphicsD auto handle = SystemAddDataPoolItem(metalPipelineStatePool, { .RenderPipelineState = pipelineState, + .RenderDepthStencilState = depthStencilState, + .RenderCullMode = cullMode, + .RenderFillMode = fillMode, .MeshShaderMetaData = meshShaderMetaData }); @@ -405,6 +475,14 @@ void MetalBindPipelineState(ElemCommandList commandList, ElemPipelineState pipel { auto renderCommandEncoder = (MTL::RenderCommandEncoder*)commandListData->CommandEncoder.get(); renderCommandEncoder->setRenderPipelineState(pipelineStateData->RenderPipelineState.get()); + + if (pipelineStateData->RenderDepthStencilState) + { + renderCommandEncoder->setDepthStencilState(pipelineStateData->RenderDepthStencilState.get()); + } + + renderCommandEncoder->setCullMode(pipelineStateData->RenderCullMode); + renderCommandEncoder->setTriangleFillMode(pipelineStateData->RenderFillMode); } } else @@ -417,6 +495,7 @@ void MetalBindPipelineState(ElemCommandList commandList, ElemPipelineState pipel commandListData->PipelineState = ELEM_HANDLE_NULL; computeCommandEncoder->setBuffer(graphicsDeviceData->ResourceArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 0); + computeCommandEncoder->setBuffer(graphicsDeviceData->SamplerArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 1); } if (commandListData->PipelineState != pipelineState) @@ -431,6 +510,9 @@ void MetalBindPipelineState(ElemCommandList commandList, ElemPipelineState pipel void MetalPushPipelineStateConstants(ElemCommandList commandList, uint32_t offsetInBytes, ElemDataSpan data) { + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto pushData = SystemDuplicateBuffer(stackMemoryArena, ReadOnlySpan(data.Items, data.Length)); + SystemAssert(commandList != ELEM_HANDLE_NULL); auto commandListData = GetMetalCommandListData(commandList); @@ -443,21 +525,27 @@ void MetalPushPipelineStateConstants(ElemCommandList commandList, uint32_t offse { auto renderCommandEncoder = (MTL::RenderCommandEncoder*)commandListData->CommandEncoder.get(); - // TODO: Amplification shader // TODO: Do we need to check if the shader stage is bound? // TODO: compute offset - // HACK: For the oment we set the slot 2 because it is the global one for bindless + // HACK: For the moment we set the slot 2 because it is the global one for bindless - renderCommandEncoder->setMeshBuffer(graphicsDeviceData->ResourceArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 0); - renderCommandEncoder->setMeshBytes(data.Items, data.Length, 2); + if (!commandListData->ArgumentBufferBound) + { + renderCommandEncoder->setMeshBuffer(graphicsDeviceData->ResourceArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 0); + renderCommandEncoder->setMeshBuffer(graphicsDeviceData->SamplerArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 1); + renderCommandEncoder->setFragmentBuffer(graphicsDeviceData->ResourceArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 0); + renderCommandEncoder->setFragmentBuffer(graphicsDeviceData->SamplerArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 1); + commandListData->ArgumentBufferBound = true; + } - renderCommandEncoder->setFragmentBuffer(graphicsDeviceData->ResourceArgumentBuffer.Storage->ArgumentBuffer.get(), 0, 0); - renderCommandEncoder->setFragmentBytes(data.Items, data.Length, 2); + renderCommandEncoder->setMeshBytes(pushData.Pointer, pushData.Length, 2); + renderCommandEncoder->setFragmentBytes(pushData.Pointer, pushData.Length, 2); } else if (commandListData->CommandEncoderType == MetalCommandEncoderType_Compute) { + // TODO: We need to bind the argument buffer too here auto computeCommandEncoder = (MTL::ComputeCommandEncoder*)commandListData->CommandEncoder.get(); - computeCommandEncoder->setBytes(data.Items, data.Length, 2); + computeCommandEncoder->setBytes(pushData.Pointer, pushData.Length, 2); } } diff --git a/src/Elemental/Apple/Graphics/MetalShader.h b/src/Elemental/Apple/Graphics/MetalShader.h index 7bf1d24f..99677e36 100644 --- a/src/Elemental/Apple/Graphics/MetalShader.h +++ b/src/Elemental/Apple/Graphics/MetalShader.h @@ -22,7 +22,9 @@ struct MetalPipelineStateData { NS::SharedPtr RenderPipelineState; NS::SharedPtr ComputePipelineState; - MetalShaderMetaData AmplificaitonShaderMetaData; + NS::SharedPtr RenderDepthStencilState; + MTL::CullMode RenderCullMode; + MTL::TriangleFillMode RenderFillMode; MetalShaderMetaData MeshShaderMetaData; MetalShaderMetaData ComputeShaderMetaData; }; diff --git a/src/Elemental/Apple/Graphics/MetalSwapChain.cpp b/src/Elemental/Apple/Graphics/MetalSwapChain.cpp index 18218ab0..809f3d16 100644 --- a/src/Elemental/Apple/Graphics/MetalSwapChain.cpp +++ b/src/Elemental/Apple/Graphics/MetalSwapChain.cpp @@ -1,4 +1,5 @@ #include "MetalSwapChain.h" +#include "MetalConfig.h" #include "MetalCommandList.h" #include "MetalGraphicsDevice.h" #include "MetalResource.h" @@ -27,8 +28,6 @@ struct Test #include "../UIKitWindow.h" #endif -#define METAL_MAX_SWAPCHAINS 10u - SystemDataPool metalSwapChainPool; void InitMetalSwapChainMemory() @@ -49,7 +48,7 @@ MetalSwapChainDataFull* GetMetalSwapChainDataFull(ElemSwapChain swapChain) return SystemGetDataPoolItemFull(metalSwapChainPool, swapChain); } -void ResizeMetalSwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t height) +void ResizeMetalSwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t height, float uiScale) { if (width == 0 || height == 0) { @@ -64,6 +63,7 @@ void ResizeMetalSwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t heig swapChainData->Width = width; swapChainData->Height = height; swapChainData->AspectRatio = (float)width / height; + swapChainData->UIScale = uiScale; swapChainData->DeviceObject->setDrawableSize(CGSizeMake(width, height)); } @@ -151,6 +151,7 @@ ElemSwapChain MetalCreateSwapChain(ElemCommandQueue commandQueue, ElemWindow win .Width = width, .Height = height, .AspectRatio = (float)width / height, + .UIScale = windowRenderSize.UIScale, .Format = ElemGraphicsFormat_B8G8R8A8_SRGB // TODO: Temporary }); @@ -185,9 +186,11 @@ ElemSwapChainInfo MetalGetSwapChainInfo(ElemSwapChain swapChain) return { + .Window = swapChainData->Window, .Width = swapChainData->Width, .Height = swapChainData->Height, .AspectRatio = swapChainData->AspectRatio, + .UIScale = swapChainData->UIScale, .Format = swapChainData->Format }; } @@ -261,12 +264,13 @@ void MetalDisplayLinkHandler::metalDisplayLinkNeedsUpdate(CA::MetalDisplayLink* auto nextPresentTimestampInSeconds = update->targetPresentationTimestamp() - swapChainData->CreationTimestamp; - auto windowSize = ElemGetWindowRenderSize(swapChainData->Window); auto sizeChanged = false; + auto windowSize = ElemGetWindowRenderSize(swapChainData->Window); + if (windowSize.Width > 0 && windowSize.Height > 0 && (windowSize.Width != swapChainData->Width || windowSize.Height != swapChainData->Height)) { - ResizeMetalSwapChain(_swapChain, windowSize.Width, windowSize.Height); + ResizeMetalSwapChain(_swapChain, windowSize.Width, windowSize.Height, windowSize.UIScale); sizeChanged = true; } @@ -279,8 +283,15 @@ void MetalDisplayLinkHandler::metalDisplayLinkNeedsUpdate(CA::MetalDisplayLink* return; } + // BUG: On iOS, the drawable will have his size change on next update + if (swapChainData->BackBufferDrawable->texture()->width() != swapChainData->Width || swapChainData->BackBufferDrawable->texture()->height() != swapChainData->Height) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Error size swapchain"); + return; + } + // TODO: Can we do something better than juste creating/destroying each time? - auto backBufferTexture = CreateMetalGraphicsResourceFromResource(swapChainData->GraphicsDevice, ElemGraphicsResourceType_Texture2D, ElemGraphicsResourceUsage_RenderTarget, NS::RetainPtr(swapChainData->BackBufferDrawable->texture()), true); + auto backBufferTexture = CreateMetalGraphicsResourceFromResource(swapChainData->GraphicsDevice, ElemGraphicsResourceType_Texture2D, ELEM_HANDLE_NULL, ElemGraphicsResourceUsage_RenderTarget, NS::RetainPtr(swapChainData->BackBufferDrawable->texture()), true); ElemSwapChainUpdateParameters updateParameters = { @@ -302,6 +313,6 @@ void MetalDisplayLinkHandler::metalDisplayLinkNeedsUpdate(CA::MetalDisplayLink* MetalFreeGraphicsResource(backBufferTexture, nullptr); - MetalProcessGraphicsResourceDeleteQueue(); + MetalProcessGraphicsResourceDeleteQueue(swapChainData->GraphicsDevice); } } diff --git a/src/Elemental/Apple/Graphics/MetalSwapChain.h b/src/Elemental/Apple/Graphics/MetalSwapChain.h index 5282b5ae..9fa83d70 100644 --- a/src/Elemental/Apple/Graphics/MetalSwapChain.h +++ b/src/Elemental/Apple/Graphics/MetalSwapChain.h @@ -28,6 +28,7 @@ struct MetalSwapChainData uint32_t Width; uint32_t Height; float AspectRatio; + float UIScale; ElemGraphicsFormat Format; }; diff --git a/src/Elemental/Apple/Inputs.cpp b/src/Elemental/Apple/Inputs.cpp index a25f868e..714ce910 100644 --- a/src/Elemental/Apple/Inputs.cpp +++ b/src/Elemental/Apple/Inputs.cpp @@ -49,6 +49,12 @@ void RemoveAppleInputDevice(void* device) ElemInputId GetAppleInputIdFromKeyCode(GC::KeyCode keyCode) { + SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "Test: %d", keyCode); + if (keyCode == GC::KeyCode::UpArrow) + { + SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "Test"); + } + switch (keyCode) { case GC::KeyCode::GraveAccentAndTilde: return ElemInputId_KeyTilde; @@ -645,6 +651,7 @@ void InitInputs(ElemWindow window) NS::NotificationCenter::defaultCenter()->addObserver(MTLSTR("GCControllerDidConnectNotification"), nullptr, nullptr, ^(NS::Notification* notification) { + // TODO: Use HID code like the rest of the platforms for gamepad? auto controller = (GC::Controller*)notification->object(); auto extendedGamepad = controller->extendedGamepad(); @@ -656,57 +663,58 @@ void InitInputs(ElemWindow window) extendedGamepad->buttonA()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonA, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonA, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->buttonB()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonB, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonB, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->buttonX()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonX, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonX, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->buttonY()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonY, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonY, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); + // TODO: Check that button ID extendedGamepad->buttonMenu()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonMenu, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonShare, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->buttonOptions()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonOptions, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonOptions, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->buttonHome()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadButtonHome, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadButtonHome, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->leftShoulder()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadLeftShoulder, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadLeftShoulder, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->rightShoulder()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadRightShoulder, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadRightShoulder, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->leftTrigger()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadLeftTrigger, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadLeftTrigger, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->rightTrigger()->setValueChangedHandler(^(GC::ControllerButtonInput* controllerButtonInput, float value, bool isPressed) { - ButtonHandler(window, ElemInputID_GamepadRightTrigger, extendedGamepad->device(), controllerButtonInput, value, isPressed); + ButtonHandler(window, ElemInputId_GamepadRightTrigger, extendedGamepad->device(), controllerButtonInput, value, isPressed); }); extendedGamepad->leftThumbstick()->setValueChangedHandler(^(GC::ControllerDirectionPad* directionPad, float xValue, float yValue) diff --git a/src/Elemental/Apple/MacOSApplication.cpp b/src/Elemental/Apple/MacOSApplication.cpp index 3fa155d3..3c66e434 100644 --- a/src/Elemental/Apple/MacOSApplication.cpp +++ b/src/Elemental/Apple/MacOSApplication.cpp @@ -195,6 +195,13 @@ bool MacOSApplicationDelegate::applicationShouldTerminateAfterLastWindowClosed(N NS::TerminateReply MacOSApplicationDelegate::applicationShouldTerminate(NS::Application* pSender) { + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto allocationInfos = SystemGetAllocationInfos(); + + SystemLogDebugMessage(ElemLogMessageCategory_Application, "Allocated lib memory before releasing: %s/%s", + SystemFormatMemorySize(stackMemoryArena, allocationInfos.CommittedBytes).Pointer, + SystemFormatMemorySize(stackMemoryArena, allocationInfos.ReservedBytes).Pointer); + ApplicationExited = true; if (_runParameters && _runParameters->FreeHandler) diff --git a/src/Elemental/Apple/PreCompiledHeader.h b/src/Elemental/Apple/PreCompiledHeader.h index 4c563f45..79eedf20 100644 --- a/src/Elemental/Apple/PreCompiledHeader.h +++ b/src/Elemental/Apple/PreCompiledHeader.h @@ -30,7 +30,6 @@ typedef signed int HRESULT; #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0) #define ElemAPI extern "C" __attribute__((visibility("default"))) -#define PackedStruct struct __attribute__((__packed__)) #define AssertIfFailed(expression) SystemAssert(SUCCEEDED((expression))) #define AssertIfFailedReturnNullHandle(expression) SystemAssertReturnNullHandle(SUCCEEDED((expression))) #define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a))) diff --git a/src/Elemental/Apple/UnityBuild.cpp b/src/Elemental/Apple/UnityBuild.cpp index aa61ba4b..f7f7fd5d 100644 --- a/src/Elemental/Apple/UnityBuild.cpp +++ b/src/Elemental/Apple/UnityBuild.cpp @@ -21,6 +21,7 @@ #include "Graphics/ShaderReader.cpp" #include "Graphics/ResourceDeleteQueue.cpp" #include "Graphics/ResourceBarrier.cpp" +#include "Graphics/UploadBufferPool.cpp" #include "Graphics/GraphicsDevice.cpp" #include "Graphics/CommandList.cpp" #include "Graphics/SwapChain.cpp" diff --git a/src/Elemental/Common/Graphics/Rendering.cpp b/src/Elemental/Common/Graphics/Rendering.cpp index d4cce4ec..97374f07 100644 --- a/src/Elemental/Common/Graphics/Rendering.cpp +++ b/src/Elemental/Common/Graphics/Rendering.cpp @@ -27,6 +27,22 @@ ElemAPI void ElemSetViewports(ElemCommandList commandList, ElemViewportSpan view DispatchGraphicsFunction(SetViewports, commandList, viewports); } +ElemAPI void ElemSetScissorRectangle(ElemCommandList commandList, const ElemRectangle* rectangle) +{ + ElemRectangleSpan rectangleSpan = + { + .Items = (ElemRectangle*)rectangle, + .Length = 1 + }; + + DispatchGraphicsFunction(SetScissorRectangles, commandList, rectangleSpan); +} + +ElemAPI void ElemSetScissorRectangles(ElemCommandList commandList, ElemRectangleSpan rectangles) +{ + DispatchGraphicsFunction(SetScissorRectangles, commandList, rectangles); +} + ElemAPI void ElemDispatchMesh(ElemCommandList commandList, uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) { DispatchGraphicsFunction(DispatchMesh, commandList, threadGroupCountX, threadGroupCountY, threadGroupCountZ); diff --git a/src/Elemental/Common/Graphics/Resource.cpp b/src/Elemental/Common/Graphics/Resource.cpp index 8954cb07..e99f4c8d 100644 --- a/src/Elemental/Common/Graphics/Resource.cpp +++ b/src/Elemental/Common/Graphics/Resource.cpp @@ -1,6 +1,16 @@ -#include "Elemental.h" +#include "Resource.h" #include "GraphicsCommon.h" +bool CheckDepthStencilFormat(ElemGraphicsFormat format) +{ + if (format == ElemGraphicsFormat_D32_FLOAT) + { + return true; + } + + return false; +} + ElemAPI ElemGraphicsHeap ElemCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uint64_t sizeInBytes, const ElemGraphicsHeapOptions* options) { DispatchReturnGraphicsFunction(CreateGraphicsHeap, graphicsDevice, sizeInBytes, options); @@ -36,9 +46,19 @@ ElemAPI ElemGraphicsResourceInfo ElemGetGraphicsResourceInfo(ElemGraphicsResourc DispatchReturnGraphicsFunction(GetGraphicsResourceInfo, resource); } -ElemAPI ElemDataSpan ElemGetGraphicsResourceDataSpan(ElemGraphicsResource resource) +ElemAPI void ElemUploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data) +{ + DispatchGraphicsFunction(UploadGraphicsBufferData, resource, offset, data); +} + +ElemAPI ElemDataSpan ElemDownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options) +{ + DispatchReturnGraphicsFunction(DownloadGraphicsBufferData, resource, options); +} + +ElemAPI void ElemCopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters) { - DispatchReturnGraphicsFunction(GetGraphicsResourceDataSpan, resource); + DispatchGraphicsFunction(CopyDataToGraphicsResource, commandList, parameters); } ElemAPI ElemGraphicsResourceDescriptor ElemCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options) @@ -61,7 +81,22 @@ ElemAPI void ElemGraphicsResourceBarrier(ElemCommandList commandList, ElemGraphi DispatchGraphicsFunction(GraphicsResourceBarrier, commandList, descriptor, options); } -ElemAPI void ElemProcessGraphicsResourceDeleteQueue() +ElemAPI void ElemProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice) +{ + DispatchGraphicsFunction(ProcessGraphicsResourceDeleteQueue, graphicsDevice); +} + +ElemAPI ElemGraphicsSampler ElemCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo) +{ + DispatchReturnGraphicsFunction(CreateGraphicsSampler, graphicsDevice, samplerInfo); +} + +ElemAPI ElemGraphicsSamplerInfo ElemGetGraphicsSamplerInfo(ElemGraphicsSampler sampler) +{ + DispatchReturnGraphicsFunction(GetGraphicsSamplerInfo, sampler); +} + +ElemAPI void ElemFreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options) { - DispatchGraphicsFunction(ProcessGraphicsResourceDeleteQueue); + DispatchGraphicsFunction(FreeGraphicsSampler, sampler, options); } diff --git a/src/Elemental/Common/Graphics/Resource.h b/src/Elemental/Common/Graphics/Resource.h new file mode 100644 index 00000000..beb78e11 --- /dev/null +++ b/src/Elemental/Common/Graphics/Resource.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../Elemental.h" + +bool CheckDepthStencilFormat(ElemGraphicsFormat format); diff --git a/src/Elemental/Common/Graphics/ResourceBarrier.cpp b/src/Elemental/Common/Graphics/ResourceBarrier.cpp index 80105a75..28cfa1bd 100644 --- a/src/Elemental/Common/Graphics/ResourceBarrier.cpp +++ b/src/Elemental/Common/Graphics/ResourceBarrier.cpp @@ -5,7 +5,9 @@ #include "SystemLogging.h" #define GRAPHICS_MAX_RESOURCEBARRIERPOOL 64 -#define GRAPHICS_MAX_RESOURCEBARRIER_RESOURCES 128 + +// TODO: Check that it takes a lot of memory +#define GRAPHICS_MAX_RESOURCEBARRIER_RESOURCES 64 struct ResourceBarrierResourceStatus { @@ -15,6 +17,8 @@ struct ResourceBarrierResourceStatus ElemGraphicsResourceBarrierLayoutType LastLayoutType; }; +// TODO: To save huge amount of memory allocation, we should allocate thoses in a separate arena +// and use reserved memory struct ResourceBarrierPoolData { uint32_t BarrierCount; @@ -150,7 +154,7 @@ void EnqueueBarrier(ResourceBarrierPool barrierPool, ElemGraphicsResourceDescrip void EnqueueBarrier(ResourceBarrierPool barrierPool, const ResourceBarrierItem* resourceBarrier) { SystemAssert(barrierPool != ELEM_HANDLE_NULL); - + auto barrierPoolData = SystemGetDataPoolItem(resourceBarrierDataPool, barrierPool); SystemAssert(barrierPoolData); @@ -231,6 +235,11 @@ ResourceBarriers GenerateBarrierCommands(MemoryArena memoryArena, ResourceBarrie if (!resourceStatus) { + if (barrierPoolData->ResourceStatusCount + 1 >= GRAPHICS_MAX_RESOURCEBARRIER_RESOURCES) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Command list barrier pool resources count exceeded."); + } + resourceStatus = &barrierPoolData->ResourceStatus[barrierPoolData->ResourceStatusCount++]; *resourceStatus = diff --git a/src/Elemental/Common/Graphics/ResourceBarrier.h b/src/Elemental/Common/Graphics/ResourceBarrier.h index 84816721..8b806c09 100644 --- a/src/Elemental/Common/Graphics/ResourceBarrier.h +++ b/src/Elemental/Common/Graphics/ResourceBarrier.h @@ -10,6 +10,7 @@ typedef ElemHandle ResourceBarrierPool; struct ResourceBarrierItem { ElemGraphicsResourceType Type; + bool IsDepthStencil; ElemGraphicsResource Resource; ElemGraphicsResourceBarrierSyncType BeforeSync; ElemGraphicsResourceBarrierSyncType AfterSync; @@ -17,6 +18,7 @@ struct ResourceBarrierItem ElemGraphicsResourceBarrierAccessType AfterAccess; ElemGraphicsResourceBarrierLayoutType BeforeLayout; ElemGraphicsResourceBarrierLayoutType AfterLayout; + // TODO: Add offset and size for buffer }; struct ResourceBarriers diff --git a/src/Elemental/Common/Graphics/ResourceDeleteQueue.cpp b/src/Elemental/Common/Graphics/ResourceDeleteQueue.cpp index 1d865c05..93982bd2 100644 --- a/src/Elemental/Common/Graphics/ResourceDeleteQueue.cpp +++ b/src/Elemental/Common/Graphics/ResourceDeleteQueue.cpp @@ -57,7 +57,8 @@ void ProcessResourceDeleteQueue() auto entry = &GraphicsResourceDeleteQueue[i]; if ((entry->Type == ResourceDeleteType_Resource && entry->Resource != ELEM_HANDLE_NULL) || - (entry->Type == ResourceDeleteType_Descriptor && (int)entry->Resource != -1)) + (entry->Type == ResourceDeleteType_Descriptor && (int)entry->Resource != -1) || + (entry->Type == ResourceDeleteType_Sampler && (int)entry->Resource != -1)) { auto fencesCompleted = true; @@ -80,6 +81,10 @@ void ProcessResourceDeleteQueue() { ElemFreeGraphicsResourceDescriptor(entry->Resource, nullptr); } + else if (entry->Type == ResourceDeleteType_Sampler) + { + ElemFreeGraphicsSampler(entry->Resource, nullptr); + } entry->Resource = ELEM_HANDLE_NULL; entry->NextEntry = GraphicsResourceDeleteQueueFreeListIndex; diff --git a/src/Elemental/Common/Graphics/ResourceDeleteQueue.h b/src/Elemental/Common/Graphics/ResourceDeleteQueue.h index f19b0eed..d93cac36 100644 --- a/src/Elemental/Common/Graphics/ResourceDeleteQueue.h +++ b/src/Elemental/Common/Graphics/ResourceDeleteQueue.h @@ -6,7 +6,8 @@ enum ResourceDeleteType { ResourceDeleteType_Resource, - ResourceDeleteType_Descriptor + ResourceDeleteType_Descriptor, + ResourceDeleteType_Sampler }; void EnqueueResourceDeleteEntry(MemoryArena memoryArena, ElemHandle resource, ResourceDeleteType type, ElemFenceSpan fences); diff --git a/src/Elemental/Common/Graphics/Shader.cpp b/src/Elemental/Common/Graphics/Shader.cpp index bc37e772..7f109607 100644 --- a/src/Elemental/Common/Graphics/Shader.cpp +++ b/src/Elemental/Common/Graphics/Shader.cpp @@ -1,6 +1,16 @@ -#include "Elemental.h" +#include "Shader.h" #include "GraphicsCommon.h" +bool IsBlendEnabled(ElemGraphicsPipelineStateRenderTarget renderTargetParameters) +{ + return !(renderTargetParameters.BlendOperation == ElemGraphicsBlendOperation_Add && + renderTargetParameters.SourceBlendFactor == ElemGraphicsBlendFactor_Zero && + renderTargetParameters.DestinationBlendFactor == ElemGraphicsBlendFactor_Zero && + renderTargetParameters.BlendOperationAlpha == ElemGraphicsBlendOperation_Add && + renderTargetParameters.SourceBlendFactorAlpha == ElemGraphicsBlendFactor_Zero && + renderTargetParameters.DestinationBlendFactorAlpha == ElemGraphicsBlendFactor_Zero); +} + ElemAPI ElemShaderLibrary ElemCreateShaderLibrary(ElemGraphicsDevice graphicsDevice, ElemDataSpan shaderLibraryData) { DispatchReturnGraphicsFunction(CreateShaderLibrary, graphicsDevice, shaderLibraryData); diff --git a/src/Elemental/Common/Graphics/Shader.h b/src/Elemental/Common/Graphics/Shader.h new file mode 100644 index 00000000..eff51299 --- /dev/null +++ b/src/Elemental/Common/Graphics/Shader.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../Elemental.h" + +bool IsBlendEnabled(ElemGraphicsPipelineStateRenderTarget renderTargetParameters); diff --git a/src/Elemental/Common/Graphics/ShaderReader.h b/src/Elemental/Common/Graphics/ShaderReader.h index cbd24461..c9860b90 100644 --- a/src/Elemental/Common/Graphics/ShaderReader.h +++ b/src/Elemental/Common/Graphics/ShaderReader.h @@ -5,11 +5,10 @@ enum ShaderType { ShaderType_Unknown = 0, - ShaderType_Amplification = 1, - ShaderType_Mesh = 2, - ShaderType_Pixel = 3, - ShaderType_Compute = 4, - ShaderType_Library = 5 + ShaderType_Mesh = 1, + ShaderType_Pixel = 2, + ShaderType_Compute = 3, + ShaderType_Library = 4 }; enum ShaderMetadataType diff --git a/src/Elemental/Common/Graphics/UploadBufferPool.cpp b/src/Elemental/Common/Graphics/UploadBufferPool.cpp new file mode 100644 index 00000000..f1a7b5ae --- /dev/null +++ b/src/Elemental/Common/Graphics/UploadBufferPool.cpp @@ -0,0 +1,97 @@ +#include "UploadBufferPool.h" +#include "SystemFunctions.h" + +template +UploadBufferMemory GetUploadBufferPoolItem(UploadBufferDevicePool* uploadBufferPool, uint64_t generation, uint64_t alignment, uint64_t sizeInBytes) +{ + if (uploadBufferPool->Generation != generation) + { + uploadBufferPool->CurrentUploadBuffer = nullptr; + uploadBufferPool->Generation = generation; + } + + if (uploadBufferPool->CurrentUploadBuffer != nullptr) + { + if (uploadBufferPool->CurrentUploadBuffer->CurrentOffset + sizeInBytes > uploadBufferPool->CurrentUploadBuffer->SizeInBytes) + { + uploadBufferPool->CurrentUploadBuffer = nullptr; + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Too big"); + } + } + + if (uploadBufferPool->CurrentUploadBuffer == nullptr) + { + uploadBufferPool->CurrentUploadBuffer = &uploadBufferPool->UploadBuffers[uploadBufferPool->CurrentUploadBufferIndex]; + uploadBufferPool->CurrentUploadBufferIndex = (uploadBufferPool->CurrentUploadBufferIndex + 1) % MAX_UPLOAD_BUFFERS; + + auto currentUploadBuffer = uploadBufferPool->CurrentUploadBuffer; + uint64_t uploadBufferSize = SystemMin(uploadBufferPool->LastBufferSize * 2u, 512u * 1024u * 1024u); + + if (uploadBufferSize == 0) + { + // TODO: put that in a constant + uploadBufferSize = 16u * 1024u * 1024u; + } + + while (uploadBufferSize < sizeInBytes) + { + uploadBufferSize <<= 1; + } + + uploadBufferPool->LastBufferSize = uploadBufferSize; + + if (currentUploadBuffer->SizeInBytes != uploadBufferSize) + { + currentUploadBuffer->SizeInBytes = uploadBufferSize; + currentUploadBuffer->IsResetNeeded = true; + } + + currentUploadBuffer->CurrentOffset = 0u; + } + + auto offset = uploadBufferPool->CurrentUploadBuffer->CurrentOffset; + auto alignedOffset = SystemAlign(offset, alignment); + + uint64_t newOffset = alignedOffset + sizeInBytes; + + // TODO: Better handle that case + if (newOffset > uploadBufferPool->CurrentUploadBuffer->SizeInBytes) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Not enough space after alignment"); + return { nullptr, 0ull }; + } + + uploadBufferPool->CurrentUploadBuffer->CurrentOffset = newOffset; + uploadBufferPool->CurrentUploadBuffer->LastUsedGeneration = generation; + + return + { + .PoolItem = uploadBufferPool->CurrentUploadBuffer, + .Offset = alignedOffset + }; +} + +template +void UpdateUploadBufferPoolItemFence(UploadBufferPoolItem* uploadBufferPoolItem, ElemFence fence) +{ + uploadBufferPoolItem->Fence = fence; +} + +template +Span*> GetUploadBufferPoolItemsToDelete(MemoryArena memoryArena, UploadBufferDevicePool* uploadBufferPool, uint64_t generation) +{ + auto result = SystemPushArray*>(memoryArena, MAX_UPLOAD_BUFFERS); + auto resultCount = 0u; + + for (uint32_t i = 0; i < MAX_UPLOAD_BUFFERS; i++) + { + auto uploadBufferPoolItem = &uploadBufferPool->UploadBuffers[i]; + + if (uploadBufferPoolItem->SizeInBytes > 0 && (generation - uploadBufferPoolItem->LastUsedGeneration > 500)) + { + result[resultCount++] = uploadBufferPoolItem; + } + } + + return result.Slice(0, resultCount); +} diff --git a/src/Elemental/Common/Graphics/UploadBufferPool.h b/src/Elemental/Common/Graphics/UploadBufferPool.h new file mode 100644 index 00000000..6830dbe0 --- /dev/null +++ b/src/Elemental/Common/Graphics/UploadBufferPool.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Elemental.h" +#include "SystemMemory.h" + +#define MAX_UPLOAD_BUFFERS 10 + +template +struct UploadBufferPoolItem +{ + T Buffer; + uint64_t SizeInBytes; + ElemFence Fence; + bool IsResetNeeded; + uint64_t CurrentOffset; + uint8_t* CpuPointer; + uint64_t LastUsedGeneration; +}; + +template +struct UploadBufferMemory +{ + UploadBufferPoolItem* PoolItem; + uint64_t Offset; +}; + +template +struct UploadBufferDevicePool +{ + UploadBufferPoolItem UploadBuffers[MAX_UPLOAD_BUFFERS]; + uint32_t CurrentUploadBufferIndex; + UploadBufferPoolItem* CurrentUploadBuffer; + uint64_t LastBufferSize; + uint64_t Generation; + bool IsInited; +}; + +template +UploadBufferMemory GetUploadBufferPoolItem(UploadBufferDevicePool* uploadBufferPool, uint64_t generation, uint64_t alignment, uint64_t sizeInBytes); + +template +void UpdateUploadBufferPoolItemFence(UploadBufferPoolItem* uploadBufferPoolItem, ElemFence fence); + +template +Span*> GetUploadBufferPoolItemsToDelete(MemoryArena memoryArena, UploadBufferDevicePool* uploadBufferPool, uint64_t generation); diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.cpp index 4056f6c0..f39ba92d 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.cpp @@ -1,12 +1,10 @@ #include "VulkanCommandList.h" +#include "VulkanConfig.h" #include "VulkanGraphicsDevice.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define VULKAN_MAX_COMMANDQUEUES 10u -#define VULKAN_MAX_COMMANDLISTS 64u - SystemDataPool vulkanCommandQueuePool; SystemDataPool vulkanCommandListPool; @@ -237,6 +235,7 @@ ElemCommandList VulkanGetCommandList(ElemCommandQueue commandQueue, const ElemCo VkCommandPoolCreateInfo createInfo = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; createInfo.queueFamilyIndex = commandQueueData->QueueFamilyIndex; + createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkCommandPool commandPool; AssertIfFailed(vkCreateCommandPool(graphicsDeviceData->Device, &createInfo, 0, &commandPool)); @@ -290,7 +289,8 @@ ElemCommandList VulkanGetCommandList(ElemCommandQueue commandQueue, const ElemCo .CommandQueue = commandQueue, .CommandAllocatorPoolItem = commandAllocatorPoolItem, .CommandListPoolItem = commandListPoolItem, - .ResourceBarrierPool = resourceBarrierPool + .ResourceBarrierPool = resourceBarrierPool, + .UploadBufferCount = 0 }); SystemAddDataPoolItemFull(vulkanCommandListPool, handle, { @@ -307,6 +307,8 @@ void VulkanCommitCommandList(ElemCommandList commandList) auto commandListData = GetVulkanCommandListData(commandList); SystemAssert(commandListData); + + FreeResourceBarrierPool(commandListData->ResourceBarrierPool); AssertIfFailed(vkEndCommandBuffer(commandListData->DeviceObject)); @@ -340,7 +342,7 @@ ElemFence VulkanExecuteCommandLists(ElemCommandQueue commandQueue, ElemCommandLi auto commandQueueToWaitData = GetVulkanCommandQueueData(fenceToWait.CommandQueue); SystemAssert(commandQueueToWaitData); - submitStageMasks[i] = VK_PIPELINE_STAGE_TASK_SHADER_BIT_EXT | VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + submitStageMasks[i] = VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; waitSemaphores[i] = commandQueueToWaitData->Fence; waitSemaphoreValues[i] = fenceToWait.FenceValue; @@ -417,8 +419,13 @@ ElemFence VulkanExecuteCommandLists(ElemCommandQueue commandQueue, ElemCommandLi } UpdateCommandAllocatorPoolItemFence(commandListData->CommandAllocatorPoolItem, fence); + + for (uint32_t j = 0; j < commandListData->UploadBufferCount; j++) + { + UpdateUploadBufferPoolItemFence(commandListData->UploadBufferPoolItems[j], fence); + } + ReleaseCommandListPoolItem(commandListData->CommandListPoolItem); - FreeResourceBarrierPool(commandListData->ResourceBarrierPool); SystemRemoveDataPoolItem(vulkanCommandListPool, commandLists.Items[i]); } @@ -446,7 +453,8 @@ void VulkanWaitForFenceOnCpu(ElemFence fence) if (fence.FenceValue > commandQueueToWaitData->LastCompletedFenceValue) { - SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Wait for fence on CPU..."); + // TODO: Activate it in a special debug mode + //SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Wait for fence on CPU..."); VkSemaphoreWaitInfo waitInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO }; waitInfo.semaphoreCount = 1; @@ -462,7 +470,6 @@ bool VulkanIsFenceCompleted(ElemFence fence) SystemAssert(fence.CommandQueue != ELEM_HANDLE_NULL); auto commandQueueToWaitData = GetVulkanCommandQueueData(fence.CommandQueue); - auto commandQueueToWaitDataFull = GetVulkanCommandQueueDataFull(fence.CommandQueue); if (!commandQueueToWaitData) { diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.h b/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.h index 0218efe0..db5f7a7d 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.h +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanCommandList.h @@ -1,8 +1,10 @@ #pragma once #include "Elemental.h" +#include "VulkanResource.h" #include "Graphics/CommandAllocatorPool.h" #include "Graphics/ResourceBarrier.h" +#include "Graphics/UploadBufferPool.h" #include "SystemSpan.h" #include "volk.h" @@ -44,6 +46,8 @@ struct VulkanCommandListData CommandListPoolItem* CommandListPoolItem; VulkanPipelineStateType PipelineStateType; ResourceBarrierPool ResourceBarrierPool; + UploadBufferPoolItem* UploadBufferPoolItems[MAX_UPLOAD_BUFFERS]; + uint32_t UploadBufferCount; }; struct VulkanCommandListDataFull diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanConfig.h b/src/Elemental/Common/Graphics/Vulkan/VulkanConfig.h new file mode 100644 index 00000000..a8da5a0c --- /dev/null +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanConfig.h @@ -0,0 +1,21 @@ +#pragma once + +#define VULKAN_MEMORY_ARENA 512 * 1024 * 1024 +#define VULKAN_READBACK_MEMORY_ARENA 32 * 1024 * 1024 + +#define VULKAN_MAX_DEVICES 10u + +#define VULKAN_MAX_COMMANDQUEUES 10u +#define VULKAN_MAX_COMMANDLISTS 64u + +#define VULKAN_MAX_GRAPHICSHEAP 32 +#define VULKAN_MAX_RESOURCES 1000000 +#define VULKAN_MAX_SAMPLERS 2048 +#define VULKAN_MAX_MIPS 16 + +#define VULKAN_MAX_SWAPCHAIN_BUFFERS 3 +#define VULKAN_MAX_SWAPCHAINS 10u + +#define VULKAN_MAX_LIBRARIES UINT16_MAX +#define VULKAN_MAX_PIPELINESTATES UINT16_MAX + diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.cpp index f9374d4e..79187401 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.cpp @@ -1,9 +1,16 @@ #include "VulkanGraphicsDevice.h" +#include "VulkanConfig.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemLogging.h" #include "SystemMemory.h" +struct VulkanDescriptorSet +{ + VkDescriptorPool DescriptorPool; + VkDescriptorSet DescriptorSet; +}; + struct VulkanDescriptorHeapFreeListItem { uint32_t Next; @@ -11,8 +18,7 @@ struct VulkanDescriptorHeapFreeListItem struct VulkanDescriptorHeapStorage { - VkDescriptorPool DescriptorPool; - VkDescriptorSet DescriptorSet; + const VulkanDescriptorSet* DescriptorSet; Span Items; uint32_t CurrentIndex; uint32_t FreeListIndex; @@ -45,6 +51,11 @@ VkBool32 VKAPI_CALL VulkanDebugReportCallback(VkDebugReportFlagsEXT flags, VkDeb return VK_FALSE; } + if (SystemFindSubString(pMessage, "BestPractices-PushConstants") != -1) + { + return VK_FALSE; + } + SystemLogMessage(messageType, ElemLogMessageCategory_Graphics, "%s", pMessage); return VK_FALSE; @@ -55,10 +66,10 @@ void InitVulkan() auto stackMemoryArena = SystemGetStackMemoryArena(); AssertIfFailed(volkInitialize()); - SystemAssert(volkGetInstanceVersion() >= VK_API_VERSION_1_3); + SystemAssert(volkGetInstanceVersion() >= VK_API_VERSION_1_4); VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO }; - appInfo.apiVersion = VK_API_VERSION_1_3; + appInfo.apiVersion = VK_API_VERSION_1_4; VkInstanceCreateInfo createInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; createInfo.pApplicationInfo = &appInfo; @@ -178,13 +189,44 @@ void InitVulkanGraphicsDeviceMemory() { if (!VulkanGraphicsMemoryArena.Storage) { - VulkanGraphicsMemoryArena = SystemAllocateMemoryArena(); + // TODO: To Review + VulkanGraphicsMemoryArena = SystemAllocateMemoryArena(256 * 1024 * 1024); vulkanGraphicsDevicePool = SystemCreateDataPool(VulkanGraphicsMemoryArena, VULKAN_MAX_DEVICES); InitVulkan(); } } +VkCompareOp ConvertToVulkanCompareFunction(ElemGraphicsCompareFunction compareFunction) +{ + switch (compareFunction) + { + case ElemGraphicsCompareFunction_Never: + return VK_COMPARE_OP_NEVER; + + case ElemGraphicsCompareFunction_Less: + return VK_COMPARE_OP_LESS; + + case ElemGraphicsCompareFunction_Equal: + return VK_COMPARE_OP_EQUAL; + + case ElemGraphicsCompareFunction_LessEqual: + return VK_COMPARE_OP_LESS_OR_EQUAL; + + case ElemGraphicsCompareFunction_Greater: + return VK_COMPARE_OP_GREATER; + + case ElemGraphicsCompareFunction_NotEqual: + return VK_COMPARE_OP_NOT_EQUAL; + + case ElemGraphicsCompareFunction_GreaterEqual: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + + case ElemGraphicsCompareFunction_Always: + return VK_COMPARE_OP_ALWAYS; + } +} + ElemGraphicsDeviceInfo VulkanConstructGraphicsDeviceInfo(MemoryArena memoryArena, VkPhysicalDeviceProperties deviceProperties, VkPhysicalDeviceMemoryProperties deviceMemoryProperties) { auto deviceName = ReadOnlySpan(deviceProperties.deviceName); @@ -200,25 +242,26 @@ ElemGraphicsDeviceInfo VulkanConstructGraphicsDeviceInfo(MemoryArena memoryArena }; } -VulkanDescriptorHeap CreateVulkanDescriptorHeap(VkDevice graphicsDevice, MemoryArena memoryArena, VkDescriptorSetLayout descriptorSetLayout, uint32_t length) +VulkanDescriptorSet* CreateVulkanDescriptorSet(MemoryArena memoryArena, VkDevice graphicsDevice, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorCount) { - VkDescriptorPoolSize poolSizes[] + VkDescriptorPoolSize poolSize { - {VK_DESCRIPTOR_TYPE_MUTABLE_EXT, length } + .type = VK_DESCRIPTOR_TYPE_MUTABLE_EXT, + .descriptorCount = descriptorCount }; VkDescriptorPoolCreateInfo createInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; - createInfo.poolSizeCount = ARRAYSIZE(poolSizes); - createInfo.pPoolSizes = poolSizes; + createInfo.poolSizeCount = 1; + createInfo.pPoolSizes = &poolSize; createInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT; - createInfo.maxSets = ARRAYSIZE(poolSizes); + createInfo.maxSets = 1; VkDescriptorPool descriptorPool; AssertIfFailed(vkCreateDescriptorPool(graphicsDevice, &createInfo, nullptr, &descriptorPool)); VkDescriptorSetVariableDescriptorCountAllocateInfo descriptorSetLayoutCount = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO }; descriptorSetLayoutCount.descriptorSetCount = 1; - descriptorSetLayoutCount.pDescriptorCounts = &length; + descriptorSetLayoutCount.pDescriptorCounts = &descriptorCount; VkDescriptorSetAllocateInfo allocateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; allocateInfo.pSetLayouts = &descriptorSetLayout; @@ -228,9 +271,25 @@ VulkanDescriptorHeap CreateVulkanDescriptorHeap(VkDevice graphicsDevice, MemoryA VkDescriptorSet descriptorSet; AssertIfFailed(vkAllocateDescriptorSets(graphicsDevice, &allocateInfo, &descriptorSet)); + + auto result = SystemPushStruct(memoryArena); + result->DescriptorPool = descriptorPool; + result->DescriptorSet = descriptorSet; + + return result; +} + +void FreeVulkanDescriptorSet(VkDevice device, const VulkanDescriptorSet* descriptorSet) +{ + SystemAssert(descriptorSet); + vkDestroyDescriptorPool(device, descriptorSet->DescriptorPool, nullptr); +} + +VulkanDescriptorHeap CreateVulkanDescriptorHeap(MemoryArena memoryArena, VkDevice graphicsDevice, VkDescriptorSetLayout descriptorSetLayout, uint32_t length) +{ + auto descriptorSet = CreateVulkanDescriptorSet(memoryArena, graphicsDevice, descriptorSetLayout, VULKAN_MAX_RESOURCES); auto descriptorStorage = SystemPushStruct(memoryArena); - descriptorStorage->DescriptorPool = descriptorPool; descriptorStorage->DescriptorSet = descriptorSet; descriptorStorage->Items = SystemPushArray(memoryArena, length); descriptorStorage->CurrentIndex = 0; @@ -242,10 +301,10 @@ VulkanDescriptorHeap CreateVulkanDescriptorHeap(VkDevice graphicsDevice, MemoryA }; } -void FreeVulkanDescriptorHeap(VkDevice device, VulkanDescriptorHeap descriptorHeap) +void FreeVulkanDescriptorHeap(VkDevice device, const VulkanDescriptorHeap descriptorHeap) { SystemAssert(descriptorHeap.Storage); - vkDestroyDescriptorPool(device, descriptorHeap.Storage->DescriptorPool, nullptr); + vkDestroyDescriptorPool(device, descriptorHeap.Storage->DescriptorSet->DescriptorPool, nullptr); } uint32_t CreateVulkanDescriptorHandle(VulkanDescriptorHeap descriptorHeap) @@ -299,7 +358,7 @@ bool VulkanCheckGraphicsDeviceCompatibility(VkPhysicalDevice device) vkGetPhysicalDeviceFeatures2(device, &features2); - if (meshShaderFeatures.meshShader && meshShaderFeatures.taskShader && presentIdFeatures.presentId) + if (meshShaderFeatures.meshShader && presentIdFeatures.presentId) { return true; } @@ -317,52 +376,38 @@ VulkanGraphicsDeviceDataFull* GetVulkanGraphicsDeviceDataFull(ElemGraphicsDevice return SystemGetDataPoolItemFull(vulkanGraphicsDevicePool, graphicsDevice); } -void CreateVulkanPipelineLayout(ElemGraphicsDevice graphicsDevice) +VkDescriptorSetLayout CreateVulkanDescriptorSetLayout(ElemGraphicsDevice graphicsDevice, VkDescriptorType* descriptorTypes, uint32_t descriptorTypeCount) { auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); SystemAssert(graphicsDeviceData); - auto graphicsDeviceDataFull = GetVulkanGraphicsDeviceDataFull(graphicsDevice); - SystemAssert(graphicsDeviceDataFull); - - VkPipelineLayoutCreateInfo layoutCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; - layoutCreateInfo.pSetLayouts = nullptr; - layoutCreateInfo.setLayoutCount = 0; - - VkPushConstantRange push_constant; - push_constant.offset = 0; - push_constant.size = 24 * 4; - push_constant.stageFlags = VK_SHADER_STAGE_ALL; - - layoutCreateInfo.pPushConstantRanges = &push_constant; - layoutCreateInfo.pushConstantRangeCount = 1; - - VkDescriptorType resourceDescriptorTypes[] = { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }; - - VkMutableDescriptorTypeListEXT descriptorSetTypeList = - { - .pDescriptorTypes = resourceDescriptorTypes, - .descriptorTypeCount = ARRAYSIZE(resourceDescriptorTypes) + VkMutableDescriptorTypeListEXT descriptorSetTypes = + { + .descriptorTypeCount = descriptorTypeCount, + .pDescriptorTypes = descriptorTypes }; VkMutableDescriptorTypeCreateInfoEXT mutableDescriptorTypeCreateInfo = { VK_STRUCTURE_TYPE_MUTABLE_DESCRIPTOR_TYPE_CREATE_INFO_EXT }; - mutableDescriptorTypeCreateInfo.pMutableDescriptorTypeLists = &descriptorSetTypeList; + mutableDescriptorTypeCreateInfo.pMutableDescriptorTypeLists = &descriptorSetTypes; mutableDescriptorTypeCreateInfo.mutableDescriptorTypeListCount = 1; - VkDescriptorBindingFlags descriptorBindingFlags = VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT | - VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | - VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT; + VkDescriptorBindingFlags descriptorBindingFlags[] = + { + VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT + }; - VkDescriptorSetLayoutBindingFlagsCreateInfo descriptorBindingFlagsCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO }; + VkDescriptorSetLayoutBindingFlagsCreateInfo descriptorBindingFlagsCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO }; descriptorBindingFlagsCreateInfo.bindingCount = 1; - descriptorBindingFlagsCreateInfo.pBindingFlags = &descriptorBindingFlags; + descriptorBindingFlagsCreateInfo.pBindingFlags = descriptorBindingFlags; descriptorBindingFlagsCreateInfo.pNext = &mutableDescriptorTypeCreateInfo; - VkDescriptorSetLayoutBinding descriptorBinding = {}; - descriptorBinding.binding = 0; - descriptorBinding.descriptorType = VK_DESCRIPTOR_TYPE_MUTABLE_EXT; - descriptorBinding.descriptorCount = VULKAN_MAX_RESOURCES; - descriptorBinding.stageFlags = VK_SHADER_STAGE_ALL; + VkDescriptorSetLayoutBinding descriptorBinding = + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_MUTABLE_EXT, + .descriptorCount = VULKAN_MAX_RESOURCES, + .stageFlags = VK_SHADER_STAGE_ALL + }; VkDescriptorSetLayoutCreateInfo descriptorSetCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO }; descriptorSetCreateInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT; @@ -370,10 +415,42 @@ void CreateVulkanPipelineLayout(ElemGraphicsDevice graphicsDevice) descriptorSetCreateInfo.pBindings = &descriptorBinding; descriptorSetCreateInfo.pNext = &descriptorBindingFlagsCreateInfo; - AssertIfFailed(vkCreateDescriptorSetLayout(graphicsDeviceData->Device, &descriptorSetCreateInfo, 0, &graphicsDeviceDataFull->ResourceDescriptorSetLayout)); + VkDescriptorSetLayout result; + AssertIfFailed(vkCreateDescriptorSetLayout(graphicsDeviceData->Device, &descriptorSetCreateInfo, 0, &result)); + + return result; +} + +void CreateVulkanPipelineLayout(ElemGraphicsDevice graphicsDevice) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto graphicsDeviceDataFull = GetVulkanGraphicsDeviceDataFull(graphicsDevice); + SystemAssert(graphicsDeviceDataFull); + + VkPipelineLayoutCreateInfo layoutCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; + + VkPushConstantRange push_constant; + push_constant.offset = 0; + push_constant.size = 24 * 4; + push_constant.stageFlags = VK_SHADER_STAGE_ALL; + + layoutCreateInfo.pPushConstantRanges = &push_constant; + layoutCreateInfo.pushConstantRangeCount = 1; + + // TODO: Recheck those + VkDescriptorType resourceDescriptorTypes[] = { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE }; + graphicsDeviceDataFull->ResourceDescriptorSetLayout = CreateVulkanDescriptorSetLayout(graphicsDevice, resourceDescriptorTypes, ARRAYSIZE(resourceDescriptorTypes)); + + VkDescriptorType samplerDescriptorTypes[] = { VK_DESCRIPTOR_TYPE_SAMPLER }; + graphicsDeviceDataFull->SamplerDescriptorSetLayout = CreateVulkanDescriptorSetLayout(graphicsDevice, samplerDescriptorTypes, ARRAYSIZE(samplerDescriptorTypes)); - layoutCreateInfo.pSetLayouts = &graphicsDeviceDataFull->ResourceDescriptorSetLayout; - layoutCreateInfo.setLayoutCount = 1; + VkDescriptorSetLayout descriptorSetLayouts[] { graphicsDeviceDataFull->ResourceDescriptorSetLayout, graphicsDeviceDataFull->SamplerDescriptorSetLayout }; + layoutCreateInfo.pSetLayouts = descriptorSetLayouts; + layoutCreateInfo.setLayoutCount = ARRAYSIZE(descriptorSetLayouts); AssertIfFailed(vkCreatePipelineLayout(graphicsDeviceData->Device, &layoutCreateInfo, 0, &graphicsDeviceData->PipelineLayout)); } @@ -392,10 +469,7 @@ void VulkanSetGraphicsOptions(const ElemGraphicsOptions* options) vulkanDebugGpuValidationEnabled = options->EnableGpuValidation; } - if (options->EnableDebugBarrierInfo) - { - VulkanDebugBarrierInfoEnabled = options->EnableDebugBarrierInfo; - } + VulkanDebugBarrierInfoEnabled = options->EnableDebugBarrierInfo; } ElemGraphicsDeviceInfoSpan VulkanGetAvailableGraphicsDevices() @@ -516,6 +590,7 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o int32_t gpuMemoryTypeIndex = -1; int32_t gpuUploadMemoryTypeIndex = -1; int32_t readBackMemoryTypeIndex = -1; + int32_t uploadMemoryTypeIndex = -1; for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) { @@ -538,11 +613,11 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o readBackMemoryTypeIndex = i; } - // TODO: Later we will need that to implement IOGraphicsQueue - /* - else if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && (memoryPropertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0) + else if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && + (memoryPropertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == 0) { - }*/ + uploadMemoryTypeIndex = i; + } } SystemAssert(gpuMemoryTypeIndex != -1 && gpuUploadMemoryTypeIndex != -1 && readBackMemoryTypeIndex != -1); @@ -568,6 +643,8 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o features.features.shaderInt16 = true; features.features.shaderInt64 = true; features.features.pipelineStatisticsQuery = true; + features.features.fillModeNonSolid = true; + features.features.samplerAnisotropy = true; VkPhysicalDeviceVulkan12Features features12 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES }; features12.timelineSemaphore = true; @@ -592,13 +669,13 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o features13.maintenance4 = true; features13.synchronization2 = true; features13.dynamicRendering = true; + features13.shaderDemoteToHelperInvocation = true; - VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR }; - maintenance5.maintenance5 = true; + VkPhysicalDeviceVulkan14Features features14 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES }; + features14.maintenance5 = true; VkPhysicalDeviceMeshShaderFeaturesEXT meshFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT }; meshFeatures.meshShader = true; - meshFeatures.taskShader = true; meshFeatures.meshShaderQueries = true; VkPhysicalDeviceMutableDescriptorTypeFeaturesEXT mutableDescriptorFeatures = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_EXT }; @@ -613,11 +690,11 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o createInfo.pNext = &features; features.pNext = &features12; features12.pNext = &features13; - features13.pNext = &presentIdFeatures; + features13.pNext = &features14; + features14.pNext = &presentIdFeatures; presentIdFeatures.pNext = &presentWaitFeatures; presentWaitFeatures.pNext = &meshFeatures; - meshFeatures.pNext = &maintenance5; - maintenance5.pNext = &mutableDescriptorFeatures; + meshFeatures.pNext = &mutableDescriptorFeatures; VkDevice device = nullptr; AssertIfFailedReturnNullHandle(vkCreateDevice(physicalDevice, &createInfo, nullptr, &device)); @@ -639,7 +716,8 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o .CopyCommandQueueIndex = copyCommandQueueIndex, .GpuMemoryTypeIndex = (uint32_t)gpuMemoryTypeIndex, .GpuUploadMemoryTypeIndex = (uint32_t)gpuUploadMemoryTypeIndex, - .ReadBackMemoryTypeIndex = (uint32_t)readBackMemoryTypeIndex + .ReadBackMemoryTypeIndex = (uint32_t)readBackMemoryTypeIndex, + .UploadMemoryTypeIndex = (uint32_t)uploadMemoryTypeIndex }); CreateVulkanPipelineLayout(handle); @@ -647,8 +725,11 @@ ElemGraphicsDevice VulkanCreateGraphicsDevice(const ElemGraphicsDeviceOptions* o auto graphicsDeviceData = GetVulkanGraphicsDeviceData(handle); auto graphicsDeviceDataFull = GetVulkanGraphicsDeviceDataFull(handle); - auto resourceDescriptorHeap = CreateVulkanDescriptorHeap(graphicsDeviceData->Device, memoryArena, graphicsDeviceDataFull->ResourceDescriptorSetLayout, VULKAN_MAX_RESOURCES); - graphicsDeviceData->ResourceDescriptorHeap = resourceDescriptorHeap; + graphicsDeviceData->ResourceDescriptorHeap = CreateVulkanDescriptorHeap(memoryArena, graphicsDeviceData->Device, graphicsDeviceDataFull->ResourceDescriptorSetLayout, VULKAN_MAX_RESOURCES); + graphicsDeviceData->SamplerDescriptorHeap = CreateVulkanDescriptorHeap(memoryArena, graphicsDeviceData->Device, graphicsDeviceDataFull->SamplerDescriptorSetLayout, VULKAN_MAX_SAMPLERS); + + // TODO: This need to be checked. We don't know how many max threads will use this. Maybe we can allocate for MAX_CONC_THREADS variable of param (that can be overriden) + graphicsDeviceData->UploadBufferPools = SystemPushArray*>(VulkanGraphicsMemoryArena, MAX_UPLOAD_BUFFERS); return handle; } @@ -662,10 +743,36 @@ void VulkanFreeGraphicsDevice(ElemGraphicsDevice graphicsDevice) auto graphicsDeviceDataFull = GetVulkanGraphicsDeviceDataFull(graphicsDevice); SystemAssert(graphicsDeviceDataFull); + + for (uint32_t i = 0; i < graphicsDeviceData->UploadBufferPools.Length; i++) + { + auto bufferPool = graphicsDeviceData->UploadBufferPools[i]; + + if (bufferPool) + { + for (uint32_t j = 0; j < MAX_UPLOAD_BUFFERS; j++) + { + auto uploadBuffer = &bufferPool->UploadBuffers[j]; + + if (uploadBuffer->Buffer.Buffer) + { + vkDestroyBuffer(graphicsDeviceData->Device, uploadBuffer->Buffer.Buffer, nullptr); + vkFreeMemory(graphicsDeviceData->Device, uploadBuffer->Buffer.DeviceMemory, nullptr); + + uploadBuffer->Buffer = {}; + *uploadBuffer = {}; + } + } + + *bufferPool = {}; + } + } FreeVulkanDescriptorHeap(graphicsDeviceData->Device, graphicsDeviceData->ResourceDescriptorHeap); + FreeVulkanDescriptorHeap(graphicsDeviceData->Device, graphicsDeviceData->SamplerDescriptorHeap); vkDestroyDescriptorSetLayout(graphicsDeviceData->Device, graphicsDeviceDataFull->ResourceDescriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(graphicsDeviceData->Device, graphicsDeviceDataFull->SamplerDescriptorSetLayout, nullptr); vkDestroyPipelineLayout(graphicsDeviceData->Device, graphicsDeviceData->PipelineLayout, nullptr); vkDestroyDevice(graphicsDeviceData->Device, nullptr); diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.h b/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.h index 8bafbe7a..7b5c14e3 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.h +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanGraphicsDevice.h @@ -2,6 +2,8 @@ #include "Elemental.h" #include "SystemMemory.h" +#include "VulkanResource.h" +#include "Graphics/UploadBufferPool.h" #ifdef WIN32 #define VK_USE_PLATFORM_WIN32_KHR @@ -10,11 +12,10 @@ #endif #include "volk.h" -#define VULKAN_MAX_DEVICES 10u -#define VULKAN_MAX_RESOURCES 400000 - struct VulkanDescriptorHeapStorage; +struct VulkanDescriptorSet; + struct VulkanDescriptorHeap { VulkanDescriptorHeapStorage* Storage; @@ -26,7 +27,11 @@ struct VulkanGraphicsDeviceData MemoryArena MemoryArena; VkPipelineLayout PipelineLayout; uint64_t CommandAllocationGeneration; + uint64_t UploadBufferGeneration; VulkanDescriptorHeap ResourceDescriptorHeap; + VulkanDescriptorHeap SamplerDescriptorHeap; + Span*> UploadBufferPools; + uint32_t CurrentUploadBufferPoolIndex; }; struct VulkanGraphicsDeviceDataFull @@ -43,7 +48,9 @@ struct VulkanGraphicsDeviceDataFull uint32_t GpuMemoryTypeIndex; uint32_t GpuUploadMemoryTypeIndex; uint32_t ReadBackMemoryTypeIndex; + uint32_t UploadMemoryTypeIndex; VkDescriptorSetLayout ResourceDescriptorSetLayout; + VkDescriptorSetLayout SamplerDescriptorSetLayout; }; extern MemoryArena VulkanGraphicsMemoryArena; @@ -54,6 +61,8 @@ extern bool VulkanDebugBarrierInfoEnabled; VulkanGraphicsDeviceData* GetVulkanGraphicsDeviceData(ElemGraphicsDevice graphicsDevice); VulkanGraphicsDeviceDataFull* GetVulkanGraphicsDeviceDataFull(ElemGraphicsDevice graphicsDevice); +VkCompareOp ConvertToVulkanCompareFunction(ElemGraphicsCompareFunction compareFunction); + void VulkanSetGraphicsOptions(const ElemGraphicsOptions* options); uint32_t CreateVulkanDescriptorHandle(VulkanDescriptorHeap descriptorHeap); diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanRendering.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanRendering.cpp index 6882c41a..b32cf195 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanRendering.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanRendering.cpp @@ -3,9 +3,48 @@ #include "VulkanCommandList.h" #include "VulkanResource.h" #include "VulkanResourceBarrier.h" -#include "SystemLogging.h" #include "SystemFunctions.h" +VkAttachmentLoadOp ConvertToVulkanRenderPassAttachmentLoadOp(ElemRenderPassLoadAction loadAction) +{ + VkAttachmentLoadOp loadOperation; + + switch (loadAction) + { + case ElemRenderPassLoadAction_Load: + loadOperation = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + + case ElemRenderPassLoadAction_Clear: + loadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + + default: + loadOperation = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } + + return loadOperation; +} + +VkAttachmentStoreOp ConvertToVulkanRenderPassAttachmentStoreOp(ElemRenderPassStoreAction storeAction) +{ + VkAttachmentStoreOp storeOperation; + + switch (storeAction) + { + case ElemRenderPassStoreAction_Store: + storeOperation = VK_ATTACHMENT_STORE_OP_STORE; + break; + + default: + storeOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; + break; + } + + return storeOperation; +} + void VulkanBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPassParameters* parameters) { // TODO: Check command list type != COMPUTE @@ -21,8 +60,10 @@ void VulkanBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPas auto commandListDataFull = GetVulkanCommandListDataFull(commandList); SystemAssert(commandListDataFull); commandListDataFull->CurrentRenderPassParameters = *parameters; + + auto depthStencilCount = (parameters->DepthStencil.DepthStencil != ELEM_HANDLE_NULL) ? 1 : 0; - auto renderingAttachments = SystemPushArray(stackMemoryArena, parameters->RenderTargets.Length); + auto renderingAttachments = SystemPushArray(stackMemoryArena, parameters->RenderTargets.Length + depthStencilCount); auto renderingWidth = 0u; auto renderingHeight = 0u; @@ -34,23 +75,8 @@ void VulkanBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPas auto textureData = GetVulkanGraphicsResourceData(renderTargetParameters.RenderTarget); SystemAssert(textureData); - VkAttachmentLoadOp loadOperation; - - switch (renderTargetParameters.LoadAction) - { - case ElemRenderPassLoadAction_Load: - loadOperation = VK_ATTACHMENT_LOAD_OP_LOAD; - break; - - case ElemRenderPassLoadAction_Clear: - loadOperation = VK_ATTACHMENT_LOAD_OP_CLEAR; - break; + auto loadOperation = ConvertToVulkanRenderPassAttachmentLoadOp(renderTargetParameters.LoadAction); - default: - loadOperation = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - break; - } - VkClearValue clearValue { .color = @@ -62,18 +88,7 @@ void VulkanBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPas }} }; - VkAttachmentStoreOp storeOperation; - - switch (renderTargetParameters.StoreAction) - { - case ElemRenderPassStoreAction_Store: - storeOperation = VK_ATTACHMENT_STORE_OP_STORE; - break; - - default: - storeOperation = VK_ATTACHMENT_STORE_OP_DONT_CARE; - break; - } + auto storeOperation = ConvertToVulkanRenderPassAttachmentStoreOp(renderTargetParameters.StoreAction); renderingAttachments[i] = { @@ -114,7 +129,67 @@ void VulkanBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPas ElemSetViewport(commandList, &viewport); } + + if (i == 0 && parameters->ScissorRectangles.Length == 0) + { + ElemRectangle rectangle = + { + .X = 0, + .Y = 0, + .Width = (float)textureData->Width, + .Height = (float)textureData->Height + }; + + ElemSetScissorRectangle(commandList, &rectangle); + } + } + } + + if (parameters->DepthStencil.DepthStencil != ELEM_HANDLE_NULL) + { + auto depthStencilParameters = parameters->DepthStencil; + + auto textureData = GetVulkanGraphicsResourceData(depthStencilParameters.DepthStencil); + SystemAssert(textureData); + + if (renderingWidth == 0 || renderingHeight == 0) + { + renderingWidth = textureData->Width; + renderingHeight = textureData->Height; } + + // TODO: Validate usage + auto loadOperation = ConvertToVulkanRenderPassAttachmentLoadOp(depthStencilParameters.DepthLoadAction); + auto storeOperation = ConvertToVulkanRenderPassAttachmentStoreOp(depthStencilParameters.DepthStoreAction); + + VkClearValue clearValue + { + .depthStencil = + { + .depth = depthStencilParameters.DepthClearValue + } + }; + + renderingAttachments[parameters->RenderTargets.Length] = + { + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = textureData->DepthStencilImageView, + .imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .loadOp = loadOperation, + .storeOp = storeOperation, + .clearValue = clearValue + }; + + ResourceBarrierItem resourceBarrier = + { + .Type = ElemGraphicsResourceType_Texture2D, + .IsDepthStencil = true, + .Resource = depthStencilParameters.DepthStencil, + .AfterAccess = ElemGraphicsResourceBarrierAccessType_DepthStencilWrite, + .AfterLayout = ElemGraphicsResourceBarrierLayoutType_DepthStencilWrite + }; + + EnqueueBarrier(commandListData->ResourceBarrierPool, &resourceBarrier); } if (parameters->Viewports.Length > 0) @@ -122,16 +197,24 @@ void VulkanBeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPas ElemSetViewports(commandList, parameters->Viewports); } - // TODO: Take into account the depth buffer because it is possible to render only - // to the depth buffer without any color render targets + if (parameters->ScissorRectangles.Length > 0) + { + ElemSetScissorRectangles(commandList, parameters->ScissorRectangles); + } + SystemAssert(renderingWidth != 0 && renderingHeight != 0); VkRenderingInfo renderingInfo = { VK_STRUCTURE_TYPE_RENDERING_INFO }; renderingInfo.renderArea.extent.width = renderingWidth; renderingInfo.renderArea.extent.height = renderingHeight; renderingInfo.layerCount = 1; - renderingInfo.colorAttachmentCount = renderingAttachments.Length; + renderingInfo.colorAttachmentCount = parameters->RenderTargets.Length; renderingInfo.pColorAttachments = renderingAttachments.Pointer; + + if (depthStencilCount > 0) + { + renderingInfo.pDepthAttachment = &renderingAttachments.Pointer[parameters->RenderTargets.Length]; + } InsertVulkanResourceBarriersIfNeeded(commandList, ElemGraphicsResourceBarrierSyncType_RenderTarget); vkCmdBeginRendering(commandListData->DeviceObject, &renderingInfo); @@ -180,6 +263,23 @@ void VulkanEndRenderPass(ElemCommandList commandList) commandQueueData->SignalPresentSemaphore = true; } } + + // HACK: Review this, it is needed to avoid a validation error but I don't see why + // we need to transition the depth buffer to read here. + if (parameters->DepthStencil.DepthStencil != ELEM_HANDLE_NULL) + { + ResourceBarrierItem resourceBarrier = + { + .Type = ElemGraphicsResourceType_Texture2D, + .IsDepthStencil = true, + .Resource = parameters->DepthStencil.DepthStencil, + .AfterSync = ElemGraphicsResourceBarrierSyncType_RenderTarget, + .AfterAccess = ElemGraphicsResourceBarrierAccessType_NoAccess, + .AfterLayout = ElemGraphicsResourceBarrierLayoutType_Read + }; + + EnqueueBarrier(commandListData->ResourceBarrierPool, &resourceBarrier); + } InsertVulkanResourceBarriersIfNeeded(commandList, ElemGraphicsResourceBarrierSyncType_None); } @@ -192,7 +292,6 @@ void VulkanSetViewports(ElemCommandList commandList, ElemViewportSpan viewports) auto stackMemoryArena = SystemGetStackMemoryArena(); auto vulkanViewports = SystemPushArray(stackMemoryArena, viewports.Length); - auto vulkanScissorRects = SystemPushArray(stackMemoryArena, viewports.Length); for (uint32_t i = 0; i < viewports.Length; i++) { @@ -205,18 +304,36 @@ void VulkanSetViewports(ElemCommandList commandList, ElemViewportSpan viewports) .minDepth = viewports.Items[i].MinDepth, .maxDepth = viewports.Items[i].MaxDepth }; + } + + auto commandListData = GetVulkanCommandListData(commandList); + SystemAssert(commandListData); + vkCmdSetViewport(commandListData->DeviceObject, 0, vulkanViewports.Length, vulkanViewports.Pointer); +} + +void VulkanSetScissorRectangles(ElemCommandList commandList, ElemRectangleSpan rectangles) +{ + // TODO: Check command list type != COMPUTE + + SystemAssert(commandList != ELEM_HANDLE_NULL); + + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto vulkanScissorRects = SystemPushArray(stackMemoryArena, rectangles.Length); + + for (uint32_t i = 0; i < rectangles.Length; i++) + { vulkanScissorRects[i] = { .offset = { - .x = (int32_t)viewports.Items[i].X, - .y = (int32_t)viewports.Items[i].Y, + .x = (int32_t)rectangles.Items[i].X, + .y = (int32_t)rectangles.Items[i].Y, }, .extent = { - .width = (uint32_t)viewports.Items[i].Width, - .height = (uint32_t)viewports.Items[i].Height + .width = (uint32_t)rectangles.Items[i].Width, + .height = (uint32_t)rectangles.Items[i].Height } }; } @@ -224,7 +341,6 @@ void VulkanSetViewports(ElemCommandList commandList, ElemViewportSpan viewports) auto commandListData = GetVulkanCommandListData(commandList); SystemAssert(commandListData); - vkCmdSetViewport(commandListData->DeviceObject, 0, vulkanViewports.Length, vulkanViewports.Pointer); vkCmdSetScissor(commandListData->DeviceObject, 0, vulkanScissorRects.Length, vulkanScissorRects.Pointer); } diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanResource.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanResource.cpp index 77f6f6d8..d189427c 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanResource.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanResource.cpp @@ -1,19 +1,25 @@ #include "VulkanResource.h" +#include "VulkanConfig.h" #include "VulkanGraphicsDevice.h" +#include "VulkanCommandList.h" +#include "Graphics/Resource.h" #include "Graphics/ResourceDeleteQueue.h" +#include "Graphics/UploadBufferPool.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define VULKAN_MAX_GRAPHICSHEAP 32 - SystemDataPool vulkanGraphicsHeapPool; SystemDataPool vulkanGraphicsResourcePool; -// TODO: This descriptor infos should be linked to the graphics device like the resource desc heaps +thread_local UploadBufferDevicePool threadVulkanUploadBufferPools[VULKAN_MAX_DEVICES]; + +// TODO: IMPORTANT: This descriptor infos should be linked to the graphics device like the resource desc heaps Span vulkanResourceDescriptorInfos; +Span vulkanSamplerInfos; // TODO: To refactor Span vulkanResourceDescriptorImageViews; +MemoryArena vulkanReadBackMemoryArena; void InitVulkanResourceMemory() { @@ -21,8 +27,12 @@ void InitVulkanResourceMemory() { vulkanGraphicsHeapPool = SystemCreateDataPool(VulkanGraphicsMemoryArena, VULKAN_MAX_GRAPHICSHEAP); vulkanGraphicsResourcePool = SystemCreateDataPool(VulkanGraphicsMemoryArena, VULKAN_MAX_RESOURCES); - vulkanResourceDescriptorInfos = SystemPushArray(VulkanGraphicsMemoryArena, VULKAN_MAX_RESOURCES); - vulkanResourceDescriptorImageViews = SystemPushArray(VulkanGraphicsMemoryArena, VULKAN_MAX_RESOURCES); + + vulkanResourceDescriptorInfos = SystemPushArray(VulkanGraphicsMemoryArena, VULKAN_MAX_RESOURCES, AllocationState_Reserved); + vulkanResourceDescriptorImageViews = SystemPushArray(VulkanGraphicsMemoryArena, VULKAN_MAX_RESOURCES, AllocationState_Reserved); + vulkanSamplerInfos = SystemPushArray(VulkanGraphicsMemoryArena, VULKAN_MAX_SAMPLERS, AllocationState_Reserved); + + vulkanReadBackMemoryArena = SystemAllocateMemoryArena(32 * 1024 * 1024); } } @@ -48,7 +58,7 @@ VkFormat ConvertToVulkanTextureFormat(ElemGraphicsFormat format) case ElemGraphicsFormat_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_SRGB; - case ElemGraphicsFormat_B8G8R8A8_UNORM: + case ElemGraphicsFormat_B8G8R8A8: return VK_FORMAT_B8G8R8A8_UNORM; case ElemGraphicsFormat_R16G16B16A16_FLOAT: @@ -56,6 +66,15 @@ VkFormat ConvertToVulkanTextureFormat(ElemGraphicsFormat format) case ElemGraphicsFormat_R32G32B32A32_FLOAT: return VK_FORMAT_R32G32B32A32_SFLOAT; + + case ElemGraphicsFormat_D32_FLOAT: + return VK_FORMAT_D32_SFLOAT; + + case ElemGraphicsFormat_BC7: + return VK_FORMAT_BC7_UNORM_BLOCK; + + case ElemGraphicsFormat_BC7_SRGB: + return VK_FORMAT_BC7_SRGB_BLOCK; case ElemGraphicsFormat_Raw: return VK_FORMAT_UNDEFINED; @@ -75,10 +94,60 @@ VkImageUsageFlags ConvertToVulkanImageUsageFlags(ElemGraphicsResourceUsage usage { result |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } + + if (usage & ElemGraphicsResourceUsage_DepthStencil) + { + result |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } return result; } +VkFilter ConvertToVulkanFilter(ElemGraphicsSamplerFilter filter) +{ + switch (filter) + { + case ElemGraphicsSamplerFilter_Nearest: + return VK_FILTER_NEAREST; + + case ElemGraphicsSamplerFilter_Linear: + return VK_FILTER_LINEAR; + } +} + +VkSamplerMipmapMode ConvertToVulkanSamplerMipMapMode(ElemGraphicsSamplerFilter filter) +{ + switch (filter) + { + case ElemGraphicsSamplerFilter_Nearest: + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + + case ElemGraphicsSamplerFilter_Linear: + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + } +} + +VkSamplerAddressMode ConvertToVulkanSamplerAddressMode(ElemGraphicsSamplerAddressMode addressMode) +{ + switch (addressMode) + { + case ElemGraphicsSamplerAddressMode_Repeat: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + + case ElemGraphicsSamplerAddressMode_RepeatMirror: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + + case ElemGraphicsSamplerAddressMode_ClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + + case ElemGraphicsSamplerAddressMode_ClampToEdgeMirror: + return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + + case ElemGraphicsSamplerAddressMode_ClampToBorderColor: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + } +} + ElemGraphicsResource CreateVulkanTextureFromResource(ElemGraphicsDevice graphicsDevice, VkImage resource, const ElemGraphicsResourceInfo* resourceInfo, bool isPresentTexture) { InitVulkanResourceMemory(); @@ -92,6 +161,7 @@ ElemGraphicsResource CreateVulkanTextureFromResource(ElemGraphicsDevice graphics auto vulkanTextureFormat = ConvertToVulkanTextureFormat(resourceInfo->Format); VkImageView renderTargetImageView = {}; + VkImageView depthStencilImageView = {}; if (resourceInfo->Usage & ElemGraphicsResourceUsage_RenderTarget) { @@ -106,11 +176,25 @@ ElemGraphicsResource CreateVulkanTextureFromResource(ElemGraphicsDevice graphics AssertIfFailed(vkCreateImageView(graphicsDeviceData->Device, &createInfo, 0, &renderTargetImageView)); } + else if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil) + { + VkImageViewCreateInfo createInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO }; + createInfo.image = resource; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = vulkanTextureFormat; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; // TODO: Maybe we need to combine that with flag Stencil depending on the format + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + createInfo.subresourceRange.layerCount = 1; + + AssertIfFailed(vkCreateImageView(graphicsDeviceData->Device, &createInfo, 0, &depthStencilImageView)); + } auto handle = SystemAddDataPoolItem(vulkanGraphicsResourcePool, { .TextureDeviceObject = resource, .Type = ElemGraphicsResourceType_Texture2D, .RenderTargetImageView = renderTargetImageView, + .DepthStencilImageView = depthStencilImageView, .Format = vulkanTextureFormat, .InternalFormat = resourceInfo->Format, .IsPresentTexture = isPresentTexture, @@ -209,8 +293,12 @@ ElemGraphicsHeap VulkanCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uin allocateInfo.allocationSize = sizeInBytes; allocateInfo.memoryTypeIndex = graphicsDeviceDataFull->GpuMemoryTypeIndex; + auto heapType = ElemGraphicsHeapType_Gpu; + if (options) { + heapType = options->HeapType; + if (options->HeapType == ElemGraphicsHeapType_GpuUpload) { allocateInfo.memoryTypeIndex = graphicsDeviceDataFull->GpuUploadMemoryTypeIndex; @@ -236,6 +324,7 @@ ElemGraphicsHeap VulkanCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uin auto handle = SystemAddDataPoolItem(vulkanGraphicsHeapPool, { .DeviceObject = deviceMemory, + .HeapType = heapType, .SizeInBytes = sizeInBytes, .GraphicsDevice = graphicsDevice, }); @@ -355,6 +444,18 @@ ElemGraphicsResource VulkanCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, return ELEM_HANDLE_NULL; } + if ((resourceInfo->Usage & ElemGraphicsResourceUsage_RenderTarget) && (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil)) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Texture2D with usage RenderTarget and DepthStencil should not be used together."); + return ELEM_HANDLE_NULL; + } + + if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil && !CheckDepthStencilFormat(resourceInfo->Format)) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Texture2D with usage DepthStencil should use a compatible format."); + return ELEM_HANDLE_NULL; + } + auto texture = CreateVulkanTexture(graphicsHeapData->GraphicsDevice, resourceInfo); AssertIfFailed(vkBindImageMemory(graphicsDeviceData->Device, texture, graphicsHeapData->DeviceObject, graphicsHeapOffset)); @@ -373,6 +474,12 @@ ElemGraphicsResource VulkanCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GraphicsBuffer usage should not be equals to RenderTarget."); return ELEM_HANDLE_NULL; } + + if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GraphicsBuffer usage should not be equals to DepthStencil."); + return ELEM_HANDLE_NULL; + } auto buffer = CreateVulkanBuffer(graphicsHeapData->GraphicsDevice, resourceInfo); @@ -425,6 +532,11 @@ void VulkanFreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGra vkDestroyImageView(graphicsDeviceData->Device, resourceData->RenderTargetImageView, nullptr); } + if (resourceData->Usage & ElemGraphicsResourceUsage_DepthStencil) + { + vkDestroyImageView(graphicsDeviceData->Device, resourceData->DepthStencilImageView, nullptr); + } + if (resourceData->TextureDeviceObject && !resourceData->IsPresentTexture) { vkDestroyImage(graphicsDeviceData->Device, resourceData->TextureDeviceObject, nullptr); @@ -458,7 +570,43 @@ ElemGraphicsResourceInfo VulkanGetGraphicsResourceInfo(ElemGraphicsResource reso }; } -ElemDataSpan VulkanGetGraphicsResourceDataSpan(ElemGraphicsResource resource) +void VulkanUploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data) +{ + SystemAssert(resource != ELEM_HANDLE_NULL); + + auto resourceData = GetVulkanGraphicsResourceData(resource); + SystemAssert(resourceData); + + if (resourceData->Type != ElemGraphicsResourceType_Buffer) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemUploadGraphicsBufferData only works with graphics buffers."); + return; + } + + auto resourceDataFull = GetVulkanGraphicsResourceDataFull(resource); + SystemAssert(resourceDataFull); + + auto graphicsHeapData = GetVulkanGraphicsHeapData(resourceDataFull->GraphicsHeap); + SystemAssert(graphicsHeapData); + + if (graphicsHeapData->HeapType != ElemGraphicsHeapType_GpuUpload) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemUploadGraphicsBufferData only works with graphics buffers allocated in a GpuUpload heap."); + return; + } + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(resourceDataFull->GraphicsDevice); + SystemAssert(graphicsDeviceData); + + uint8_t* cpuPointer = nullptr; + AssertIfFailed(vkMapMemory(graphicsDeviceData->Device, graphicsHeapData->DeviceObject, resourceDataFull->GraphicsHeapOffset + offset, data.Length, 0, (void**)&cpuPointer)); + + memcpy(cpuPointer, data.Items, data.Length); + + vkUnmapMemory(graphicsDeviceData->Device, graphicsHeapData->DeviceObject); +} + +ElemDataSpan VulkanDownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options) { SystemAssert(resource != ELEM_HANDLE_NULL); @@ -467,25 +615,276 @@ ElemDataSpan VulkanGetGraphicsResourceDataSpan(ElemGraphicsResource resource) if (resourceData->Type != ElemGraphicsResourceType_Buffer) { - SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GetGraphicsResourceDataSpan only works with graphics buffers."); + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData only works with graphics buffers."); return {}; } - if (resourceData->CpuDataPointer == nullptr) - { - auto resourceDataFull = GetVulkanGraphicsResourceDataFull(resource); - SystemAssert(resourceDataFull); + auto resourceDataFull = GetVulkanGraphicsResourceDataFull(resource); + SystemAssert(resourceDataFull); - auto graphicsDeviceData = GetVulkanGraphicsDeviceData(resourceDataFull->GraphicsDevice); - SystemAssert(graphicsDeviceData); + auto graphicsHeapData = GetVulkanGraphicsHeapData(resourceDataFull->GraphicsHeap); + SystemAssert(graphicsHeapData); + + if (graphicsHeapData->HeapType != ElemGraphicsHeapType_GpuUpload && graphicsHeapData->HeapType != ElemGraphicsHeapType_Readback) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData only works with graphics buffers allocated in a Readback heap or GpuUpload heap. (%d)", graphicsHeapData->HeapType); + return {}; + } + + if (graphicsHeapData->HeapType != ElemGraphicsHeapType_Readback) + { + SystemLogWarningMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData works faster with graphics buffers allocated in a Readback heap."); + } + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(resourceDataFull->GraphicsDevice); + SystemAssert(graphicsDeviceData); + + auto offset = 0u; + auto sizeInBytes = resourceData->Width; + + if (options) + { + offset = options->Offset; + + if (options->SizeInBytes != 0) + { + sizeInBytes = options->SizeInBytes; + } + } + + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto downloadedData = SystemPushArray(vulkanReadBackMemoryArena, sizeInBytes); + + uint8_t* cpuPointer = nullptr; + AssertIfFailed(vkMapMemory(graphicsDeviceData->Device, graphicsHeapData->DeviceObject, resourceDataFull->GraphicsHeapOffset + offset, sizeInBytes, 0, (void**)&cpuPointer)); + + memcpy(downloadedData.Pointer, cpuPointer, sizeInBytes); + + vkUnmapMemory(graphicsDeviceData->Device, graphicsHeapData->DeviceObject); + + return { .Items = downloadedData.Pointer, .Length = (uint32_t)downloadedData.Length }; +} + +VulkanUploadBuffer CreateVulkanUploadBuffer(ElemGraphicsDevice graphicsDevice, uint64_t sizeInBytes) +{ + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Create Vulkan UploadBuffer with : %d", sizeInBytes); + SystemAssert(graphicsDevice); + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto graphicsDeviceDataFull = GetVulkanGraphicsDeviceDataFull(graphicsDevice); + SystemAssert(graphicsDeviceDataFull); + + VkBufferCreateInfo createInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + createInfo.size = sizeInBytes; + createInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + + VkBuffer buffer; + AssertIfFailed(vkCreateBuffer(graphicsDeviceData->Device, &createInfo, nullptr, &buffer)); + + if (VulkanDebugLayerEnabled) + { + VkDebugUtilsObjectNameInfoEXT nameInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT }; + nameInfo.objectType = VK_OBJECT_TYPE_BUFFER; + nameInfo.objectHandle = (uint64_t)buffer; + nameInfo.pObjectName = "ElementalUploadBuffer"; + + AssertIfFailed(vkSetDebugUtilsObjectNameEXT(graphicsDeviceData->Device, &nameInfo)); + } + + VkMemoryAllocateInfo allocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + allocateInfo.allocationSize = sizeInBytes; + allocateInfo.memoryTypeIndex = graphicsDeviceDataFull->UploadMemoryTypeIndex; + + VkDeviceMemory deviceMemory; + AssertIfFailed(vkAllocateMemory(graphicsDeviceData->Device, &allocateInfo, nullptr, &deviceMemory)); + AssertIfFailed(vkBindBufferMemory(graphicsDeviceData->Device, buffer, deviceMemory, 0)); + + return + { + .Buffer = buffer, + .DeviceMemory = deviceMemory + }; +} + +UploadBufferMemory GetVulkanUploadBuffer(ElemGraphicsDevice graphicsDevice, uint64_t alignment, uint64_t sizeInBytes) +{ + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto graphicsIdUnpacked = UnpackSystemDataPoolHandle(graphicsDevice); + auto uploadBufferPool = &threadVulkanUploadBufferPools[graphicsIdUnpacked.Index]; - auto graphicsHeapData = GetVulkanGraphicsHeapData(resourceDataFull->GraphicsHeap); - SystemAssert(graphicsHeapData); + if (!uploadBufferPool->IsInited) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Init pool"); + + auto poolIndex = SystemAtomicAdd(graphicsDeviceData->CurrentUploadBufferPoolIndex, 1); + graphicsDeviceData->UploadBufferPools[poolIndex] = uploadBufferPool; + uploadBufferPool->IsInited = true; + } + + auto uploadBuffer = GetUploadBufferPoolItem(uploadBufferPool, graphicsDeviceData->UploadBufferGeneration, alignment, sizeInBytes); + + if (uploadBuffer.PoolItem->IsResetNeeded) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to create upload buffer with size: %d...", uploadBuffer.PoolItem->SizeInBytes); + + if (uploadBuffer.PoolItem->Fence.FenceValue > 0) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Fence: %d", uploadBuffer.PoolItem->Fence.FenceValue); + VulkanWaitForFenceOnCpu(uploadBuffer.PoolItem->Fence); + } + + if (uploadBuffer.PoolItem->Buffer.Buffer != nullptr) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to delete buffer"); + + vkDestroyBuffer(graphicsDeviceData->Device, uploadBuffer.PoolItem->Buffer.Buffer, nullptr); + vkFreeMemory(graphicsDeviceData->Device, uploadBuffer.PoolItem->Buffer.DeviceMemory, nullptr); + + uploadBuffer.PoolItem->Buffer = {}; + } + + uploadBuffer.PoolItem->Buffer = CreateVulkanUploadBuffer(graphicsDevice, uploadBuffer.PoolItem->SizeInBytes); + uploadBuffer.PoolItem->IsResetNeeded = false; + } + + return uploadBuffer; +} + +void CreateVulkanCopyTextureBarrier(VkCommandBuffer commandBuffer, VkImage image, uint32_t mipLevel, bool beforeCopy) +{ + VkImageMemoryBarrier2 barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 }; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = mipLevel; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + + if (beforeCopy) + { + barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_NONE; + barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + } + else + { + barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT | + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + + VkDependencyInfo dependencyInfo = { VK_STRUCTURE_TYPE_DEPENDENCY_INFO }; + dependencyInfo.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); +} + +void VulkanCopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters) +{ + // TODO: Implement optimizations on the copy queue. On windows, use DirectStorage + auto stackMemoryArena = SystemGetStackMemoryArena(); + + SystemAssert(commandList != ELEM_HANDLE_NULL); + + SystemAssert(parameters); + SystemAssert(parameters->Resource != ELEM_HANDLE_NULL); + + auto commandListData = GetVulkanCommandListData(commandList); + SystemAssert(commandListData); + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(commandListData->GraphicsDevice); + SystemAssert(graphicsDeviceData); + + auto resourceData = GetVulkanGraphicsResourceData(parameters->Resource); + SystemAssert(resourceData); - AssertIfFailed(vkMapMemory(graphicsDeviceData->Device, graphicsHeapData->DeviceObject, resourceDataFull->GraphicsHeapOffset, resourceData->Width, 0, &resourceData->CpuDataPointer)); - } + auto resourceDataFull = GetVulkanGraphicsResourceDataFull(parameters->Resource); + SystemAssert(resourceDataFull); + + ReadOnlySpan sourceData; + + // TODO: Implement file source + if (parameters->SourceType == ElemCopyDataSourceType_Memory) + { + sourceData = ReadOnlySpan(parameters->SourceMemoryData.Items, parameters->SourceMemoryData.Length); + } + + auto uploadBufferAlignment = 4u; + auto uploadBufferSizeInBytes = sourceData.Length; + + auto uploadBuffer = GetVulkanUploadBuffer(commandListData->GraphicsDevice, uploadBufferAlignment, uploadBufferSizeInBytes); + SystemAssert(uploadBuffer.Offset + sourceData.Length <= uploadBuffer.PoolItem->SizeInBytes); + + // TODO: Can we do better here? + auto uploadBufferAlreadyInCommandList = false; + + for (uint32_t i = 0; i < MAX_UPLOAD_BUFFERS; i++) + { + if (commandListData->UploadBufferPoolItems[i] == uploadBuffer.PoolItem) + { + uploadBufferAlreadyInCommandList = true; + break; + } + } + + if (!uploadBufferAlreadyInCommandList) + { + commandListData->UploadBufferPoolItems[commandListData->UploadBufferCount++] = uploadBuffer.PoolItem; + } + + uint8_t* cpuPointer = nullptr; + AssertIfFailed(vkMapMemory(graphicsDeviceData->Device, uploadBuffer.PoolItem->Buffer.DeviceMemory, uploadBuffer.Offset, sourceData.Length, 0, (void**)&cpuPointer)); + + memcpy(cpuPointer, sourceData.Pointer, sourceData.Length); + + if (resourceData->Type == ElemGraphicsResourceType_Buffer) + { + VkBufferCopy copyRegion + { + .srcOffset = uploadBuffer.Offset, + .dstOffset = parameters->BufferOffset, + .size = sourceData.Length + }; + + vkCmdCopyBuffer(commandListData->DeviceObject, uploadBuffer.PoolItem->Buffer.Buffer, resourceData->BufferDeviceObject, 1, ©Region); + } + else if (resourceData->Type == ElemGraphicsResourceType_Texture2D) + { + auto mipLevel = parameters->TextureMipLevel; + auto mipWidth = SystemMax(1u, resourceData->Width >> mipLevel); + auto mipHeight = SystemMax(1u, resourceData->Height >> mipLevel); - return { .Items = (uint8_t*)resourceData->CpuDataPointer, .Length = resourceData->Width }; + CreateVulkanCopyTextureBarrier(commandListData->DeviceObject, resourceData->TextureDeviceObject, mipLevel, true); + + VkBufferImageCopy region = + { + .bufferOffset = uploadBuffer.Offset, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, mipLevel, 0, 1 }, + .imageOffset = { 0, 0, 0 }, + .imageExtent = { mipWidth, mipHeight, 1 }, + }; + + vkCmdCopyBufferToImage(commandListData->DeviceObject, uploadBuffer.PoolItem->Buffer.Buffer, resourceData->TextureDeviceObject, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + CreateVulkanCopyTextureBarrier(commandListData->DeviceObject, resourceData->TextureDeviceObject, mipLevel, false); + } + + vkUnmapMemory(graphicsDeviceData->Device, uploadBuffer.PoolItem->Buffer.DeviceMemory); } ElemGraphicsResourceDescriptor VulkanCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options) @@ -530,8 +929,14 @@ ElemGraphicsResourceDescriptor VulkanCreateGraphicsResourceDescriptor(ElemGraphi auto descriptorHeap = graphicsDeviceData->ResourceDescriptorHeap; auto descriptorHandle = CreateVulkanDescriptorHandle(descriptorHeap); + if ((descriptorHandle % 1000) == 0) + { + SystemCommitMemory(VulkanGraphicsMemoryArena, &vulkanResourceDescriptorInfos[descriptorHandle], 1000 * sizeof(ElemGraphicsResourceDescriptorInfo)); + SystemCommitMemory(VulkanGraphicsMemoryArena, &vulkanResourceDescriptorImageViews[descriptorHandle], 1000 * sizeof(VkImageView)); + } + VkWriteDescriptorSet descriptor = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; - descriptor.dstSet = descriptorHeap.Storage->DescriptorSet; + descriptor.dstSet = descriptorHeap.Storage->DescriptorSet->DescriptorSet; descriptor.dstBinding = 0; descriptor.dstArrayElement = descriptorHandle; descriptor.descriptorCount = 1; @@ -551,11 +956,11 @@ ElemGraphicsResourceDescriptor VulkanCreateGraphicsResourceDescriptor(ElemGraphi createInfo.image = resourceData->TextureDeviceObject; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = resourceData->Format; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.aspectMask = (resourceData->Usage & ElemGraphicsResourceUsage_DepthStencil) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; // TODO: Stencil? createInfo.subresourceRange.baseMipLevel = textureMipIndex; createInfo.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; createInfo.subresourceRange.layerCount = 1; - + VkImageView imageView; AssertIfFailed(vkCreateImageView(graphicsDeviceData->Device, &createInfo, 0, &imageView)); @@ -571,6 +976,11 @@ ElemGraphicsResourceDescriptor VulkanCreateGraphicsResourceDescriptor(ElemGraphi vkUpdateDescriptorSets(graphicsDeviceData->Device, 1, &descriptor, 0, nullptr); + if ((descriptorHandle % 1024) == 0) + { + SystemCommitMemory(VulkanGraphicsMemoryArena, &vulkanResourceDescriptorInfos[descriptorHandle], 1024 * sizeof(ElemGraphicsResourceDescriptorInfo)); + } + vulkanResourceDescriptorInfos[descriptorHandle].Resource = resource; vulkanResourceDescriptorInfos[descriptorHandle].Usage = usage; @@ -623,7 +1033,147 @@ void VulkanFreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor descrip vulkanResourceDescriptorInfos[descriptor].Resource = ELEM_HANDLE_NULL; } -void VulkanProcessGraphicsResourceDeleteQueue() +void VulkanProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice) { + auto stackMemoryArena = SystemGetStackMemoryArena(); + ProcessResourceDeleteQueue(); + SystemClearMemoryArena(vulkanReadBackMemoryArena); + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + graphicsDeviceData->UploadBufferGeneration++; + + for (uint32_t i = 0; i < graphicsDeviceData->UploadBufferPools.Length; i++) + { + auto bufferPool = graphicsDeviceData->UploadBufferPools[i]; + + if (bufferPool) + { + auto uploadBuffersToDelete = GetUploadBufferPoolItemsToDelete(stackMemoryArena, bufferPool, graphicsDeviceData->UploadBufferGeneration); + + for (uint32_t j = 0; j < uploadBuffersToDelete.Length; j++) + { + auto uploadBufferToDelete = uploadBuffersToDelete[j]; + + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to purge upload buffer: Size=%d", uploadBufferToDelete->SizeInBytes); + + vkDestroyBuffer(graphicsDeviceData->Device, uploadBufferToDelete->Buffer.Buffer, nullptr); + vkFreeMemory(graphicsDeviceData->Device, uploadBufferToDelete->Buffer.DeviceMemory, nullptr); + + uploadBufferToDelete->Buffer = {}; + + uploadBufferToDelete->CurrentOffset = 0; + uploadBufferToDelete->SizeInBytes = 0; + } + } + } +} + +ElemGraphicsSampler VulkanCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo) +{ + InitVulkanResourceMemory(); + + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto descriptorHeap = graphicsDeviceData->SamplerDescriptorHeap; + auto descriptorHandle = CreateVulkanDescriptorHandle(descriptorHeap); + + auto localSamplerInfo = *samplerInfo; + + if (localSamplerInfo.MaxAnisotropy == 0) + { + localSamplerInfo.MaxAnisotropy = 1; + } + + auto borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + + if (localSamplerInfo.BorderColor.Red == 1.0f && localSamplerInfo.BorderColor.Green == 1.0f && localSamplerInfo.BorderColor.Blue == 1.0f && localSamplerInfo.BorderColor.Alpha == 1.0f) + { + borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + } + + VkSamplerCreateInfo samplerCreateInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + samplerCreateInfo.minFilter = ConvertToVulkanFilter(localSamplerInfo.MinFilter); + samplerCreateInfo.magFilter = ConvertToVulkanFilter(localSamplerInfo.MagFilter); + samplerCreateInfo.mipmapMode = ConvertToVulkanSamplerMipMapMode(localSamplerInfo.MipFilter); + samplerCreateInfo.addressModeU = ConvertToVulkanSamplerAddressMode(localSamplerInfo.AddressU); + samplerCreateInfo.addressModeV = ConvertToVulkanSamplerAddressMode(localSamplerInfo.AddressV); + samplerCreateInfo.addressModeW = ConvertToVulkanSamplerAddressMode(localSamplerInfo.AddressW); + samplerCreateInfo.anisotropyEnable = localSamplerInfo.MaxAnisotropy > 1; + samplerCreateInfo.maxAnisotropy = localSamplerInfo.MaxAnisotropy; + samplerCreateInfo.compareEnable = localSamplerInfo.CompareFunction != ElemGraphicsCompareFunction_Never; + samplerCreateInfo.compareOp = ConvertToVulkanCompareFunction(localSamplerInfo.CompareFunction); + samplerCreateInfo.minLod = localSamplerInfo.MinLod; + samplerCreateInfo.maxLod = localSamplerInfo.MaxLod == 0 ? VULKAN_MAX_MIPS : localSamplerInfo.MaxLod; + samplerCreateInfo.borderColor = borderColor; + + VkSampler sampler; + AssertIfFailed(vkCreateSampler(graphicsDeviceData->Device, &samplerCreateInfo, 0, &sampler)); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.sampler = sampler; + + VkWriteDescriptorSet descriptor = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET }; + descriptor.dstSet = descriptorHeap.Storage->DescriptorSet->DescriptorSet; + descriptor.dstBinding = 0; + descriptor.dstArrayElement = descriptorHandle; + descriptor.descriptorCount = 1; + descriptor.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + descriptor.pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(graphicsDeviceData->Device, 1, &descriptor, 0, nullptr); + + if ((descriptorHandle % 1024) == 0) + { + SystemCommitMemory(VulkanGraphicsMemoryArena, &vulkanSamplerInfos[descriptorHandle], 1024 * sizeof(ElemGraphicsSamplerInfo)); + } + + vulkanSamplerInfos[descriptorHandle].GraphicsDevice = graphicsDevice; + vulkanSamplerInfos[descriptorHandle].VulkanSampler = sampler; + vulkanSamplerInfos[descriptorHandle].SamplerInfo = localSamplerInfo; + return descriptorHandle; +} + +ElemGraphicsSamplerInfo VulkanGetGraphicsSamplerInfo(ElemGraphicsSampler sampler) +{ + InitVulkanResourceMemory(); + + if (sampler == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Sampler is invalid."); + return {}; + } + + return vulkanSamplerInfos[sampler].SamplerInfo; +} + +void VulkanFreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options) +{ + InitVulkanResourceMemory(); + + if (sampler == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Sampler is invalid."); + return; + } + + if (options && options->FencesToWait.Length > 0) + { + EnqueueResourceDeleteEntry(VulkanGraphicsMemoryArena, sampler, ResourceDeleteType_Sampler, options->FencesToWait); + return; + } + + auto samplerInfo = vulkanSamplerInfos[sampler]; + + if (samplerInfo.VulkanSampler != VK_NULL_HANDLE) + { + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(samplerInfo.GraphicsDevice); + SystemAssert(graphicsDeviceData); + + vkDestroySampler(graphicsDeviceData->Device, samplerInfo.VulkanSampler, nullptr); + vulkanSamplerInfos[sampler] = {}; + } } diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanResource.h b/src/Elemental/Common/Graphics/Vulkan/VulkanResource.h index a2ae99f3..26b9b91c 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanResource.h +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanResource.h @@ -6,6 +6,7 @@ struct VulkanGraphicsHeapData { VkDeviceMemory DeviceObject; + ElemGraphicsHeapType HeapType; uint64_t SizeInBytes; ElemGraphicsDevice GraphicsDevice; }; @@ -17,6 +18,7 @@ struct VulkanGraphicsResourceData VkImage TextureDeviceObject; ElemGraphicsResourceType Type; VkImageView RenderTargetImageView; + VkImageView DepthStencilImageView; VkFormat Format; ElemGraphicsFormat InternalFormat; bool IsPresentTexture; @@ -24,7 +26,6 @@ struct VulkanGraphicsResourceData uint32_t Height; uint32_t MipLevels; ElemGraphicsResourceUsage Usage; - void* CpuDataPointer; }; struct VulkanGraphicsResourceDataFull @@ -34,6 +35,19 @@ struct VulkanGraphicsResourceDataFull uint64_t GraphicsHeapOffset; }; +struct VulkanUploadBuffer +{ + VkBuffer Buffer; + VkDeviceMemory DeviceMemory; +}; + +struct VulkanGraphicsSamplerInfo +{ + ElemGraphicsDevice GraphicsDevice; + VkSampler VulkanSampler; + ElemGraphicsSamplerInfo SamplerInfo; +}; + VulkanGraphicsHeapData* GetVulkanGraphicsHeapData(ElemGraphicsHeap graphicsHeap); VulkanGraphicsResourceData* GetVulkanGraphicsResourceData(ElemGraphicsResource resource); @@ -51,12 +65,19 @@ ElemGraphicsResourceInfo VulkanCreateTexture2DResourceInfo(ElemGraphicsDevice gr ElemGraphicsResource VulkanCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, uint64_t graphicsHeapOffset, const ElemGraphicsResourceInfo* resourceInfo); void VulkanFreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGraphicsResourceOptions* options); ElemGraphicsResourceInfo VulkanGetGraphicsResourceInfo(ElemGraphicsResource resource); -ElemDataSpan VulkanGetGraphicsResourceDataSpan(ElemGraphicsResource resource); + +void VulkanUploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data); +ElemDataSpan VulkanDownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options); +void VulkanCopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters); ElemGraphicsResourceDescriptor VulkanCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options); ElemGraphicsResourceDescriptorInfo VulkanGetGraphicsResourceDescriptorInfo(ElemGraphicsResourceDescriptor descriptor); void VulkanFreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor descriptor, const ElemFreeGraphicsResourceDescriptorOptions* options); -void VulkanProcessGraphicsResourceDeleteQueue(void); +void VulkanProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice); void VulkanGraphicsResourceBarrier(ElemCommandList commandList, ElemGraphicsResourceDescriptor descriptor, const ElemGraphicsResourceBarrierOptions* options); + +ElemGraphicsSampler VulkanCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo); +ElemGraphicsSamplerInfo VulkanGetGraphicsSamplerInfo(ElemGraphicsSampler sampler); +void VulkanFreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options); diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanResourceBarrier.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanResourceBarrier.cpp index 02626771..5dfefe28 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanResourceBarrier.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanResourceBarrier.cpp @@ -4,7 +4,7 @@ #include "SystemFunctions.h" #include "SystemMemory.h" -VkPipelineStageFlags2 ConvertToVulkanBarrierSync(ElemGraphicsResourceBarrierSyncType syncType) +VkPipelineStageFlags2 ConvertToVulkanBarrierSync(ElemGraphicsResourceBarrierSyncType syncType, bool isDepthStencil) { switch (syncType) { @@ -15,7 +15,19 @@ VkPipelineStageFlags2 ConvertToVulkanBarrierSync(ElemGraphicsResourceBarrierSync return VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; case ElemGraphicsResourceBarrierSyncType_RenderTarget: - return VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_2_TASK_SHADER_BIT_EXT | VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + { + if (!isDepthStencil) + { + return VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT | + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + } + else + { + return VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + } + } } } @@ -35,6 +47,9 @@ VkAccessFlags2 ConvertToVulkanBarrierAccess(ElemGraphicsResourceBarrierAccessTyp case ElemGraphicsResourceBarrierAccessType_RenderTarget: return VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + case ElemGraphicsResourceBarrierAccessType_DepthStencilWrite: + return VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case ElemGraphicsResourceBarrierAccessType_Write: return VK_ACCESS_2_SHADER_WRITE_BIT; } @@ -60,6 +75,9 @@ VkImageLayout ConvertToVulkanBarrierLayout(ElemGraphicsResourceBarrierLayoutType case ElemGraphicsResourceBarrierLayoutType_RenderTarget: return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + case ElemGraphicsResourceBarrierLayoutType_DepthStencilWrite: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case ElemGraphicsResourceBarrierLayoutType_Present: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; } @@ -101,8 +119,8 @@ void InsertVulkanResourceBarriersIfNeeded(ElemCommandList commandList, ElemGraph vulkanBufferBarrier->sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; vulkanBufferBarrier->buffer = graphicsResourceData->BufferDeviceObject; vulkanBufferBarrier->size = graphicsResourceData->Width; - vulkanBufferBarrier->srcStageMask = ConvertToVulkanBarrierSync(barrier.BeforeSync); - vulkanBufferBarrier->dstStageMask = ConvertToVulkanBarrierSync(barrier.AfterSync); + vulkanBufferBarrier->srcStageMask = ConvertToVulkanBarrierSync(barrier.BeforeSync, false); + vulkanBufferBarrier->dstStageMask = ConvertToVulkanBarrierSync(barrier.AfterSync, false); vulkanBufferBarrier->srcAccessMask = ConvertToVulkanBarrierAccess(barrier.BeforeAccess); vulkanBufferBarrier->dstAccessMask = ConvertToVulkanBarrierAccess(barrier.AfterAccess); } @@ -124,13 +142,13 @@ void InsertVulkanResourceBarriersIfNeeded(ElemCommandList commandList, ElemGraph vulkanTextureBarrier->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; vulkanTextureBarrier->image = graphicsResourceData->TextureDeviceObject; - vulkanTextureBarrier->srcStageMask = ConvertToVulkanBarrierSync(barrier.BeforeSync); - vulkanTextureBarrier->dstStageMask = ConvertToVulkanBarrierSync(barrier.AfterSync); + vulkanTextureBarrier->srcStageMask = ConvertToVulkanBarrierSync(barrier.BeforeSync, barrier.IsDepthStencil); + vulkanTextureBarrier->dstStageMask = ConvertToVulkanBarrierSync(barrier.AfterSync, barrier.IsDepthStencil); vulkanTextureBarrier->srcAccessMask = ConvertToVulkanBarrierAccess(barrier.BeforeAccess); vulkanTextureBarrier->dstAccessMask = ConvertToVulkanBarrierAccess(barrier.AfterAccess); vulkanTextureBarrier->oldLayout = ConvertToVulkanBarrierLayout(barrier.BeforeLayout); vulkanTextureBarrier->newLayout = ConvertToVulkanBarrierLayout(barrier.AfterLayout); - vulkanTextureBarrier->subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vulkanTextureBarrier->subresourceRange.aspectMask = graphicsResourceData->Usage & ElemGraphicsResourceUsage_DepthStencil ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; vulkanTextureBarrier->subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; vulkanTextureBarrier->subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; } diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanShader.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanShader.cpp index 64a636c4..a9d6e8b9 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanShader.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanShader.cpp @@ -1,15 +1,15 @@ #include "VulkanShader.h" +#include "VulkanConfig.h" #include "VulkanGraphicsDevice.h" #include "VulkanCommandList.h" #include "VulkanResource.h" #include "VulkanResourceBarrier.h" +#include "Graphics/Resource.h" +#include "Graphics/Shader.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define VULKAN_MAX_LIBRARIES UINT16_MAX -#define VULKAN_MAX_PIPELINESTATES UINT16_MAX - SystemDataPool vulkanShaderLibraryPool; SystemDataPool vulkanPipelineStatePool; @@ -65,9 +65,6 @@ VkShaderStageFlagBits ConvertShaderTypeToVulkan(ShaderType shaderType) { switch (shaderType) { - case ShaderType_Amplification: - return VK_SHADER_STAGE_TASK_BIT_EXT; - case ShaderType_Mesh: return VK_SHADER_STAGE_MESH_BIT_EXT; @@ -83,6 +80,93 @@ VkShaderStageFlagBits ConvertShaderTypeToVulkan(ShaderType shaderType) } } +VkPolygonMode ConvertToVulkanPolygonMode(ElemGraphicsFillMode fillMode) +{ + switch (fillMode) + { + case ElemGraphicsFillMode_Solid: + return VK_POLYGON_MODE_FILL; + + case ElemGraphicsFillMode_Wireframe: + return VK_POLYGON_MODE_LINE; + } +} + +VkCullModeFlags ConvertToVulkanCullMode(ElemGraphicsCullMode cullMode) +{ + switch (cullMode) + { + case ElemGraphicsCullMode_BackFace: + return VK_CULL_MODE_BACK_BIT; + + case ElemGraphicsCullMode_FrontFace: + return VK_CULL_MODE_FRONT_BIT; + + case ElemGraphicsCullMode_None: + return VK_CULL_MODE_NONE; + } +} + +VkBlendOp ConvertToVulkanBlendOperation(ElemGraphicsBlendOperation blendOperation) +{ + switch (blendOperation) + { + case ElemGraphicsBlendOperation_Add: + return VK_BLEND_OP_ADD; + + case ElemGraphicsBlendOperation_Subtract: + return VK_BLEND_OP_SUBTRACT; + + case ElemGraphicsBlendOperation_ReverseSubtract: + return VK_BLEND_OP_REVERSE_SUBTRACT; + + case ElemGraphicsBlendOperation_Min: + return VK_BLEND_OP_MIN; + + case ElemGraphicsBlendOperation_Max: + return VK_BLEND_OP_MAX; + } +} + +VkBlendFactor ConvertToVulkanBlendFactor(ElemGraphicsBlendFactor blendFactor) +{ + switch (blendFactor) + { + case ElemGraphicsBlendFactor_Zero: + return VK_BLEND_FACTOR_ZERO; + + case ElemGraphicsBlendFactor_One: + return VK_BLEND_FACTOR_ONE; + + case ElemGraphicsBlendFactor_SourceColor: + return VK_BLEND_FACTOR_SRC_COLOR; + + case ElemGraphicsBlendFactor_InverseSourceColor: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + + case ElemGraphicsBlendFactor_SourceAlpha: + return VK_BLEND_FACTOR_SRC_ALPHA; + + case ElemGraphicsBlendFactor_InverseSourceAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + + case ElemGraphicsBlendFactor_DestinationColor: + return VK_BLEND_FACTOR_DST_COLOR; + + case ElemGraphicsBlendFactor_InverseDestinationColor: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + + case ElemGraphicsBlendFactor_DestinationAlpha: + return VK_BLEND_FACTOR_DST_ALPHA; + + case ElemGraphicsBlendFactor_InverseDestinationAlpha: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + + case ElemGraphicsBlendFactor_SourceAlphaSaturated: + return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; + } +} + VkPipelineShaderStageCreateInfo GetVulkanShaderFunctionStageCreateInfo(MemoryArena memoryArena, VulkanShaderLibraryData* shaderLibraryData, ShaderType shaderType, const char* function) { SystemAssert(function); @@ -165,14 +249,77 @@ ElemPipelineState VulkanCompileGraphicsPipelineState(ElemGraphicsDevice graphics auto shaderLibraryData= GetVulkanShaderLibraryData(parameters->ShaderLibrary); SystemAssert(shaderLibraryData); + + // TODO: Extract functions + auto renderTargetFormats = SystemPushArray(stackMemoryArena, parameters->RenderTargets.Length); + auto renderTargetBlendStates = SystemPushArray(stackMemoryArena, parameters->RenderTargets.Length); - VkGraphicsPipelineCreateInfo createInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + for (uint32_t i = 0; i < parameters->RenderTargets.Length; i++) + { + auto renderTargetParameters = parameters->RenderTargets.Items[i]; + + renderTargetFormats[i] = ConvertToVulkanTextureFormat(renderTargetParameters.Format); + + renderTargetBlendStates[i] = + { + .blendEnable = IsBlendEnabled(renderTargetParameters), + .srcColorBlendFactor = ConvertToVulkanBlendFactor(renderTargetParameters.SourceBlendFactor), + .dstColorBlendFactor = ConvertToVulkanBlendFactor(renderTargetParameters.DestinationBlendFactor), + .colorBlendOp = ConvertToVulkanBlendOperation(renderTargetParameters.BlendOperation), + .srcAlphaBlendFactor = ConvertToVulkanBlendFactor(renderTargetParameters.SourceBlendFactorAlpha), + .dstAlphaBlendFactor = ConvertToVulkanBlendFactor(renderTargetParameters.DestinationBlendFactorAlpha), + .alphaBlendOp = ConvertToVulkanBlendOperation(renderTargetParameters.BlendOperationAlpha), + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT + }; + } + + VkFormat depthFormat = VK_FORMAT_UNDEFINED; + VkPipelineDepthStencilStateCreateInfo depthStencilState = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; + + if (CheckDepthStencilFormat(parameters->DepthStencil.Format)) + { + depthFormat = ConvertToVulkanTextureFormat(parameters->DepthStencil.Format); + depthStencilState.depthTestEnable = true; + + if (!parameters->DepthStencil.DepthDisableWrite) + { + depthStencilState.depthWriteEnable = true; + } + + depthStencilState.depthCompareOp = ConvertToVulkanCompareFunction(parameters->DepthStencil.DepthCompareFunction); + } + + VkPipelineRenderingCreateInfo renderingCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO }; + renderingCreateInfo.colorAttachmentCount = renderTargetFormats.Length; + renderingCreateInfo.pColorAttachmentFormats = renderTargetFormats.Pointer; + renderingCreateInfo.depthAttachmentFormat = depthFormat; + + VkPipelineColorBlendStateCreateInfo colorBlendState = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; + colorBlendState.attachmentCount = renderTargetBlendStates.Length; + colorBlendState.pAttachments = renderTargetBlendStates.Pointer; + + VkPipelineMultisampleStateCreateInfo multisampleState = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; + multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineRasterizationStateCreateInfo rasterizationState = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; + rasterizationState.polygonMode = ConvertToVulkanPolygonMode(parameters->FillMode); + rasterizationState.cullMode = ConvertToVulkanCullMode(parameters->CullMode); + rasterizationState.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizationState.lineWidth = 1; + + VkPipelineViewportStateCreateInfo viewportState = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + + VkPipelineDynamicStateCreateInfo dynamicState = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; + dynamicState.dynamicStateCount = sizeof(dynamicStates) / sizeof(dynamicStates[0]); + dynamicState.pDynamicStates = dynamicStates; VkPipelineShaderStageCreateInfo stages[3] = {}; uint32_t stageCount = 0; - // TODO: Amplification shader - if (parameters->MeshShaderFunction) { auto shaderStage = GetVulkanShaderFunctionStageCreateInfo(stackMemoryArena, shaderLibraryData, ShaderType_Mesh, parameters->MeshShaderFunction); @@ -196,54 +343,17 @@ ElemPipelineState VulkanCompileGraphicsPipelineState(ElemGraphicsDevice graphics stages[stageCount++] = shaderStage; } - + + VkGraphicsPipelineCreateInfo createInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO }; + createInfo.pColorBlendState = &colorBlendState; + createInfo.pDepthStencilState = &depthStencilState; + createInfo.pNext = &renderingCreateInfo; createInfo.stageCount = stageCount; createInfo.pStages = stages; - - VkPipelineViewportStateCreateInfo viewportState = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO }; - viewportState.viewportCount = 1; - viewportState.scissorCount = 1; createInfo.pViewportState = &viewportState; - - VkPipelineRasterizationStateCreateInfo rasterizationState = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO }; - rasterizationState.lineWidth = 1.0f; - rasterizationState.cullMode = VK_CULL_MODE_NONE;//VK_CULL_MODE_BACK_BIT; - rasterizationState.frontFace = VK_FRONT_FACE_CLOCKWISE; createInfo.pRasterizationState = &rasterizationState; - - VkPipelineMultisampleStateCreateInfo multisampleState = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO }; - multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; createInfo.pMultisampleState = &multisampleState; - - VkPipelineDepthStencilStateCreateInfo depthStencilState = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO }; - createInfo.pDepthStencilState = &depthStencilState; - - // TODO: To Refactor! - VkPipelineColorBlendAttachmentState renderTargetBlendState = { - false, - VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, - VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, - VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT - }; - - VkPipelineColorBlendStateCreateInfo colorBlendState = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO }; - colorBlendState.attachmentCount = 1; - colorBlendState.pAttachments = &renderTargetBlendState; - createInfo.pColorBlendState = &colorBlendState; - - VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - - VkPipelineDynamicStateCreateInfo dynamicState = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO }; - dynamicState.dynamicStateCount = sizeof(dynamicStates) / sizeof(dynamicStates[0]); - dynamicState.pDynamicStates = dynamicStates; createInfo.pDynamicState = &dynamicState; - - VkFormat formats[] = { ConvertToVulkanTextureFormat(parameters->TextureFormats.Items[0]) }; // TODO: Fill Correct Back Buffer Format - - VkPipelineRenderingCreateInfo renderingCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO }; - renderingCreateInfo.colorAttachmentCount = 1; // TODO: Change that - renderingCreateInfo.pColorAttachmentFormats = formats; - createInfo.pNext = &renderingCreateInfo; createInfo.layout = graphicsDeviceData->PipelineLayout; VkPipeline pipelineState; @@ -340,8 +450,13 @@ void VulkanBindPipelineState(ElemCommandList commandList, ElemPipelineState pipe auto bindPoint = (pipelineStateData->PipelineStateType == VulkanPipelineStateType_Graphics) ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE; - VkDescriptorSet descriptorSets = { graphicsDeviceData->ResourceDescriptorHeap.Storage->DescriptorSet }; - vkCmdBindDescriptorSets(commandListData->DeviceObject, bindPoint, graphicsDeviceData->PipelineLayout, 0, 1, &descriptorSets, 0, nullptr); + VkDescriptorSet descriptorSets[] = + { + graphicsDeviceData->ResourceDescriptorHeap.Storage->DescriptorSet->DescriptorSet, + graphicsDeviceData->SamplerDescriptorHeap.Storage->DescriptorSet->DescriptorSet + }; + + vkCmdBindDescriptorSets(commandListData->DeviceObject, bindPoint, graphicsDeviceData->PipelineLayout, 0, ARRAYSIZE(descriptorSets), descriptorSets, 0, nullptr); vkCmdBindPipeline(commandListData->DeviceObject, bindPoint, pipelineStateData->PipelineState); } diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.cpp b/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.cpp index b47ad190..c0145732 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.cpp +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.cpp @@ -1,4 +1,5 @@ #include "VulkanSwapChain.h" +#include "VulkanConfig.h" #include "VulkanGraphicsDevice.h" #include "VulkanCommandList.h" #include "VulkanResource.h" @@ -15,8 +16,6 @@ #include "../Elemental/Linux/WaylandWindow.h" #endif -#define VULKAN_MAX_SWAPCHAINS 10u - SystemDataPool vulkanSwapChainPool; void InitVulkanSwapChainMemory() @@ -76,6 +75,8 @@ void CreateVulkanSwapChainBackBuffers(ElemSwapChain swapChain, uint32_t width, u VkSwapchainKHR CreateVulkanSwapChainObject(ElemGraphicsDevice graphicsDevice, VkSurfaceKHR windowSurface, VkSwapchainCreateInfoKHR* swapChainCreateInfo, VkSwapchainKHR oldSwapChain) { + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto graphicsDeviceData = GetVulkanGraphicsDeviceData(graphicsDevice); SystemAssert(graphicsDeviceData); @@ -85,6 +86,14 @@ VkSwapchainKHR CreateVulkanSwapChainObject(ElemGraphicsDevice graphicsDevice, Vk VkSurfaceCapabilitiesKHR surfaceCapabilities = {}; AssertIfFailed(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(graphicsDeviceDataFull->PhysicalDevice, windowSurface, &surfaceCapabilities)); + uint32_t presentModeCount = 0; + AssertIfFailed(vkGetPhysicalDeviceSurfacePresentModesKHR(graphicsDeviceDataFull->PhysicalDevice, windowSurface, &presentModeCount, nullptr)); + + auto presentModes = SystemPushArray(stackMemoryArena, presentModeCount); + AssertIfFailed(vkGetPhysicalDeviceSurfacePresentModesKHR(graphicsDeviceDataFull->PhysicalDevice, windowSurface, &presentModeCount, presentModes.Pointer)); + + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Present Mode count: %d", presentModeCount); + VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) @@ -113,7 +122,7 @@ VkSwapchainKHR CreateVulkanSwapChainObject(ElemGraphicsDevice graphicsDevice, Vk return swapChain; } -void ResizeVulkanSwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t height) +void ResizeVulkanSwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t height, float uiScale) { if (width == 0 || height == 0) { @@ -138,17 +147,19 @@ void ResizeVulkanSwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t hei swapChainData->Width = width; swapChainData->Height = height; swapChainData->AspectRatio = (float)width / height; + swapChainData->UIScale = uiScale; auto fence = CreateVulkanCommandQueueFence(swapChainData->CommandQueue); VulkanWaitForFenceOnCpu(fence); - swapChainData->PresentId = 0; + swapChainData->PresentId = 1; auto oldSwapChain = swapChainData->DeviceObject; auto swapChainCreateInfo = &swapChainDataFull->CreateInfo; swapChainCreateInfo->imageExtent.width = width; swapChainCreateInfo->imageExtent.height = height; + // BUG: The resizing works well but not fullscreen! (Windows / Linux?) swapChainData->DeviceObject = CreateVulkanSwapChainObject(swapChainData->CommandQueue, swapChainData->WindowSurface, swapChainCreateInfo, oldSwapChain); for (uint32_t i = 0; i < VULKAN_MAX_SWAPCHAIN_BUFFERS; i++) @@ -188,11 +199,17 @@ void CheckVulkanAvailableSwapChain(ElemHandle handle) // Looks like a windowed issue where we miss the vsync // The issue seems to be resolved for now... to monitor! #ifdef _WIN32 - auto presentId = swapChainData->PresentId - swapChainData->FrameLatency; + //auto presentId = swapChainData->PresentId - swapChainData->FrameLatency; - if (vkWaitForPresentKHR(graphicsDeviceData->Device, swapChainData->DeviceObject, presentId, 0) != VK_SUCCESS) + //if (presentId < 10) + //SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "PresentID: %d", presentId); + + //if (vkWaitForPresentKHR(graphicsDeviceData->Device, swapChainData->DeviceObject, presentId, 0) != VK_SUCCESS) { - return; + // BUG: There is still an error here, we need to investigate + // Sometimes when we resize it is good + //SystemLogWarningMessage(ElemLogMessageCategory_Graphics, "Cannot update the swapchain because there was an error waiting for the present ID %d.", presentId); + // return; } #endif } @@ -207,7 +224,7 @@ void CheckVulkanAvailableSwapChain(ElemHandle handle) if (windowSize.Width > 0 && windowSize.Height > 0 && (windowSize.Width != swapChainData->Width || windowSize.Height != swapChainData->Height)) { - ResizeVulkanSwapChain(handle, windowSize.Width, windowSize.Height); + ResizeVulkanSwapChain(handle, windowSize.Width, windowSize.Height, windowSize.UIScale); sizeChanged = true; } @@ -390,7 +407,7 @@ ElemSwapChain VulkanCreateSwapChain(ElemCommandQueue commandQueue, ElemWindow wi VkFenceCreateInfo fenceCreateInfo = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; AssertIfFailed(vkCreateFence(graphicsDeviceData->Device, &fenceCreateInfo, 0, &acquireFence )); - uint64_t creationTimestamp; + uint64_t creationTimestamp = 0; // TODO: Put that in a system function //QueryPerformanceCounter(&creationTimestamp); @@ -408,7 +425,9 @@ ElemSwapChain VulkanCreateSwapChain(ElemCommandQueue commandQueue, ElemWindow wi .Width = width, .Height = height, .AspectRatio = (float)width / height, + .UIScale = windowRenderSize.UIScale, .Format = ElemGraphicsFormat_B8G8R8A8_SRGB, // TODO: change that + .PresentId = 1, .FrameLatency = frameLatency, .TargetFPS = targetFPS }); @@ -474,9 +493,11 @@ ElemSwapChainInfo VulkanGetSwapChainInfo(ElemSwapChain swapChain) return { + .Window = swapChainData->Window, .Width = swapChainData->Width, .Height = swapChainData->Height, .AspectRatio = swapChainData->AspectRatio, + .UIScale = swapChainData->UIScale, .Format = swapChainData->Format }; } @@ -498,9 +519,11 @@ void VulkanPresentSwapChain(ElemSwapChain swapChain) auto commandQueueData = GetVulkanCommandQueueData(swapChainData->CommandQueue); SystemAssert(commandQueueData); + auto presentId = swapChainData->PresentId++; + VkPresentIdKHR presentIdInfo = { VK_STRUCTURE_TYPE_PRESENT_ID_KHR }; presentIdInfo.swapchainCount = 1; - presentIdInfo.pPresentIds = &swapChainData->PresentId; + presentIdInfo.pPresentIds = &presentId; VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; presentInfo.waitSemaphoreCount = 1; @@ -513,7 +536,6 @@ void VulkanPresentSwapChain(ElemSwapChain swapChain) AssertIfFailed(vkQueuePresentKHR(commandQueueData->DeviceObject, &presentInfo)); VulkanResetCommandAllocation(swapChainData->GraphicsDevice); - VulkanProcessGraphicsResourceDeleteQueue(); - swapChainData->PresentId++; + VulkanProcessGraphicsResourceDeleteQueue(swapChainData->GraphicsDevice); } diff --git a/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.h b/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.h index c2e89047..130a7da5 100644 --- a/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.h +++ b/src/Elemental/Common/Graphics/Vulkan/VulkanSwapChain.h @@ -1,6 +1,7 @@ #pragma once #include "Elemental.h" +#include "VulkanConfig.h" #ifdef WIN32 #define VK_USE_PLATFORM_WIN32_KHR @@ -9,8 +10,6 @@ #endif #include "volk.h" -#define VULKAN_MAX_SWAPCHAIN_BUFFERS 3 - struct VulkanSwapChainData { VkSwapchainKHR DeviceObject; @@ -28,6 +27,7 @@ struct VulkanSwapChainData uint32_t Width; uint32_t Height; float AspectRatio; + float UIScale; ElemGraphicsFormat Format; bool PresentCalled; uint64_t PresentId; diff --git a/src/Elemental/Common/Inputs/HidDevices.cpp b/src/Elemental/Common/Inputs/HidDevices.cpp index 9f9386dd..91198c92 100644 --- a/src/Elemental/Common/Inputs/HidDevices.cpp +++ b/src/Elemental/Common/Inputs/HidDevices.cpp @@ -1,328 +1,25 @@ #include "HidDevices.h" +#include "HidUtils.h" #include "Inputs.h" -#include "SystemFunctions.h" -enum HidGamepadVendor : uint32_t -{ - HidGamepadVendor_Microsoft = 0x045E, - HidGamepadVendor_Sony = 0x054C -}; - -enum HidGamepadProduct : uint32_t -{ - HidGamepadProduct_XboxOneWirelessOldDriver = 0x02E0, - HidGamepadProduct_XboxOneUsb = 0x02FF, - HidGamepadProduct_XboxOneWireless = 0x02FD, - HidGamepadProduct_DualShock4OldDriver = 0x5C4, - HidGamepadProduct_DualShock4 = 0x9cc -}; - -typedef void (*ProcessHidGamepadDataPtr)(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds); - -struct HidGamepadHandler -{ - HidGamepadVendor Vendor; - HidGamepadProduct Product; - ProcessHidGamepadDataPtr ProcessDataHandler; -}; - -PackedStruct XboxOneWirelessOldDriverGamepadReport -{ - uint8_t Padding; - uint16_t LeftStickX; - uint16_t LeftStickY; - uint16_t RightStickX; - uint16_t RightStickY; - uint16_t Triggers; - uint16_t Buttons; - uint8_t Dpad; -}; +#include "HidDualSenseGamepad.cpp" +#include "HidXboxOneGamepad.cpp" +#include "HidSwitchGamepad.cpp" -PackedStruct XboxOneWirelessGamepadReport +CheckHidGamepadSupportPtr HidGamepadDeviceModules[] = { - uint8_t Padding; - uint16_t LeftStickX; - uint16_t LeftStickY; - uint16_t RightStickX; - uint16_t RightStickY; - uint16_t LeftTrigger; - uint16_t RightTrigger; - uint8_t Dpad; - uint32_t Buttons; -}; - -float NormalizeInputValue(uint32_t value, uint32_t maxValue, float deadZone) -{ - // TODO: Allows the configuration of deadzone - float normalizedValue = ((float)value / (float)maxValue); - - if (normalizedValue < deadZone) - { - return 0.0f; - } - - return normalizedValue; -} - -float NormalizeInputValueSigned(uint32_t value, uint32_t maxValue, float deadZone) -{ - // TODO: Allows the configuration of deadzone - float normalizedValue = ((float)value / (float)maxValue) * 2.0f - 1.0f; - - if (normalizedValue < deadZone && normalizedValue > -deadZone) - { - return 0.0f; - } - - return normalizedValue; -} - -void ProcessXboxOneWirelessGamepadReport(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) -{ - auto xboxReport = (XboxOneWirelessGamepadReport*)hidReport.Pointer; - - // TODO: Also here we need to send the event only if the analog value has changed - float leftStickX = NormalizeInputValueSigned(xboxReport->LeftStickX, 65535, 0.2f); - - if (leftStickX <= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickXNegative, - .InputType = ElemInputType_Analog, - .Value = -leftStickX, - .ElapsedSeconds = elapsedSeconds - }); - } - - if (leftStickX >= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickXPositive, - .InputType = ElemInputType_Analog, - .Value = leftStickX, - .ElapsedSeconds = elapsedSeconds - }); - } - - float leftStickY = -NormalizeInputValueSigned(xboxReport->LeftStickY, 65535, 0.2f); - - if (leftStickY <= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickYNegative, - .InputType = ElemInputType_Analog, - .Value = -leftStickY, - .ElapsedSeconds = elapsedSeconds - }); - } - // TODO: check if the value is inversed!! - if (leftStickY >= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickYPositive, - .InputType = ElemInputType_Analog, - .Value = leftStickY, - .ElapsedSeconds = elapsedSeconds - }); - } - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadLeftTrigger, - .InputType = ElemInputType_Analog, - .Value = NormalizeInputValue(xboxReport->LeftTrigger, 1024, 0.1f), - .ElapsedSeconds = elapsedSeconds - }); - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadRightTrigger, - .InputType = ElemInputType_Analog, - .Value = NormalizeInputValue(xboxReport->RightTrigger, 1024, 0.1f), - .ElapsedSeconds = elapsedSeconds - }); - - // TODO: For buttons we need to keep track of old report to see if there was a change - // Maybe we can do the same as with delta inputs! - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadButtonA, - .InputType = ElemInputType_Digital, - .Value = (xboxReport->Buttons & 0x01) ? 1.0f : 0.0f, - .ElapsedSeconds = elapsedSeconds - }); - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadLeftShoulder, - .InputType = ElemInputType_Digital, - .Value = (xboxReport->Buttons & 0x40) ? 1.0f : 0.0f, - .ElapsedSeconds = elapsedSeconds - }); - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadRightShoulder, - .InputType = ElemInputType_Digital, - .Value = (xboxReport->Buttons & 0x80) ? 1.0f : 0.0f, - .ElapsedSeconds = elapsedSeconds - }); -} - -void ProcessXboxOneWirelessOldDriverGamepadReport(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) -{ - auto xboxReport = (XboxOneWirelessOldDriverGamepadReport*)hidReport.Pointer; - - // TODO: Also here we need to send the event only if the analog value has changed - float leftStickX = NormalizeInputValueSigned(xboxReport->LeftStickX, 65535, 0.2f); - - if (leftStickX <= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickXNegative, - .InputType = ElemInputType_Analog, - .Value = -leftStickX, - .ElapsedSeconds = elapsedSeconds - }); - } - - if (leftStickX >= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickXPositive, - .InputType = ElemInputType_Analog, - .Value = leftStickX, - .ElapsedSeconds = elapsedSeconds - }); - } - - float leftStickY = -NormalizeInputValueSigned(xboxReport->LeftStickY, 65535, 0.2f); - - if (leftStickY <= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickYNegative, - .InputType = ElemInputType_Analog, - .Value = -leftStickY, - .ElapsedSeconds = elapsedSeconds - }); - } - // TODO: check if the value is inversed!! - if (leftStickY >= 0) - { - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputId_GamepadLeftStickYPositive, - .InputType = ElemInputType_Analog, - .Value = leftStickY, - .ElapsedSeconds = elapsedSeconds - }); - } - - const uint16_t midpoint = 32768; - - uint8_t left, right; - - // Calculate the right trigger value as a difference from midpoint - if (xboxReport->Triggers <= midpoint) { - right = static_cast((midpoint - xboxReport->Triggers) >> 7); - } else { - right = 0; - } - - // Calculate the left trigger value as a difference from midpoint - if (xboxReport->Triggers >= midpoint) { - left = static_cast((xboxReport->Triggers - midpoint) >> 7); - } else { - left = 0; - } - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadLeftTrigger, - .InputType = ElemInputType_Analog, - .Value = NormalizeInputValue(left, 255, 0.1f), - .ElapsedSeconds = elapsedSeconds - }); - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadRightTrigger, - .InputType = ElemInputType_Analog, - .Value = NormalizeInputValue(right, 255, 0.1f), - .ElapsedSeconds = elapsedSeconds - }); - - // TODO: For buttons we need to keep track of old report to see if there was a change - // Maybe we can do the same as with delta inputs! - - - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadButtonA, - .InputType = ElemInputType_Digital, - .Value = (xboxReport->Buttons & 0x01) ? 1.0f : 0.0f, - .ElapsedSeconds = elapsedSeconds - }); - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadLeftShoulder, - .InputType = ElemInputType_Digital, - .Value = (xboxReport->Buttons & 0x10) ? 1.0f : 0.0f, - .ElapsedSeconds = elapsedSeconds - }); - - AddInputEvent({ - .Window = window, - .InputDevice = inputDevice, - .InputId = ElemInputID_GamepadRightShoulder, - .InputType = ElemInputType_Digital, - .Value = (xboxReport->Buttons & 0x20) ? 1.0f : 0.0f, - .ElapsedSeconds = elapsedSeconds - }); -} - -HidGamepadHandler HidGamepadHandlers[] = -{ - { .Vendor = HidGamepadVendor_Microsoft, .Product = HidGamepadProduct_XboxOneWirelessOldDriver, .ProcessDataHandler = ProcessXboxOneWirelessOldDriverGamepadReport }, - { .Vendor = HidGamepadVendor_Microsoft, .Product = HidGamepadProduct_XboxOneUsb, .ProcessDataHandler = ProcessXboxOneWirelessOldDriverGamepadReport }, - { .Vendor = HidGamepadVendor_Microsoft, .Product = HidGamepadProduct_XboxOneWireless, .ProcessDataHandler = ProcessXboxOneWirelessGamepadReport } + CheckHidXboxOneGamepadSupport, + CheckHidSwitchProGamepadSupport, + CheckHidDualSenseGamepadSupport }; bool IsHidDeviceSupported(uint32_t vendorId, uint32_t productId) { - for (uint32_t i = 0; i < ARRAYSIZE(HidGamepadHandlers); i++) + for (uint32_t i = 0; i < ARRAYSIZE(HidGamepadDeviceModules); i++) { - auto handler = HidGamepadHandlers[i]; + auto handler = HidGamepadDeviceModules[i](vendorId, productId); - if (vendorId == handler.Vendor && productId == handler.Product) + if (handler) { return true; } @@ -330,19 +27,25 @@ bool IsHidDeviceSupported(uint32_t vendorId, uint32_t productId) return false; } + void ProcessHidDeviceData(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) { auto inputDeviceData = GetInputDeviceData(inputDevice); SystemAssert(inputDeviceData); - - for (uint32_t i = 0; i < ARRAYSIZE(HidGamepadHandlers); i++) + + if (inputDeviceData->HidDeviceHandler == nullptr) { - auto handler = HidGamepadHandlers[i]; - - if (inputDeviceData->HidVendorId == handler.Vendor && inputDeviceData->HidProductId == handler.Product) + for (uint32_t i = 0; i < ARRAYSIZE(HidGamepadDeviceModules); i++) { - handler.ProcessDataHandler(window, inputDevice, hidReport, elapsedSeconds); - return; + inputDeviceData->HidDeviceHandler = (void*)HidGamepadDeviceModules[i](inputDeviceData->HidVendorId, inputDeviceData->HidProductId); + + if (inputDeviceData->HidDeviceHandler) + { + break; + } } } + + SystemAssert(inputDeviceData->HidDeviceHandler); + ((ProcessHidGamepadInputReportPtr)inputDeviceData->HidDeviceHandler)(window, inputDevice, hidReport, elapsedSeconds); } diff --git a/src/Elemental/Common/Inputs/HidDevices.h b/src/Elemental/Common/Inputs/HidDevices.h index 0ee6a0f2..3b55ea40 100644 --- a/src/Elemental/Common/Inputs/HidDevices.h +++ b/src/Elemental/Common/Inputs/HidDevices.h @@ -1,6 +1,6 @@ #pragma once -#include "Elemental.h" +#include "../Elemental.h" #include "SystemSpan.h" bool IsHidDeviceSupported(uint32_t vendorId, uint32_t productId); diff --git a/src/Elemental/Common/Inputs/HidDualSenseGamepad.cpp b/src/Elemental/Common/Inputs/HidDualSenseGamepad.cpp new file mode 100644 index 00000000..7c2db73e --- /dev/null +++ b/src/Elemental/Common/Inputs/HidDualSenseGamepad.cpp @@ -0,0 +1,549 @@ +#pragma once + +#include "../Elemental.h" +#include "Inputs.h" +#include "HidUtils.h" +#include "SystemFunctions.h" +#include "SystemDataPool.h" + +// Based on the spec: https://controllers.fandom.com/wiki/Sony_DualSense + +#define HID_DUALSENSE_VENDOR_ID 0x054C +#define HID_DUALSENSE_PRODUCT_ID 0x0CE6 + +#define HID_DUALSENSE_STICK_RANGE 255 +#define HID_DUALSENSE_STICK_DEAD_ZONE 0.1f +#define HID_DUALSENSE_TOUCH_MAX_FINGERS 2 +#define HID_DUALSENSE_TOUCH_DEAD_ZONE 0.75f +#define HID_DUALSENSE_TOUCH_SENSITIVITY 0.15f +#define HID_DUALSENSE_TOUCH_WIDTH 1920.0f +#define HID_DUALSENSE_TOUCH_HEIGHT 1070.0f +#define HID_DUALSENSE_ANGULAR_VELOCITY_DEAD_ZONE 0.1f +#define HID_DUALSENSE_ACCELEROMETER_DEAD_ZONE 0.05f + +enum HidDualSenseGamepadInputReportType +{ + HidDualSenseGamepadInputReportType_BluetoothStandard = 0x01, + HidDualSenseGamepadInputReportType_BluetoothExtended = 0x31, +}; + +enum HidDualSenseGamepadOutputReportType +{ + //HidDualSenseGamepadOutputReportType_SubCommand = 0x01 +}; + +enum HidDualSenseGamepadFeatureReportType +{ + HidDualSenseGamepadFeatureReportType_Calibration = 0x05 +}; + +enum HidDualSenseGamepadButton : uint16_t +{ + HidDualSenseGamepadButton_DpadUp = 1 << 0, + HidDualSenseGamepadButton_DpadRight = 1 << 1, + HidDualSenseGamepadButton_DpadDown = 1 << 2, + HidDualSenseGamepadButton_DpadLeft = 1 << 3, + HidDualSenseGamepadButton_Square = 1 << 4, + HidDualSenseGamepadButton_Cross = 1 << 5, + HidDualSenseGamepadButton_Circle = 1 << 6, + HidDualSenseGamepadButton_Triangle = 1 << 7, + HidDualSenseGamepadButton_L1 = 1 << 8, + HidDualSenseGamepadButton_R1 = 1 << 9, + HidDualSenseGamepadButton_L2 = 1 << 10, + HidDualSenseGamepadButton_R2 = 1 << 11, + HidDualSenseGamepadButton_Share = 1 << 12, + HidDualSenseGamepadButton_Options = 1 << 13, + HidDualSenseGamepadButton_L3 = 1 << 14, + HidDualSenseGamepadButton_R3 = 1 << 15 +}; + +enum HidDualSenseGamepadSystemButton : uint8_t +{ + HidDualSenseGamepadSystemButton_Home = 1 << 0, + HidDualSenseGamepadSystemButton_Pad = 1 << 1, +}; + +struct __attribute__((__packed__)) HidDualSenseGamepadTouchData +{ + uint32_t PackedData[2]; + uint8_t Timestamp; +}; + +struct __attribute__((__packed__)) HidDualSenseGamepadInputReport +{ + uint8_t LeftStickX; + uint8_t LeftStickY; + uint8_t RightStickX; + uint8_t RightStickY; + uint8_t LeftTrigger; + uint8_t RightTrigger; + uint8_t SequenceNumber; + uint16_t Buttons; + uint8_t SystemButtons; + uint8_t Reserved1; + uint32_t Reserved2; + int16_t AngularVelocityX; + int16_t AngularVelocityY; + int16_t AngularVelocityZ; + int16_t AccelerometerX; + int16_t AccelerometerY; + int16_t AccelerometerZ; + uint32_t SensorTimestamp; + int8_t Temperature; + HidDualSenseGamepadTouchData TouchData; +}; + +struct __attribute__((__packed__)) HidDualSenseGamepadInputReportBluetoothStandard +{ + uint8_t LeftStickX; + uint8_t LeftStickY; + uint8_t RightStickX; + uint8_t RightStickY; + uint16_t Buttons; + uint8_t SystemButtons; + uint8_t LeftTrigger; + uint8_t RightTrigger; +}; + +struct __attribute__((__packed__)) HidDualSenseGamepadFeatureReportCalibration +{ + uint8_t ReportId; + int16_t GyroPitchBias; + int16_t GyroYawBias; + int16_t GyroRollBias; + int16_t GyroPitchPlus; + int16_t GyroPitchMinus; + int16_t GyroYawPlus; + int16_t GyroYawMinus; + int16_t GyroRollPlus; + int16_t GyroRollMinus; + int16_t GyroSpeedPlus; + int16_t GyroSpeedMinus; + int16_t AccelXPlus; + int16_t AccelXMinus; + int16_t AccelYPlus; + int16_t AccelYMinus; + int16_t AccelZPlus; + int16_t AccelZMinus; + int64_t Unknown; +}; + +struct HidDualSenseGamepadTouchState +{ + uint8_t FingerIndex; + bool IsTouching; + int32_t PreviousPositionX; + int32_t PreviousPositionY; + float PreviousDeltaX; + uint8_t PreviousDeltaXSteps; + float PreviousDeltaY; + uint8_t PreviousDeltaYSteps; +}; + +struct HidDualSenseGamepadData +{ + float PreviousLeftStickX; + float PreviousLeftStickY; + float PreviousRightStickX; + float PreviousRightStickY; + float PreviousLeftTrigger; + float PreviousRightTrigger; + uint16_t PreviousButtons; + uint8_t PreviousMappedDpad; + uint8_t PreviousSystemButtons; + float PreviousAccelerometerX; + float PreviousAccelerometerY; + float PreviousAccelerometerZ; + HidDualSenseGamepadTouchState TouchState[HID_DUALSENSE_TOUCH_MAX_FINGERS]; + bool IsCalibrated; + HidDualSenseGamepadFeatureReportCalibration CalibrationData; +}; + +static SystemDataPool hidDualSenseGamepadDataPool; + +void InitHidDualSenseGamepadMemory() +{ + if (hidDualSenseGamepadDataPool.Storage == nullptr) + { + hidDualSenseGamepadDataPool = SystemCreateDataPool(InputsMemoryArena, MAX_INPUT_DEVICES); + } +} + +HidDualSenseGamepadData* GetHidDualSenseGamepadData(ElemHandle hidDevice) +{ + return SystemGetDataPoolItem(hidDualSenseGamepadDataPool, hidDevice); +} + +uint8_t MapHidDualSenseGamepadDpad(uint8_t dpadData) +{ + static const uint8_t mapping[8] = { + HidDualSenseGamepadButton_DpadUp, + HidDualSenseGamepadButton_DpadUp | HidDualSenseGamepadButton_DpadRight, + HidDualSenseGamepadButton_DpadRight, + HidDualSenseGamepadButton_DpadRight | HidDualSenseGamepadButton_DpadDown, + HidDualSenseGamepadButton_DpadDown, + HidDualSenseGamepadButton_DpadDown | HidDualSenseGamepadButton_DpadLeft, + HidDualSenseGamepadButton_DpadLeft, + HidDualSenseGamepadButton_DpadLeft | HidDualSenseGamepadButton_DpadUp + }; + + return (dpadData & 8) ? 0 : mapping[dpadData % 8]; +} + +void ParseHidDualSenseGamepadTouchData(ElemWindow window, ElemInputDevice inputDevice, double elapsedSeconds, HidDualSenseGamepadTouchState* touchState, uint8_t fingerIndex, uint32_t touchData) +{ + auto isTouching = !((touchData >> 7) & 0x01); + auto positionX = (int32_t)((touchData >> 8) & 0xFFF); + auto positionY = (int32_t)((touchData >> 20) & 0xFFF); + + auto deltaX = 0.0f; + auto deltaY = 0.0f; + + if (isTouching != touchState->IsTouching) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_Touch, + .InputType = ElemInputType_Digital, + .Value = isTouching ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + if (!isTouching) + { + if (touchState->PreviousDeltaXSteps < 50) + { + deltaX = touchState->PreviousDeltaX; + } + + if (touchState->PreviousDeltaYSteps < 50) + { + deltaY = touchState->PreviousDeltaY; + } + } + } + else + { + deltaX = (float)(positionX - touchState->PreviousPositionX) * HID_DUALSENSE_TOUCH_SENSITIVITY; + deltaY = (float)(positionY - touchState->PreviousPositionY) * (HID_DUALSENSE_TOUCH_WIDTH / HID_DUALSENSE_TOUCH_HEIGHT) * HID_DUALSENSE_TOUCH_SENSITIVITY; + } + + if (deltaX < -HID_DUALSENSE_TOUCH_DEAD_ZONE) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_TouchXNegative, + .InputType = ElemInputType_Delta, + .Value = -deltaX, + .ElapsedSeconds = elapsedSeconds + }); + + touchState->PreviousDeltaX = deltaX; + touchState->PreviousDeltaXSteps = 0; + } + + if (deltaX > HID_DUALSENSE_TOUCH_DEAD_ZONE) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_TouchXPositive, + .InputType = ElemInputType_Delta, + .Value = deltaX, + .ElapsedSeconds = elapsedSeconds + }); + + touchState->PreviousDeltaX = deltaX; + touchState->PreviousDeltaXSteps = 0; + } + + if (deltaY < -HID_DUALSENSE_TOUCH_DEAD_ZONE) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_TouchYNegative, + .InputType = ElemInputType_Delta, + .Value = -deltaY, + .ElapsedSeconds = elapsedSeconds + }); + + touchState->PreviousDeltaY = deltaY; + touchState->PreviousDeltaYSteps = 0; + } + + if (deltaY > HID_DUALSENSE_TOUCH_DEAD_ZONE) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_TouchYPositive, + .InputType = ElemInputType_Delta, + .Value = deltaY, + .ElapsedSeconds = elapsedSeconds + }); + + touchState->PreviousDeltaY = deltaY; + touchState->PreviousDeltaYSteps = 0; + } + + if (touchState->PreviousDeltaXSteps == 0 || (isTouching != touchState->IsTouching)) + { + auto normalizedPositionX = (float)positionX / HID_DUALSENSE_TOUCH_WIDTH; + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_TouchXAbsolutePosition, + .InputType = ElemInputType_Absolute, + .Value = normalizedPositionX, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (touchState->PreviousDeltaYSteps == 0 || (isTouching != touchState->IsTouching)) + { + auto normalizedPositionY = (float)positionY / HID_DUALSENSE_TOUCH_HEIGHT; + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputDeviceTypeIndex = fingerIndex, + .InputId = ElemInputId_TouchYAbsolutePosition, + .InputType = ElemInputType_Absolute, + .Value = -normalizedPositionY, + .ElapsedSeconds = elapsedSeconds + }); + } + + touchState->IsTouching = isTouching; + touchState->PreviousPositionX = positionX; + touchState->PreviousPositionY = positionY; + + if (touchState->PreviousDeltaXSteps < 255) + { + touchState->PreviousDeltaXSteps += 1; + } + + if (touchState->PreviousDeltaYSteps < 255) + { + touchState->PreviousDeltaYSteps += 1; + } +} + +float NormalizeHidDualSenseGamepadAngularVelocity(int16_t value, int16_t bias, int16_t thresholdMinus, int16_t thresholdPlus, int16_t speedMinus, int16_t speedPlus) +{ + float normalizedValue; + + if (value < 0) + { + normalizedValue = (float)(SystemMax(value, thresholdMinus) + bias) / speedMinus; + } + else + { + normalizedValue = (float)(SystemMin(value, thresholdPlus) + bias) / speedPlus; + } + + if (normalizedValue < HID_DUALSENSE_ANGULAR_VELOCITY_DEAD_ZONE && normalizedValue > -HID_DUALSENSE_ANGULAR_VELOCITY_DEAD_ZONE) + { + return 0.0f; + } + + return normalizedValue; +} + +float NormalizeHidDualSenseGamepadAccelerometer(int16_t value, float previousValue, int16_t thresholdMinus, int16_t thresholdPlus) +{ + auto bias = (thresholdPlus + thresholdMinus) / 2.0f; + auto scaleFactor = (thresholdPlus - thresholdMinus) / 2.0f; + float normalizedValue = (value - bias) / scaleFactor; + + if (SystemAbs(normalizedValue - previousValue) < HID_DUALSENSE_ACCELEROMETER_DEAD_ZONE) + { + return previousValue; + } + + return normalizedValue; +} + +void ProcessHidDualSenseGamepadInputReport(ElemWindow window, ElemInputDevice inputDevice, HidDualSenseGamepadInputReport* inputReport, double elapsedSeconds) +{ + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData != ELEM_HANDLE_NULL); + + if (inputDeviceData->HidDeviceData != ELEM_HANDLE_NULL) + { + auto hidData = GetHidDualSenseGamepadData(inputDeviceData->HidDeviceData); + SystemAssert(hidData); + + auto leftStickX = NormalizeInputValueSigned(inputReport->LeftStickX, HID_DUALSENSE_STICK_RANGE, HID_DUALSENSE_STICK_DEAD_ZONE); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, leftStickX, hidData->PreviousLeftStickX, ElemInputId_GamepadLeftStickXNegative, ElemInputId_GamepadLeftStickXPositive); + + auto leftStickY = -NormalizeInputValueSigned(inputReport->LeftStickY, HID_DUALSENSE_STICK_RANGE, HID_DUALSENSE_STICK_DEAD_ZONE); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, leftStickY, hidData->PreviousLeftStickY, ElemInputId_GamepadLeftStickYNegative, ElemInputId_GamepadLeftStickYPositive); + + auto rightStickX = NormalizeInputValueSigned(inputReport->RightStickX, HID_DUALSENSE_STICK_RANGE, HID_DUALSENSE_STICK_DEAD_ZONE); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, rightStickX, hidData->PreviousRightStickX, ElemInputId_GamepadRightStickXNegative, ElemInputId_GamepadRightStickXPositive); + + auto rightStickY = -NormalizeInputValueSigned(inputReport->RightStickY, HID_DUALSENSE_STICK_RANGE, HID_DUALSENSE_STICK_DEAD_ZONE); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, rightStickY, hidData->PreviousRightStickY, ElemInputId_GamepadRightStickYNegative, ElemInputId_GamepadRightStickYPositive); + + auto leftTrigger = NormalizeInputValue(inputReport->LeftTrigger, HID_DUALSENSE_STICK_RANGE, HID_DUALSENSE_STICK_DEAD_ZONE); + ProcessHidGamepadTrigger(window, inputDevice, elapsedSeconds, leftTrigger, hidData->PreviousLeftTrigger, ElemInputId_GamepadLeftTrigger); + + auto rightTrigger = NormalizeInputValue(inputReport->RightTrigger, HID_DUALSENSE_STICK_RANGE, HID_DUALSENSE_STICK_DEAD_ZONE); + ProcessHidGamepadTrigger(window, inputDevice, elapsedSeconds, rightTrigger, hidData->PreviousRightTrigger, ElemInputId_GamepadRightTrigger); + + auto mappedDpad = MapHidDualSenseGamepadDpad(inputReport->Buttons); + + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, mappedDpad, hidData->PreviousMappedDpad, HidDualSenseGamepadButton_DpadUp, ElemInputId_GamepadDpadUp); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, mappedDpad, hidData->PreviousMappedDpad, HidDualSenseGamepadButton_DpadRight, ElemInputId_GamepadDpadRight); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, mappedDpad, hidData->PreviousMappedDpad, HidDualSenseGamepadButton_DpadDown, ElemInputId_GamepadDpadDown); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, mappedDpad, hidData->PreviousMappedDpad, HidDualSenseGamepadButton_DpadLeft, ElemInputId_GamepadDpadLeft); + + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_Cross, ElemInputId_GamepadButtonA); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_Circle, ElemInputId_GamepadButtonB); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_Square, ElemInputId_GamepadButtonX); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_Triangle, ElemInputId_GamepadButtonY); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_L1, ElemInputId_GamepadLeftShoulder); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_R1, ElemInputId_GamepadRightShoulder); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_Share, ElemInputId_GamepadButtonShare); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_Options, ElemInputId_GamepadButtonOptions); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_L3, ElemInputId_GamepadLeftStickButton); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->Buttons, hidData->PreviousButtons, HidDualSenseGamepadButton_R3, ElemInputId_GamepadRightStickButton); + + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->SystemButtons, hidData->PreviousSystemButtons, HidDualSenseGamepadSystemButton_Home, ElemInputId_GamepadButtonHome); + ProcessHidGamepadButton(window, inputDevice, elapsedSeconds, inputReport->SystemButtons, hidData->PreviousSystemButtons, HidDualSenseGamepadSystemButton_Pad, ElemInputId_GamepadTouchButton); + + // TODO: DualSense Edge buttons? + + if (hidData->IsCalibrated) + { + for (uint32_t i = 0; i < HID_DUALSENSE_TOUCH_MAX_FINGERS; i++) + { + ParseHidDualSenseGamepadTouchData(window, inputDevice, elapsedSeconds, &hidData->TouchState[i], i, inputReport->TouchData.PackedData[i]); + } + } + + auto calibration = hidData->CalibrationData; + + auto angularVelocityX = NormalizeHidDualSenseGamepadAngularVelocity(inputReport->AngularVelocityX, calibration.GyroPitchBias, calibration.GyroPitchMinus, calibration.GyroPitchPlus, calibration.GyroSpeedMinus, calibration.GyroSpeedPlus); + ProcessHidGamepadDeltaAxe(window, inputDevice, elapsedSeconds, angularVelocityX, ElemInputId_AngularVelocityXNegative, ElemInputId_AngularVelocityXPositive); + + auto angularVelocityY = NormalizeHidDualSenseGamepadAngularVelocity(inputReport->AngularVelocityY, calibration.GyroYawBias, calibration.GyroYawMinus, calibration.GyroYawPlus, calibration.GyroSpeedMinus, calibration.GyroSpeedPlus); + ProcessHidGamepadDeltaAxe(window, inputDevice, elapsedSeconds, angularVelocityY, ElemInputId_AngularVelocityYNegative, ElemInputId_AngularVelocityYPositive); + + auto angularVelocityZ = NormalizeHidDualSenseGamepadAngularVelocity(inputReport->AngularVelocityZ, calibration.GyroRollBias, calibration.GyroRollMinus, calibration.GyroRollPlus, calibration.GyroSpeedMinus, calibration.GyroSpeedPlus); + ProcessHidGamepadDeltaAxe(window, inputDevice, elapsedSeconds, angularVelocityZ, ElemInputId_AngularVelocityZNegative, ElemInputId_AngularVelocityZPositive); + + auto accelerometerX = NormalizeHidDualSenseGamepadAccelerometer(inputReport->AccelerometerX, hidData->PreviousAccelerometerX, calibration.AccelXMinus, calibration.AccelXPlus); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, accelerometerX, hidData->PreviousAccelerometerX, ElemInputId_AccelerometerXNegative, ElemInputId_AccelerometerXPositive); + + auto accelerometerY = NormalizeHidDualSenseGamepadAccelerometer(inputReport->AccelerometerY, hidData->PreviousAccelerometerY, calibration.AccelYMinus, calibration.AccelYPlus); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, accelerometerY, hidData->PreviousAccelerometerY, ElemInputId_AccelerometerYNegative, ElemInputId_AccelerometerYPositive); + + auto accelerometerZ = NormalizeHidDualSenseGamepadAccelerometer(inputReport->AccelerometerZ, hidData->PreviousAccelerometerZ, calibration.AccelZMinus, calibration.AccelZPlus); + ProcessHidGamepadStick(window, inputDevice, elapsedSeconds, accelerometerZ, hidData->PreviousAccelerometerZ, ElemInputId_AccelerometerZNegative, ElemInputId_AccelerometerZPositive); + + hidData->PreviousLeftStickX = leftStickX; + hidData->PreviousLeftStickY = leftStickY; + hidData->PreviousRightStickX = rightStickX; + hidData->PreviousRightStickY = rightStickY; + hidData->PreviousLeftTrigger = leftTrigger; + hidData->PreviousRightTrigger = rightTrigger; + hidData->PreviousButtons = inputReport->Buttons; + hidData->PreviousMappedDpad = mappedDpad; + hidData->PreviousSystemButtons = inputReport->SystemButtons; + hidData->PreviousAccelerometerX = accelerometerX; + hidData->PreviousAccelerometerY = accelerometerY; + hidData->PreviousAccelerometerZ = accelerometerZ; + } +} + +void ProcessHidDualSenseGamepadInputReportBluetoothStandard(ElemWindow window, ElemInputDevice inputDevice, HidDualSenseGamepadInputReportBluetoothStandard* inputReport, double elapsedSeconds) +{ + SystemLogWarningMessage(ElemLogMessageCategory_Inputs, "Using DualSense GamePad Bluetooth Standard Report %f", elapsedSeconds); + + HidDualSenseGamepadInputReport mappedInputReport = + { + .LeftStickX = inputReport->LeftStickX, + .LeftStickY = inputReport->LeftStickY, + .RightStickX = inputReport->RightStickX, + .RightStickY = inputReport->RightStickY, + .LeftTrigger = inputReport->LeftTrigger, + .RightTrigger = inputReport->RightTrigger, + .Buttons = inputReport->Buttons, + .SystemButtons = inputReport->SystemButtons + }; + + ProcessHidDualSenseGamepadInputReport(window, inputDevice, &mappedInputReport, elapsedSeconds); +} + +void ProcessHidDualSenseGamepadInputReport(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) +{ + // TODO: We need to differentiate USB / Bluetooth? How can we do that? + + InitHidDualSenseGamepadMemory(); + + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData != ELEM_HANDLE_NULL); + + if (inputDeviceData->HidDeviceData == ELEM_HANDLE_NULL) + { + inputDeviceData->HidDeviceData = SystemAddDataPoolItem(hidDualSenseGamepadDataPool, {}); + } + + auto hidDeviceData = GetHidDualSenseGamepadData(inputDeviceData->HidDeviceData); + SystemAssert(hidDeviceData); + + if (!hidDeviceData->IsCalibrated) + { + HidDualSenseGamepadFeatureReportCalibration calibrationFeatureReport = + { + .ReportId = HidDualSenseGamepadFeatureReportType_Calibration, + }; + + auto result = PlatformHidGetFeatureReport(inputDevice, ReadOnlySpan((uint8_t*)&calibrationFeatureReport, sizeof(calibrationFeatureReport))); + SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "Reading DualSense Calibration Data: (Result=%d)", result); + + if (result) + { + hidDeviceData->IsCalibrated = true; + hidDeviceData->CalibrationData = calibrationFeatureReport; + + // TODO: Set light of current player + // TODO: Have a system to deactivate some features if not needed or to optin. (Like the gyro and accel) + } + } + + auto reportId = hidReport[0]; + + if (reportId == HidDualSenseGamepadInputReportType_BluetoothStandard) + { + auto inputReport = (HidDualSenseGamepadInputReportBluetoothStandard*)(++hidReport.Pointer); + ProcessHidDualSenseGamepadInputReportBluetoothStandard(window, inputDevice, inputReport, elapsedSeconds); + } + else if (reportId == HidDualSenseGamepadInputReportType_BluetoothExtended) + { + auto inputReport = (HidDualSenseGamepadInputReport*)(hidReport.Pointer + 2); + ProcessHidDualSenseGamepadInputReport(window, inputDevice, inputReport, elapsedSeconds); + } +} + +ProcessHidGamepadInputReportPtr CheckHidDualSenseGamepadSupport(uint32_t vendorId, uint32_t productId) +{ + if (vendorId == HID_DUALSENSE_VENDOR_ID && productId == HID_DUALSENSE_PRODUCT_ID) + { + return ProcessHidDualSenseGamepadInputReport; + } + + return nullptr; +} diff --git a/src/Elemental/Common/Inputs/HidSwitchGamepad.cpp b/src/Elemental/Common/Inputs/HidSwitchGamepad.cpp new file mode 100644 index 00000000..6448da14 --- /dev/null +++ b/src/Elemental/Common/Inputs/HidSwitchGamepad.cpp @@ -0,0 +1,270 @@ +#pragma once + +#include "../Elemental.h" +#include "Inputs.h" +#include "HidUtils.h" +#include "SystemFunctions.h" +#include "SystemDataPool.h" + +// Based on the spec: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_notes.md + +#define HID_SWITCH_VENDOR_ID 0x057E +#define HID_SWITCH_PRODUCT_ID 0x2009 + +#define HID_SWITCH_STICK_RANGE 65535 +#define HID_SWITCH_STICK_DEAD_ZONE 0.2f + +enum HidSwitchGamepadInputReportType +{ + HidSwitchGamepadInputReportType_Standard = 0x3F, + HidSwitchGamepadInputReportType_SubCommand = 0x21 +}; + +enum HidSwitchGamepadOutputReportType +{ + HidSwitchGamepadOutputReportType_SubCommand = 0x01 +}; + +enum HidSwitchGamepadSubCommandType : uint8_t +{ + HidSwitchGamepadSubCommandType_DeviceInfo = 0x02 +}; + +enum HidSwitchGamepadControllerType : uint8_t +{ + HidSwitchGamepadControllerType_LeftJoyCon = 0x01, + HidSwitchGamepadControllerType_RightJoyCon = 0x02, + HidSwitchGamepadControllerType_ProController = 0x03, +}; + +struct HidSwitchGamepadData +{ + uint8_t CurrentOutputPacketNumber; +}; + +struct __attribute__((__packed__)) HidSwitchGamepadInputReportStandard +{ + uint8_t Buttons[2]; + uint8_t Dpad; + uint16_t LeftStickX; + uint16_t LeftStickY; + uint16_t RightStickX; + uint16_t RightStickY; +}; + +struct __attribute__((__packed__)) HidSwitchGamepadInputReportExtended +{ + uint8_t Timer; + uint8_t BatteryConnection; + uint8_t Buttons[3]; + uint8_t LeftStick[3]; + uint8_t RightStick[3]; +}; + +struct __attribute__((__packed__)) HidSwitchGamepadInputReportDeviceInfo +{ + uint16_t FirmwareVersion; + HidSwitchGamepadControllerType ControllerType; +}; + +struct __attribute__((__packed__)) HidSwitchGamepadOutputReportSubCommand +{ + uint8_t ReportId; + uint8_t GlobalPacketNumber; + // TODO: Rumble Data + uint8_t RumbleData[8]; + HidSwitchGamepadSubCommandType SubCommandId; + uint8_t Data[53]; +}; + +struct __attribute__((__packed__)) HidSwitchGamepadFeatureReportSetupMemoryRead +{ + uint8_t ReportId; + uint32_t Address; + uint16_t Size; + uint8_t Checksum; +}; + +struct __attribute__((__packed__)) HidSwitchGamepadFeatureReportMemoryRead +{ + uint8_t ReportId; + uint8_t Checksum; + uint8_t Data[1024]; +}; + +static SystemDataPool hidSwitchGamepadDataPool; + +inline void InitHidSwitchGamepadMemory() +{ + if (hidSwitchGamepadDataPool.Storage == nullptr) + { + hidSwitchGamepadDataPool = SystemCreateDataPool(InputsMemoryArena, MAX_INPUT_DEVICES); + } +} + +inline HidSwitchGamepadData* GetHidSwitchGamepadData(ElemHandle hidDevice) +{ + return SystemGetDataPoolItem(hidSwitchGamepadDataPool, hidDevice); +} + +inline bool SendHidSwitchGamepadSubCommand(ElemInputDevice inputDevice, HidSwitchGamepadSubCommandType subCommandType, ReadOnlySpan data) +{ + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData != ELEM_HANDLE_NULL); + + auto hidData = GetHidSwitchGamepadData(inputDeviceData->HidDeviceData); + SystemAssert(hidData); + + HidSwitchGamepadOutputReportSubCommand outputReport = + { + .ReportId = HidSwitchGamepadOutputReportType_SubCommand, + .GlobalPacketNumber = hidData->CurrentOutputPacketNumber, + .SubCommandId = subCommandType + }; + + hidData->CurrentOutputPacketNumber = (hidData->CurrentOutputPacketNumber + 1) % 255; + + return PlatformHidSendOutputReport(inputDevice, ReadOnlySpan((uint8_t*)&outputReport, sizeof(outputReport))); +} + +inline void ProcessHidSwitchGamepadInputReportStandard(ElemWindow window, ElemInputDevice inputDevice, HidSwitchGamepadInputReportStandard* inputReport, double elapsedSeconds) +{ + // TODO: Also here we need to send the event only if the analog value has changed + float leftStickX = NormalizeInputValueSigned(inputReport->LeftStickX, HID_SWITCH_STICK_RANGE, HID_SWITCH_STICK_DEAD_ZONE); + + if (leftStickX <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickXNegative, + .InputType = ElemInputType_Analog, + .Value = -leftStickX, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (leftStickX >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickXPositive, + .InputType = ElemInputType_Analog, + .Value = leftStickX, + .ElapsedSeconds = elapsedSeconds + }); + } + + float leftStickY = -NormalizeInputValueSigned(inputReport->LeftStickY, HID_SWITCH_STICK_RANGE, HID_SWITCH_STICK_DEAD_ZONE); + + if (leftStickY <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickYNegative, + .InputType = ElemInputType_Analog, + .Value = -leftStickY, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (leftStickY >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickYPositive, + .InputType = ElemInputType_Analog, + .Value = leftStickY, + .ElapsedSeconds = elapsedSeconds + }); + } + + // TODO: TESTING ONLY + if (inputReport->Buttons[0] & 0x01) + { + SendHidSwitchGamepadSubCommand(inputDevice, HidSwitchGamepadSubCommandType_DeviceInfo, ReadOnlySpan()); + } +} + +inline void ProcessHidSwitchGamepadInputReport(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) +{ + InitHidSwitchGamepadMemory(); + + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData != ELEM_HANDLE_NULL); + + if (inputDeviceData->HidDeviceData == ELEM_HANDLE_NULL) + { + // Read Config + HidSwitchGamepadFeatureReportSetupMemoryRead featureReport = + { + .ReportId = 0x71, + .Address = 0xF8000000 + 0x6000, + .Size = 0xA000, + .Checksum = 0 + }; + + auto pointer = (uint8_t*)&featureReport; + auto sumBytes = 0u; + + for (uint32_t i = 0; i < sizeof(featureReport); i++) + { + sumBytes += pointer[i]; + } + + featureReport.Checksum = 0x100 - sumBytes; + + auto result = PlatformHidSendFeatureReport(inputDevice, ReadOnlySpan((uint8_t*)&featureReport, sizeof(featureReport))); + SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "Send Feature Result: %d", result); + + HidSwitchGamepadFeatureReportMemoryRead readFeatureReport = + { + .ReportId = 0x72, + .Checksum = 0 + }; + + result = PlatformHidGetFeatureReport(inputDevice, ReadOnlySpan((uint8_t*)&readFeatureReport, sizeof(readFeatureReport))); + SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "Get Feature Result: %d", result); + + inputDeviceData->HidDeviceData = SystemAddDataPoolItem(hidSwitchGamepadDataPool, {}); + } + + auto reportId = hidReport[0]; + + //SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "SwitchGamePad %d", reportId); + + // TODO: Use the the advanced report + if (reportId == HidSwitchGamepadInputReportType_Standard) + { + auto inputReport = (HidSwitchGamepadInputReportStandard*)(++hidReport.Pointer); + ProcessHidSwitchGamepadInputReportStandard(window, inputDevice, inputReport, elapsedSeconds); + } + else if (reportId == HidSwitchGamepadInputReportType_SubCommand) + { + auto hidData = GetHidSwitchGamepadData(inputDeviceData->HidDeviceData); + SystemAssert(hidData); + + + auto inputReport = (HidSwitchGamepadInputReportExtended*)(++hidReport.Pointer); + + uint8_t *data = inputReport->LeftStick; + uint16_t stick_horizontal = data[0] | ((data[1] & 0xF) << 8); + uint16_t stick_vertical = (data[1] >> 4) | (data[2] << 4); + + SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "Report: Timer=%d, X=%d, Y=%d", inputReport->Timer, stick_horizontal, stick_vertical); + //SystemLogDebugMessage(ElemLogMessageCategory_Inputs, "ProcessSubCommandtype: %d, Version: %d, Type: %d", subCommandType, inputReport->FirmwareVersion, inputReport->ControllerType); + } +} + +ProcessHidGamepadInputReportPtr CheckHidSwitchProGamepadSupport(uint32_t vendorId, uint32_t productId) +{ + if (vendorId == HID_SWITCH_VENDOR_ID && productId == HID_SWITCH_PRODUCT_ID) + { + return ProcessHidSwitchGamepadInputReport; + } + + return nullptr; +} diff --git a/src/Elemental/Common/Inputs/HidUtils.h b/src/Elemental/Common/Inputs/HidUtils.h new file mode 100644 index 00000000..80240b59 --- /dev/null +++ b/src/Elemental/Common/Inputs/HidUtils.h @@ -0,0 +1,123 @@ +#pragma once + +#include "../Elemental.h" +#include "Inputs.h" + +typedef void (*ProcessHidGamepadInputReportPtr)(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds); +typedef ProcessHidGamepadInputReportPtr (*CheckHidGamepadSupportPtr)(uint32_t vendorId, uint32_t productId); + +// TODO: Create a CPP file +// TODO: Rename that file + +inline float NormalizeInputValue(uint32_t value, uint32_t maxValue, float deadZone) +{ + // TODO: Allows the configuration of deadzone + float normalizedValue = ((float)value / (float)maxValue); + + if (normalizedValue < deadZone) + { + return 0.0f; + } + + return normalizedValue; +} + +inline float NormalizeInputValueSigned(uint32_t value, uint32_t maxValue, float deadZone) +{ + // TODO: Allows the configuration of deadzone + float normalizedValue = ((float)value / (float)maxValue) * 2.0f - 1.0f; + + if (normalizedValue < deadZone && normalizedValue > -deadZone) + { + return 0.0f; + } + + return normalizedValue; +} + +inline void ProcessHidGamepadDeltaAxe(ElemWindow window, ElemInputDevice inputDevice, double elapsedSeconds, float axeData, ElemInputId negativeInputId, ElemInputId positiveInputId) +{ + if (axeData < 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = negativeInputId, + .InputType = ElemInputType_Delta, + .Value = -axeData, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (axeData > 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = positiveInputId, + .InputType = ElemInputType_Delta, + .Value = axeData, + .ElapsedSeconds = elapsedSeconds + }); + } +} + +inline void ProcessHidGamepadStick(ElemWindow window, ElemInputDevice inputDevice, double elapsedSeconds, float stickData, float previousStickData, ElemInputId negativeInputId, ElemInputId positiveInputId) +{ + if (stickData != previousStickData) + { + if (stickData <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = negativeInputId, + .InputType = ElemInputType_Analog, + .Value = -stickData, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (stickData >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = positiveInputId, + .InputType = ElemInputType_Analog, + .Value = stickData, + .ElapsedSeconds = elapsedSeconds + }); + } + } +} + +inline void ProcessHidGamepadTrigger(ElemWindow window, ElemInputDevice inputDevice, double elapsedSeconds, float data, float previousData, ElemInputId inputId) +{ + if (data != previousData) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = inputId, + .InputType = ElemInputType_Analog, + .Value = data, + .ElapsedSeconds = elapsedSeconds + }); + } +} + +inline void ProcessHidGamepadButton(ElemWindow window, ElemInputDevice inputDevice, double elapsedSeconds, uint16_t currentButtons, uint16_t previousButtons, uint16_t buttonType, ElemInputId inputId) +{ + if ((currentButtons & buttonType) != (previousButtons & buttonType)) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = inputId, + .InputType = ElemInputType_Digital, + .Value = (currentButtons & buttonType) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + } +} diff --git a/src/Elemental/Common/Inputs/HidXboxOneGamepad.cpp b/src/Elemental/Common/Inputs/HidXboxOneGamepad.cpp new file mode 100644 index 00000000..a8c5d012 --- /dev/null +++ b/src/Elemental/Common/Inputs/HidXboxOneGamepad.cpp @@ -0,0 +1,318 @@ +#pragma once + +#include "../Elemental.h" +#include "Inputs.h" +#include "HidUtils.h" +#include "SystemFunctions.h" +#include "SystemDataPool.h" + +#define HID_XBOXONE_VENDOR_ID 0x045E +#define HID_XBOXONE_PRODUCT_ID 0x02FD +#define HID_XBOXONE_OLD_DRIVER_PRODUCT_ID 0x02E0 +#define HID_XBOXONE_USB_PRODUCT_ID 0x02FF + +struct __attribute__((__packed__)) XboxOneWirelessOldDriverGamepadReport +{ + uint8_t Padding; + uint16_t LeftStickX; + uint16_t LeftStickY; + uint16_t RightStickX; + uint16_t RightStickY; + uint16_t Triggers; + uint16_t Buttons; + uint8_t Dpad; +}; + +struct __attribute__((__packed__)) XboxOneWirelessGamepadReport +{ + uint8_t Padding; + uint16_t LeftStickX; + uint16_t LeftStickY; + uint16_t RightStickX; + uint16_t RightStickY; + uint16_t LeftTrigger; + uint16_t RightTrigger; + uint8_t Dpad; + uint32_t Buttons; +}; + +struct __attribute__((__packed__)) DualSenseSimpleGamepadReport +{ + uint8_t LeftStickX; + uint8_t LeftStickY; + uint16_t RightStickX; + uint16_t RightStickY; + uint16_t LeftTrigger; + uint16_t RightTrigger; + uint8_t Dpad; + uint32_t Buttons; +}; + +void ProcessXboxOneWirelessGamepadReport(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) +{ + auto xboxReport = (XboxOneWirelessGamepadReport*)hidReport.Pointer; + + // TODO: Also here we need to send the event only if the analog value has changed + float leftStickX = NormalizeInputValueSigned(xboxReport->LeftStickX, 65535, 0.2f); + + if (leftStickX <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickXNegative, + .InputType = ElemInputType_Analog, + .Value = -leftStickX, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (leftStickX >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickXPositive, + .InputType = ElemInputType_Analog, + .Value = leftStickX, + .ElapsedSeconds = elapsedSeconds + }); + } + + float leftStickY = -NormalizeInputValueSigned(xboxReport->LeftStickY, 65535, 0.2f); + + if (leftStickY <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickYNegative, + .InputType = ElemInputType_Analog, + .Value = -leftStickY, + .ElapsedSeconds = elapsedSeconds + }); + } + // TODO: check if the value is inversed!! + if (leftStickY >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickYPositive, + .InputType = ElemInputType_Analog, + .Value = leftStickY, + .ElapsedSeconds = elapsedSeconds + }); + } + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftTrigger, + .InputType = ElemInputType_Analog, + .Value = NormalizeInputValue(xboxReport->LeftTrigger, 1024, 0.1f), + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadRightTrigger, + .InputType = ElemInputType_Analog, + .Value = NormalizeInputValue(xboxReport->RightTrigger, 1024, 0.1f), + .ElapsedSeconds = elapsedSeconds + }); + + // TODO: For buttons we need to keep track of old report to see if there was a change + // Maybe we can do the same as with delta inputs! + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadButtonA, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x01) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadButtonB, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x02) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftShoulder, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x40) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadRightShoulder, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x80) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); +} + +void ProcessXboxOneWirelessOldDriverGamepadReport(ElemWindow window, ElemInputDevice inputDevice, ReadOnlySpan hidReport, double elapsedSeconds) +{ + auto xboxReport = (XboxOneWirelessOldDriverGamepadReport*)hidReport.Pointer; + + // TODO: Also here we need to send the event only if the analog value has changed + float leftStickX = NormalizeInputValueSigned(xboxReport->LeftStickX, 65535, 0.2f); + + if (leftStickX <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickXNegative, + .InputType = ElemInputType_Analog, + .Value = -leftStickX, + .ElapsedSeconds = elapsedSeconds + }); + } + + if (leftStickX >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickXPositive, + .InputType = ElemInputType_Analog, + .Value = leftStickX, + .ElapsedSeconds = elapsedSeconds + }); + } + + float leftStickY = -NormalizeInputValueSigned(xboxReport->LeftStickY, 65535, 0.2f); + + if (leftStickY <= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickYNegative, + .InputType = ElemInputType_Analog, + .Value = -leftStickY, + .ElapsedSeconds = elapsedSeconds + }); + } + // TODO: check if the value is inversed!! + if (leftStickY >= 0) + { + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftStickYPositive, + .InputType = ElemInputType_Analog, + .Value = leftStickY, + .ElapsedSeconds = elapsedSeconds + }); + } + + const uint16_t midpoint = 32768; + + uint8_t left, right; + + // Calculate the right trigger value as a difference from midpoint + if (xboxReport->Triggers <= midpoint) { + right = static_cast((midpoint - xboxReport->Triggers) >> 7); + } else { + right = 0; + } + + // Calculate the left trigger value as a difference from midpoint + if (xboxReport->Triggers >= midpoint) { + left = static_cast((xboxReport->Triggers - midpoint) >> 7); + } else { + left = 0; + } + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftTrigger, + .InputType = ElemInputType_Analog, + .Value = NormalizeInputValue(left, 255, 0.1f), + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadRightTrigger, + .InputType = ElemInputType_Analog, + .Value = NormalizeInputValue(right, 255, 0.1f), + .ElapsedSeconds = elapsedSeconds + }); + + // TODO: For buttons we need to keep track of old report to see if there was a change + // Maybe we can do the same as with delta inputs! + + + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadButtonA, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x01) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadButtonB, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x02) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadLeftShoulder, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x10) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); + + AddInputEvent({ + .Window = window, + .InputDevice = inputDevice, + .InputId = ElemInputId_GamepadRightShoulder, + .InputType = ElemInputType_Digital, + .Value = (xboxReport->Buttons & 0x20) ? 1.0f : 0.0f, + .ElapsedSeconds = elapsedSeconds + }); +} + + +ProcessHidGamepadInputReportPtr CheckHidXboxOneGamepadSupport(uint32_t vendorId, uint32_t productId) +{ + if (vendorId == HID_XBOXONE_VENDOR_ID) + { + if (productId == HID_XBOXONE_OLD_DRIVER_PRODUCT_ID || productId == HID_XBOXONE_USB_PRODUCT_ID) + { + return ProcessXboxOneWirelessOldDriverGamepadReport; + } + else if (productId == HID_XBOXONE_PRODUCT_ID) + { + return ProcessXboxOneWirelessGamepadReport; + } + } + + return nullptr; +} + diff --git a/src/Elemental/Common/Inputs/Inputs.cpp b/src/Elemental/Common/Inputs/Inputs.cpp index 6102721b..8298f032 100644 --- a/src/Elemental/Common/Inputs/Inputs.cpp +++ b/src/Elemental/Common/Inputs/Inputs.cpp @@ -5,7 +5,7 @@ #include "SystemMemory.h" #include "SystemPlatformFunctions.h" -MemoryArena inputsMemoryArena; +MemoryArena InputsMemoryArena; Span inputEvents; uint32_t currentInputEventsIndex; uint32_t currentInputEventsWriteIndex; @@ -22,14 +22,14 @@ SystemDataPool inputDevicePool; void InitInputsMemory() { - if (inputsMemoryArena.Storage == nullptr) + if (InputsMemoryArena.Storage == nullptr) { - inputsMemoryArena = SystemAllocateMemoryArena(); - inputEvents = SystemPushArray(inputsMemoryArena, MAX_INPUT_EVENTS * 2); - deltaInputsToReset = SystemPushArrayZero(inputsMemoryArena, MAX_INPUT_EVENTS * 2); + InputsMemoryArena = SystemAllocateMemoryArena(); + inputEvents = SystemPushArray(InputsMemoryArena, MAX_INPUT_EVENTS * 2); + deltaInputsToReset = SystemPushArrayZero(InputsMemoryArena, MAX_INPUT_EVENTS * 2); - inputDevices = SystemPushArrayZero(inputsMemoryArena, MAX_INPUT_DEVICES); - inputDevicePool = SystemCreateDataPool(inputsMemoryArena, MAX_INPUT_DEVICES); + inputDevices = SystemPushArrayZero(InputsMemoryArena, MAX_INPUT_DEVICES); + inputDevicePool = SystemCreateDataPool(InputsMemoryArena, MAX_INPUT_DEVICES); currentInputEventsIndex = 0; currentInputEventsWriteIndex = 0; @@ -107,21 +107,6 @@ void AddInputEvent(ElemInputEvent inputEvent, bool needReset) inputEvents[currentInputEventsIndex * MAX_INPUT_EVENTS + currentInputEventsWriteIndex++] = inputEvent; } -ElemAPI ElemInputDeviceInfo ElemGetInputDeviceInfo(ElemInputDevice inputDevice) -{ - auto inputDeviceData = GetInputDeviceData(inputDevice); - SystemAssert(inputDeviceData); - - auto inputDeviceDataFull = GetInputDeviceDataFull(inputDevice); - SystemAssert(inputDeviceDataFull); - - return - { - .Handle = inputDevice, - .DeviceType = inputDeviceData->InputDeviceType, - }; -} - void ResetInputsFrame() { InitInputsMemory(); @@ -139,6 +124,7 @@ void ResetInputsFrame() for (uint32_t i = 0; i < previousDeltaInputsToResetWriteIndex; i++) { + // TODO: Investigate if we process multiple time the same event?? if (previousDeltaToReset[i].InputId != ElemInputId_Unknown) { auto inputEvent = previousDeltaToReset[i]; @@ -148,6 +134,21 @@ void ResetInputsFrame() } } +ElemAPI ElemInputDeviceInfo ElemGetInputDeviceInfo(ElemInputDevice inputDevice) +{ + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData); + + auto inputDeviceDataFull = GetInputDeviceDataFull(inputDevice); + SystemAssert(inputDeviceDataFull); + + return + { + .Handle = inputDevice, + .DeviceType = inputDeviceData->InputDeviceType, + }; +} + ElemAPI ElemInputStream ElemGetInputStream() { InitInputsMemory(); diff --git a/src/Elemental/Common/Inputs/Inputs.h b/src/Elemental/Common/Inputs/Inputs.h index 87146c90..996922f1 100644 --- a/src/Elemental/Common/Inputs/Inputs.h +++ b/src/Elemental/Common/Inputs/Inputs.h @@ -1,6 +1,7 @@ #pragma once #include "../Elemental.h" +#include "SystemMemory.h" #define MAX_INPUT_DEVICES 64u #define MAX_INPUT_EVENTS 1024u @@ -11,6 +12,9 @@ struct InputDeviceData ElemInputDeviceType InputDeviceType; uint32_t HidVendorId; uint32_t HidProductId; + void* PlatformData; + ElemHandle HidDeviceData; + void* HidDeviceHandler; }; struct InputDeviceDataFull @@ -18,6 +22,8 @@ struct InputDeviceDataFull uint32_t reserved; }; +extern MemoryArena InputsMemoryArena; + InputDeviceData* GetInputDeviceData(ElemInputDevice inputDevice); InputDeviceDataFull* GetInputDeviceDataFull(ElemInputDevice inputDevice); @@ -26,3 +32,8 @@ void RemoveInputDevice(ElemInputDevice inputDevice); void AddInputEvent(ElemInputEvent inputEvent, bool needReset = false); void ResetInputsFrame(); + +bool PlatformHidSendOutputReport(ElemInputDevice inputDevice, ReadOnlySpan data); +bool PlatformHidSendFeatureReport(ElemInputDevice inputDevice, ReadOnlySpan data); +bool PlatformHidGetFeatureReport(ElemInputDevice inputDevice, ReadOnlySpan data); + diff --git a/src/Elemental/Common/SystemDataPool.cpp b/src/Elemental/Common/SystemDataPool.cpp index 9e1e2d08..4a374128 100644 --- a/src/Elemental/Common/SystemDataPool.cpp +++ b/src/Elemental/Common/SystemDataPool.cpp @@ -2,8 +2,6 @@ #include "SystemFunctions.h" #include "SystemLogging.h" -// TODO: Implement commit memory for big data pools - #define SYSTEM_DATAPOOL_INDEX_EMPTY UINT32_MAX template @@ -50,6 +48,7 @@ SystemDataPool SystemCreateDataPool(MemoryArena memoryArena, size_t ma { auto storage = SystemPushStructZero>(memoryArena); storage->MemoryArena = memoryArena; + storage->Data = SystemPushArray>(memoryArena, maxItems, AllocationState_Reserved); if (!IsTypeEmpty()) @@ -93,11 +92,12 @@ ElemHandle SystemAddDataPoolItem(SystemDataPool dataPool, T data) } index = SystemAtomicAdd(storage->CurrentIndex, 1); - SystemCommitMemory>(storage->MemoryArena, storage->Data.Slice(index), true); + + SystemCommitMemory>(storage->MemoryArena, storage->Data.Slice(index, 1000), true); if (!IsTypeEmpty()) { - SystemCommitMemory(storage->MemoryArena, storage->DataFull.Slice(index), true); + SystemCommitMemory(storage->MemoryArena, storage->DataFull.Slice(index, 1000), true); } } diff --git a/src/Elemental/Common/SystemFunctions.cpp b/src/Elemental/Common/SystemFunctions.cpp index 4e4e2eea..d611abee 100644 --- a/src/Elemental/Common/SystemFunctions.cpp +++ b/src/Elemental/Common/SystemFunctions.cpp @@ -17,7 +17,7 @@ size_t SystemRoundUpToPowerOf2(size_t value) return result; } -size_t SystemAlignToPowerOf2(size_t offset, size_t alignment) +size_t SystemAlign(size_t offset, size_t alignment) { return (offset + alignment - 1) & ~(alignment - 1); } @@ -432,6 +432,25 @@ ReadOnlySpan SystemConvertWideCharToUtf8(MemoryArena memoryArena, ReadOnly return destination; } +ReadOnlySpan SystemFormatMemorySize(MemoryArena memoryArena, uint64_t bytes) +{ + const char* suffixes[] = { "B", "KB", "MB", "GB", "TB" }; // Extend if more are needed + double size = bytes; + size_t i = 0; + + while (size >= 1024 && i < sizeof(suffixes)/sizeof(suffixes[0]) - 1) + { + size /= 1024.0; + i++; + } + + auto result = SystemPushArray(memoryArena, 50); + // TODO: Get rid of this + snprintf(result.Pointer, result.Length, "%.2f %s", size, suffixes[i]); + + return result; +} + //--------------------------------------------------------------------------------------------------------------- // IO functions @@ -482,7 +501,7 @@ Span SystemFileReadBytes(MemoryArena memoryArena, ReadOnlySpan pa auto fileData = SystemPushArray(memoryArena, fileSizeInBytes); SystemPlatformFileReadBytes(path, fileData); - + return fileData; } diff --git a/src/Elemental/Common/SystemFunctions.h b/src/Elemental/Common/SystemFunctions.h index 3a40afd8..a405a757 100644 --- a/src/Elemental/Common/SystemFunctions.h +++ b/src/Elemental/Common/SystemFunctions.h @@ -39,7 +39,7 @@ size_t SystemRoundUpToPowerOf2(size_t value); /** - * Aligns the given offset to the specified power of two alignment. + * Aligns the given offset to the specified alignment. * * This function adjusts the input offset to the nearest equal or higher multiple of the specified alignment. * The alignment value must be a power of 2. If the alignment is not a power of 2, the behavior of the function @@ -50,7 +50,7 @@ size_t SystemRoundUpToPowerOf2(size_t value); * @return The aligned offset. If the input alignment is not a power of 2, the behavior is undefined or it * may return the input offset, depending on implementation. */ -size_t SystemAlignToPowerOf2(size_t offset, size_t alignment); +size_t SystemAlign(size_t offset, size_t alignment); /** * Rounds the given double value to the nearest integer. @@ -199,6 +199,8 @@ ReadOnlySpan SystemConvertUtf8ToWideChar(MemoryArena memoryArena, ReadO */ ReadOnlySpan SystemConvertWideCharToUtf8(MemoryArena memoryArena, ReadOnlySpan source); +// TODO: Unit test +ReadOnlySpan SystemFormatMemorySize(MemoryArena memoryArena, uint64_t bytes); //--------------------------------------------------------------------------------------------------------------- // IO functions diff --git a/src/Elemental/Common/SystemMemory.cpp b/src/Elemental/Common/SystemMemory.cpp index 73be00f8..ee65c654 100644 --- a/src/Elemental/Common/SystemMemory.cpp +++ b/src/Elemental/Common/SystemMemory.cpp @@ -5,7 +5,7 @@ #ifdef ElemAPI #include "SystemLogging.h" #else -#define SystemLogErrorMessage(category, format, ...) +#define SystemLogErrorMessage(category, format, ...) printf(format __VA_OPT__(,) __VA_ARGS__) #endif #define MEMORYARENA_DEFAULT_SIZE 64 * 1024 * 1024 @@ -423,7 +423,7 @@ void SystemDecommitMemory(MemoryArena memoryArena, void* pointer, size_t sizeInB void* SystemPushMemory(MemoryArena memoryArena, size_t sizeInBytes, AllocationState state) { - sizeInBytes = SystemAlignToPowerOf2(sizeInBytes, MEMORYARENA_DEFAULT_ALIGNMENT); + sizeInBytes = SystemAlign(sizeInBytes, MEMORYARENA_DEFAULT_ALIGNMENT); auto workingMemoryArena = GetStackWorkingMemoryArena(memoryArena); auto storage = workingMemoryArena.Storage; @@ -431,7 +431,7 @@ void* SystemPushMemory(MemoryArena memoryArena, size_t sizeInBytes, AllocationSt if (allocatedSize + sizeInBytes > storage->SizeInBytes) { - SystemLogErrorMessage(ElemLogMessageCategory_Memory, "Cannot push to memory arena with: %u (Allocated size is: %u, Max size is: %u)", sizeInBytes, allocatedSize, storage->SizeInBytes); + SystemLogErrorMessage(ElemLogMessageCategory_Memory, "Cannot push to memory arena with: %d (Allocated size is: %d, Max size is: %d)", (uint32_t)sizeInBytes, (uint32_t)allocatedSize, (uint32_t)storage->SizeInBytes); return nullptr; } @@ -462,7 +462,7 @@ void SystemPopMemory(MemoryArena memoryArena, size_t sizeInBytes) if (sizeInBytes > allocatedSize) { - SystemLogErrorMessage(ElemLogMessageCategory_Memory, "Cannot pop memory arena with: %u (Allocated size is: %u)", sizeInBytes, allocatedSize); + SystemLogErrorMessage(ElemLogMessageCategory_Memory, "Cannot pop memory arena with: %u (Allocated size is: %u)", (uint32_t)sizeInBytes, (uint32_t)allocatedSize); return; } @@ -470,7 +470,6 @@ void SystemPopMemory(MemoryArena memoryArena, size_t sizeInBytes) if (memoryArena.Storage == stackMemoryArenaStorage) { - pointer = storage->CurrentPointer; storage->CurrentPointer -= sizeInBytes; } else diff --git a/src/Elemental/Elemental.h b/src/Elemental/Elemental.h index b9e7db10..34fdbbbb 100644 --- a/src/Elemental/Elemental.h +++ b/src/Elemental/Elemental.h @@ -42,7 +42,7 @@ typedef uint64_t ElemHandle; #define ELEM_HANDLE_NULL 0u //-------------------------------------------------------------------------------- -// ##Module_Application## +// Module: Elemental //-------------------------------------------------------------------------------- /** @@ -184,6 +184,8 @@ typedef struct float UIScale; // Current state of the window. ElemWindowState WindowState; + // TODO: Return refresh rate + // TODO: Return an ElemMonitor so that we can get the info about the monitor } ElemWindowSize; // TODO: Comments @@ -273,6 +275,11 @@ ElemAPI void ElemSetWindowTitle(ElemWindow window, const char* title); */ ElemAPI void ElemSetWindowState(ElemWindow window, ElemWindowState windowState); +// TODO: Function to get monitor infos + +// TODO: We need to be able to detect if the window is in focus mode. When a game loose focus, +// it should be able to disable mouse tracking (for camera etc.) + // TODO: Comments // TODO: Make sure the coordinates are consistent accross all platforms ElemAPI void ElemShowWindowCursor(ElemWindow window); @@ -298,6 +305,8 @@ typedef ElemHandle ElemCommandQueue; */ typedef ElemHandle ElemCommandList; +typedef ElemHandle ElemIOCommandQueue; + /** * Handle that represents a swap chain. */ @@ -318,6 +327,11 @@ typedef ElemHandle ElemGraphicsResource; */ typedef int32_t ElemGraphicsResourceDescriptor; +/** + * Handle that represents a graphics resource descriptor. + */ +typedef int32_t ElemGraphicsSampler; + /** * Handle that represents a shader library. */ @@ -349,7 +363,9 @@ typedef enum // Command queue for graphics operations. ElemCommandQueueType_Graphics = 0, // Command queue for compute operations. - ElemCommandQueueType_Compute = 1 + ElemCommandQueueType_Compute = 1, + // TODO: Create a dedicated IO queue + //ElemCommandQueueType_IO = 2 } ElemCommandQueueType; /** @@ -366,7 +382,9 @@ typedef enum typedef enum { ElemGraphicsHeapType_Gpu = 0, - ElemGraphicsHeapType_GpuUpload = 1, + // TODO: Still not convinced about that one. should we let GPU be gpu upload by default? + // TODO: This may cause a bug in vulkan with textures (flickering) + ElemGraphicsHeapType_GpuUpload = 1, ElemGraphicsHeapType_Readback = 2 } ElemGraphicsHeapType; @@ -377,22 +395,26 @@ typedef enum { ElemGraphicsFormat_Raw, ElemGraphicsFormat_B8G8R8A8_SRGB, - ElemGraphicsFormat_B8G8R8A8_UNORM, + ElemGraphicsFormat_B8G8R8A8, ElemGraphicsFormat_R16G16B16A16_FLOAT, ElemGraphicsFormat_R32G32B32A32_FLOAT, + ElemGraphicsFormat_D32_FLOAT, + ElemGraphicsFormat_BC7, + ElemGraphicsFormat_BC7_SRGB } ElemGraphicsFormat; typedef enum { ElemGraphicsResourceType_Buffer, - ElemGraphicsResourceType_Texture2D + ElemGraphicsResourceType_Texture2D // TODO: Do we keep the distinction for 2D? Maybe just Texture is enough } ElemGraphicsResourceType; typedef enum { ElemGraphicsResourceUsage_Read = 0x00, ElemGraphicsResourceUsage_Write = 0x01, - ElemGraphicsResourceUsage_RenderTarget = 0x02 + ElemGraphicsResourceUsage_RenderTarget = 0x02, + ElemGraphicsResourceUsage_DepthStencil = 0x04 } ElemGraphicsResourceUsage; typedef enum @@ -401,11 +423,75 @@ typedef enum ElemGraphicsResourceDescriptorUsage_Write = 0x01 } ElemGraphicsResourceDescriptorUsage; +typedef enum +{ + ElemGraphicsSamplerFilter_Nearest = 0, + ElemGraphicsSamplerFilter_Linear = 1 +} ElemGraphicsSamplerFilter; + +typedef enum +{ + ElemGraphicsSamplerAddressMode_Repeat = 0, + ElemGraphicsSamplerAddressMode_RepeatMirror = 1, + ElemGraphicsSamplerAddressMode_ClampToEdge = 2, + ElemGraphicsSamplerAddressMode_ClampToEdgeMirror = 3, + ElemGraphicsSamplerAddressMode_ClampToBorderColor = 4 +} ElemGraphicsSamplerAddressMode; + +typedef enum +{ + ElemGraphicsFillMode_Solid = 0, + ElemGraphicsFillMode_Wireframe = 1 +} ElemGraphicsFillMode; + +typedef enum +{ + ElemGraphicsCullMode_BackFace = 0, + ElemGraphicsCullMode_FrontFace = 1, + ElemGraphicsCullMode_None = 2 +} ElemGraphicsCullMode; + +typedef enum +{ + ElemGraphicsBlendOperation_Add = 0, + ElemGraphicsBlendOperation_Subtract = 1, + ElemGraphicsBlendOperation_ReverseSubtract = 2, + ElemGraphicsBlendOperation_Min = 3, + ElemGraphicsBlendOperation_Max = 4, +} ElemGraphicsBlendOperation; + +typedef enum +{ + ElemGraphicsBlendFactor_Zero = 0, + ElemGraphicsBlendFactor_One = 1, + ElemGraphicsBlendFactor_SourceColor = 2, + ElemGraphicsBlendFactor_InverseSourceColor = 3, + ElemGraphicsBlendFactor_SourceAlpha = 4, + ElemGraphicsBlendFactor_InverseSourceAlpha = 5, + ElemGraphicsBlendFactor_DestinationColor = 6, + ElemGraphicsBlendFactor_InverseDestinationColor = 7, + ElemGraphicsBlendFactor_DestinationAlpha = 8, + ElemGraphicsBlendFactor_InverseDestinationAlpha = 9, + ElemGraphicsBlendFactor_SourceAlphaSaturated = 10, +} ElemGraphicsBlendFactor; + +typedef enum +{ + ElemGraphicsCompareFunction_Never = 0, + ElemGraphicsCompareFunction_Less = 1, + ElemGraphicsCompareFunction_Equal = 2, + ElemGraphicsCompareFunction_LessEqual = 3, + ElemGraphicsCompareFunction_Greater = 4, + ElemGraphicsCompareFunction_NotEqual = 5, + ElemGraphicsCompareFunction_GreaterEqual = 6, + ElemGraphicsCompareFunction_Always = 7 +} ElemGraphicsCompareFunction; + typedef enum { ElemGraphicsResourceBarrierSyncType_None, ElemGraphicsResourceBarrierSyncType_Compute, - ElemGraphicsResourceBarrierSyncType_RenderTarget + ElemGraphicsResourceBarrierSyncType_RenderTarget, } ElemGraphicsResourceBarrierSyncType; typedef enum @@ -413,7 +499,8 @@ typedef enum ElemGraphicsResourceBarrierAccessType_NoAccess, ElemGraphicsResourceBarrierAccessType_Read, ElemGraphicsResourceBarrierAccessType_Write, - ElemGraphicsResourceBarrierAccessType_RenderTarget + ElemGraphicsResourceBarrierAccessType_RenderTarget, + ElemGraphicsResourceBarrierAccessType_DepthStencilWrite, } ElemGraphicsResourceBarrierAccessType; typedef enum @@ -422,6 +509,7 @@ typedef enum ElemGraphicsResourceBarrierLayoutType_Read, ElemGraphicsResourceBarrierLayoutType_Write, ElemGraphicsResourceBarrierLayoutType_RenderTarget, + ElemGraphicsResourceBarrierLayoutType_DepthStencilWrite, ElemGraphicsResourceBarrierLayoutType_Present } ElemGraphicsResourceBarrierLayoutType; @@ -430,12 +518,12 @@ typedef enum */ typedef enum { + // Clears to a predefined value. + ElemRenderPassLoadAction_Clear = 0, // Discards the previous contents. - ElemRenderPassLoadAction_Discard = 0, + ElemRenderPassLoadAction_Discard = 1, // Loads the existing contents. - ElemRenderPassLoadAction_Load = 1, - // Clears to a predefined value. - ElemRenderPassLoadAction_Clear = 2 + ElemRenderPassLoadAction_Load = 2, } ElemRenderPassLoadAction; /** @@ -449,6 +537,69 @@ typedef enum ElemRenderPassStoreAction_Discard = 1 } ElemRenderPassStoreAction; +/** + * Represents RGBA color. + */ +typedef struct +{ + // Red component. + float Red; + // Green component. + float Green; + // Blue component. + float Blue; + // Alpha component. + float Alpha; +} ElemColor; + +typedef struct +{ + // X coordinate of the rectangle's top left corner. + float X; + // Y coordinate of the rectangle's top left corner. + float Y; + // Width of the rectangle. + float Width; + // Height of the rectangle. + float Height; +} ElemRectangle; + +/** + * Represents a collection of rectangles. + */ +typedef struct +{ + // Pointer to an array of ElemRectangle. + ElemRectangle* Items; + // Number of items in the array. + uint32_t Length; +} ElemRectangleSpan; + +typedef struct +{ + float X, Y, Z; +} ElemVector3; + +typedef union +{ + // TODO: Fix compilation warning + struct + { + float X, Y, Z, W; + }; + + struct + { + ElemVector3 XYZ; + }; +} ElemVector4; + +typedef struct +{ + ElemVector3 MinPoint; + ElemVector3 MaxPoint; +} ElemBoundingBox; + /** * Configuration options for graphics initialization. */ @@ -519,6 +670,12 @@ typedef struct const char* DebugName; } ElemCommandListOptions; +typedef struct +{ + // Optional debug name for the command queue. + const char* DebugName; +} ElemIOCommandQueueOptions; + /** * Represents a fence for command list synchronization. */ @@ -581,12 +738,14 @@ typedef struct */ typedef struct { + ElemWindow Window; // Width of the swap chain in pixels. uint32_t Width; // Height of the swap chain in pixels. uint32_t Height; // Aspect ratio of the swap chain. float AspectRatio; + float UIScale; // Format of the textures used in the swap chain. ElemGraphicsFormat Format; } ElemSwapChainInfo; @@ -625,6 +784,7 @@ typedef struct } ElemGraphicsResourceInfoOptions; // TODO: Mip Levels +// TODO: Clear values typedef struct { ElemGraphicsResourceType Type; @@ -644,6 +804,7 @@ typedef struct ElemFenceSpan FencesToWait; } ElemFreeGraphicsResourceOptions; +// TODO: Here, we could add options to support StructuredBuffer (we need a different stride for that) typedef struct { uint32_t TextureMipIndex; @@ -662,16 +823,82 @@ typedef struct ElemFenceSpan FencesToWait; } ElemFreeGraphicsResourceDescriptorOptions; +typedef struct +{ + ElemGraphicsSamplerFilter MinFilter; + ElemGraphicsSamplerFilter MagFilter; + ElemGraphicsSamplerFilter MipFilter; + ElemGraphicsSamplerAddressMode AddressU; + ElemGraphicsSamplerAddressMode AddressV; + ElemGraphicsSamplerAddressMode AddressW; + uint32_t MaxAnisotropy; + ElemGraphicsCompareFunction CompareFunction; + // TODO: Remove this one and do an enum instead + ElemColor BorderColor; + float MinLod; + float MaxLod; + // TODO: Minimum/Maximum filters? +} ElemGraphicsSamplerInfo; + +typedef struct +{ + // Fences that the execution should wait on before starting. + ElemFenceSpan FencesToWait; +} ElemFreeGraphicsSamplerOptions; + +typedef struct +{ + uint32_t Offset; + uint32_t SizeInBytes; +} ElemDownloadGraphicsBufferDataOptions; + +typedef enum +{ + ElemCopyDataSourceType_Memory = 0, + ElemCopyDataSourceType_File = 1, +} ElemCopyDataSourceType; + +typedef struct +{ + ElemGraphicsResource Resource; + uint32_t BufferOffset; + uint32_t TextureMipLevel; + ElemCopyDataSourceType SourceType; + uint32_t SourceFilePath; + uint32_t SourceFileOffset; + uint32_t SourceFileSizeInBytes; + ElemDataSpan SourceMemoryData; + // TODO: Allow specifying texture rowSizeInBytes? +} ElemCopyDataToGraphicsResourceParameters; + +typedef struct +{ + ElemGraphicsFormat Format; + ElemGraphicsBlendOperation BlendOperation; + ElemGraphicsBlendFactor SourceBlendFactor; + ElemGraphicsBlendFactor DestinationBlendFactor; + ElemGraphicsBlendOperation BlendOperationAlpha; + ElemGraphicsBlendFactor SourceBlendFactorAlpha; + ElemGraphicsBlendFactor DestinationBlendFactorAlpha; +} ElemGraphicsPipelineStateRenderTarget; + +typedef struct +{ + ElemGraphicsFormat Format; + bool DepthDisableWrite; + ElemGraphicsCompareFunction DepthCompareFunction; +} ElemGraphicsPipelineStateDepthStencil; + /** * Represents a collection of texture formats. */ typedef struct { - // Pointer to an array of ElemGraphicsFormat. - ElemGraphicsFormat* Items; + // Pointer to an array of ElemGraphicsPipelineStateRenderTarget. + ElemGraphicsPipelineStateRenderTarget* Items; // Number of items in the array. uint32_t Length; -} ElemGraphicsFormatSpan; +} ElemGraphicsPipelineStateRenderTargetSpan; /** * Parameters for creating a graphics pipeline state. @@ -684,8 +911,10 @@ typedef struct const char* MeshShaderFunction; // Function name of the pixel shader in the shader library. const char* PixelShaderFunction; - // Supported texture formats for the pipeline state. - ElemGraphicsFormatSpan TextureFormats; + ElemGraphicsPipelineStateRenderTargetSpan RenderTargets; + ElemGraphicsPipelineStateDepthStencil DepthStencil; + ElemGraphicsFillMode FillMode; + ElemGraphicsCullMode CullMode; // Optional debug name for the pipeline state. const char* DebugName; } ElemGraphicsPipelineStateParameters; @@ -713,21 +942,6 @@ typedef struct ElemGraphicsResourceBarrierLayoutType AfterLayout; } ElemGraphicsResourceBarrierOptions; -/** - * Represents RGBA color. - */ -typedef struct -{ - // Red component. - float Red; - // Green component. - float Green; - // Blue component. - float Blue; - // Alpha component. - float Alpha; -} ElemColor; - /** * Defines a viewport for rendering. */ @@ -764,7 +978,6 @@ typedef struct typedef struct { ElemGraphicsResource RenderTarget; - // Color to clear the render target with if the load action is clear. ElemColor ClearColor; // Action to take when loading data into the render target at the beginning of a render pass. @@ -773,6 +986,15 @@ typedef struct ElemRenderPassStoreAction StoreAction; } ElemRenderPassRenderTarget; +typedef struct +{ + ElemGraphicsResource DepthStencil; + // TODO: Specify read or write mode ? (write by default) + float DepthClearValue; + ElemRenderPassLoadAction DepthLoadAction; + ElemRenderPassStoreAction DepthStoreAction; +} ElemRenderPassDepthBufferStencil; + /** * Represents a collection of render pass targets. */ @@ -791,8 +1013,11 @@ typedef struct { // Render targets to be used in the render pass. ElemRenderPassRenderTargetSpan RenderTargets; + ElemRenderPassDepthBufferStencil DepthStencil; // Viewports to be used in the render pass. ElemViewportSpan Viewports; + + ElemRectangleSpan ScissorRectangles; } ElemBeginRenderPassParameters; /** @@ -886,6 +1111,8 @@ ElemAPI ElemFence ElemExecuteCommandList(ElemCommandQueue commandQueue, ElemComm */ ElemAPI ElemFence ElemExecuteCommandLists(ElemCommandQueue commandQueue, ElemCommandListSpan commandLists, const ElemExecuteCommandListOptions* options); +// TODO: Add a create fence function for a given commandqueue? + /** * Waits for a fence to reach its signaled state on the CPU, effectively synchronizing CPU and GPU operations. * @param fence The fence to wait on. @@ -933,19 +1160,28 @@ ElemAPI void ElemPresentSwapChain(ElemSwapChain swapChain); ElemAPI ElemGraphicsHeap ElemCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uint64_t sizeInBytes, const ElemGraphicsHeapOptions* options); ElemAPI void ElemFreeGraphicsHeap(ElemGraphicsHeap graphicsHeap); +// TODO: uint64_t for sizeInBytes? ElemAPI ElemGraphicsResourceInfo ElemCreateGraphicsBufferResourceInfo(ElemGraphicsDevice graphicsDevice, uint32_t sizeInBytes, ElemGraphicsResourceUsage usage, const ElemGraphicsResourceInfoOptions* options); ElemAPI ElemGraphicsResourceInfo ElemCreateTexture2DResourceInfo(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, uint32_t mipLevels, ElemGraphicsFormat format, ElemGraphicsResourceUsage usage, const ElemGraphicsResourceInfoOptions* options); ElemAPI ElemGraphicsResource ElemCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, uint64_t graphicsHeapOffset, const ElemGraphicsResourceInfo* resourceInfo); ElemAPI void ElemFreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGraphicsResourceOptions* options); ElemAPI ElemGraphicsResourceInfo ElemGetGraphicsResourceInfo(ElemGraphicsResource resource); -ElemAPI ElemDataSpan ElemGetGraphicsResourceDataSpan(ElemGraphicsResource resource); + +// TODO: uint64_t for offset? +ElemAPI void ElemUploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data); +ElemAPI ElemDataSpan ElemDownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options); +ElemAPI void ElemCopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters); ElemAPI ElemGraphicsResourceDescriptor ElemCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options); ElemAPI ElemGraphicsResourceDescriptorInfo ElemGetGraphicsResourceDescriptorInfo(ElemGraphicsResourceDescriptor descriptor); ElemAPI void ElemFreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor descriptor, const ElemFreeGraphicsResourceDescriptorOptions* options); -ElemAPI void ElemProcessGraphicsResourceDeleteQueue(void); +ElemAPI void ElemProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice); + +ElemAPI ElemGraphicsSampler ElemCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo); +ElemAPI ElemGraphicsSamplerInfo ElemGetGraphicsSamplerInfo(ElemGraphicsSampler sampler); +ElemAPI void ElemFreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options); /** * Creates a shader library from provided binary data, allowing shaders to be loaded and used by graphics pipeline states. @@ -1031,6 +1267,10 @@ ElemAPI void ElemSetViewport(ElemCommandList commandList, const ElemViewport* vi */ ElemAPI void ElemSetViewports(ElemCommandList commandList, ElemViewportSpan viewports); +ElemAPI void ElemSetScissorRectangle(ElemCommandList commandList, const ElemRectangle* rectangle); + +ElemAPI void ElemSetScissorRectangles(ElemCommandList commandList, ElemRectangleSpan rectangles); + /** * Dispatches a mesh shader operation on a command list, specifying the number of thread groups in each dimension. * @param commandList The command list on which the mesh operation is to be dispatched. @@ -1183,17 +1423,17 @@ typedef enum ElemInputId_MouseWheelPositive = 115, ElemInputId_MouseHorizontalWheelNegative = 116, ElemInputId_MouseHorizontalWheelPositive = 117, - ElemInputID_GamepadButtonA = 118, - ElemInputID_GamepadButtonB = 119, - ElemInputID_GamepadButtonX = 120, - ElemInputID_GamepadButtonY = 121, - ElemInputID_GamepadButtonMenu = 122, - ElemInputID_GamepadButtonOptions = 123, - ElemInputID_GamepadButtonHome = 124, - ElemInputID_GamepadLeftShoulder = 125, - ElemInputID_GamepadRightShoulder = 126, - ElemInputID_GamepadLeftTrigger = 127, - ElemInputID_GamepadRightTrigger = 128, + ElemInputId_GamepadButtonA = 118, + ElemInputId_GamepadButtonB = 119, + ElemInputId_GamepadButtonX = 120, + ElemInputId_GamepadButtonY = 121, + ElemInputId_GamepadButtonShare = 122, + ElemInputId_GamepadButtonOptions = 123, + ElemInputId_GamepadButtonHome = 124, + ElemInputId_GamepadLeftShoulder = 125, + ElemInputId_GamepadRightShoulder = 126, + ElemInputId_GamepadLeftTrigger = 127, + ElemInputId_GamepadRightTrigger = 128, ElemInputId_GamepadLeftStickXNegative = 129, ElemInputId_GamepadLeftStickXPositive = 130, ElemInputId_GamepadLeftStickYNegative = 131, @@ -1208,27 +1448,36 @@ typedef enum ElemInputId_GamepadDpadRight = 140, ElemInputId_GamepadDpadDown = 141, ElemInputId_GamepadDpadLeft = 142, - ElemInputId_Touch = 143, - ElemInputId_TouchXNegative = 144, - ElemInputId_TouchXPositive = 145, - ElemInputId_TouchYNegative = 146, - ElemInputId_TouchYPositive = 147, - ElemInputId_TouchXAbsolutePosition = 148, - ElemInputId_TouchYAbsolutePosition = 149 + ElemInputId_GamepadTouchButton = 143, + ElemInputId_AngularVelocityXNegative = 144, + ElemInputId_AngularVelocityXPositive = 145, + ElemInputId_AngularVelocityYNegative = 146, + ElemInputId_AngularVelocityYPositive = 147, + ElemInputId_AngularVelocityZNegative = 148, + ElemInputId_AngularVelocityZPositive = 149, + ElemInputId_AccelerometerXNegative = 150, + ElemInputId_AccelerometerXPositive = 151, + ElemInputId_AccelerometerYNegative = 152, + ElemInputId_AccelerometerYPositive = 153, + ElemInputId_AccelerometerZNegative = 154, + ElemInputId_AccelerometerZPositive = 155, + ElemInputId_Touch = 156, + ElemInputId_TouchXNegative = 157, + ElemInputId_TouchXPositive = 158, + ElemInputId_TouchYNegative = 159, + ElemInputId_TouchYPositive = 160, + ElemInputId_TouchXAbsolutePosition = 161, + ElemInputId_TouchYAbsolutePosition = 162 } ElemInputId; typedef struct { ElemInputDevice Handle; ElemInputDeviceType DeviceType; + // TODO: Add device name + // TODO: Add device capabilities for gamepad: Gyros, Touchpad, etc. } ElemInputDeviceInfo; -typedef struct -{ - ElemInputDeviceInfo* Items; - uint32_t Length; -} ElemInputDeviceInfoSpan; - typedef struct { ElemWindow Window; diff --git a/src/Elemental/ElementalLoader.c b/src/Elemental/ElementalLoader.c index 6a9acf63..f16e6e98 100644 --- a/src/Elemental/ElementalLoader.c +++ b/src/Elemental/ElementalLoader.c @@ -54,18 +54,23 @@ typedef struct ElementalFunctions ElemSwapChainInfo (*ElemGetSwapChainInfo)(ElemSwapChain); void (*ElemSetSwapChainTiming)(ElemSwapChain, unsigned int, unsigned int); void (*ElemPresentSwapChain)(ElemSwapChain); - ElemGraphicsHeap (*ElemCreateGraphicsHeap)(ElemGraphicsDevice, unsigned long long, ElemGraphicsHeapOptions const *); + ElemGraphicsHeap (*ElemCreateGraphicsHeap)(ElemGraphicsDevice, uint64_t, ElemGraphicsHeapOptions const *); void (*ElemFreeGraphicsHeap)(ElemGraphicsHeap); ElemGraphicsResourceInfo (*ElemCreateGraphicsBufferResourceInfo)(ElemGraphicsDevice, unsigned int, ElemGraphicsResourceUsage, ElemGraphicsResourceInfoOptions const *); ElemGraphicsResourceInfo (*ElemCreateTexture2DResourceInfo)(ElemGraphicsDevice, unsigned int, unsigned int, unsigned int, ElemGraphicsFormat, ElemGraphicsResourceUsage, ElemGraphicsResourceInfoOptions const *); - ElemGraphicsResource (*ElemCreateGraphicsResource)(ElemGraphicsHeap, unsigned long long, ElemGraphicsResourceInfo const *); + ElemGraphicsResource (*ElemCreateGraphicsResource)(ElemGraphicsHeap, uint64_t, ElemGraphicsResourceInfo const *); void (*ElemFreeGraphicsResource)(ElemGraphicsResource, ElemFreeGraphicsResourceOptions const *); ElemGraphicsResourceInfo (*ElemGetGraphicsResourceInfo)(ElemGraphicsResource); - ElemDataSpan (*ElemGetGraphicsResourceDataSpan)(ElemGraphicsResource); + void (*ElemUploadGraphicsBufferData)(ElemGraphicsResource, unsigned int, ElemDataSpan); + ElemDataSpan (*ElemDownloadGraphicsBufferData)(ElemGraphicsResource, ElemDownloadGraphicsBufferDataOptions const *); + void (*ElemCopyDataToGraphicsResource)(ElemCommandList, ElemCopyDataToGraphicsResourceParameters const *); ElemGraphicsResourceDescriptor (*ElemCreateGraphicsResourceDescriptor)(ElemGraphicsResource, ElemGraphicsResourceDescriptorUsage, ElemGraphicsResourceDescriptorOptions const *); ElemGraphicsResourceDescriptorInfo (*ElemGetGraphicsResourceDescriptorInfo)(ElemGraphicsResourceDescriptor); void (*ElemFreeGraphicsResourceDescriptor)(ElemGraphicsResourceDescriptor, ElemFreeGraphicsResourceDescriptorOptions const *); - void (*ElemProcessGraphicsResourceDeleteQueue)(void); + void (*ElemProcessGraphicsResourceDeleteQueue)(ElemGraphicsDevice); + ElemGraphicsSampler (*ElemCreateGraphicsSampler)(ElemGraphicsDevice, ElemGraphicsSamplerInfo const *); + ElemGraphicsSamplerInfo (*ElemGetGraphicsSamplerInfo)(ElemGraphicsSampler); + void (*ElemFreeGraphicsSampler)(ElemGraphicsSampler, ElemFreeGraphicsSamplerOptions const *); ElemShaderLibrary (*ElemCreateShaderLibrary)(ElemGraphicsDevice, ElemDataSpan); void (*ElemFreeShaderLibrary)(ElemShaderLibrary); ElemPipelineState (*ElemCompileGraphicsPipelineState)(ElemGraphicsDevice, ElemGraphicsPipelineStateParameters const *); @@ -79,6 +84,8 @@ typedef struct ElementalFunctions void (*ElemEndRenderPass)(ElemCommandList); void (*ElemSetViewport)(ElemCommandList, ElemViewport const *); void (*ElemSetViewports)(ElemCommandList, ElemViewportSpan); + void (*ElemSetScissorRectangle)(ElemCommandList, ElemRectangle const *); + void (*ElemSetScissorRectangles)(ElemCommandList, ElemRectangleSpan); void (*ElemDispatchMesh)(ElemCommandList, unsigned int, unsigned int, unsigned int); ElemInputDeviceInfo (*ElemGetInputDeviceInfo)(ElemInputDevice); ElemInputStream (*ElemGetInputStream)(void); @@ -165,18 +172,23 @@ static bool LoadElementalFunctionPointers(void) listElementalFunctions.ElemGetSwapChainInfo = (ElemSwapChainInfo (*)(ElemSwapChain))GetElementalFunctionPointer("ElemGetSwapChainInfo"); listElementalFunctions.ElemSetSwapChainTiming = (void (*)(ElemSwapChain, unsigned int, unsigned int))GetElementalFunctionPointer("ElemSetSwapChainTiming"); listElementalFunctions.ElemPresentSwapChain = (void (*)(ElemSwapChain))GetElementalFunctionPointer("ElemPresentSwapChain"); - listElementalFunctions.ElemCreateGraphicsHeap = (ElemGraphicsHeap (*)(ElemGraphicsDevice, unsigned long long, ElemGraphicsHeapOptions const *))GetElementalFunctionPointer("ElemCreateGraphicsHeap"); + listElementalFunctions.ElemCreateGraphicsHeap = (ElemGraphicsHeap (*)(ElemGraphicsDevice, uint64_t, ElemGraphicsHeapOptions const *))GetElementalFunctionPointer("ElemCreateGraphicsHeap"); listElementalFunctions.ElemFreeGraphicsHeap = (void (*)(ElemGraphicsHeap))GetElementalFunctionPointer("ElemFreeGraphicsHeap"); listElementalFunctions.ElemCreateGraphicsBufferResourceInfo = (ElemGraphicsResourceInfo (*)(ElemGraphicsDevice, unsigned int, ElemGraphicsResourceUsage, ElemGraphicsResourceInfoOptions const *))GetElementalFunctionPointer("ElemCreateGraphicsBufferResourceInfo"); listElementalFunctions.ElemCreateTexture2DResourceInfo = (ElemGraphicsResourceInfo (*)(ElemGraphicsDevice, unsigned int, unsigned int, unsigned int, ElemGraphicsFormat, ElemGraphicsResourceUsage, ElemGraphicsResourceInfoOptions const *))GetElementalFunctionPointer("ElemCreateTexture2DResourceInfo"); - listElementalFunctions.ElemCreateGraphicsResource = (ElemGraphicsResource (*)(ElemGraphicsHeap, unsigned long long, ElemGraphicsResourceInfo const *))GetElementalFunctionPointer("ElemCreateGraphicsResource"); + listElementalFunctions.ElemCreateGraphicsResource = (ElemGraphicsResource (*)(ElemGraphicsHeap, uint64_t, ElemGraphicsResourceInfo const *))GetElementalFunctionPointer("ElemCreateGraphicsResource"); listElementalFunctions.ElemFreeGraphicsResource = (void (*)(ElemGraphicsResource, ElemFreeGraphicsResourceOptions const *))GetElementalFunctionPointer("ElemFreeGraphicsResource"); listElementalFunctions.ElemGetGraphicsResourceInfo = (ElemGraphicsResourceInfo (*)(ElemGraphicsResource))GetElementalFunctionPointer("ElemGetGraphicsResourceInfo"); - listElementalFunctions.ElemGetGraphicsResourceDataSpan = (ElemDataSpan (*)(ElemGraphicsResource))GetElementalFunctionPointer("ElemGetGraphicsResourceDataSpan"); + listElementalFunctions.ElemUploadGraphicsBufferData = (void (*)(ElemGraphicsResource, unsigned int, ElemDataSpan))GetElementalFunctionPointer("ElemUploadGraphicsBufferData"); + listElementalFunctions.ElemDownloadGraphicsBufferData = (ElemDataSpan (*)(ElemGraphicsResource, ElemDownloadGraphicsBufferDataOptions const *))GetElementalFunctionPointer("ElemDownloadGraphicsBufferData"); + listElementalFunctions.ElemCopyDataToGraphicsResource = (void (*)(ElemCommandList, ElemCopyDataToGraphicsResourceParameters const *))GetElementalFunctionPointer("ElemCopyDataToGraphicsResource"); listElementalFunctions.ElemCreateGraphicsResourceDescriptor = (ElemGraphicsResourceDescriptor (*)(ElemGraphicsResource, ElemGraphicsResourceDescriptorUsage, ElemGraphicsResourceDescriptorOptions const *))GetElementalFunctionPointer("ElemCreateGraphicsResourceDescriptor"); listElementalFunctions.ElemGetGraphicsResourceDescriptorInfo = (ElemGraphicsResourceDescriptorInfo (*)(ElemGraphicsResourceDescriptor))GetElementalFunctionPointer("ElemGetGraphicsResourceDescriptorInfo"); listElementalFunctions.ElemFreeGraphicsResourceDescriptor = (void (*)(ElemGraphicsResourceDescriptor, ElemFreeGraphicsResourceDescriptorOptions const *))GetElementalFunctionPointer("ElemFreeGraphicsResourceDescriptor"); - listElementalFunctions.ElemProcessGraphicsResourceDeleteQueue = (void (*)(void))GetElementalFunctionPointer("ElemProcessGraphicsResourceDeleteQueue"); + listElementalFunctions.ElemProcessGraphicsResourceDeleteQueue = (void (*)(ElemGraphicsDevice))GetElementalFunctionPointer("ElemProcessGraphicsResourceDeleteQueue"); + listElementalFunctions.ElemCreateGraphicsSampler = (ElemGraphicsSampler (*)(ElemGraphicsDevice, ElemGraphicsSamplerInfo const *))GetElementalFunctionPointer("ElemCreateGraphicsSampler"); + listElementalFunctions.ElemGetGraphicsSamplerInfo = (ElemGraphicsSamplerInfo (*)(ElemGraphicsSampler))GetElementalFunctionPointer("ElemGetGraphicsSamplerInfo"); + listElementalFunctions.ElemFreeGraphicsSampler = (void (*)(ElemGraphicsSampler, ElemFreeGraphicsSamplerOptions const *))GetElementalFunctionPointer("ElemFreeGraphicsSampler"); listElementalFunctions.ElemCreateShaderLibrary = (ElemShaderLibrary (*)(ElemGraphicsDevice, ElemDataSpan))GetElementalFunctionPointer("ElemCreateShaderLibrary"); listElementalFunctions.ElemFreeShaderLibrary = (void (*)(ElemShaderLibrary))GetElementalFunctionPointer("ElemFreeShaderLibrary"); listElementalFunctions.ElemCompileGraphicsPipelineState = (ElemPipelineState (*)(ElemGraphicsDevice, ElemGraphicsPipelineStateParameters const *))GetElementalFunctionPointer("ElemCompileGraphicsPipelineState"); @@ -190,6 +202,8 @@ static bool LoadElementalFunctionPointers(void) listElementalFunctions.ElemEndRenderPass = (void (*)(ElemCommandList))GetElementalFunctionPointer("ElemEndRenderPass"); listElementalFunctions.ElemSetViewport = (void (*)(ElemCommandList, ElemViewport const *))GetElementalFunctionPointer("ElemSetViewport"); listElementalFunctions.ElemSetViewports = (void (*)(ElemCommandList, ElemViewportSpan))GetElementalFunctionPointer("ElemSetViewports"); + listElementalFunctions.ElemSetScissorRectangle = (void (*)(ElemCommandList, ElemRectangle const *))GetElementalFunctionPointer("ElemSetScissorRectangle"); + listElementalFunctions.ElemSetScissorRectangles = (void (*)(ElemCommandList, ElemRectangleSpan))GetElementalFunctionPointer("ElemSetScissorRectangles"); listElementalFunctions.ElemDispatchMesh = (void (*)(ElemCommandList, unsigned int, unsigned int, unsigned int))GetElementalFunctionPointer("ElemDispatchMesh"); listElementalFunctions.ElemGetInputDeviceInfo = (ElemInputDeviceInfo (*)(ElemInputDevice))GetElementalFunctionPointer("ElemGetInputDeviceInfo"); listElementalFunctions.ElemGetInputStream = (ElemInputStream (*)(void))GetElementalFunctionPointer("ElemGetInputStream"); @@ -1023,7 +1037,7 @@ static inline void ElemPresentSwapChain(ElemSwapChain swapChain) listElementalFunctions.ElemPresentSwapChain(swapChain); } -static inline ElemGraphicsHeap ElemCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, unsigned long long sizeInBytes, ElemGraphicsHeapOptions const * options) +static inline ElemGraphicsHeap ElemCreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uint64_t sizeInBytes, ElemGraphicsHeapOptions const * options) { if (!LoadElementalFunctionPointers()) { @@ -1133,7 +1147,7 @@ static inline ElemGraphicsResourceInfo ElemCreateTexture2DResourceInfo(ElemGraph return listElementalFunctions.ElemCreateTexture2DResourceInfo(graphicsDevice, width, height, mipLevels, format, usage, options); } -static inline ElemGraphicsResource ElemCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, unsigned long long graphicsHeapOffset, ElemGraphicsResourceInfo const * resourceInfo) +static inline ElemGraphicsResource ElemCreateGraphicsResource(ElemGraphicsHeap graphicsHeap, uint64_t graphicsHeapOffset, ElemGraphicsResourceInfo const * resourceInfo) { if (!LoadElementalFunctionPointers()) { @@ -1212,7 +1226,24 @@ static inline ElemGraphicsResourceInfo ElemGetGraphicsResourceInfo(ElemGraphicsR return listElementalFunctions.ElemGetGraphicsResourceInfo(resource); } -static inline ElemDataSpan ElemGetGraphicsResourceDataSpan(ElemGraphicsResource resource) +static inline void ElemUploadGraphicsBufferData(ElemGraphicsResource resource, unsigned int offset, ElemDataSpan data) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + return; + } + + if (!listElementalFunctions.ElemUploadGraphicsBufferData) + { + assert(listElementalFunctions.ElemUploadGraphicsBufferData); + return; + } + + listElementalFunctions.ElemUploadGraphicsBufferData(resource, offset, data); +} + +static inline ElemDataSpan ElemDownloadGraphicsBufferData(ElemGraphicsResource resource, ElemDownloadGraphicsBufferDataOptions const * options) { if (!LoadElementalFunctionPointers()) { @@ -1227,9 +1258,9 @@ static inline ElemDataSpan ElemGetGraphicsResourceDataSpan(ElemGraphicsResource return result; } - if (!listElementalFunctions.ElemGetGraphicsResourceDataSpan) + if (!listElementalFunctions.ElemDownloadGraphicsBufferData) { - assert(listElementalFunctions.ElemGetGraphicsResourceDataSpan); + assert(listElementalFunctions.ElemDownloadGraphicsBufferData); #ifdef __cplusplus ElemDataSpan result = {}; @@ -1240,7 +1271,24 @@ static inline ElemDataSpan ElemGetGraphicsResourceDataSpan(ElemGraphicsResource return result; } - return listElementalFunctions.ElemGetGraphicsResourceDataSpan(resource); + return listElementalFunctions.ElemDownloadGraphicsBufferData(resource, options); +} + +static inline void ElemCopyDataToGraphicsResource(ElemCommandList commandList, ElemCopyDataToGraphicsResourceParameters const * parameters) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + return; + } + + if (!listElementalFunctions.ElemCopyDataToGraphicsResource) + { + assert(listElementalFunctions.ElemCopyDataToGraphicsResource); + return; + } + + listElementalFunctions.ElemCopyDataToGraphicsResource(commandList, parameters); } static inline ElemGraphicsResourceDescriptor ElemCreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, ElemGraphicsResourceDescriptorOptions const * options) @@ -1322,7 +1370,7 @@ static inline void ElemFreeGraphicsResourceDescriptor(ElemGraphicsResourceDescri listElementalFunctions.ElemFreeGraphicsResourceDescriptor(descriptor, options); } -static inline void ElemProcessGraphicsResourceDeleteQueue(void) +static inline void ElemProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice) { if (!LoadElementalFunctionPointers()) { @@ -1336,7 +1384,86 @@ static inline void ElemProcessGraphicsResourceDeleteQueue(void) return; } - listElementalFunctions.ElemProcessGraphicsResourceDeleteQueue(); + listElementalFunctions.ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); +} + +static inline ElemGraphicsSampler ElemCreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, ElemGraphicsSamplerInfo const * samplerInfo) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + + #ifdef __cplusplus + ElemGraphicsSampler result = {}; + #else + ElemGraphicsSampler result = (ElemGraphicsSampler){0}; + #endif + + return result; + } + + if (!listElementalFunctions.ElemCreateGraphicsSampler) + { + assert(listElementalFunctions.ElemCreateGraphicsSampler); + + #ifdef __cplusplus + ElemGraphicsSampler result = {}; + #else + ElemGraphicsSampler result = (ElemGraphicsSampler){0}; + #endif + + return result; + } + + return listElementalFunctions.ElemCreateGraphicsSampler(graphicsDevice, samplerInfo); +} + +static inline ElemGraphicsSamplerInfo ElemGetGraphicsSamplerInfo(ElemGraphicsSampler sampler) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + + #ifdef __cplusplus + ElemGraphicsSamplerInfo result = {}; + #else + ElemGraphicsSamplerInfo result = (ElemGraphicsSamplerInfo){0}; + #endif + + return result; + } + + if (!listElementalFunctions.ElemGetGraphicsSamplerInfo) + { + assert(listElementalFunctions.ElemGetGraphicsSamplerInfo); + + #ifdef __cplusplus + ElemGraphicsSamplerInfo result = {}; + #else + ElemGraphicsSamplerInfo result = (ElemGraphicsSamplerInfo){0}; + #endif + + return result; + } + + return listElementalFunctions.ElemGetGraphicsSamplerInfo(sampler); +} + +static inline void ElemFreeGraphicsSampler(ElemGraphicsSampler sampler, ElemFreeGraphicsSamplerOptions const * options) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + return; + } + + if (!listElementalFunctions.ElemFreeGraphicsSampler) + { + assert(listElementalFunctions.ElemFreeGraphicsSampler); + return; + } + + listElementalFunctions.ElemFreeGraphicsSampler(sampler, options); } static inline ElemShaderLibrary ElemCreateShaderLibrary(ElemGraphicsDevice graphicsDevice, ElemDataSpan shaderLibraryData) @@ -1602,6 +1729,40 @@ static inline void ElemSetViewports(ElemCommandList commandList, ElemViewportSpa listElementalFunctions.ElemSetViewports(commandList, viewports); } +static inline void ElemSetScissorRectangle(ElemCommandList commandList, ElemRectangle const * rectangle) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + return; + } + + if (!listElementalFunctions.ElemSetScissorRectangle) + { + assert(listElementalFunctions.ElemSetScissorRectangle); + return; + } + + listElementalFunctions.ElemSetScissorRectangle(commandList, rectangle); +} + +static inline void ElemSetScissorRectangles(ElemCommandList commandList, ElemRectangleSpan rectangles) +{ + if (!LoadElementalFunctionPointers()) + { + assert(libraryElemental); + return; + } + + if (!listElementalFunctions.ElemSetScissorRectangles) + { + assert(listElementalFunctions.ElemSetScissorRectangles); + return; + } + + listElementalFunctions.ElemSetScissorRectangles(commandList, rectangles); +} + static inline void ElemDispatchMesh(ElemCommandList commandList, unsigned int threadGroupCountX, unsigned int threadGroupCountY, unsigned int threadGroupCountZ) { if (!LoadElementalFunctionPointers()) diff --git a/src/Elemental/Linux/PreCompiledHeader.h b/src/Elemental/Linux/PreCompiledHeader.h index 84bb345e..bafde8f7 100644 --- a/src/Elemental/Linux/PreCompiledHeader.h +++ b/src/Elemental/Linux/PreCompiledHeader.h @@ -33,7 +33,6 @@ typedef signed int HRESULT; #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0) #define ElemAPI extern "C" __attribute__((visibility("default"))) -#define PackedStruct struct __attribute__((__packed__)) #define AssertIfFailed(expression) SystemAssert(SUCCEEDED((expression))) #define AssertIfFailedReturnNullHandle(expression) SystemAssertReturnNullHandle(SUCCEEDED((expression))) #define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a))) diff --git a/src/Elemental/Linux/UnityBuild.cpp b/src/Elemental/Linux/UnityBuild.cpp index a47e62d1..d2443844 100644 --- a/src/Elemental/Linux/UnityBuild.cpp +++ b/src/Elemental/Linux/UnityBuild.cpp @@ -12,17 +12,22 @@ #include "Graphics/Vulkan/VulkanGraphicsDevice.cpp" #include "Graphics/Vulkan/VulkanCommandList.cpp" #include "Graphics/Vulkan/VulkanSwapChain.cpp" -#include "Graphics/Vulkan/VulkanTexture.cpp" +#include "Graphics/Vulkan/VulkanResource.cpp" +#include "Graphics/Vulkan/VulkanResourceBarrier.cpp" #include "Graphics/Vulkan/VulkanShader.cpp" #include "Graphics/Vulkan/VulkanRendering.cpp" #include "Graphics/ShaderReader.cpp" +#include "Graphics/ResourceDeleteQueue.cpp" #include "Graphics/CommandAllocatorPool.cpp" +#include "Graphics/ResourceBarrier.cpp" #include "Graphics/GraphicsDevice.cpp" #include "Graphics/CommandList.cpp" #include "Graphics/SwapChain.cpp" +#include "Graphics/Resource.cpp" #include "Graphics/Shader.cpp" #include "Graphics/Rendering.cpp" +#include "Graphics/UploadBufferPool.cpp" #include "Inputs/Inputs.cpp" #include "Inputs/HidDevices.cpp" diff --git a/src/Elemental/Microsoft/Graphics/DirectX12CommandList.cpp b/src/Elemental/Microsoft/Graphics/DirectX12CommandList.cpp index 89939524..6d142edb 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12CommandList.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12CommandList.cpp @@ -1,12 +1,10 @@ #include "DirectX12CommandList.h" +#include "DirectX12Config.h" #include "DirectX12GraphicsDevice.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define DIRECTX12_MAX_COMMANDQUEUES 10u -#define DIRECTX12_MAX_COMMANDLISTS 64u - SystemDataPool directX12CommandQueuePool; SystemDataPool directX12CommandListPool; @@ -98,6 +96,7 @@ ElemCommandQueue DirectX12CreateCommandQueue(ElemGraphicsDevice graphicsDevice, commandQueue->SetName(SystemConvertUtf8ToWideChar(stackMemoryArena, options->DebugName).Pointer); } + // TODO: This need to be checked. We don't know how many max threads will use this. Maybe we can allocate for MAX_CONC_THREADS variable of param (that can be overriden) auto commandAllocators = SystemPushArray>(DirectX12MemoryArena, DIRECTX12_MAX_COMMANDLISTS); auto commandLists = SystemPushArray>(DirectX12MemoryArena, DIRECTX12_MAX_COMMANDLISTS * DIRECTX12_MAX_COMMANDLISTS); @@ -229,10 +228,11 @@ ElemCommandList DirectX12GetCommandList(ElemCommandQueue commandQueue, const Ele AssertIfFailedReturnNullHandle(commandListPoolItem->CommandList->Reset(commandAllocatorPoolItem->CommandAllocator, nullptr)); - auto descriptorHeaps = SystemPushArray(stackMemoryArena, 1); + auto descriptorHeaps = SystemPushArray(stackMemoryArena, 2); descriptorHeaps[0] = graphicsDeviceData->ResourceDescriptorHeap.Storage->DescriptorHeap.Get(); + descriptorHeaps[1] = graphicsDeviceData->SamplerDescriptorHeap.Storage->DescriptorHeap.Get(); - commandListPoolItem->CommandList->SetDescriptorHeaps(1, descriptorHeaps.Pointer); + commandListPoolItem->CommandList->SetDescriptorHeaps(2, descriptorHeaps.Pointer); // TODO: Can we set the root signature for all types all the time? commandListPoolItem->CommandList->SetGraphicsRootSignature(graphicsDeviceData->RootSignature.Get()); @@ -245,7 +245,8 @@ ElemCommandList DirectX12GetCommandList(ElemCommandQueue commandQueue, const Ele .CommandAllocatorPoolItem = commandAllocatorPoolItem, .CommandListPoolItem = commandListPoolItem, .GraphicsDevice = commandQueueData->GraphicsDevice, - .ResourceBarrierPool = resourceBarrierPool + .ResourceBarrierPool = resourceBarrierPool, + .UploadBufferCount = 0 }); SystemAddDataPoolItemFull(directX12CommandListPool, handle, { @@ -261,6 +262,8 @@ void DirectX12CommitCommandList(ElemCommandList commandList) auto commandListData = GetDirectX12CommandListData(commandList); AssertIfFailed(commandListData->DeviceObject->Close()); + FreeResourceBarrierPool(commandListData->ResourceBarrierPool); + commandListData->IsCommitted = true; threadDirectX12CommandBufferCommitted = true; } @@ -326,8 +329,13 @@ ElemFence DirectX12ExecuteCommandLists(ElemCommandQueue commandQueue, ElemComman } UpdateCommandAllocatorPoolItemFence(commandListData->CommandAllocatorPoolItem, fence); + + for (uint32_t j = 0; j < commandListData->UploadBufferCount; j++) + { + UpdateUploadBufferPoolItemFence(commandListData->UploadBufferPoolItems[j], fence); + } + ReleaseCommandListPoolItem(commandListData->CommandListPoolItem); - FreeResourceBarrierPool(commandListData->ResourceBarrierPool); SystemRemoveDataPoolItem(directX12CommandListPool, commandLists.Items[i]); } @@ -353,7 +361,8 @@ void DirectX12WaitForFenceOnCpu(ElemFence fence) if (fence.FenceValue > commandQueueToWaitDataFull->LastCompletedFenceValue) { - SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Wait for Fence on CPU..."); + // TODO: Activate that message with a debug flag + //SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Wait for Fence on CPU..."); commandQueueToWaitDataFull->Fence->SetEventOnCompletion(fence.FenceValue, directX12GlobalFenceEvent); // TODO: It is a little bit extreme to block at infinity. We should set a timeout and break the program. diff --git a/src/Elemental/Microsoft/Graphics/DirectX12CommandList.h b/src/Elemental/Microsoft/Graphics/DirectX12CommandList.h index adf05a97..7ad54d54 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12CommandList.h +++ b/src/Elemental/Microsoft/Graphics/DirectX12CommandList.h @@ -4,6 +4,7 @@ #include "SystemSpan.h" #include "Graphics/CommandAllocatorPool.h" #include "Graphics/ResourceBarrier.h" +#include "Graphics/UploadBufferPool.h" enum DirectX12PipelineStateType { @@ -39,6 +40,8 @@ struct DirectX12CommandListData ElemGraphicsDevice GraphicsDevice; bool IsCommitted; ResourceBarrierPool ResourceBarrierPool; + UploadBufferPoolItem>* UploadBufferPoolItems[MAX_UPLOAD_BUFFERS]; + uint32_t UploadBufferCount; }; struct DirectX12CommandListDataFull diff --git a/src/Elemental/Microsoft/Graphics/DirectX12Config.h b/src/Elemental/Microsoft/Graphics/DirectX12Config.h new file mode 100644 index 00000000..94c66542 --- /dev/null +++ b/src/Elemental/Microsoft/Graphics/DirectX12Config.h @@ -0,0 +1,25 @@ +#pragma once + +#define D3D12SDK_VERSION 614 +#define D3D12SDK_PATH ".\\" + +#define DIRECTX12_MEMORY_ARENA 512 * 1024 * 1024 +#define DIRECTX12_READBACK_MEMORY_ARENA 32 * 1024 * 1024 + +#define DIRECTX12_MAX_DEVICES 10u +#define DIRECTX12_MAX_RTVS 1024u + +#define DIRECTX12_MAX_COMMANDQUEUES 10u +#define DIRECTX12_MAX_COMMANDLISTS 64u + +#define DIRECTX12_MAX_GRAPHICSHEAP 32 +#define DIRECTX12_MAX_RESOURCES 1000000 +#define DIRECTX12_MAX_SAMPLERS 2048 +#define DIRECTX12_MAX_MIPS 16 + +#define DIRECTX12_MAX_SWAPCHAIN_BUFFERS 3 +#define DIRECTX12_MAX_SWAPCHAINS 10u + +#define DIRECTX12_MAX_LIBRARIES UINT16_MAX +#define DIRECTX12_MAX_PIPELINESTATES UINT16_MAX + diff --git a/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.cpp b/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.cpp index 862d1968..956f9c51 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.cpp @@ -1,13 +1,10 @@ #include "DirectX12GraphicsDevice.h" +#include "DirectX12Config.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemLogging.h" #include "SystemMemory.h" -#define D3D12SDK_VERSION 614 -#define D3D12SDK_PATH ".\\" -#define DIRECTX12_MAX_RTVS 1024u - struct DirectX12DescriptorHeapFreeListItem { uint32_t Next; @@ -36,6 +33,8 @@ ComPtr dxgiDebugInterface; ComPtr directX12DebugInterface; ComPtr directX12DeviceFactory; +// TODO: For time queries, use https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12commandqueue-getclockcalibration + void InitDirectX12() { auto stackMemoryArena = SystemGetStackMemoryArena(); @@ -105,7 +104,8 @@ void InitDirectX12GraphicsDeviceMemory() { if (!DirectX12MemoryArena.Storage) { - DirectX12MemoryArena = SystemAllocateMemoryArena(); + // TODO: To review + DirectX12MemoryArena = SystemAllocateMemoryArena(DIRECTX12_MEMORY_ARENA); directX12GraphicsDevicePool = SystemCreateDataPool(DirectX12MemoryArena, DIRECTX12_MAX_DEVICES); InitDirectX12(); @@ -182,6 +182,36 @@ bool DirectX12CheckGraphicsDeviceCompatibility(ComPtr graphicsDe return false; } +D3D12_COMPARISON_FUNC ConvertToDirectX12CompareFunction(ElemGraphicsCompareFunction compareFunction) +{ + switch (compareFunction) + { + case ElemGraphicsCompareFunction_Never: + return D3D12_COMPARISON_FUNC_NEVER; + + case ElemGraphicsCompareFunction_Less: + return D3D12_COMPARISON_FUNC_LESS; + + case ElemGraphicsCompareFunction_Equal: + return D3D12_COMPARISON_FUNC_EQUAL; + + case ElemGraphicsCompareFunction_LessEqual: + return D3D12_COMPARISON_FUNC_LESS_EQUAL; + + case ElemGraphicsCompareFunction_Greater: + return D3D12_COMPARISON_FUNC_GREATER; + + case ElemGraphicsCompareFunction_NotEqual: + return D3D12_COMPARISON_FUNC_NOT_EQUAL; + + case ElemGraphicsCompareFunction_GreaterEqual: + return D3D12_COMPARISON_FUNC_GREATER_EQUAL; + + case ElemGraphicsCompareFunction_Always: + return D3D12_COMPARISON_FUNC_ALWAYS; + } +} + DirectX12DescriptorHeap CreateDirectX12DescriptorHeap(ComPtr graphicsDevice, MemoryArena memoryArena, D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, uint32_t length) { D3D12_DESCRIPTOR_HEAP_DESC descriptorHeapDesc = @@ -285,7 +315,7 @@ ComPtr CreateDirectX12RootSignature(ComPtr rootSignatureDesc.Desc_1_1.pParameters = rootParameters; rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0; rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr; - rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED; + rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED | D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED; ComPtr serializedRootSignature; AssertIfFailed(D3D12SerializeVersionedRootSignature(&rootSignatureDesc, serializedRootSignature.GetAddressOf(), nullptr)); @@ -403,15 +433,23 @@ ElemGraphicsDevice DirectX12CreateGraphicsDevice(const ElemGraphicsDeviceOptions auto memoryArena = SystemAllocateMemoryArena(); auto resourceDescriptorHeap = CreateDirectX12DescriptorHeap(device, memoryArena, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, DIRECTX12_MAX_RESOURCES); + auto samplerDescriptorHeap = CreateDirectX12DescriptorHeap(device, memoryArena, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, DIRECTX12_MAX_SAMPLERS); auto rtvDescriptorHeap = CreateDirectX12DescriptorHeap(device, memoryArena, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, DIRECTX12_MAX_RTVS); + auto dsvDescriptorHeap = CreateDirectX12DescriptorHeap(device, memoryArena, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, DIRECTX12_MAX_RTVS); auto rootSignature = CreateDirectX12RootSignature(device); + // TODO: This need to be checked. We don't know how many max threads will use this. Maybe we can allocate for MAX_CONC_THREADS variable of param (that can be overriden) + auto uploadBuffers = SystemPushArray>*>(DirectX12MemoryArena, MAX_UPLOAD_BUFFERS); + auto handle = SystemAddDataPoolItem(directX12GraphicsDevicePool, { .Device = device, .RootSignature = rootSignature, .ResourceDescriptorHeap = resourceDescriptorHeap, + .SamplerDescriptorHeap = samplerDescriptorHeap, .RTVDescriptorHeap = rtvDescriptorHeap, - .MemoryArena = memoryArena + .DSVDescriptorHeap = dsvDescriptorHeap, + .MemoryArena = memoryArena, + .UploadBufferPools = uploadBuffers }); SystemAddDataPoolItemFull(directX12GraphicsDevicePool, handle, { @@ -433,8 +471,32 @@ void DirectX12FreeGraphicsDevice(ElemGraphicsDevice graphicsDevice) auto graphicsDeviceDataFull = GetDirectX12GraphicsDeviceDataFull(graphicsDevice); SystemAssert(graphicsDeviceDataFull); + for (uint32_t i = 0; i < graphicsDeviceData->UploadBufferPools.Length; i++) + { + auto bufferPool = graphicsDeviceData->UploadBufferPools[i]; + + if (bufferPool) + { + for (uint32_t j = 0; j < MAX_UPLOAD_BUFFERS; j++) + { + auto uploadBuffer = &bufferPool->UploadBuffers[j]; + + if (uploadBuffer && uploadBuffer->Buffer) + { + uploadBuffer->Buffer.Reset(); + *uploadBuffer = {}; + } + } + + *bufferPool = {}; + } + } + FreeDirectX12DescriptorHeap(graphicsDeviceData->ResourceDescriptorHeap); + FreeDirectX12DescriptorHeap(graphicsDeviceData->SamplerDescriptorHeap); FreeDirectX12DescriptorHeap(graphicsDeviceData->RTVDescriptorHeap); + FreeDirectX12DescriptorHeap(graphicsDeviceData->DSVDescriptorHeap); + graphicsDeviceData->Device.Reset(); graphicsDeviceData->RootSignature.Reset(); diff --git a/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.h b/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.h index a8e7e205..f1deac26 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.h +++ b/src/Elemental/Microsoft/Graphics/DirectX12GraphicsDevice.h @@ -1,12 +1,9 @@ #pragma once #include "Elemental.h" +#include "Graphics/UploadBufferPool.h" #include "SystemMemory.h" -#define DIRECTX12_MAX_DEVICES 10u -//#define DIRECTX12_MAX_RESOURCES 1000000 -#define DIRECTX12_MAX_RESOURCES 500000 - struct DirectX12DescriptorHeapStorage; struct DirectX12DescriptorHeap @@ -19,9 +16,14 @@ struct DirectX12GraphicsDeviceData ComPtr Device; ComPtr RootSignature; uint64_t CommandAllocationGeneration; + uint64_t UploadBufferGeneration; DirectX12DescriptorHeap ResourceDescriptorHeap; + DirectX12DescriptorHeap SamplerDescriptorHeap; DirectX12DescriptorHeap RTVDescriptorHeap; + DirectX12DescriptorHeap DSVDescriptorHeap; MemoryArena MemoryArena; + Span>*> UploadBufferPools; + uint32_t CurrentUploadBufferPoolIndex; }; struct DirectX12GraphicsDeviceDataFull @@ -40,6 +42,8 @@ extern ComPtr DxgiInfoQueue; DirectX12GraphicsDeviceData* GetDirectX12GraphicsDeviceData(ElemGraphicsDevice graphicsDevice); DirectX12GraphicsDeviceDataFull* GetDirectX12GraphicsDeviceDataFull(ElemGraphicsDevice graphicsDevice); +D3D12_COMPARISON_FUNC ConvertToDirectX12CompareFunction(ElemGraphicsCompareFunction compareFunction); + D3D12_CPU_DESCRIPTOR_HANDLE CreateDirectX12DescriptorHandle(DirectX12DescriptorHeap descriptorHeap); void FreeDirectX12DescriptorHandle(DirectX12DescriptorHeap descriptorHeap, D3D12_CPU_DESCRIPTOR_HANDLE handle); uint32_t ConvertDirectX12DescriptorHandleToIndex(DirectX12DescriptorHeap descriptorHeap, D3D12_CPU_DESCRIPTOR_HANDLE handle); diff --git a/src/Elemental/Microsoft/Graphics/DirectX12Rendering.cpp b/src/Elemental/Microsoft/Graphics/DirectX12Rendering.cpp index b90fe6a4..33af446d 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12Rendering.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12Rendering.cpp @@ -5,6 +5,46 @@ #include "DirectX12Resource.h" #include "SystemFunctions.h" +D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE ConvertToDirectX12RenderPassBeginningAccessType(ElemRenderPassLoadAction loadAction) +{ + D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE beginAccessType; + + switch (loadAction) + { + case ElemRenderPassLoadAction_Load: + beginAccessType = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE; + break; + + case ElemRenderPassLoadAction_Clear: + beginAccessType = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR; + break; + + default: + beginAccessType = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD; + break; + } + + return beginAccessType; +} + +D3D12_RENDER_PASS_ENDING_ACCESS_TYPE ConvertToDirectX12RenderPassEndingAccessType(ElemRenderPassStoreAction storeAction) +{ + D3D12_RENDER_PASS_ENDING_ACCESS_TYPE endAccessType; + + switch (storeAction) + { + case ElemRenderPassStoreAction_Store: + endAccessType = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; + break; + + default: + endAccessType = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD; + break; + } + + return endAccessType; +} + void DirectX12BeginRenderPass(ElemCommandList commandList, const ElemBeginRenderPassParameters* parameters) { // TODO: Check command list type != COMPUTE @@ -35,23 +75,7 @@ void DirectX12BeginRenderPass(ElemCommandList commandList, const ElemBeginRender SystemAssert(textureData); // TODO: Validate usage - - D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE beginAccessType; - - switch (renderTargetParameters.LoadAction) - { - case ElemRenderPassLoadAction_Load: - beginAccessType = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE; - break; - - case ElemRenderPassLoadAction_Clear: - beginAccessType = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR; - break; - - default: - beginAccessType = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD; - break; - } + auto beginAccessType = ConvertToDirectX12RenderPassBeginningAccessType(renderTargetParameters.LoadAction); D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS beginAccessClearValue { @@ -68,18 +92,7 @@ void DirectX12BeginRenderPass(ElemCommandList commandList, const ElemBeginRender } }; - D3D12_RENDER_PASS_ENDING_ACCESS_TYPE endAccessType; - - switch (renderTargetParameters.StoreAction) - { - case ElemRenderPassStoreAction_Store: - endAccessType = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; - break; - - default: - endAccessType = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD; - break; - } + auto endAccessType = ConvertToDirectX12RenderPassEndingAccessType(renderTargetParameters.StoreAction); renderTargetDescList[i] = { @@ -112,15 +125,74 @@ void DirectX12BeginRenderPass(ElemCommandList commandList, const ElemBeginRender ElemSetViewport(commandList, &viewport); } + + if (i == 0 && parameters->ScissorRectangles.Length == 0) + { + ElemRectangle rectangle = + { + .X = 0, + .Y = 0, + .Width = (float)textureData->Width, + .Height = (float)textureData->Height + }; + + ElemSetScissorRectangle(commandList, &rectangle); + } } + D3D12_RENDER_PASS_DEPTH_STENCIL_DESC depthStencilDesc = {}; + + if (parameters->DepthStencil.DepthStencil != ELEM_HANDLE_NULL) + { + auto depthStencilParameters = parameters->DepthStencil; + + auto textureData = GetDirectX12GraphicsResourceData(depthStencilParameters.DepthStencil); + SystemAssert(textureData); + + // TODO: Validate usage + auto depthBeginAccessType = ConvertToDirectX12RenderPassBeginningAccessType(depthStencilParameters.DepthLoadAction); + auto depthEndAccessType = ConvertToDirectX12RenderPassEndingAccessType(depthStencilParameters.DepthStoreAction); + + D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS beginAccessClearValue + { + .ClearValue = + { + .Format = textureData->DirectX12Format, + .DepthStencil = { .Depth = depthStencilParameters.DepthClearValue } + } + }; + + depthStencilDesc = + { + .cpuDescriptor = textureData->DsvHandle, + .DepthBeginningAccess = { .Type = depthBeginAccessType, .Clear = beginAccessClearValue }, + .DepthEndingAccess = { .Type = depthEndAccessType } + }; + + ResourceBarrierItem resourceBarrier = + { + .Type = ElemGraphicsResourceType_Texture2D, + .IsDepthStencil = true, + .Resource = depthStencilParameters.DepthStencil, + .AfterAccess = ElemGraphicsResourceBarrierAccessType_DepthStencilWrite, + .AfterLayout = ElemGraphicsResourceBarrierLayoutType_DepthStencilWrite + }; + + EnqueueBarrier(commandListData->ResourceBarrierPool, &resourceBarrier); + } + if (parameters->Viewports.Length > 0) { ElemSetViewports(commandList, parameters->Viewports); } + if (parameters->ScissorRectangles.Length > 0) + { + ElemSetScissorRectangles(commandList, parameters->ScissorRectangles); + } + InsertDirectX12ResourceBarriersIfNeeded(commandList, ElemGraphicsResourceBarrierSyncType_RenderTarget); - commandListData->DeviceObject->BeginRenderPass(renderTargetDescList.Length, renderTargetDescList.Pointer, nullptr, D3D12_RENDER_PASS_FLAG_NONE); + commandListData->DeviceObject->BeginRenderPass(renderTargetDescList.Length, renderTargetDescList.Pointer, parameters->DepthStencil.DepthStencil != ELEM_HANDLE_NULL ? &depthStencilDesc : nullptr, D3D12_RENDER_PASS_FLAG_NONE); } void DirectX12EndRenderPass(ElemCommandList commandList) @@ -171,7 +243,6 @@ void DirectX12SetViewports(ElemCommandList commandList, ElemViewportSpan viewpor auto stackMemoryArena = SystemGetStackMemoryArena(); auto directX12Viewports = SystemPushArray(stackMemoryArena, viewports.Length); - auto directX12ScissorRects = SystemPushArray(stackMemoryArena, viewports.Length); for (uint32_t i = 0; i < viewports.Length; i++) { @@ -184,20 +255,37 @@ void DirectX12SetViewports(ElemCommandList commandList, ElemViewportSpan viewpor .MinDepth = viewports.Items[i].MinDepth, .MaxDepth = viewports.Items[i].MaxDepth }; + } + + auto commandListData = GetDirectX12CommandListData(commandList); + SystemAssert(commandListData); + + commandListData->DeviceObject->RSSetViewports(directX12Viewports.Length, directX12Viewports.Pointer); +} + +void DirectX12SetScissorRectangles(ElemCommandList commandList, ElemRectangleSpan rectangles) +{ + // TODO: Check command list type != COMPUTE + + SystemAssert(commandList != ELEM_HANDLE_NULL); + + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto directX12ScissorRects = SystemPushArray(stackMemoryArena, rectangles.Length); + for (uint32_t i = 0; i < rectangles.Length; i++) + { directX12ScissorRects[i] = { - .left = (int32_t)viewports.Items[i].X, - .top = (int32_t)viewports.Items[i].Y, - .right = (int32_t)viewports.Items[i].X + (int32_t)viewports.Items[i].Width, - .bottom = (int32_t)viewports.Items[i].Y + (int32_t)viewports.Items[i].Height + .left = (int32_t)rectangles.Items[i].X, + .top = (int32_t)rectangles.Items[i].Y, + .right = (int32_t)rectangles.Items[i].X + (int32_t)rectangles.Items[i].Width, + .bottom = (int32_t)rectangles.Items[i].Y + (int32_t)rectangles.Items[i].Height }; } auto commandListData = GetDirectX12CommandListData(commandList); SystemAssert(commandListData); - commandListData->DeviceObject->RSSetViewports(directX12Viewports.Length, directX12Viewports.Pointer); commandListData->DeviceObject->RSSetScissorRects(directX12ScissorRects.Length, directX12ScissorRects.Pointer); } diff --git a/src/Elemental/Microsoft/Graphics/DirectX12Resource.cpp b/src/Elemental/Microsoft/Graphics/DirectX12Resource.cpp index 06802d75..56714532 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12Resource.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12Resource.cpp @@ -1,17 +1,23 @@ #include "DirectX12Resource.h" +#include "DirectX12Config.h" #include "DirectX12GraphicsDevice.h" +#include "DirectX12CommandList.h" +#include "Graphics/Resource.h" #include "Graphics/ResourceDeleteQueue.h" +#include "Graphics/UploadBufferPool.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define DIRECTX12_MAX_GRAPHICSHEAP 32 - SystemDataPool directX12GraphicsHeapPool; SystemDataPool directX12GraphicsResourcePool; +thread_local UploadBufferDevicePool> threadDirectX12UploadBufferPools[DIRECTX12_MAX_DEVICES]; + // TODO: This descriptor infos should be linked to the graphics device like the resource desc heaps Span directX12ResourceDescriptorInfos; +Span directX12SamplerInfos; +MemoryArena directX12ReadBackMemoryArena; void InitDirectX12ResourceMemory() { @@ -19,7 +25,12 @@ void InitDirectX12ResourceMemory() { directX12GraphicsHeapPool = SystemCreateDataPool(DirectX12MemoryArena, DIRECTX12_MAX_GRAPHICSHEAP); directX12GraphicsResourcePool = SystemCreateDataPool(DirectX12MemoryArena, DIRECTX12_MAX_RESOURCES); - directX12ResourceDescriptorInfos = SystemPushArray(DirectX12MemoryArena, DIRECTX12_MAX_RESOURCES); + + directX12ResourceDescriptorInfos = SystemPushArray(DirectX12MemoryArena, DIRECTX12_MAX_RESOURCES, AllocationState_Reserved); + directX12SamplerInfos = SystemPushArray(DirectX12MemoryArena, DIRECTX12_MAX_SAMPLERS, AllocationState_Reserved); + + // TODO: Allow to increase the size as a parameter + directX12ReadBackMemoryArena = SystemAllocateMemoryArena(DIRECTX12_READBACK_MEMORY_ARENA); } } @@ -73,7 +84,40 @@ DXGI_FORMAT ConvertDirectX12FormatWithoutSrgbIfNeeded(DXGI_FORMAT format) } } -ElemGraphicsResource CreateDirectX12GraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ComPtr resource, bool isPresentTexture) +D3D12_FILTER_TYPE ConvertToDirectX12FilterType(ElemGraphicsSamplerFilter filter) +{ + switch (filter) + { + case ElemGraphicsSamplerFilter_Nearest: + return D3D12_FILTER_TYPE_POINT; + + case ElemGraphicsSamplerFilter_Linear: + return D3D12_FILTER_TYPE_LINEAR; + } +} + +D3D12_TEXTURE_ADDRESS_MODE ConvertToDirectX12TextureAddressMode(ElemGraphicsSamplerAddressMode addressMode) +{ + switch (addressMode) + { + case ElemGraphicsSamplerAddressMode_Repeat: + return D3D12_TEXTURE_ADDRESS_MODE_WRAP; + + case ElemGraphicsSamplerAddressMode_RepeatMirror: + return D3D12_TEXTURE_ADDRESS_MODE_MIRROR; + + case ElemGraphicsSamplerAddressMode_ClampToEdge: + return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + + case ElemGraphicsSamplerAddressMode_ClampToEdgeMirror: + return D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE; + + case ElemGraphicsSamplerAddressMode_ClampToBorderColor: + return D3D12_TEXTURE_ADDRESS_MODE_BORDER; + } +} + +ElemGraphicsResource CreateDirectX12GraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ElemGraphicsHeap heap, ComPtr resource, bool isPresentTexture) { InitDirectX12ResourceMemory(); @@ -82,6 +126,7 @@ ElemGraphicsResource CreateDirectX12GraphicsResourceFromResource(ElemGraphicsDev auto resourceDesc = resource->GetDesc(); D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = {}; + D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = {}; if (resourceDesc.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) { @@ -95,21 +140,35 @@ ElemGraphicsResource CreateDirectX12GraphicsResourceFromResource(ElemGraphicsDev graphicsDeviceData->Device->CreateRenderTargetView(resource.Get(), &renderTargetViewDesc, rtvHandle); } + else if (resourceDesc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) + { + dsvHandle = CreateDirectX12DescriptorHandle(graphicsDeviceData->DSVDescriptorHeap); + + D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc = + { + .Format = ConvertDirectX12FormatToSrgbIfNeeded(resourceDesc.Format), + .ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D, + }; + + graphicsDeviceData->Device->CreateDepthStencilView(resource.Get(), &depthStencilViewDesc, dsvHandle); + } auto handle = SystemAddDataPoolItem(directX12GraphicsResourcePool, { .DeviceObject = resource, .RtvHandle = rtvHandle, + .DsvHandle = dsvHandle, .Type = type, .DirectX12Format = resourceDesc.Format, .DirectX12Flags = resourceDesc.Flags, + .GraphicsHeap = heap, .Width = (uint32_t)resourceDesc.Width, .Height = type != ElemGraphicsResourceType_Buffer ? (uint32_t)resourceDesc.Height : 0, .MipLevels = resourceDesc.MipLevels, - .IsPresentTexture = isPresentTexture + .IsPresentTexture = isPresentTexture, }); SystemAddDataPoolItemFull(directX12GraphicsResourcePool, handle, { - .GraphicsDevice = graphicsDevice + .GraphicsDevice = graphicsDevice, }); return handle; @@ -122,7 +181,7 @@ DXGI_FORMAT ConvertToDirectX12TextureFormat(ElemGraphicsFormat format) case ElemGraphicsFormat_B8G8R8A8_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; - case ElemGraphicsFormat_B8G8R8A8_UNORM: + case ElemGraphicsFormat_B8G8R8A8: return DXGI_FORMAT_B8G8R8A8_UNORM; case ElemGraphicsFormat_R16G16B16A16_FLOAT: @@ -131,6 +190,15 @@ DXGI_FORMAT ConvertToDirectX12TextureFormat(ElemGraphicsFormat format) case ElemGraphicsFormat_R32G32B32A32_FLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case ElemGraphicsFormat_D32_FLOAT: + return DXGI_FORMAT_D32_FLOAT; + + case ElemGraphicsFormat_BC7: + return DXGI_FORMAT_BC7_UNORM; + + case ElemGraphicsFormat_BC7_SRGB: + return DXGI_FORMAT_BC7_UNORM_SRGB; + case ElemGraphicsFormat_Raw: return DXGI_FORMAT_UNKNOWN; } @@ -144,7 +212,7 @@ ElemGraphicsFormat ConvertFromDirectX12TextureFormat(DXGI_FORMAT format) return ElemGraphicsFormat_B8G8R8A8_SRGB; case DXGI_FORMAT_B8G8R8A8_UNORM: - return ElemGraphicsFormat_B8G8R8A8_UNORM; + return ElemGraphicsFormat_B8G8R8A8; case DXGI_FORMAT_R16G16B16A16_FLOAT: return ElemGraphicsFormat_R16G16B16A16_FLOAT; @@ -152,6 +220,15 @@ ElemGraphicsFormat ConvertFromDirectX12TextureFormat(DXGI_FORMAT format) case DXGI_FORMAT_R32G32B32A32_FLOAT: return ElemGraphicsFormat_R32G32B32A32_FLOAT; + case DXGI_FORMAT_D32_FLOAT: + return ElemGraphicsFormat_D32_FLOAT; + + case DXGI_FORMAT_BC7_UNORM: + return ElemGraphicsFormat_BC7; + + case DXGI_FORMAT_BC7_UNORM_SRGB: + return ElemGraphicsFormat_BC7_SRGB; + default: return ElemGraphicsFormat_Raw; } @@ -171,6 +248,11 @@ D3D12_RESOURCE_FLAGS ConvertToDirectX12ResourceFlags(ElemGraphicsResourceUsage u result |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; } + if (usage & ElemGraphicsResourceUsage_DepthStencil) + { + result |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + } + return result; } @@ -187,6 +269,11 @@ ElemGraphicsResourceUsage ConvertFromDirectX12ResourceFlags(D3D12_RESOURCE_FLAGS { result = (ElemGraphicsResourceUsage)(result | ElemGraphicsResourceUsage_RenderTarget); } + + if (flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) + { + result = (ElemGraphicsResourceUsage)(result | ElemGraphicsResourceUsage_DepthStencil); + } return result; } @@ -283,12 +370,10 @@ ElemGraphicsHeap DirectX12CreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, .DeviceObject = graphicsHeap, .SizeInBytes = sizeInBytes, .GraphicsDevice = graphicsDevice, + .HeapDescription = heapDesc, + .HeapType = heapProperties.Type }); - SystemAddDataPoolItemFull(directX12GraphicsHeapPool, handle, { - .HeapDescription = heapDesc - }); - return handle; } @@ -376,6 +461,8 @@ ElemGraphicsResource DirectX12CreateGraphicsResource(ElemGraphicsHeap graphicsHe SystemAssert(graphicsDeviceData); D3D12_BARRIER_LAYOUT initialState = D3D12_BARRIER_LAYOUT_COMMON; + + // TODO: Handle clear value D3D12_CLEAR_VALUE* clearValue = nullptr; D3D12_RESOURCE_DESC1 resourceDescription = {}; @@ -400,6 +487,18 @@ ElemGraphicsResource DirectX12CreateGraphicsResource(ElemGraphicsHeap graphicsHe return ELEM_HANDLE_NULL; } + if ((resourceInfo->Usage & ElemGraphicsResourceUsage_RenderTarget) && (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil)) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Texture2D with usage RenderTarget and DepthStencil should not be used together."); + return ELEM_HANDLE_NULL; + } + + if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil && !CheckDepthStencilFormat(resourceInfo->Format)) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Texture2D with usage DepthStencil should use a compatible format."); + return ELEM_HANDLE_NULL; + } + resourceDescription = CreateDirectX12TextureDescription(resourceInfo); } else @@ -410,11 +509,17 @@ ElemGraphicsResource DirectX12CreateGraphicsResource(ElemGraphicsHeap graphicsHe return ELEM_HANDLE_NULL; } - if ((resourceInfo->Usage & ElemGraphicsResourceUsage_RenderTarget)) + if (resourceInfo->Usage & ElemGraphicsResourceUsage_RenderTarget) { SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GraphicsBuffer usage should not be equals to RenderTarget."); return ELEM_HANDLE_NULL; } + + if (resourceInfo->Usage & ElemGraphicsResourceUsage_DepthStencil) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GraphicsBuffer usage should not be equals to DepthStencil."); + return ELEM_HANDLE_NULL; + } initialState = D3D12_BARRIER_LAYOUT_UNDEFINED; resourceDescription = CreateDirectX12BufferDescription(resourceInfo); @@ -435,7 +540,7 @@ ElemGraphicsResource DirectX12CreateGraphicsResource(ElemGraphicsHeap graphicsHe resource->SetName(SystemConvertUtf8ToWideChar(stackMemoryArena, resourceInfo->DebugName).Pointer); } - return CreateDirectX12GraphicsResourceFromResource(graphicsHeapData->GraphicsDevice, resourceInfo->Type, resource, false); + return CreateDirectX12GraphicsResourceFromResource(graphicsHeapData->GraphicsDevice, resourceInfo->Type, graphicsHeap, resource, false); } void DirectX12FreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGraphicsResourceOptions* options) @@ -491,26 +596,277 @@ ElemGraphicsResourceInfo DirectX12GetGraphicsResourceInfo(ElemGraphicsResource r }; } -ElemDataSpan DirectX12GetGraphicsResourceDataSpan(ElemGraphicsResource resource) +void DirectX12UploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data) { SystemAssert(resource != ELEM_HANDLE_NULL); auto resourceData = GetDirectX12GraphicsResourceData(resource); SystemAssert(resourceData); + auto heapData = GetDirectX12GraphicsHeapData(resourceData->GraphicsHeap); + SystemAssert(heapData); + if (resourceData->Type != ElemGraphicsResourceType_Buffer) { - SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "GetGraphicsResourceDataSpan only works with graphics buffers."); + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemUploadGraphicsBufferData only works with graphics buffers."); + return; + } + + if (heapData->HeapType != D3D12_HEAP_TYPE_GPU_UPLOAD) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemUploadGraphicsBufferData only works with graphics buffers allocated in a GpuUpload heap."); + return; + } + + if (resourceData->CpuDataPointer == nullptr) + { + D3D12_RANGE readRange = { 0, 0 }; + resourceData->DeviceObject->Map(0, &readRange, &resourceData->CpuDataPointer); + } + + auto destinationPointer = (uint8_t*)resourceData->CpuDataPointer + offset; + memcpy(destinationPointer, data.Items, data.Length); +} + +ElemDataSpan DirectX12DownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options) +{ + SystemAssert(resource != ELEM_HANDLE_NULL); + + auto resourceData = GetDirectX12GraphicsResourceData(resource); + SystemAssert(resourceData); + + auto heapData = GetDirectX12GraphicsHeapData(resourceData->GraphicsHeap); + SystemAssert(heapData); + + if (resourceData->Type != ElemGraphicsResourceType_Buffer) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData only works with graphics buffers."); + return {}; + } + + if (heapData->HeapType != D3D12_HEAP_TYPE_CUSTOM && heapData->HeapType != D3D12_HEAP_TYPE_GPU_UPLOAD) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData only works with graphics buffers allocated in a Readback heap or GpuUpload heap. (%d)", heapData->HeapType); return {}; } + if (heapData->HeapType != D3D12_HEAP_TYPE_CUSTOM) + { + SystemLogWarningMessage(ElemLogMessageCategory_Graphics, "ElemDownloadGraphicsBufferData works faster with graphics buffers allocated in a Readback heap."); + } + if (resourceData->CpuDataPointer == nullptr) { D3D12_RANGE readRange = { 0, 0 }; resourceData->DeviceObject->Map(0, &readRange, &resourceData->CpuDataPointer); } - return { .Items = (uint8_t*)resourceData->CpuDataPointer, .Length = resourceData->Width }; + auto offset = 0u; + auto sizeInBytes = resourceData->Width; + + if (options) + { + offset = options->Offset; + + if (options->SizeInBytes != 0) + { + sizeInBytes = options->SizeInBytes; + } + } + + auto downloadedData = SystemPushArray(directX12ReadBackMemoryArena, sizeInBytes); + memcpy(downloadedData.Pointer, (uint8_t*)resourceData->CpuDataPointer + offset, sizeInBytes); + + return { .Items = downloadedData.Pointer, .Length = (uint32_t)downloadedData.Length }; +} + +ComPtr CreateDirectX12UploadBuffer(ComPtr graphicsDevice, uint64_t sizeInBytes) +{ + D3D12_RESOURCE_DESC bufferDescription = + { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = sizeInBytes, + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = + { + .Count = 1, + .Quality = 0 + }, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + D3D12_HEAP_PROPERTIES heapProperties = { .Type = D3D12_HEAP_TYPE_UPLOAD }; + + ComPtr resource; + AssertIfFailed(graphicsDevice->CreateCommittedResource1(&heapProperties, + D3D12_HEAP_FLAG_NONE, + &bufferDescription, + D3D12_RESOURCE_STATE_COMMON, + nullptr, + nullptr, + IID_PPV_ARGS(resource.GetAddressOf()))); + + resource->SetName(L"ElementalUploadBuffer"); + + return resource; +} + +UploadBufferMemory> GetDirectX12UploadBuffer(ElemGraphicsDevice graphicsDevice, uint64_t alignment, uint64_t sizeInBytes) +{ + auto graphicsDeviceData = GetDirectX12GraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto graphicsIdUnpacked = UnpackSystemDataPoolHandle(graphicsDevice); + auto uploadBufferPool = &threadDirectX12UploadBufferPools[graphicsIdUnpacked.Index]; + + if (!uploadBufferPool->IsInited) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Init pool"); + + auto poolIndex = SystemAtomicAdd(graphicsDeviceData->CurrentUploadBufferPoolIndex, 1); + graphicsDeviceData->UploadBufferPools[poolIndex] = uploadBufferPool; + uploadBufferPool->IsInited = true; + } + + auto uploadBuffer = GetUploadBufferPoolItem(uploadBufferPool, graphicsDeviceData->UploadBufferGeneration, alignment, sizeInBytes); + + if (uploadBuffer.PoolItem->IsResetNeeded) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to create upload buffer with size: %d...", uploadBuffer.PoolItem->SizeInBytes); + + if (uploadBuffer.PoolItem->Fence.FenceValue > 0) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Fence: %d", uploadBuffer.PoolItem->Fence.FenceValue); + DirectX12WaitForFenceOnCpu(uploadBuffer.PoolItem->Fence); + } + + if (uploadBuffer.PoolItem->Buffer != nullptr) + { + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to delete buffer"); + uploadBuffer.PoolItem->Buffer.Reset(); + } + + uploadBuffer.PoolItem->Buffer = CreateDirectX12UploadBuffer(graphicsDeviceData->Device, uploadBuffer.PoolItem->SizeInBytes); + + D3D12_RANGE readRange = { 0, 0 }; + uploadBuffer.PoolItem->Buffer->Map(0, &readRange, (void**)&uploadBuffer.PoolItem->CpuPointer); + + uploadBuffer.PoolItem->IsResetNeeded = false; + } + + return uploadBuffer; +} + +void DirectX12CopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters) +{ + // TODO: Implement optimizations on the copy queue. On windows, use DirectStorage + auto stackMemoryArena = SystemGetStackMemoryArena(); + + SystemAssert(commandList != ELEM_HANDLE_NULL); + + SystemAssert(parameters); + SystemAssert(parameters->Resource != ELEM_HANDLE_NULL); + + auto commandListData = GetDirectX12CommandListData(commandList); + SystemAssert(commandListData); + + auto graphicsDeviceData = GetDirectX12GraphicsDeviceData(commandListData->GraphicsDevice); + SystemAssert(graphicsDeviceData); + + auto resourceData = GetDirectX12GraphicsResourceData(parameters->Resource); + SystemAssert(resourceData); + + auto resourceDataFull = GetDirectX12GraphicsResourceDataFull(parameters->Resource); + SystemAssert(resourceDataFull); + + ReadOnlySpan sourceData; + + // TODO: Implement file source + if (parameters->SourceType == ElemCopyDataSourceType_Memory) + { + sourceData = ReadOnlySpan(parameters->SourceMemoryData.Items, parameters->SourceMemoryData.Length); + } + + auto uploadBufferAlignment = 4u; + auto uploadBufferSizeInBytes = sourceData.Length; + DirectX12GraphicsTextureMipCopyInfo textureMipCopyInfo = {}; + + if (resourceData->Type == ElemGraphicsResourceType_Texture2D) + { + uploadBufferAlignment = D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT; + auto resourceDesc = resourceData->DeviceObject->GetDesc(); + + graphicsDeviceData->Device->GetCopyableFootprints(&resourceDesc, + parameters->TextureMipLevel, 1, 0, + &textureMipCopyInfo.PlacedFootprint, + &textureMipCopyInfo.RowCount, + &textureMipCopyInfo.SourceRowSizeInBytes, + &uploadBufferSizeInBytes); + } + + auto uploadBuffer = GetDirectX12UploadBuffer(commandListData->GraphicsDevice, uploadBufferAlignment, uploadBufferSizeInBytes); + SystemAssert(uploadBuffer.Offset + sourceData.Length <= uploadBuffer.PoolItem->SizeInBytes); + + // TODO: Can we do better here? + auto uploadBufferAlreadyInCommandList = false; + + for (uint32_t i = 0; i < MAX_UPLOAD_BUFFERS; i++) + { + if (commandListData->UploadBufferPoolItems[i] == uploadBuffer.PoolItem) + { + uploadBufferAlreadyInCommandList = true; + break; + } + } + + if (!uploadBufferAlreadyInCommandList) + { + commandListData->UploadBufferPoolItems[commandListData->UploadBufferCount++] = uploadBuffer.PoolItem; + } + + if (resourceData->Type == ElemGraphicsResourceType_Buffer) + { + memcpy(uploadBuffer.PoolItem->CpuPointer + uploadBuffer.Offset, sourceData.Pointer, sourceData.Length); + commandListData->DeviceObject->CopyBufferRegion(resourceData->DeviceObject.Get(), parameters->BufferOffset, uploadBuffer.PoolItem->Buffer.Get(), uploadBuffer.Offset, sourceData.Length); + } + else if (resourceData->Type == ElemGraphicsResourceType_Texture2D) + { + auto placedFootprint = textureMipCopyInfo.PlacedFootprint; + auto sourceRowSizeInBytes = textureMipCopyInfo.SourceRowSizeInBytes; + + auto destData = uploadBuffer.PoolItem->CpuPointer + uploadBuffer.Offset; + + for (uint32_t i = 0; i < textureMipCopyInfo.RowCount; i++) + { + auto uploadBufferRowData = destData + i * placedFootprint.Footprint.RowPitch; + auto sourceRowData = sourceData.Pointer + i * sourceRowSizeInBytes; + + memcpy(uploadBufferRowData, sourceRowData, sourceRowSizeInBytes); + } + + placedFootprint.Offset = uploadBuffer.Offset; + + D3D12_TEXTURE_COPY_LOCATION sourceLocation = + { + .pResource = uploadBuffer.PoolItem->Buffer.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT, + .PlacedFootprint = placedFootprint + }; + + D3D12_TEXTURE_COPY_LOCATION destinationLocation = + { + .pResource = resourceData->DeviceObject.Get(), + .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, + .SubresourceIndex = parameters->TextureMipLevel + }; + + commandListData->DeviceObject->CopyTextureRegion(&destinationLocation, 0, 0, 0, &sourceLocation, nullptr); + } } ElemGraphicsResourceDescriptor DirectX12CreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options) @@ -559,7 +915,8 @@ ElemGraphicsResourceDescriptor DirectX12CreateGraphicsResourceDescriptor(ElemGra { D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = { - .Format = resourceData->DirectX12Format, + // TODO: Make a function to handle depth stencil formats checks + .Format = resourceData->DirectX12Format == DXGI_FORMAT_D32_FLOAT ? DXGI_FORMAT_R32_FLOAT : resourceData->DirectX12Format, .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, }; @@ -616,6 +973,11 @@ ElemGraphicsResourceDescriptor DirectX12CreateGraphicsResourceDescriptor(ElemGra auto index = ConvertDirectX12DescriptorHandleToIndex(descriptorHeap, descriptorHandle); + if ((index % 1024) == 0) + { + SystemCommitMemory(DirectX12MemoryArena, &directX12ResourceDescriptorInfos[index], 1024 * sizeof(ElemGraphicsResourceDescriptorInfo)); + } + directX12ResourceDescriptorInfos[index].Resource = resource; directX12ResourceDescriptorInfos[index].Usage = usage; @@ -650,7 +1012,123 @@ void DirectX12FreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor desc directX12ResourceDescriptorInfos[descriptor].Resource = ELEM_HANDLE_NULL; } -void DirectX12ProcessGraphicsResourceDeleteQueue() +void DirectX12ProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice) { + auto stackMemoryArena = SystemGetStackMemoryArena(); + ProcessResourceDeleteQueue(); + SystemClearMemoryArena(directX12ReadBackMemoryArena); + + auto graphicsDeviceData = GetDirectX12GraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + graphicsDeviceData->UploadBufferGeneration++; + + for (uint32_t i = 0; i < graphicsDeviceData->UploadBufferPools.Length; i++) + { + auto bufferPool = graphicsDeviceData->UploadBufferPools[i]; + + if (bufferPool) + { + auto uploadBuffersToDelete = GetUploadBufferPoolItemsToDelete(stackMemoryArena, bufferPool, graphicsDeviceData->UploadBufferGeneration); + + for (uint32_t j = 0; j < uploadBuffersToDelete.Length; j++) + { + auto uploadBufferToDelete = uploadBuffersToDelete[j]; + + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Need to purge upload buffer: Size=%d", uploadBufferToDelete->SizeInBytes); + + uploadBufferToDelete->Buffer.Reset(); + uploadBufferToDelete->CurrentOffset = 0; + uploadBufferToDelete->SizeInBytes = 0; + } + } + } +} + +ElemGraphicsSampler DirectX12CreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo) +{ + InitDirectX12ResourceMemory(); + + auto graphicsDeviceData = GetDirectX12GraphicsDeviceData(graphicsDevice); + SystemAssert(graphicsDeviceData); + + auto descriptorHeap = graphicsDeviceData->SamplerDescriptorHeap; + auto descriptorHandle = CreateDirectX12DescriptorHandle(descriptorHeap); + + auto localSamplerInfo = *samplerInfo; + + if (localSamplerInfo.MaxAnisotropy == 0) + { + localSamplerInfo.MaxAnisotropy = 1; + } + + auto minFilter = ConvertToDirectX12FilterType(localSamplerInfo.MinFilter); + auto magFilter = ConvertToDirectX12FilterType(localSamplerInfo.MagFilter); + auto mipFilter = ConvertToDirectX12FilterType(localSamplerInfo.MipFilter); + auto reduction = localSamplerInfo.CompareFunction == ElemGraphicsCompareFunction_Never ? D3D12_FILTER_REDUCTION_TYPE_STANDARD : D3D12_FILTER_REDUCTION_TYPE_COMPARISON; + auto borderColor = localSamplerInfo.BorderColor; + auto filter = D3D12_ENCODE_BASIC_FILTER(minFilter, magFilter, mipFilter, reduction); + + if (localSamplerInfo.MaxAnisotropy > 1) + { + filter = (D3D12_FILTER)(D3D12_ANISOTROPIC_FILTERING_BIT | filter); + } + + D3D12_SAMPLER_DESC samplerDesc = + { + .Filter = filter, + .AddressU = ConvertToDirectX12TextureAddressMode(localSamplerInfo.AddressU), + .AddressV = ConvertToDirectX12TextureAddressMode(localSamplerInfo.AddressV), + .AddressW = ConvertToDirectX12TextureAddressMode(localSamplerInfo.AddressW), + .MaxAnisotropy = localSamplerInfo.MaxAnisotropy, + .ComparisonFunc = ConvertToDirectX12CompareFunction(localSamplerInfo.CompareFunction), + .BorderColor = { borderColor.Red, borderColor.Green, borderColor.Blue, borderColor.Alpha }, + .MinLOD = localSamplerInfo.MinLod, + .MaxLOD = localSamplerInfo.MaxLod == 0 ? 1000 : localSamplerInfo.MaxLod + }; + + graphicsDeviceData->Device->CreateSampler(&samplerDesc, descriptorHandle); + + auto index = ConvertDirectX12DescriptorHandleToIndex(descriptorHeap, descriptorHandle); + + if ((index % 1024) == 0) + { + SystemCommitMemory(DirectX12MemoryArena, &directX12SamplerInfos[index], 1024 * sizeof(ElemGraphicsSamplerInfo)); + } + + directX12SamplerInfos[index] = localSamplerInfo; + return index; +} + +ElemGraphicsSamplerInfo DirectX12GetGraphicsSamplerInfo(ElemGraphicsSampler sampler) +{ + InitDirectX12ResourceMemory(); + + if (sampler == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Sampler is invalid."); + return {}; + } + + return directX12SamplerInfos[sampler]; +} + +void DirectX12FreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options) +{ + InitDirectX12ResourceMemory(); + + if (sampler == -1) + { + SystemLogErrorMessage(ElemLogMessageCategory_Graphics, "Sampler is invalid."); + return; + } + + if (options && options->FencesToWait.Length > 0) + { + EnqueueResourceDeleteEntry(DirectX12MemoryArena, sampler, ResourceDeleteType_Sampler, options->FencesToWait); + return; + } + + directX12SamplerInfos[sampler] = {}; } diff --git a/src/Elemental/Microsoft/Graphics/DirectX12Resource.h b/src/Elemental/Microsoft/Graphics/DirectX12Resource.h index 2a1a408d..f63d55bb 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12Resource.h +++ b/src/Elemental/Microsoft/Graphics/DirectX12Resource.h @@ -7,21 +7,35 @@ struct DirectX12GraphicsHeapData ComPtr DeviceObject; uint64_t SizeInBytes; ElemGraphicsDevice GraphicsDevice; + D3D12_HEAP_DESC HeapDescription; + D3D12_HEAP_TYPE HeapType; }; // TODO: To review we don't use it!!! struct DirectX12GraphicsHeapDataFull { - D3D12_HEAP_DESC HeapDescription; + uint32_t reserved; }; +struct DirectX12GraphicsTextureMipCopyInfo +{ + uint32_t RowCount; + uint64_t SourceRowSizeInBytes; + uint32_t UploadBufferSizeInBytes; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT PlacedFootprint; +}; + +// TODO: Optimize this structure because it is the most used struct DirectX12GraphicsResourceData { ComPtr DeviceObject; + // TODO: We can maybe merge the two? D3D12_CPU_DESCRIPTOR_HANDLE RtvHandle; + D3D12_CPU_DESCRIPTOR_HANDLE DsvHandle; ElemGraphicsResourceType Type; DXGI_FORMAT DirectX12Format; D3D12_RESOURCE_FLAGS DirectX12Flags; + ElemGraphicsHeap GraphicsHeap; uint32_t Width; uint32_t Height; uint32_t MipLevels; @@ -40,8 +54,9 @@ DirectX12GraphicsHeapDataFull* GetDirectX12GraphicsHeapDataFull(ElemGraphicsHeap DirectX12GraphicsResourceData* GetDirectX12GraphicsResourceData(ElemGraphicsResource resource); DirectX12GraphicsResourceDataFull* GetDirectX12GraphicsResourceDataFull(ElemGraphicsResource resource); -ElemGraphicsResource CreateDirectX12GraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ComPtr resource, bool isPresentTexture); +ElemGraphicsResource CreateDirectX12GraphicsResourceFromResource(ElemGraphicsDevice graphicsDevice, ElemGraphicsResourceType type, ElemGraphicsHeap heap, ComPtr resource, bool isPresentTexture); DXGI_FORMAT ConvertToDirectX12TextureFormat(ElemGraphicsFormat format); +bool CheckDirectX12DepthStencilFormat(ElemGraphicsFormat format); ElemGraphicsHeap DirectX12CreateGraphicsHeap(ElemGraphicsDevice graphicsDevice, uint64_t sizeInBytes, const ElemGraphicsHeapOptions* options); void DirectX12FreeGraphicsHeap(ElemGraphicsHeap graphicsHeap); @@ -52,12 +67,19 @@ ElemGraphicsResourceInfo DirectX12CreateTexture2DResourceInfo(ElemGraphicsDevice ElemGraphicsResource DirectX12CreateGraphicsResource(ElemGraphicsHeap graphicsHeap, uint64_t graphicsHeapOffset, const ElemGraphicsResourceInfo* resourceInfo); void DirectX12FreeGraphicsResource(ElemGraphicsResource resource, const ElemFreeGraphicsResourceOptions* options); ElemGraphicsResourceInfo DirectX12GetGraphicsResourceInfo(ElemGraphicsResource resource); -ElemDataSpan DirectX12GetGraphicsResourceDataSpan(ElemGraphicsResource resource); + +void DirectX12UploadGraphicsBufferData(ElemGraphicsResource resource, uint32_t offset, ElemDataSpan data); +ElemDataSpan DirectX12DownloadGraphicsBufferData(ElemGraphicsResource resource, const ElemDownloadGraphicsBufferDataOptions* options); +void DirectX12CopyDataToGraphicsResource(ElemCommandList commandList, const ElemCopyDataToGraphicsResourceParameters* parameters); ElemGraphicsResourceDescriptor DirectX12CreateGraphicsResourceDescriptor(ElemGraphicsResource resource, ElemGraphicsResourceDescriptorUsage usage, const ElemGraphicsResourceDescriptorOptions* options); ElemGraphicsResourceDescriptorInfo DirectX12GetGraphicsResourceDescriptorInfo(ElemGraphicsResourceDescriptor descriptor); void DirectX12FreeGraphicsResourceDescriptor(ElemGraphicsResourceDescriptor descriptor, const ElemFreeGraphicsResourceDescriptorOptions* options); -void DirectX12ProcessGraphicsResourceDeleteQueue(void); +void DirectX12ProcessGraphicsResourceDeleteQueue(ElemGraphicsDevice graphicsDevice); void DirectX12GraphicsResourceBarrier(ElemCommandList commandList, ElemGraphicsResourceDescriptor descriptor, const ElemGraphicsResourceBarrierOptions* options); + +ElemGraphicsSampler DirectX12CreateGraphicsSampler(ElemGraphicsDevice graphicsDevice, const ElemGraphicsSamplerInfo* samplerInfo); +ElemGraphicsSamplerInfo DirectX12GetGraphicsSamplerInfo(ElemGraphicsSampler sampler); +void DirectX12FreeGraphicsSampler(ElemGraphicsSampler sampler, const ElemFreeGraphicsSamplerOptions* options); diff --git a/src/Elemental/Microsoft/Graphics/DirectX12ResourceBarrier.cpp b/src/Elemental/Microsoft/Graphics/DirectX12ResourceBarrier.cpp index a4b2a577..73a19b27 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12ResourceBarrier.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12ResourceBarrier.cpp @@ -4,7 +4,7 @@ #include "SystemFunctions.h" #include "SystemMemory.h" -D3D12_BARRIER_SYNC ConvertToDirectX12BarrierSync(ElemGraphicsResourceBarrierSyncType syncType) +D3D12_BARRIER_SYNC ConvertToDirectX12BarrierSync(ElemGraphicsResourceBarrierSyncType syncType, bool isDepthStencil) { switch (syncType) { @@ -15,7 +15,7 @@ D3D12_BARRIER_SYNC ConvertToDirectX12BarrierSync(ElemGraphicsResourceBarrierSync return D3D12_BARRIER_SYNC_COMPUTE_SHADING; case ElemGraphicsResourceBarrierSyncType_RenderTarget: - return D3D12_BARRIER_SYNC_RENDER_TARGET; + return !isDepthStencil ? D3D12_BARRIER_SYNC_RENDER_TARGET : D3D12_BARRIER_SYNC_DEPTH_STENCIL; } } @@ -35,6 +35,9 @@ D3D12_BARRIER_ACCESS ConvertToDirectX12BarrierAccess(ElemGraphicsResourceBarrier case ElemGraphicsResourceBarrierAccessType_RenderTarget: return D3D12_BARRIER_ACCESS_RENDER_TARGET; + case ElemGraphicsResourceBarrierAccessType_DepthStencilWrite: + return D3D12_BARRIER_ACCESS_DEPTH_STENCIL_WRITE; + case ElemGraphicsResourceBarrierAccessType_Write: return D3D12_BARRIER_ACCESS_UNORDERED_ACCESS; } @@ -60,6 +63,9 @@ D3D12_BARRIER_LAYOUT ConvertToDirectX12BarrierLayout(ElemGraphicsResourceBarrier case ElemGraphicsResourceBarrierLayoutType_RenderTarget: return D3D12_BARRIER_LAYOUT_RENDER_TARGET; + case ElemGraphicsResourceBarrierLayoutType_DepthStencilWrite: + return D3D12_BARRIER_LAYOUT_DEPTH_STENCIL_WRITE; + case ElemGraphicsResourceBarrierLayoutType_Present: return D3D12_BARRIER_LAYOUT_PRESENT; } @@ -107,8 +113,8 @@ void InsertDirectX12ResourceBarriersIfNeeded(ElemCommandList commandList, ElemGr directX12BufferBarrier->pResource = graphicsResourceData->DeviceObject.Get(); directX12BufferBarrier->Size = graphicsResourceData->Width; - directX12BufferBarrier->SyncBefore = ConvertToDirectX12BarrierSync(barrier.BeforeSync); - directX12BufferBarrier->SyncAfter = ConvertToDirectX12BarrierSync(barrier.AfterSync); + directX12BufferBarrier->SyncBefore = ConvertToDirectX12BarrierSync(barrier.BeforeSync, false); + directX12BufferBarrier->SyncAfter = ConvertToDirectX12BarrierSync(barrier.AfterSync, false); directX12BufferBarrier->AccessBefore = ConvertToDirectX12BarrierAccess(barrier.BeforeAccess); directX12BufferBarrier->AccessAfter = ConvertToDirectX12BarrierAccess(barrier.AfterAccess); } @@ -119,11 +125,11 @@ void InsertDirectX12ResourceBarriersIfNeeded(ElemCommandList commandList, ElemGr auto directX12TextureBarriers = SystemPushArray(stackMemoryArena, barriersInfo.TextureBarriers.Length); D3D12_BARRIER_GROUP directX12TextureBarriersGroup = - { - .Type = D3D12_BARRIER_TYPE_TEXTURE, - .NumBarriers = (uint32_t)barriersInfo.TextureBarriers.Length, - .pTextureBarriers = directX12TextureBarriers.Pointer - }; + { + .Type = D3D12_BARRIER_TYPE_TEXTURE, + .NumBarriers = (uint32_t)barriersInfo.TextureBarriers.Length, + .pTextureBarriers = directX12TextureBarriers.Pointer + }; barrierGroups[barrierGroupCount++] = directX12TextureBarriersGroup; @@ -136,8 +142,8 @@ void InsertDirectX12ResourceBarriersIfNeeded(ElemCommandList commandList, ElemGr SystemAssert(graphicsResourceData); directX12TextureBarrier->pResource = graphicsResourceData->DeviceObject.Get(); - directX12TextureBarrier->SyncBefore = ConvertToDirectX12BarrierSync(barrier.BeforeSync); - directX12TextureBarrier->SyncAfter = ConvertToDirectX12BarrierSync(barrier.AfterSync); + directX12TextureBarrier->SyncBefore = ConvertToDirectX12BarrierSync(barrier.BeforeSync, barrier.IsDepthStencil); + directX12TextureBarrier->SyncAfter = ConvertToDirectX12BarrierSync(barrier.AfterSync, barrier.IsDepthStencil); directX12TextureBarrier->AccessBefore = ConvertToDirectX12BarrierAccess(barrier.BeforeAccess); directX12TextureBarrier->AccessAfter = ConvertToDirectX12BarrierAccess(barrier.AfterAccess); directX12TextureBarrier->LayoutBefore = ConvertToDirectX12BarrierLayout(barrier.BeforeLayout); diff --git a/src/Elemental/Microsoft/Graphics/DirectX12Shader.cpp b/src/Elemental/Microsoft/Graphics/DirectX12Shader.cpp index 879fe890..35b7b466 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12Shader.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12Shader.cpp @@ -2,14 +2,13 @@ #include "DirectX12GraphicsDevice.h" #include "DirectX12Resource.h" #include "DirectX12CommandList.h" +#include "Graphics/Resource.h" #include "Graphics/ShaderReader.h" +#include "Graphics/Shader.h" #include "SystemDataPool.h" #include "SystemFunctions.h" #include "SystemMemory.h" -#define DIRECTX12_MAX_LIBRARIES UINT16_MAX -#define DIRECTX12_MAX_PIPELINESTATES UINT16_MAX - SystemDataPool directX12ShaderLibraryPool; SystemDataPool directX12PipelineStatePool; @@ -94,6 +93,93 @@ D3D12_SHADER_BYTECODE GetDirectX12ShaderFunctionByteCode(DirectX12ShaderLibraryD return result; } +D3D12_FILL_MODE ConvertToDirectX12FillMode(ElemGraphicsFillMode fillMode) +{ + switch (fillMode) + { + case ElemGraphicsFillMode_Solid: + return D3D12_FILL_MODE_SOLID; + + case ElemGraphicsFillMode_Wireframe: + return D3D12_FILL_MODE_WIREFRAME; + } +} + +D3D12_CULL_MODE ConvertToDirectX12CullMode(ElemGraphicsCullMode cullMode) +{ + switch (cullMode) + { + case ElemGraphicsCullMode_BackFace: + return D3D12_CULL_MODE_BACK; + + case ElemGraphicsCullMode_FrontFace: + return D3D12_CULL_MODE_FRONT; + + case ElemGraphicsCullMode_None: + return D3D12_CULL_MODE_NONE; + } +} + +D3D12_BLEND_OP ConvertToDirectX12BlendOperation(ElemGraphicsBlendOperation blendOperation) +{ + switch (blendOperation) + { + case ElemGraphicsBlendOperation_Add: + return D3D12_BLEND_OP_ADD; + + case ElemGraphicsBlendOperation_Subtract: + return D3D12_BLEND_OP_SUBTRACT; + + case ElemGraphicsBlendOperation_ReverseSubtract: + return D3D12_BLEND_OP_REV_SUBTRACT; + + case ElemGraphicsBlendOperation_Min: + return D3D12_BLEND_OP_MIN; + + case ElemGraphicsBlendOperation_Max: + return D3D12_BLEND_OP_MAX; + } +} + +D3D12_BLEND ConvertToDirectX12Blend(ElemGraphicsBlendFactor blendFactor) +{ + switch (blendFactor) + { + case ElemGraphicsBlendFactor_Zero: + return D3D12_BLEND_ZERO; + + case ElemGraphicsBlendFactor_One: + return D3D12_BLEND_ONE; + + case ElemGraphicsBlendFactor_SourceColor: + return D3D12_BLEND_SRC_COLOR; + + case ElemGraphicsBlendFactor_InverseSourceColor: + return D3D12_BLEND_INV_SRC_COLOR; + + case ElemGraphicsBlendFactor_SourceAlpha: + return D3D12_BLEND_SRC_ALPHA; + + case ElemGraphicsBlendFactor_InverseSourceAlpha: + return D3D12_BLEND_INV_SRC_ALPHA; + + case ElemGraphicsBlendFactor_DestinationColor: + return D3D12_BLEND_DEST_COLOR; + + case ElemGraphicsBlendFactor_InverseDestinationColor: + return D3D12_BLEND_INV_DEST_COLOR; + + case ElemGraphicsBlendFactor_DestinationAlpha: + return D3D12_BLEND_DEST_ALPHA; + + case ElemGraphicsBlendFactor_InverseDestinationAlpha: + return D3D12_BLEND_INV_DEST_ALPHA; + + case ElemGraphicsBlendFactor_SourceAlphaSaturated: + return D3D12_BLEND_SRC_ALPHA_SAT; + } +} + ElemShaderLibrary DirectX12CreateShaderLibrary(ElemGraphicsDevice graphicsDevice, ElemDataSpan shaderLibraryData) { InitDirectX12ShaderMemory(); @@ -142,94 +228,73 @@ ComPtr CreateDirectX12OldPSO(ElemGraphicsDevice graphicsDev auto shaderLibraryData= GetDirectX12ShaderLibraryData(parameters->ShaderLibrary); SystemAssert(shaderLibraryData); - ComPtr pipelineState; + // TODO: Extract methods (this will be easier to switch to new PipelineState later) + + // TODO: Render targets + D3D12_RT_FORMAT_ARRAY renderTargets = + { + .NumRenderTargets = parameters->RenderTargets.Length + }; + + D3D12_BLEND_DESC blendState = + { + .IndependentBlendEnable = false, // TODO: I think that one is needed + }; - D3D12_RT_FORMAT_ARRAY renderTargets = {}; + for (uint32_t i = 0; i < parameters->RenderTargets.Length; i++) + { + auto renderTargetParameters = parameters->RenderTargets.Items[i]; + + renderTargets.RTFormats[i] = ConvertToDirectX12TextureFormat(renderTargetParameters.Format); + + blendState.RenderTarget[i] = + { + .BlendEnable = IsBlendEnabled(renderTargetParameters), + .SrcBlend = ConvertToDirectX12Blend(renderTargetParameters.SourceBlendFactor), + .DestBlend = ConvertToDirectX12Blend(renderTargetParameters.DestinationBlendFactor), + .BlendOp = ConvertToDirectX12BlendOperation(renderTargetParameters.BlendOperation), + .SrcBlendAlpha = ConvertToDirectX12Blend(renderTargetParameters.SourceBlendFactorAlpha), + .DestBlendAlpha = ConvertToDirectX12Blend(renderTargetParameters.DestinationBlendFactorAlpha), + .BlendOpAlpha = ConvertToDirectX12BlendOperation(renderTargetParameters.BlendOperationAlpha), + .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL + }; + } - renderTargets.NumRenderTargets = 1; - renderTargets.RTFormats[0] = ConvertToDirectX12TextureFormat(parameters->TextureFormats.Items[0]); // TODO: Fill Correct Back Buffer Format - DXGI_FORMAT depthFormat = DXGI_FORMAT_UNKNOWN; + D3D12_DEPTH_STENCIL_DESC2 depthStencilState = {}; - /*if (renderPassDescriptor.DepthTexturePointer.HasValue) + if (CheckDepthStencilFormat(parameters->DepthStencil.Format)) { - // TODO: Change that - depthFormat = DXGI_FORMAT_D32_FLOAT; - }*/ + depthFormat = ConvertToDirectX12TextureFormat(parameters->DepthStencil.Format); + depthStencilState.DepthEnable = true; + + if (!parameters->DepthStencil.DepthDisableWrite) + { + depthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + } + + depthStencilState.DepthFunc = ConvertToDirectX12CompareFunction(parameters->DepthStencil.DepthCompareFunction); + } DXGI_SAMPLE_DESC sampleDesc = {}; sampleDesc.Count = 1; sampleDesc.Quality = 0; - D3D12_RASTERIZER_DESC rasterizerState = {}; - rasterizerState.FillMode = D3D12_FILL_MODE_SOLID; - rasterizerState.CullMode = D3D12_CULL_MODE_NONE; // D3D12_CULL_MODE_BACK; + D3D12_RASTERIZER_DESC2 rasterizerState = {}; + rasterizerState.FillMode = ConvertToDirectX12FillMode(parameters->FillMode); + rasterizerState.CullMode = ConvertToDirectX12CullMode(parameters->CullMode); rasterizerState.FrontCounterClockwise = false; rasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; rasterizerState.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; rasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; rasterizerState.DepthClipEnable = true; - rasterizerState.MultisampleEnable = false; - rasterizerState.AntialiasedLineEnable = false; + rasterizerState.LineRasterizationMode = D3D12_LINE_RASTERIZATION_MODE_ALIASED; // TODO: Check those 2 rasterizerState.ForcedSampleCount = 0; rasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; - /* - D3D12_DEPTH_STENCIL_DESC depthStencilState = {}; - - if (renderPassDescriptor.DepthBufferOperation != GraphicsDepthBufferOperation::DepthNone) - { - depthStencilState.DepthEnable = true; - depthStencilState.StencilEnable = false; - - if (renderPassDescriptor.DepthBufferOperation == GraphicsDepthBufferOperation::ClearWrite || - renderPassDescriptor.DepthBufferOperation == GraphicsDepthBufferOperation::Write) - { - depthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; - } - - if (renderPassDescriptor.DepthBufferOperation == GraphicsDepthBufferOperation::CompareEqual) - { - depthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_EQUAL; - } - - else - { - depthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER; - } - } -*/ - D3D12_BLEND_DESC blendState = {}; - blendState.AlphaToCoverageEnable = false; - blendState.IndependentBlendEnable = false; -/* - if (renderPassDescriptor.RenderTarget1BlendOperation.HasValue) - { - auto blendOperation = renderPassDescriptor.RenderTarget1BlendOperation.Value; - blendState.RenderTarget[0] = InitBlendState(blendOperation); - } - - else - {*/ - blendState.RenderTarget[0] = { - false, - false, - D3D12_BLEND_ONE, - D3D12_BLEND_ZERO, - D3D12_BLEND_OP_ADD, - D3D12_BLEND_ONE, - D3D12_BLEND_ZERO, - D3D12_BLEND_OP_ADD, - D3D12_LOGIC_OP_NOOP, - D3D12_COLOR_WRITE_ENABLE_ALL, - }; // InitBlendState(GraphicsBlendOperation::None); - //} - - D3D12_PIPELINE_STATE_STREAM_DESC psoStream = {}; GraphicsPso psoDesc = {}; - psoDesc.RootSignature = graphicsDeviceData->RootSignature.Get(); if (parameters->MeshShaderFunction) @@ -257,15 +322,17 @@ ComPtr CreateDirectX12OldPSO(ElemGraphicsDevice graphicsDev } psoDesc.RenderTargets = renderTargets; - psoDesc.SampleDesc = sampleDesc; - psoDesc.RasterizerState = rasterizerState; psoDesc.DepthStencilFormat = depthFormat; - //psoDesc.DepthStencilState = depthStencilState; + psoDesc.DepthStencilState = depthStencilState; + psoDesc.RasterizerState = rasterizerState; psoDesc.BlendState = blendState; + psoDesc.SampleDesc = sampleDesc; + D3D12_PIPELINE_STATE_STREAM_DESC psoStream = {}; psoStream.SizeInBytes = sizeof(GraphicsPso); psoStream.pPipelineStateSubobjectStream = &psoDesc; + ComPtr pipelineState; AssertIfFailed(graphicsDeviceData->Device->CreatePipelineState(&psoStream, IID_PPV_ARGS(pipelineState.GetAddressOf()))); return pipelineState; @@ -288,8 +355,8 @@ ElemPipelineState DirectX12CompileGraphicsPipelineState(ElemGraphicsDevice graph // TODO: Test for now without stack auto stackMemoryArena = SystemGetStackMemoryArena(); - auto stateSubObjects = SystemPushArray(stackMemoryArena, 32); - auto stateSubObjectsIndex = 0u; + //auto stateSubObjects = SystemPushArray(stackMemoryArena, 32); + //auto stateSubObjectsIndex = 0u; auto dxilLibraries = SystemPushArray(stackMemoryArena, 8); auto dxilLibrariesIndex = 0u; @@ -494,6 +561,7 @@ void DirectX12BindPipelineState(ElemCommandList commandList, ElemPipelineState p auto pipelineStateData = GetDirectX12PipelineStateData(pipelineState); SystemAssert(pipelineStateData); + /* D3D12_SET_PROGRAM_DESC programDesc = { .Type = D3D12_PROGRAM_TYPE_GENERIC_PIPELINE, @@ -501,7 +569,7 @@ void DirectX12BindPipelineState(ElemCommandList commandList, ElemPipelineState p { .ProgramIdentifier = pipelineStateData->ProgramIdentifier } - }; + };*/ commandListData->PipelineStateType = pipelineStateData->PipelineStateType; diff --git a/src/Elemental/Microsoft/Graphics/DirectX12Shader.h b/src/Elemental/Microsoft/Graphics/DirectX12Shader.h index c44b0d3f..b88357d3 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12Shader.h +++ b/src/Elemental/Microsoft/Graphics/DirectX12Shader.h @@ -59,13 +59,12 @@ struct GraphicsPso { public: PsoSubObject(RootSignature, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE, ID3D12RootSignature*); - PsoSubObject(AS, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS, D3D12_SHADER_BYTECODE); PsoSubObject(MS, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MS, D3D12_SHADER_BYTECODE); PsoSubObject(PS, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS, D3D12_SHADER_BYTECODE); PsoSubObject(RenderTargets, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS, D3D12_RT_FORMAT_ARRAY); PsoSubObject(SampleDesc, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC, DXGI_SAMPLE_DESC); - PsoSubObject(RasterizerState, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER, D3D12_RASTERIZER_DESC); + PsoSubObject(RasterizerState, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER2, D3D12_RASTERIZER_DESC2); PsoSubObject(DepthStencilFormat, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT, DXGI_FORMAT); - PsoSubObject(DepthStencilState, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL, D3D12_DEPTH_STENCIL_DESC); + PsoSubObject(DepthStencilState, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL2, D3D12_DEPTH_STENCIL_DESC2); PsoSubObject(BlendState, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND, D3D12_BLEND_DESC); }; diff --git a/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.cpp b/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.cpp index 1abf17c7..1cf6efb8 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.cpp +++ b/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.cpp @@ -9,7 +9,6 @@ #include "SystemFunctions.h" #include "SystemMemory.h" -#define DIRECTX12_MAX_SWAPCHAINS 10u // TODO: Without FPS limit we can maybe debug UAV Barrier #define ENABLE_TEARING 0 @@ -52,16 +51,18 @@ void CreateDirectX12SwapChainRenderTargetViews(ElemSwapChain swapChain) ComPtr backBuffer; AssertIfFailed(swapChainData->DeviceObject->GetBuffer(i, IID_PPV_ARGS(backBuffer.GetAddressOf()))); - swapChainData->BackBufferTextures[i] = CreateDirectX12GraphicsResourceFromResource(swapChainDataFull->GraphicsDevice, ElemGraphicsResourceType_Texture2D, backBuffer, true); + swapChainData->BackBufferTextures[i] = CreateDirectX12GraphicsResourceFromResource(swapChainDataFull->GraphicsDevice, ElemGraphicsResourceType_Texture2D, ELEM_HANDLE_NULL, backBuffer, true); } } -void ResizeDirectX12SwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t height) +void ResizeDirectX12SwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t height, float uiScale) { if (width == 0 || height == 0) { return; } + + // BUG: When switching to a screen with a different refresh rate! SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Resize Swapchain to %dx%d.", width, height); SystemAssert(swapChain != ELEM_HANDLE_NULL); @@ -72,6 +73,7 @@ void ResizeDirectX12SwapChain(ElemSwapChain swapChain, uint32_t width, uint32_t swapChainData->Width = width; swapChainData->Height = height; swapChainData->AspectRatio = (float)width / height; + swapChainData->UIScale = uiScale; auto fence = CreateDirectX12CommandQueueFence(swapChainData->CommandQueue); ElemWaitForFenceOnCpu(fence); @@ -193,6 +195,12 @@ void CheckDirectX12AvailableSwapChain(ElemHandle handle) // TODO: If delta time is above a thresold, take the delta time based on target FPS auto deltaTime = vSyncDelta * refreshInterval;//(nextVSyncQPCTime.QuadPart - swapChainData->PreviousTargetPresentationTimestamp.QuadPart) / ticksPerSecond; + + if (deltaTime <= 0.0f) + { + deltaTime = 1.0f / 60.0f; + } + swapChainData->PreviousTargetPresentationTimestamp = nextVSyncQPCTime; if (vSyncDelta > 1) @@ -207,7 +215,7 @@ void CheckDirectX12AvailableSwapChain(ElemHandle handle) if (windowSize.Width > 0 && windowSize.Height > 0 && (windowSize.Width != swapChainData->Width || windowSize.Height != swapChainData->Height)) { - ResizeDirectX12SwapChain(handle, windowSize.Width, windowSize.Height); + ResizeDirectX12SwapChain(handle, windowSize.Width, windowSize.Height, windowSize.UIScale); sizeChanged = true; } @@ -327,6 +335,8 @@ ElemSwapChain DirectX12CreateSwapChain(ElemCommandQueue commandQueue, ElemWindow .Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT #endif }; + + SystemLogDebugMessage(ElemLogMessageCategory_Graphics, "Create swapchain with size: %dx%d@%f. (%dHz)", windowRenderSize.Width, windowRenderSize.Height, windowRenderSize.UIScale, targetFPS); ComPtr swapChain; AssertIfFailedReturnNullHandle(DxgiFactory->CreateSwapChainForHwnd(commandQueueData->DeviceObject.Get(), windowData->WindowHandle, &swapChainDesc, nullptr, nullptr, (IDXGISwapChain1**)swapChain.GetAddressOf())); @@ -360,6 +370,7 @@ ElemSwapChain DirectX12CreateSwapChain(ElemCommandQueue commandQueue, ElemWindow .Width = width, .Height = height, .AspectRatio = (float)width / height, + .UIScale = windowRenderSize.UIScale, .Format = ElemGraphicsFormat_B8G8R8A8_SRGB, // TODO: change that .FrameLatency = frameLatency, .TargetFPS = targetFPS @@ -410,9 +421,11 @@ ElemSwapChainInfo DirectX12GetSwapChainInfo(ElemSwapChain swapChain) return { + .Window = swapChainData->Window, .Width = swapChainData->Width, .Height = swapChainData->Height, .AspectRatio = swapChainData->AspectRatio, + .UIScale = swapChainData->UIScale, .Format = swapChainData->Format }; } @@ -453,7 +466,7 @@ void DirectX12PresentSwapChain(ElemSwapChain swapChain) // TODO: Compute vsync interval based on target fps (rework that, it only works for multiples for now!) // TODO: We need to take into account the current present time with the nextPresentTime computed from update function // For example, if we present and we missed a vsync, the calculation should take that into account - auto vsyncInteval = windowData->MonitorRefreshRate / swapChainData->TargetFPS; + //auto vsyncInteval = windowData->MonitorRefreshRate / swapChainData->TargetFPS; // TODO: Control the next vsync for frame pacing (eg: running at 30fps on a 120hz screen) //AssertIfFailed(swapChainData->DeviceObject->Present(vsyncInteval, 0)); @@ -464,5 +477,5 @@ void DirectX12PresentSwapChain(ElemSwapChain swapChain) #endif DirectX12ResetCommandAllocation(swapChainDataFull->GraphicsDevice); - DirectX12ProcessGraphicsResourceDeleteQueue(); + DirectX12ProcessGraphicsResourceDeleteQueue(swapChainDataFull->GraphicsDevice); } diff --git a/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.h b/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.h index 20668391..9ae4e6f5 100644 --- a/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.h +++ b/src/Elemental/Microsoft/Graphics/DirectX12SwapChain.h @@ -1,9 +1,8 @@ #pragma once +#include "DirectX12Config.h" #include "Elemental.h" -#define DIRECTX12_MAX_SWAPCHAIN_BUFFERS 3 - // TODO: Review structure struct DirectX12SwapChainData { @@ -19,6 +18,7 @@ struct DirectX12SwapChainData uint32_t Width; uint32_t Height; float AspectRatio; + float UIScale; ElemGraphicsFormat Format; bool PresentCalled; uint32_t FrameLatency; diff --git a/src/Elemental/Microsoft/PreCompiledHeader.h b/src/Elemental/Microsoft/PreCompiledHeader.h index bc0ea493..1fc7654c 100644 --- a/src/Elemental/Microsoft/PreCompiledHeader.h +++ b/src/Elemental/Microsoft/PreCompiledHeader.h @@ -18,7 +18,6 @@ #include -#define PackedStruct struct __attribute__((__packed__)) #define ElemAPI extern "C" __declspec(dllexport) #define AssertIfFailed(expression) SystemAssert(SUCCEEDED((expression))) #define AssertIfFailedReturnNullHandle(expression) SystemAssertReturnNullHandle(SUCCEEDED((expression))) diff --git a/src/Elemental/Microsoft/SystemPlatformFunctions.cpp b/src/Elemental/Microsoft/SystemPlatformFunctions.cpp index 3e635275..709a9add 100644 --- a/src/Elemental/Microsoft/SystemPlatformFunctions.cpp +++ b/src/Elemental/Microsoft/SystemPlatformFunctions.cpp @@ -4,7 +4,9 @@ #ifdef ElemAPI #include "SystemLogging.h" #else +#ifndef SystemLogErrorMessage #define SystemLogErrorMessage(category, format, ...) +#endif #endif SystemPlatformAllocationInfos systemPlatformAllocationInfos; @@ -122,11 +124,11 @@ size_t SystemPlatformFileGetSizeInBytes(ReadOnlySpan path) auto stackMemoryArena = SystemGetStackMemoryArena(); auto pathWide = SystemConvertUtf8ToWideChar(stackMemoryArena, path); - auto fileHandle = CreateFile(pathWide.Pointer, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + auto fileHandle = CreateFile(pathWide.Pointer, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr); if (fileHandle == INVALID_HANDLE_VALUE) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open file %s for reading. (Error code: %d)", path.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open file %s for reading. (Error code: %d)", path.Pointer, (int32_t)GetLastError()); return 0; } @@ -141,18 +143,18 @@ void SystemPlatformFileWriteBytes(ReadOnlySpan path, ReadOnlySpan auto stackMemoryArena = SystemGetStackMemoryArena(); auto pathWide = SystemConvertUtf8ToWideChar(stackMemoryArena, path); - auto fileHandle = CreateFile(pathWide.Pointer, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + auto fileHandle = CreateFile(pathWide.Pointer, GENERIC_WRITE, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (fileHandle == INVALID_HANDLE_VALUE) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open file %s for writing. (Error code: %d)", path.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open file %s for writing. (Error code: %d)", path.Pointer, (int32_t)GetLastError()); return; } DWORD bytesWritten; if (!WriteFile(fileHandle, data.Pointer, data.Length, &bytesWritten, nullptr) || bytesWritten != data.Length) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Error writing to file %s. (Error code: %d)", path.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Error writing to file %s. (Error code: %d)", path.Pointer, (int32_t)GetLastError()); } CloseHandle(fileHandle); @@ -163,18 +165,18 @@ void SystemPlatformFileReadBytes(ReadOnlySpan path, Span data) auto stackMemoryArena = SystemGetStackMemoryArena(); auto pathWide = SystemConvertUtf8ToWideChar(stackMemoryArena, path); - auto fileHandle = CreateFile(pathWide.Pointer, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + auto fileHandle = CreateFile(pathWide.Pointer, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr); if (fileHandle == INVALID_HANDLE_VALUE) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open file %s for reading. (Error code: %d)", path.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open file %s for reading. (Error code: %d)", path.Pointer, (int32_t)GetLastError()); return; } DWORD bytesRead; if (!ReadFile(fileHandle, data.Pointer, data.Length, &bytesRead, nullptr) || bytesRead != data.Length) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Error reading file %s. (Error code: %d)", path.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Error reading file %s. (Error code: %d)", path.Pointer, (int32_t)GetLastError()); } CloseHandle(fileHandle); @@ -187,7 +189,7 @@ void SystemPlatformFileDelete(ReadOnlySpan path) if (!DeleteFile(pathWide.Pointer)) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot delete file %s. (Error code: %d)", path.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot delete file %s. (Error code: %d)", path.Pointer, (int32_t)GetLastError()); } } @@ -204,7 +206,7 @@ ReadOnlySpan SystemPlatformExecuteProcess(MemoryArena memoryArena, ReadOnl HANDLE readPipe, writePipe; if (!CreatePipe(&readPipe, &writePipe, &pipeAttributes, 0)) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open read/write pipe for launching command: %s (Error code: %d)", command.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot open read/write pipe for launching command: %s (Error code: %d)", command.Pointer, (int32_t)GetLastError()); return ReadOnlySpan(); } @@ -217,7 +219,7 @@ ReadOnlySpan SystemPlatformExecuteProcess(MemoryArena memoryArena, ReadOnl PROCESS_INFORMATION processInfo {}; if (!CreateProcess(nullptr, (LPWSTR)commandWide.Pointer, nullptr, nullptr, true, 0, nullptr, nullptr, &startupInfo, &processInfo)) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot create process for launching command: %s (Error code: %d)", command.Pointer, GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot create process for launching command: %s (Error code: %d)", command.Pointer, (int32_t)GetLastError()); return ReadOnlySpan(); } @@ -266,7 +268,7 @@ void* SystemPlatformCreateThread(void* threadFunction, void* parameters) if (threadHandle == nullptr) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot create thread (Error code: %d)", GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Cannot create thread (Error code: %d)", (int32_t)GetLastError()); return nullptr; } @@ -285,7 +287,7 @@ void SystemPlatformWaitThread(void* thread) if (waitResult != WAIT_OBJECT_0) { - SystemLogErrorMessage(ElemLogMessageCategory_Application, "Failed to wait on thread (Error code: %d)", GetLastError()); + SystemLogErrorMessage(ElemLogMessageCategory_Application, "Failed to wait on thread (Error code: %d)", (int32_t)GetLastError()); } } diff --git a/src/Elemental/Microsoft/UnityBuild.cpp b/src/Elemental/Microsoft/UnityBuild.cpp index 46ecbd32..2d0d6432 100644 --- a/src/Elemental/Microsoft/UnityBuild.cpp +++ b/src/Elemental/Microsoft/UnityBuild.cpp @@ -33,6 +33,7 @@ #include "Graphics/Resource.cpp" #include "Graphics/Shader.cpp" #include "Graphics/Rendering.cpp" +#include "Graphics/UploadBufferPool.cpp" #include "Inputs/Inputs.cpp" #include "Inputs/HidDevices.cpp" diff --git a/src/Elemental/Microsoft/Win32Application.cpp b/src/Elemental/Microsoft/Win32Application.cpp index ca780e78..e8a3e76e 100644 --- a/src/Elemental/Microsoft/Win32Application.cpp +++ b/src/Elemental/Microsoft/Win32Application.cpp @@ -94,6 +94,7 @@ ElemAPI ElemSystemInfo ElemGetSystemInfo() ElemAPI int32_t ElemRunApplication(const ElemRunApplicationParameters* parameters) { + auto stackMemoryArena = SystemGetStackMemoryArena(); InitWin32ApplicationMemory(); if (parameters->InitHandler) @@ -118,6 +119,12 @@ ElemAPI int32_t ElemRunApplication(const ElemRunApplicationParameters* parameter } } + auto allocationInfos = SystemGetAllocationInfos(); + + SystemLogDebugMessage(ElemLogMessageCategory_Application, "Allocated lib memory before releasing: %s/%s", + SystemFormatMemorySize(stackMemoryArena, allocationInfos.CommittedBytes).Pointer, + SystemFormatMemorySize(stackMemoryArena, allocationInfos.ReservedBytes).Pointer); + if (parameters->FreeHandler) { parameters->FreeHandler(parameters->Payload); diff --git a/src/Elemental/Microsoft/Win32Inputs.cpp b/src/Elemental/Microsoft/Win32Inputs.cpp index 6ad386c5..05fcaab8 100644 --- a/src/Elemental/Microsoft/Win32Inputs.cpp +++ b/src/Elemental/Microsoft/Win32Inputs.cpp @@ -39,6 +39,7 @@ ElemInputDevice AddWin32RawInputDevice(HANDLE device, DWORD type) } else if (type == RIM_TYPEHID) { + // BUG: Crash when steam input is activated if (!IsHidDeviceSupported(rawInputDeviceInfo->hid.dwVendorId, rawInputDeviceInfo->hid.dwProductId)) { SystemLogWarningMessage(ElemLogMessageCategory_Inputs, "HID Input device is not supported. (Vendor: %d, Product: %d)", rawInputDeviceInfo->hid.dwVendorId, rawInputDeviceInfo->hid.dwProductId); @@ -516,3 +517,91 @@ void ProcessWin32RawInput(ElemWindow window, LPARAM lParam) ProcessHidDeviceData(window, inputDevice, ReadOnlySpan(rawInputData->data.hid.bRawData, rawInputData->data.hid.dwSizeHid), elapsedSeconds); } } + +void CreateWin32HidHandleIfNeeded(ElemInputDevice inputDevice) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData); + + if (!inputDeviceData->PlatformData) + { + uint32_t inputDevicePathSize; + AssertIfFailed(GetRawInputDeviceInfo(inputDeviceData->PlatformHandle, RIDI_DEVICENAME, nullptr, &inputDevicePathSize)); + + auto rawInputDeviceInfo = SystemPushArray(stackMemoryArena, inputDevicePathSize).Pointer; + + AssertIfFailed(GetRawInputDeviceInfo(inputDeviceData->PlatformHandle, RIDI_DEVICENAME, rawInputDeviceInfo, &inputDevicePathSize)); + + auto inputDeviceHandle = CreateFile(rawInputDeviceInfo, + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + 0, + nullptr); + + if (inputDeviceHandle == INVALID_HANDLE_VALUE) + { + SystemLogErrorMessage(ElemLogMessageCategory_Inputs, "Cannot get hid device."); + return; + } + + inputDeviceData->PlatformData = inputDeviceHandle; + } +} + +bool PlatformHidSendOutputReport(ElemInputDevice inputDevice, ReadOnlySpan data) +{ + CreateWin32HidHandleIfNeeded(inputDevice); + + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData); + + auto success = WriteFile(inputDeviceData->PlatformData, data.Pointer, data.Length, nullptr, nullptr); + + if (!success) + { + SystemLogErrorMessage(ElemLogMessageCategory_Inputs, "Error Code: %d", GetLastError()); + return false; + } + + return true; +} + +bool PlatformHidSendFeatureReport(ElemInputDevice inputDevice, ReadOnlySpan data) +{ + CreateWin32HidHandleIfNeeded(inputDevice); + + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData); + + auto success = HidD_SetFeature(inputDeviceData->PlatformData, (void*)data.Pointer, data.Length); + + if (!success) + { + SystemLogErrorMessage(ElemLogMessageCategory_Inputs, "Error Code: %d", GetLastError()); + return false; + } + + return true; +} + +bool PlatformHidGetFeatureReport(ElemInputDevice inputDevice, ReadOnlySpan data) +{ + CreateWin32HidHandleIfNeeded(inputDevice); + + auto inputDeviceData = GetInputDeviceData(inputDevice); + SystemAssert(inputDeviceData); + + auto success = HidD_GetFeature(inputDeviceData->PlatformData, (void*)data.Pointer, data.Length); + + if (!success) + { + SystemLogErrorMessage(ElemLogMessageCategory_Inputs, "Error Code: %d", GetLastError()); + return false; + } + + return true; +} diff --git a/src/Elemental/Microsoft/Win32Window.cpp b/src/Elemental/Microsoft/Win32Window.cpp index 10a43b07..4ec840bf 100644 --- a/src/Elemental/Microsoft/Win32Window.cpp +++ b/src/Elemental/Microsoft/Win32Window.cpp @@ -343,6 +343,7 @@ ElemAPI void ElemFreeWindow(ElemWindow window) SystemRemoveDataPoolItem(windowDataPool, window); } +// BUG: We have a bug when we switch to the TV with the refresh rate ElemAPI ElemWindowSize ElemGetWindowRenderSize(ElemWindow window) { SystemAssert(window != ELEM_HANDLE_NULL); diff --git a/src/ElementalTools/CMakeLists.txt b/src/ElementalTools/CMakeLists.txt index 36482805..72744b9c 100644 --- a/src/ElementalTools/CMakeLists.txt +++ b/src/ElementalTools/CMakeLists.txt @@ -1,12 +1,12 @@ if(WIN32) - set(ELEM_PLATFORM_DIR Microsoft) + set(ELEM_PLATFORM_DIR Platforms/Microsoft) elseif(APPLE) - set(ELEM_PLATFORM_DIR Apple) + set(ELEM_PLATFORM_DIR Platforms/Apple) elseif(LINUX) - set(ELEM_PLATFORM_DIR Linux) + set(ELEM_PLATFORM_DIR Platforms/Linux) endif() -add_library(ElementalTools SHARED Common/UnityBuild.cpp) +add_library(ElementalTools SHARED UnityBuild.cpp) target_include_directories(ElementalTools PUBLIC .) target_precompile_headers(ElementalTools PRIVATE ${ELEM_PLATFORM_DIR}/PreCompiledHeader.h) diff --git a/src/ElementalTools/Common/UnityBuild.cpp b/src/ElementalTools/Common/UnityBuild.cpp deleted file mode 100644 index 4486cc3f..00000000 --- a/src/ElementalTools/Common/UnityBuild.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "ShaderCompiler.cpp" -#include "ShaderCompilerUtils.cpp" -#include "DirectXShaderCompiler.cpp" - -#ifndef __linux__ -#include "MetalShaderConverter.cpp" -#endif - -#include "SystemFunctions.cpp" -#include "SystemDictionary.cpp" -#include "SystemMemory.cpp" -#include "SystemPlatformFunctions.cpp" - -#ifndef _WIN32 -#include "PosixPlatformFunctions.cpp" -#endif diff --git a/src/ElementalTools/ElementalTools.h b/src/ElementalTools/ElementalTools.h index a407ec6f..a5c9d851 100644 --- a/src/ElementalTools/ElementalTools.h +++ b/src/ElementalTools/ElementalTools.h @@ -37,30 +37,9 @@ #endif //------------------------------------------------------------------------ -// ##Module_Application## +// Module: Elemental Tools //------------------------------------------------------------------------ -/** - * Enumerates supported shader languages. - */ -typedef enum -{ - // Unknown shader language. - ElemShaderLanguage_Unknown = 0, - // High Level Shading Language used by DirectX. - ElemShaderLanguage_Hlsl = 1, - // OpenGL Shading Language. - ElemShaderLanguage_Glsl = 2, - // Metal Shading Language for Apple devices. - ElemShaderLanguage_Msl = 3, - // DirectX Intermediate Language. - ElemShaderLanguage_Dxil = 4, - // Standard Portable Intermediate Representation. - ElemShaderLanguage_Spirv = 5, - // Intermediate representation for Metal. - ElemShaderLanguage_MetalIR = 6 -} ElemShaderLanguage; - /** * Enumerates supported graphics APIs. */ @@ -113,6 +92,12 @@ typedef struct uint32_t Length; } ElemToolsDataSpan; +typedef struct +{ + uint32_t* Items; + uint32_t Length; +} ElemUInt32Span; + /** * Represents a message produced by the tools, including a type and text. */ @@ -135,16 +120,79 @@ typedef struct uint32_t Length; } ElemToolsMessageSpan; -/** - * Represents shader source data, including language and the actual code. - */ typedef struct { - // Language of the shader source code. - ElemShaderLanguage ShaderLanguage; - // Raw shader source code data. + float X, Y; +} ElemToolsVector2; + +typedef struct +{ + float X, Y, Z; +} ElemToolsVector3; + +typedef union +{ + struct + { + float X, Y, Z, W; + }; + + struct + { + ElemToolsVector3 XYZ; + }; +} ElemToolsVector4; + +typedef union +{ + float m[4][4]; + ElemToolsVector4 Rows[4]; +} ElemToolsMatrix4x4; + +typedef struct +{ + ElemToolsVector3 MinPoint; + ElemToolsVector3 MaxPoint; +} ElemToolsBoundingBox; + +typedef struct +{ ElemToolsDataSpan Data; -} ElemShaderSourceData; + uint32_t VertexSize; + uint32_t VertexCount; + // TODO: Add vertex description structure +} ElemVertexBuffer; + +typedef ElemToolsDataSpan (*ElemToolsLoadFileHandlerPtr)(const char* path); + +ElemToolsAPI void ElemToolsConfigureFileIO(ElemToolsLoadFileHandlerPtr loadFileHandler); + + +//------------------------------------------------------------------------ +// Module: Shaders +//------------------------------------------------------------------------ + +// TODO: Change the list +/** + * Enumerates supported shader languages. + */ +typedef enum +{ + // Unknown shader language. + ElemShaderLanguage_Unknown = 0, + // High Level Shading Language used by DirectX. + ElemShaderLanguage_Hlsl = 1, + // Slang Shading Language. + ElemShaderLanguage_Slang = 2, + // Metal Shading Language for Apple devices. + ElemShaderLanguage_Msl = 3, + // DirectX Intermediate Language. + ElemShaderLanguage_Dxil = 4, + // Standard Portable Intermediate Representation. + ElemShaderLanguage_Spirv = 5, + // Intermediate representation for Metal. + ElemShaderLanguage_MetalIR = 6 +} ElemShaderLanguage; /** * Configuration options for compiling shaders. @@ -153,6 +201,9 @@ typedef struct { // If true, compile shaders in debug mode to provide more information. bool DebugMode; + + // TODO: Add the ability to add an override for shader language + // TODO: Add the ability to choose Matrix packing options (default to row major for now?) } ElemCompileShaderOptions; /** @@ -160,6 +211,7 @@ typedef struct */ typedef struct { + // TODO: Add source language // Compiled shader binary data. ElemToolsDataSpan Data; // Messages generated during the compilation. @@ -185,10 +237,246 @@ ElemToolsAPI bool ElemCanCompileShader(ElemShaderLanguage shaderLanguage, ElemTo * @param options Compilation options such as debug mode. * @return The result of the compilation, including any binaries and messages. */ -ElemToolsAPI ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform, const ElemShaderSourceData* sourceData, const ElemCompileShaderOptions* options); +// TODO: Put graphics api and platform into options (default to current one) +ElemToolsAPI ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform, const char* path, const ElemCompileShaderOptions* options); // TODO: Can we compile multiple source files into one library? + +//------------------------------------------------------------------------ +// Module: SceneLoading +//------------------------------------------------------------------------ + +typedef enum +{ + ElemSceneFormat_Unknown = 0, + ElemSceneFormat_Obj = 1, + ElemSceneFormat_Gltf = 2 +} ElemSceneFormat; + +typedef enum +{ + ElemSceneCoordinateSystem_LeftHanded = 0, + ElemSceneCoordinateSystem_RightHanded = 1 +} ElemSceneCoordinateSystem; + +typedef enum +{ + ElemSceneNodeType_Unknown = 0, + ElemSceneNodeType_Mesh = 1 +} ElemSceneNodeType; + +typedef struct +{ + ElemSceneCoordinateSystem CoordinateSystem; + bool FlipVerticalTextureCoordinates; + float Scaling; + ElemToolsVector3 Rotation; + ElemToolsVector3 Translation; + // TODO: Don't add options for the format here but in the builder instead + // we still want the same format mechanism but hardcoded + + // TODO: Add options to specify the vertex format wanted +} ElemLoadSceneOptions; + +typedef struct +{ + ElemVertexBuffer VertexBuffer; + ElemUInt32Span IndexBuffer; + ElemToolsBoundingBox BoundingBox; + // TODO: SphereVolume? + int32_t MaterialId; +} ElemSceneMeshPrimitive; + +typedef struct +{ + ElemSceneMeshPrimitive* Items; + uint32_t Length; +} ElemSceneMeshPrimitiveSpan; + +typedef struct +{ + const char* Name; + ElemSceneMeshPrimitiveSpan MeshPrimitives; + ElemToolsBoundingBox BoundingBox; + // TODO: SphereVolume? +} ElemSceneMesh; + +typedef struct +{ + ElemSceneMesh* Items; + uint32_t Length; +} ElemSceneMeshSpan; + +typedef struct +{ + const char* Name; + const char* AlbedoTexturePath; + const char* NormalTexturePath; + ElemToolsVector4 AlbedoFactor; +} ElemSceneMaterial; + +typedef struct +{ + ElemSceneMaterial* Items; + uint32_t Length; +} ElemSceneMaterialSpan; + +typedef struct +{ + const char* Name; + ElemSceneNodeType NodeType; + int32_t ReferenceIndex; + ElemToolsVector4 Rotation; + float Scale; + ElemToolsVector3 Translation; + // TODO: Children +} ElemSceneNode; + +typedef struct +{ + ElemSceneNode* Items; + uint32_t Length; +} ElemSceneNodeSpan; + +typedef struct +{ + ElemSceneFormat SceneFormat; + ElemSceneCoordinateSystem CoordinateSystem; + + ElemSceneMeshSpan Meshes; + ElemSceneMaterialSpan Materials; + ElemSceneNodeSpan Nodes; + + ElemToolsMessageSpan Messages; + bool HasErrors; +} ElemLoadSceneResult; + +ElemToolsAPI ElemLoadSceneResult ElemLoadScene(const char* path, const ElemLoadSceneOptions* options); + + +//------------------------------------------------------------------------ +// Module: Meshes +//------------------------------------------------------------------------ + +// TODO: Allow to specify the offset of the vertex position? (For now we assume vertex position is at offset 0) +typedef struct +{ + // TODO: Allow bypass mesh format + // TODO: Allow customize index packing + uint32_t Reserved; +} ElemBuildMeshletsOptions; + +typedef struct +{ + // TODO: Add base vertex + uint32_t VertexIndexOffset; + uint32_t VertexIndexCount; + uint32_t TriangleOffset; + uint32_t TriangleCount; +} ElemMeshlet; + +typedef struct +{ + ElemMeshlet* Items; + uint32_t Length; +} ElemMeshletSpan; + +typedef struct +{ + uint8_t MeshletMaxVertexCount; + uint8_t MeshletMaxTriangleCount; + ElemVertexBuffer VertexBuffer; + ElemMeshletSpan Meshlets; + ElemUInt32Span MeshletVertexIndexBuffer; + ElemUInt32Span MeshletTriangleIndexBuffer; + ElemToolsMessageSpan Messages; + bool HasErrors; +} ElemBuildMeshletResult; + +ElemToolsAPI ElemBuildMeshletResult ElemBuildMeshlets(ElemVertexBuffer vertexBuffer, ElemUInt32Span indexBuffer, const ElemBuildMeshletsOptions* options); + +// TODO: Do an optimize mesh function that can be used to optimize a normal mesh (for Raytracing for example) + +//------------------------------------------------------------------------ +// Module: Textures +//------------------------------------------------------------------------ + +typedef enum +{ + ElemToolsGraphicsFormat_Unknown, + ElemToolsGraphicsFormat_R8G8B8A8, + ElemToolsGraphicsFormat_BC7 +} ElemToolsGraphicsFormat; + +typedef enum +{ + ElemLoadTextureFileFormat_Unknown = 0, + ElemLoadTextureFileFormat_Tga = 1, + ElemLoadTextureFileFormat_Jpg = 2, + ElemLoadTextureFileFormat_Png = 3, + ElemLoadTextureFileFormat_Dds = 4, +} ElemLoadTextureFileFormat; + +typedef struct +{ + uint32_t Width; + uint32_t Height; + ElemToolsDataSpan Data; +} ElemTextureMipData; + +typedef struct +{ + ElemTextureMipData* Items; + uint32_t Length; +} ElemTextureMipDataSpan; + +typedef struct +{ + uint32_t Reserved; +} ElemLoadTextureOptions; + +typedef struct +{ + ElemLoadTextureFileFormat FileFormat; + ElemToolsGraphicsFormat Format; + uint32_t Width; + uint32_t Height; + ElemTextureMipDataSpan MipData; + ElemToolsMessageSpan Messages; + bool HasErrors; +} ElemLoadTextureResult; + +typedef struct +{ + // TODO: Alpha options + uint32_t Reserved; +} ElemGenerateTextureMipDataOptions; + +typedef struct +{ + ElemTextureMipDataSpan MipData; + ElemToolsMessageSpan Messages; + bool HasErrors; +} ElemGenerateTextureMipDataResult; + +typedef struct +{ + uint32_t Reserved; +} ElemCompressTextureMipDataOptions; + +typedef struct +{ + ElemToolsGraphicsFormat Format; + ElemTextureMipData MipData; + ElemToolsMessageSpan Messages; + bool HasErrors; +} ElemCompressTextureMipDataResult; + +ElemToolsAPI ElemLoadTextureResult ElemLoadTexture(const char* path, const ElemLoadTextureOptions* options); +ElemToolsAPI ElemGenerateTextureMipDataResult ElemGenerateTextureMipData(ElemToolsGraphicsFormat format, const ElemTextureMipData* baseMip, const ElemGenerateTextureMipDataOptions* options); +ElemToolsAPI ElemCompressTextureMipDataResult ElemCompressTextureMipData(ElemToolsGraphicsFormat format, const ElemTextureMipData* mipData, const ElemCompressTextureMipDataOptions* options); + #ifdef UseToolsLoader #ifndef ElementalToolsLoader #include "ElementalToolsLoader.c" diff --git a/src/ElementalTools/ElementalToolsLoader.c b/src/ElementalTools/ElementalToolsLoader.c index 01d7d73c..6174f24d 100644 --- a/src/ElementalTools/ElementalToolsLoader.c +++ b/src/ElementalTools/ElementalToolsLoader.c @@ -23,8 +23,14 @@ static int functionPointersLoadedElementalTools = 0; typedef struct ElementalToolsFunctions { + void (*ElemToolsConfigureFileIO)(ElemToolsLoadFileHandlerPtr); bool (*ElemCanCompileShader)(ElemShaderLanguage, ElemToolsGraphicsApi, ElemToolsPlatform); - ElemShaderCompilationResult (*ElemCompileShaderLibrary)(ElemToolsGraphicsApi, ElemToolsPlatform, const ElemShaderSourceData*, const ElemCompileShaderOptions*); + ElemShaderCompilationResult (*ElemCompileShaderLibrary)(ElemToolsGraphicsApi, ElemToolsPlatform, char const *, ElemCompileShaderOptions const *); + ElemLoadSceneResult (*ElemLoadScene)(char const *, ElemLoadSceneOptions const *); + ElemBuildMeshletResult (*ElemBuildMeshlets)(ElemVertexBuffer, ElemUInt32Span, ElemBuildMeshletsOptions const *); + ElemLoadTextureResult (*ElemLoadTexture)(char const *, ElemLoadTextureOptions const *); + ElemGenerateTextureMipDataResult (*ElemGenerateTextureMipData)(ElemToolsGraphicsFormat, ElemTextureMipData const *, ElemGenerateTextureMipDataOptions const *); + ElemCompressTextureMipDataResult (*ElemCompressTextureMipData)(ElemToolsGraphicsFormat, ElemTextureMipData const *, ElemCompressTextureMipDataOptions const *); } ElementalToolsFunctions; @@ -77,14 +83,37 @@ static bool LoadElementalToolsFunctionPointers(void) return functionPointersLoadedElementalTools; } + listElementalToolsFunctions.ElemToolsConfigureFileIO = (void (*)(ElemToolsLoadFileHandlerPtr))GetElementalToolsFunctionPointer("ElemToolsConfigureFileIO"); listElementalToolsFunctions.ElemCanCompileShader = (bool (*)(ElemShaderLanguage, ElemToolsGraphicsApi, ElemToolsPlatform))GetElementalToolsFunctionPointer("ElemCanCompileShader"); - listElementalToolsFunctions.ElemCompileShaderLibrary = (ElemShaderCompilationResult (*)(ElemToolsGraphicsApi, ElemToolsPlatform, const ElemShaderSourceData*, const ElemCompileShaderOptions*))GetElementalToolsFunctionPointer("ElemCompileShaderLibrary"); + listElementalToolsFunctions.ElemCompileShaderLibrary = (ElemShaderCompilationResult (*)(ElemToolsGraphicsApi, ElemToolsPlatform, char const *, ElemCompileShaderOptions const *))GetElementalToolsFunctionPointer("ElemCompileShaderLibrary"); + listElementalToolsFunctions.ElemLoadScene = (ElemLoadSceneResult (*)(char const *, ElemLoadSceneOptions const *))GetElementalToolsFunctionPointer("ElemLoadScene"); + listElementalToolsFunctions.ElemBuildMeshlets = (ElemBuildMeshletResult (*)(ElemVertexBuffer, ElemUInt32Span, ElemBuildMeshletsOptions const *))GetElementalToolsFunctionPointer("ElemBuildMeshlets"); + listElementalToolsFunctions.ElemLoadTexture = (ElemLoadTextureResult (*)(char const *, ElemLoadTextureOptions const *))GetElementalToolsFunctionPointer("ElemLoadTexture"); + listElementalToolsFunctions.ElemGenerateTextureMipData = (ElemGenerateTextureMipDataResult (*)(ElemToolsGraphicsFormat, ElemTextureMipData const *, ElemGenerateTextureMipDataOptions const *))GetElementalToolsFunctionPointer("ElemGenerateTextureMipData"); + listElementalToolsFunctions.ElemCompressTextureMipData = (ElemCompressTextureMipDataResult (*)(ElemToolsGraphicsFormat, ElemTextureMipData const *, ElemCompressTextureMipDataOptions const *))GetElementalToolsFunctionPointer("ElemCompressTextureMipData"); functionPointersLoadedElementalTools = 1; return true; } +static inline void ElemToolsConfigureFileIO(ElemToolsLoadFileHandlerPtr loadFileHandler) +{ + if (!LoadElementalToolsFunctionPointers()) + { + assert(libraryElementalTools); + return; + } + + if (!listElementalToolsFunctions.ElemToolsConfigureFileIO) + { + assert(listElementalToolsFunctions.ElemToolsConfigureFileIO); + return; + } + + listElementalToolsFunctions.ElemToolsConfigureFileIO(loadFileHandler); +} + static inline bool ElemCanCompileShader(ElemShaderLanguage shaderLanguage, ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform) { if (!LoadElementalToolsFunctionPointers()) @@ -116,7 +145,7 @@ static inline bool ElemCanCompileShader(ElemShaderLanguage shaderLanguage, ElemT return listElementalToolsFunctions.ElemCanCompileShader(shaderLanguage, graphicsApi, platform); } -static inline ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform, const ElemShaderSourceData* sourceData, const ElemCompileShaderOptions* options) +static inline ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform, char const * path, ElemCompileShaderOptions const * options) { if (!LoadElementalToolsFunctionPointers()) { @@ -144,5 +173,160 @@ static inline ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGrap return result; } - return listElementalToolsFunctions.ElemCompileShaderLibrary(graphicsApi, platform, sourceData, options); + return listElementalToolsFunctions.ElemCompileShaderLibrary(graphicsApi, platform, path, options); +} + +static inline ElemLoadSceneResult ElemLoadScene(char const * path, ElemLoadSceneOptions const * options) +{ + if (!LoadElementalToolsFunctionPointers()) + { + assert(libraryElementalTools); + + #ifdef __cplusplus + ElemLoadSceneResult result = {}; + #else + ElemLoadSceneResult result = (ElemLoadSceneResult){0}; + #endif + + return result; + } + + if (!listElementalToolsFunctions.ElemLoadScene) + { + assert(listElementalToolsFunctions.ElemLoadScene); + + #ifdef __cplusplus + ElemLoadSceneResult result = {}; + #else + ElemLoadSceneResult result = (ElemLoadSceneResult){0}; + #endif + + return result; + } + + return listElementalToolsFunctions.ElemLoadScene(path, options); +} + +static inline ElemBuildMeshletResult ElemBuildMeshlets(ElemVertexBuffer vertexBuffer, ElemUInt32Span indexBuffer, ElemBuildMeshletsOptions const * options) +{ + if (!LoadElementalToolsFunctionPointers()) + { + assert(libraryElementalTools); + + #ifdef __cplusplus + ElemBuildMeshletResult result = {}; + #else + ElemBuildMeshletResult result = (ElemBuildMeshletResult){0}; + #endif + + return result; + } + + if (!listElementalToolsFunctions.ElemBuildMeshlets) + { + assert(listElementalToolsFunctions.ElemBuildMeshlets); + + #ifdef __cplusplus + ElemBuildMeshletResult result = {}; + #else + ElemBuildMeshletResult result = (ElemBuildMeshletResult){0}; + #endif + + return result; + } + + return listElementalToolsFunctions.ElemBuildMeshlets(vertexBuffer, indexBuffer, options); +} + +static inline ElemLoadTextureResult ElemLoadTexture(char const * path, ElemLoadTextureOptions const * options) +{ + if (!LoadElementalToolsFunctionPointers()) + { + assert(libraryElementalTools); + + #ifdef __cplusplus + ElemLoadTextureResult result = {}; + #else + ElemLoadTextureResult result = (ElemLoadTextureResult){0}; + #endif + + return result; + } + + if (!listElementalToolsFunctions.ElemLoadTexture) + { + assert(listElementalToolsFunctions.ElemLoadTexture); + + #ifdef __cplusplus + ElemLoadTextureResult result = {}; + #else + ElemLoadTextureResult result = (ElemLoadTextureResult){0}; + #endif + + return result; + } + + return listElementalToolsFunctions.ElemLoadTexture(path, options); +} + +static inline ElemGenerateTextureMipDataResult ElemGenerateTextureMipData(ElemToolsGraphicsFormat format, ElemTextureMipData const * baseMip, ElemGenerateTextureMipDataOptions const * options) +{ + if (!LoadElementalToolsFunctionPointers()) + { + assert(libraryElementalTools); + + #ifdef __cplusplus + ElemGenerateTextureMipDataResult result = {}; + #else + ElemGenerateTextureMipDataResult result = (ElemGenerateTextureMipDataResult){0}; + #endif + + return result; + } + + if (!listElementalToolsFunctions.ElemGenerateTextureMipData) + { + assert(listElementalToolsFunctions.ElemGenerateTextureMipData); + + #ifdef __cplusplus + ElemGenerateTextureMipDataResult result = {}; + #else + ElemGenerateTextureMipDataResult result = (ElemGenerateTextureMipDataResult){0}; + #endif + + return result; + } + + return listElementalToolsFunctions.ElemGenerateTextureMipData(format, baseMip, options); +} + +static inline ElemCompressTextureMipDataResult ElemCompressTextureMipData(ElemToolsGraphicsFormat format, ElemTextureMipData const * mipData, ElemCompressTextureMipDataOptions const * options) +{ + if (!LoadElementalToolsFunctionPointers()) + { + assert(libraryElementalTools); + + #ifdef __cplusplus + ElemCompressTextureMipDataResult result = {}; + #else + ElemCompressTextureMipDataResult result = (ElemCompressTextureMipDataResult){0}; + #endif + + return result; + } + + if (!listElementalToolsFunctions.ElemCompressTextureMipData) + { + assert(listElementalToolsFunctions.ElemCompressTextureMipData); + + #ifdef __cplusplus + ElemCompressTextureMipDataResult result = {}; + #else + ElemCompressTextureMipDataResult result = (ElemCompressTextureMipDataResult){0}; + #endif + + return result; + } + + return listElementalToolsFunctions.ElemCompressTextureMipData(format, mipData, options); } diff --git a/src/ElementalTools/Meshes/MeshletBuilder.cpp b/src/ElementalTools/Meshes/MeshletBuilder.cpp new file mode 100644 index 00000000..62c4e78f --- /dev/null +++ b/src/ElementalTools/Meshes/MeshletBuilder.cpp @@ -0,0 +1,133 @@ +#include "ElementalTools.h" +#include "SystemMemory.h" + +// TODO: Do one for each thread +static MemoryArena MeshletBuilderMemoryArena; + +void InitMeshletBuilderMemoryArena() +{ + if (MeshletBuilderMemoryArena.Storage == nullptr) + { + MeshletBuilderMemoryArena = SystemAllocateMemoryArena(512 * 1024 * 1024); + } + + SystemClearMemoryArena(MeshletBuilderMemoryArena); +} + +ElemToolsAPI ElemBuildMeshletResult ElemBuildMeshlets(ElemVertexBuffer vertexBuffer, ElemUInt32Span indexBuffer, const ElemBuildMeshletsOptions* options) +{ + InitMeshletBuilderMemoryArena(); + + // TODO: Error messages + + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto indexCount = vertexBuffer.Data.Length / vertexBuffer.VertexSize; + uint32_t* indexBufferPointer = nullptr; + + if (indexBuffer.Length > 0) + { + indexBufferPointer = indexBuffer.Items; + indexCount = indexBuffer.Length; + } + + auto vertexRemap = SystemPushArrayZero(stackMemoryArena, indexCount); + + auto vertexCount = meshopt_generateVertexRemap(vertexRemap.Pointer, indexBufferPointer, indexCount, vertexBuffer.Data.Items, indexCount, vertexBuffer.VertexSize); + + auto vertexList = SystemPushArrayZero(MeshletBuilderMemoryArena, vertexCount * vertexBuffer.VertexSize); + auto indexList = SystemPushArrayZero(MeshletBuilderMemoryArena, indexCount); + + meshopt_remapVertexBuffer(vertexList.Pointer, vertexBuffer.Data.Items, indexCount, vertexBuffer.VertexSize, vertexRemap.Pointer); + meshopt_remapIndexBuffer(indexList.Pointer, indexBufferPointer, indexCount, vertexRemap.Pointer); + + meshopt_optimizeVertexCache(indexList.Pointer, indexList.Pointer, indexCount, vertexCount); + meshopt_optimizeVertexFetch(vertexList.Pointer, indexList.Pointer, indexCount, vertexList.Pointer, vertexCount, vertexBuffer.VertexSize); + + // TODO: Compute bounding sphere and bounding box + + // TODO: Review the default values + // TODO: Allow customisation + uint8_t meshletMaxVertexCount = 64u; + uint8_t meshletMaxTriangleCount = 64u; + auto coneWeight = 0.5f; + + auto meshletCount = (uint32_t)meshopt_buildMeshletsBound(indexCount, meshletMaxVertexCount, meshletMaxTriangleCount); + + auto meshOptMeshletList = SystemPushArray(stackMemoryArena, meshletCount); + auto meshletVertexIndexList = SystemPushArray(MeshletBuilderMemoryArena, meshletCount * meshletMaxVertexCount); + auto meshletTriangleIndexListRaw = SystemPushArray(stackMemoryArena, meshletCount * meshletMaxTriangleCount * 3); + auto meshletTriangleIndexList = SystemPushArrayZero(MeshletBuilderMemoryArena, meshletCount * meshletMaxTriangleCount); + + meshletCount = meshopt_buildMeshlets(meshOptMeshletList.Pointer, + meshletVertexIndexList.Pointer, + meshletTriangleIndexListRaw.Pointer, + indexList.Pointer, + indexCount, + (const float*)vertexList.Pointer, + vertexCount, + vertexBuffer.VertexSize, + meshletMaxVertexCount, + meshletMaxTriangleCount, + coneWeight); + + + auto meshletVertexIndexCount = 0u; + auto meshletTriangleIndexCount = 0u; + + //printf("MeshletCount: %d\n", meshletCount); + + auto meshletList = SystemPushArray(MeshletBuilderMemoryArena, meshletCount); + + for (uint32_t i = 0; i < meshletCount; i++) + { + auto meshlet = meshOptMeshletList[i]; + + meshopt_optimizeMeshlet(&meshletVertexIndexList[meshlet.vertex_offset], + &meshletTriangleIndexListRaw[meshlet.triangle_offset], + meshlet.triangle_count, + meshlet.vertex_count); + + /*auto meshletBounds = meshopt_computeMeshletBounds(&meshletVertexIndexList[meshlet.vertex_offset], + &meshletTriangleIndexListRaw[meshlet.triangle_offset], + meshlet.triangle_count, + (const float*)vertexList.Pointer, + vertexCount, + vertexBuffer.VertexSize);*/ + + // TODO: Cone and sphere + + meshletList[i] = + { + .VertexIndexOffset = meshlet.vertex_offset, + .VertexIndexCount = meshlet.vertex_count, + .TriangleOffset = meshlet.triangle_offset / 3, + .TriangleCount = meshlet.triangle_count + }; + + for (uint32_t j = 0; j < meshlet.triangle_count; j++) + { + auto pointer = &meshletTriangleIndexListRaw[meshlet.triangle_offset + j * 3]; + + auto p0 = pointer[0]; + auto p1 = pointer[1]; + auto p2 = pointer[2]; + + meshletTriangleIndexList[meshletList[i].TriangleOffset + j] = ((uint32_t)p2) << 16 | ((uint32_t)p1) << 8 | (uint32_t)p0; + } + + meshletVertexIndexCount += meshlet.vertex_count; + meshletTriangleIndexCount = fmax(meshletTriangleIndexCount, meshlet.triangle_offset / 3 + meshlet.triangle_count); + } + + // TODO: Output everything in separate memory arena + return + { + .MeshletMaxVertexCount = meshletMaxVertexCount, + .MeshletMaxTriangleCount = meshletMaxTriangleCount, + .VertexBuffer = { .Data = { .Items = vertexList.Pointer, .Length = (uint32_t)vertexList.Length }, .VertexSize = vertexBuffer.VertexSize, .VertexCount = vertexBuffer.VertexCount }, + .Meshlets = { .Items = meshletList.Pointer, .Length = (uint32_t)meshletList.Length }, + .MeshletVertexIndexBuffer = { .Items = meshletVertexIndexList.Pointer, .Length = meshletVertexIndexCount }, + .MeshletTriangleIndexBuffer = { .Items = meshletTriangleIndexList.Pointer, .Length = meshletTriangleIndexCount } + }; +} diff --git a/src/ElementalTools/Apple/CMakeLists.txt b/src/ElementalTools/Platforms/Apple/CMakeLists.txt similarity index 91% rename from src/ElementalTools/Apple/CMakeLists.txt rename to src/ElementalTools/Platforms/Apple/CMakeLists.txt index acdf1418..2fdc7cef 100644 --- a/src/ElementalTools/Apple/CMakeLists.txt +++ b/src/ElementalTools/Platforms/Apple/CMakeLists.txt @@ -1,4 +1,4 @@ -target_include_directories(ElementalTools PRIVATE ../../Elemental/Apple/) +target_include_directories(ElementalTools PRIVATE ../../../Elemental/Apple/) target_include_directories(ElementalTools PRIVATE ./Interop/) target_compile_options(ElementalTools PRIVATE -fms-extensions -fno-objc-arc -Wno-language-extension-token) target_link_options(ElementalTools PRIVATE "SHELL:-framework Metal" "SHELL:-framework Foundation") diff --git a/src/ElementalTools/Apple/Info.plist b/src/ElementalTools/Platforms/Apple/Info.plist similarity index 100% rename from src/ElementalTools/Apple/Info.plist rename to src/ElementalTools/Platforms/Apple/Info.plist diff --git a/src/ElementalTools/Apple/Interop/DxilRootSignature.h b/src/ElementalTools/Platforms/Apple/Interop/DxilRootSignature.h similarity index 100% rename from src/ElementalTools/Apple/Interop/DxilRootSignature.h rename to src/ElementalTools/Platforms/Apple/Interop/DxilRootSignature.h diff --git a/src/ElementalTools/Apple/Interop/oaidl.h b/src/ElementalTools/Platforms/Apple/Interop/oaidl.h similarity index 100% rename from src/ElementalTools/Apple/Interop/oaidl.h rename to src/ElementalTools/Platforms/Apple/Interop/oaidl.h diff --git a/src/ElementalTools/Apple/Interop/ocidl.h b/src/ElementalTools/Platforms/Apple/Interop/ocidl.h similarity index 100% rename from src/ElementalTools/Apple/Interop/ocidl.h rename to src/ElementalTools/Platforms/Apple/Interop/ocidl.h diff --git a/src/ElementalTools/Apple/Interop/rpc.h b/src/ElementalTools/Platforms/Apple/Interop/rpc.h similarity index 100% rename from src/ElementalTools/Apple/Interop/rpc.h rename to src/ElementalTools/Platforms/Apple/Interop/rpc.h diff --git a/src/ElementalTools/Apple/Interop/rpcndr.h b/src/ElementalTools/Platforms/Apple/Interop/rpcndr.h similarity index 100% rename from src/ElementalTools/Apple/Interop/rpcndr.h rename to src/ElementalTools/Platforms/Apple/Interop/rpcndr.h diff --git a/src/ElementalTools/Apple/PreCompiledHeader.h b/src/ElementalTools/Platforms/Apple/PreCompiledHeader.h similarity index 83% rename from src/ElementalTools/Apple/PreCompiledHeader.h rename to src/ElementalTools/Platforms/Apple/PreCompiledHeader.h index 43e32427..cda785d5 100644 --- a/src/ElementalTools/Apple/PreCompiledHeader.h +++ b/src/ElementalTools/Platforms/Apple/PreCompiledHeader.h @@ -8,6 +8,7 @@ #define ElemToolsAPI extern "C" __attribute__((visibility("default"))) #define AssertIfFailed(expression) SystemAssert(SUCCEEDED((expression))) #define AssertIfFailedReturnNullHandle(expression) SystemAssertReturnNullHandle(SUCCEEDED((expression))) + #define ComPtr CComPtr #undef _WIN32 #include @@ -36,6 +37,15 @@ #include "metal_irconverter/metal_irconverter.h" +#include "meshoptimizer.h" +#include "fast_obj.h" +#include "cgltf.h" + +#include "stb_image.h" +#include "stb_image_resize2.h" +#include "mikktspace.h" +#include "mikktspace.h" + #ifdef _WASDEBUG #define _DEBUG #endif diff --git a/src/ElementalTools/Linux/CMakeLists.txt b/src/ElementalTools/Platforms/Linux/CMakeLists.txt similarity index 85% rename from src/ElementalTools/Linux/CMakeLists.txt rename to src/ElementalTools/Platforms/Linux/CMakeLists.txt index 7df60e2f..bba19a77 100644 --- a/src/ElementalTools/Linux/CMakeLists.txt +++ b/src/ElementalTools/Platforms/Linux/CMakeLists.txt @@ -1,4 +1,4 @@ -target_include_directories(ElementalTools PRIVATE ../../Elemental/Linux/) +target_include_directories(ElementalTools PRIVATE ../../../Elemental/Linux/) target_include_directories(ElementalTools PRIVATE ./Interop/) target_compile_options(ElementalTools PRIVATE -fms-extensions -Wno-language-extension-token) diff --git a/src/ElementalTools/Linux/Interop/DxilRootSignature.h b/src/ElementalTools/Platforms/Linux/Interop/DxilRootSignature.h similarity index 100% rename from src/ElementalTools/Linux/Interop/DxilRootSignature.h rename to src/ElementalTools/Platforms/Linux/Interop/DxilRootSignature.h diff --git a/src/ElementalTools/Linux/Interop/oaidl.h b/src/ElementalTools/Platforms/Linux/Interop/oaidl.h similarity index 100% rename from src/ElementalTools/Linux/Interop/oaidl.h rename to src/ElementalTools/Platforms/Linux/Interop/oaidl.h diff --git a/src/ElementalTools/Linux/Interop/ocidl.h b/src/ElementalTools/Platforms/Linux/Interop/ocidl.h similarity index 100% rename from src/ElementalTools/Linux/Interop/ocidl.h rename to src/ElementalTools/Platforms/Linux/Interop/ocidl.h diff --git a/src/ElementalTools/Linux/Interop/rpc.h b/src/ElementalTools/Platforms/Linux/Interop/rpc.h similarity index 100% rename from src/ElementalTools/Linux/Interop/rpc.h rename to src/ElementalTools/Platforms/Linux/Interop/rpc.h diff --git a/src/ElementalTools/Linux/Interop/rpcndr.h b/src/ElementalTools/Platforms/Linux/Interop/rpcndr.h similarity index 100% rename from src/ElementalTools/Linux/Interop/rpcndr.h rename to src/ElementalTools/Platforms/Linux/Interop/rpcndr.h diff --git a/src/ElementalTools/Linux/PreCompiledHeader.h b/src/ElementalTools/Platforms/Linux/PreCompiledHeader.h similarity index 83% rename from src/ElementalTools/Linux/PreCompiledHeader.h rename to src/ElementalTools/Platforms/Linux/PreCompiledHeader.h index 9e8af397..542d9365 100644 --- a/src/ElementalTools/Linux/PreCompiledHeader.h +++ b/src/ElementalTools/Platforms/Linux/PreCompiledHeader.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,13 @@ #include "dxcapi.h" #include "d3d12shader.h" +#include "meshoptimizer.h" +#include "fast_obj.h" +#include "cgltf.h" +#include "stb_image.h" +#include "stb_image_resize2.h" +#include "mikktspace.h" + #ifdef _WASDEBUG #define _DEBUG #endif diff --git a/src/ElementalTools/Microsoft/CMakeLists.txt b/src/ElementalTools/Platforms/Microsoft/CMakeLists.txt similarity index 87% rename from src/ElementalTools/Microsoft/CMakeLists.txt rename to src/ElementalTools/Platforms/Microsoft/CMakeLists.txt index b9e442d7..f07383a5 100644 --- a/src/ElementalTools/Microsoft/CMakeLists.txt +++ b/src/ElementalTools/Platforms/Microsoft/CMakeLists.txt @@ -1,6 +1,6 @@ target_compile_options(ElementalTools PRIVATE -EHs) -target_include_directories(ElementalTools PRIVATE ../../Elemental/Microsoft) +target_include_directories(ElementalTools PRIVATE ../../../Elemental/Microsoft) target_link_libraries(ElementalTools PRIVATE Dwmapi UxTheme onecore) target_compile_definitions(ElementalTools PRIVATE UNICODE _UNICODE _WINDOWS _WIN32) diff --git a/src/ElementalTools/Microsoft/PreCompiledHeader.h b/src/ElementalTools/Platforms/Microsoft/PreCompiledHeader.h similarity index 80% rename from src/ElementalTools/Microsoft/PreCompiledHeader.h rename to src/ElementalTools/Platforms/Microsoft/PreCompiledHeader.h index 7629f119..deaff135 100644 --- a/src/ElementalTools/Microsoft/PreCompiledHeader.h +++ b/src/ElementalTools/Platforms/Microsoft/PreCompiledHeader.h @@ -1,4 +1,6 @@ #pragma once +#define _CRT_SECURE_NO_WARNINGS +#define NOMINMAX #include #include @@ -35,6 +37,13 @@ using namespace Microsoft::WRL; #include "metal_irconverter/metal_irconverter.h" +#include "meshoptimizer.h" +#include "fast_obj.h" +#include "cgltf.h" +#include "stb_image.h" +#include "stb_image_resize2.h" +#include "mikktspace.h" + #ifdef _WASDEBUG #define _DEBUG #endif diff --git a/src/ElementalTools/SceneLoading/SceneLoader.cpp b/src/ElementalTools/SceneLoading/SceneLoader.cpp new file mode 100644 index 00000000..6603e2c4 --- /dev/null +++ b/src/ElementalTools/SceneLoading/SceneLoader.cpp @@ -0,0 +1,95 @@ +#include "SceneLoader.h" +#include "SceneLoaderObj.cpp" +#include "SceneLoaderGltf.cpp" + +// TODO: Do one for each thread +static MemoryArena SceneLoaderMemoryArena; + +void InitSceneLoaderMemoryArena() +{ + if (SceneLoaderMemoryArena.Storage == nullptr) + { + SceneLoaderMemoryArena = SystemAllocateMemoryArena(512 * 1024 * 1024); + } + + SystemClearMemoryArena(SceneLoaderMemoryArena); +} + +MemoryArena GetSceneLoaderMemoryArena() +{ + return SceneLoaderMemoryArena; +} + +ElemToolsMatrix4x4 CreateSceneLoaderGlobalTransformMatrix(const ElemLoadSceneOptions* options) +{ + auto result = ElemToolsCreateIdentityMatrix(); + + ElemToolsVector4 rotationQuaternion = ElemToolsMulQuat(ElemToolsCreateQuaternion((ElemToolsVector3){ 1, 0, 0 }, options->Rotation.X), + ElemToolsMulQuat(ElemToolsCreateQuaternion((ElemToolsVector3){ 0, 0, 1 }, options->Rotation.Z), + ElemToolsCreateQuaternion((ElemToolsVector3){ 0, 1, 0 }, options->Rotation.Y))); + + result = ElemToolsMulMatrix4x4(ElemToolsCreateRotationMatrix(rotationQuaternion), result); + result = ElemToolsMulMatrix4x4(ElemToolsCreateScaleMatrix(options->Scaling), result); + result = ElemToolsMulMatrix4x4(ElemToolsCreateTranslationMatrix(options->Translation), result); + + return result; +} + +ElemSceneFormat GetSceneFormatFromPath(const char* path) +{ + auto pathSpan = ReadOnlySpan(path); + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto lastIndex = SystemLastIndexOf(path, '.'); + SystemAssert(lastIndex != -1); + + auto extension = SystemPushArray(stackMemoryArena, pathSpan.Length - lastIndex); + SystemCopyBuffer(extension, pathSpan.Slice(lastIndex + 1)); + + if (SystemFindSubString(extension, "obj") != -1) + { + return ElemSceneFormat_Obj; + } + else if (SystemFindSubString(extension, "gltf") != -1) + { + return ElemSceneFormat_Gltf; + } + + return ElemSceneFormat_Unknown; +} + +ElemToolsAPI ElemLoadSceneResult ElemLoadScene(const char* path, const ElemLoadSceneOptions* options) +{ + InitSceneLoaderMemoryArena(); + + ElemLoadSceneOptions loadSceneOptions = {}; + + if (options) + { + loadSceneOptions = *options; + } + + if (loadSceneOptions.Scaling == 0.0f) + { + loadSceneOptions.Scaling = 1.0f; + } + + auto sceneFormat = GetSceneFormatFromPath(path); + + // TODO: Refactor that with array entries and function pointer + switch (sceneFormat) + { + case ElemSceneFormat_Obj: + return LoadObjScene(path, &loadSceneOptions); + + case ElemSceneFormat_Gltf: + return LoadGltfScene(path, &loadSceneOptions); + + default: + return + { + .Messages = ConstructErrorMessageSpan(GetSceneLoaderMemoryArena(), "Scene format is not supported."), + .HasErrors = true + }; + }; +} diff --git a/src/ElementalTools/SceneLoading/SceneLoader.h b/src/ElementalTools/SceneLoading/SceneLoader.h new file mode 100644 index 00000000..f286de28 --- /dev/null +++ b/src/ElementalTools/SceneLoading/SceneLoader.h @@ -0,0 +1,7 @@ +#pragma once + +#include "ElementalTools.h" +#include "SystemMemory.h" + +MemoryArena GetSceneLoaderMemoryArena(); +ElemToolsMatrix4x4 CreateSceneLoaderGlobalTransformMatrix(const ElemLoadSceneOptions* options); diff --git a/src/ElementalTools/SceneLoading/SceneLoaderGltf.cpp b/src/ElementalTools/SceneLoading/SceneLoaderGltf.cpp new file mode 100644 index 00000000..ef84bb89 --- /dev/null +++ b/src/ElementalTools/SceneLoading/SceneLoaderGltf.cpp @@ -0,0 +1,393 @@ +#include "SceneLoader.h" +#include "ToolsUtils.h" +#include "ElementalTools.h" +#include "SystemMemory.h" +#include "SystemFunctions.h" + +cgltf_result ClgtfFileRead(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, const char* path, cgltf_size* size, void** data) +{ + auto objFileData = LoadFileData(path); + + if (objFileData.Length == 0) + { + return cgltf_result_io_error; + } + + *data = (uint8_t*)objFileData.Pointer; + *size = objFileData.Length; + + return cgltf_result_success; +} + +void ClgtfFileRelease(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data) +{ +} + +ElemVertexBuffer ConstructGltfVertexBuffer(MemoryArena memoryArena, const cgltf_primitive* gltfPrimitive, ElemSceneCoordinateSystem coordinateSystem, ElemToolsBoundingBox* boundingBox) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + + // TODO: Config of the vertex components to load + // TODO: Support vertex compression (eg: float16 for positions?) + // TODO: Tangent + auto positionSize = sizeof(float) * 3; + auto normalSize = sizeof(float) * 3; + auto tangentSize = sizeof(float) * 4; + auto textureCoordinatesSize = sizeof(float) * 2; + auto maxVertexSize = positionSize + normalSize + tangentSize + textureCoordinatesSize; + + // TODO: Temporary + auto realVertexSize = maxVertexSize; + auto vertexCount = gltfPrimitive->attributes[0].data->count; + + // TODO: Combine that with the components selection + auto positionAccessor = cgltf_find_accessor(gltfPrimitive, cgltf_attribute_type_position, 0); + auto normalAccessor = cgltf_find_accessor(gltfPrimitive, cgltf_attribute_type_normal, 0); + auto tangentAccessor = cgltf_find_accessor(gltfPrimitive, cgltf_attribute_type_tangent, 0); + auto textureCoordinatesAccessor = cgltf_find_accessor(gltfPrimitive, cgltf_attribute_type_texcoord, 0); + + // TODO: Rework this + auto gltfPositionBuffer = SystemPushArray(stackMemoryArena, vertexCount * 3); + auto gltfNormalBuffer = SystemPushArray(stackMemoryArena, vertexCount * 3); + auto gltfTangentBuffer = SystemPushArray(stackMemoryArena, vertexCount * 4); + auto gltfTextureCoordinatesBuffer = SystemPushArray(stackMemoryArena, vertexCount * 2); + + SystemAssert(positionAccessor); + cgltf_accessor_unpack_floats(positionAccessor, gltfPositionBuffer.Pointer, gltfPositionBuffer.Length); + + if (normalAccessor) + { + cgltf_accessor_unpack_floats(normalAccessor, gltfNormalBuffer.Pointer, gltfNormalBuffer.Length); + } + + if (tangentAccessor) + { + cgltf_accessor_unpack_floats(tangentAccessor, gltfTangentBuffer.Pointer, gltfTangentBuffer.Length); + } + + if (textureCoordinatesAccessor) + { + cgltf_accessor_unpack_floats(textureCoordinatesAccessor, gltfTextureCoordinatesBuffer.Pointer, gltfTextureCoordinatesBuffer.Length); + } + + auto vertexBuffer = SystemPushArray(memoryArena, vertexCount * maxVertexSize); + auto currentVertexBufferPointer = vertexBuffer.Pointer; + + for (uint32_t i = 0; i < vertexCount; i++) + { + // Position + for (uint32_t j = 0; j < 3; j++) + { + ((float*)currentVertexBufferPointer)[j] = gltfPositionBuffer[i * 3 + j]; + + if (coordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + ((float*)currentVertexBufferPointer)[2] = -((float*)currentVertexBufferPointer)[2]; + } + } + + AddPointToBoundingBox({ ((float*)currentVertexBufferPointer)[0], ((float*)currentVertexBufferPointer)[1], ((float*)currentVertexBufferPointer)[2]}, boundingBox); + currentVertexBufferPointer += 3 * sizeof(float); + + // Normal + for (uint32_t j = 0; j < 3; j++) + { + ((float*)currentVertexBufferPointer)[j] = gltfNormalBuffer[i * 3 + j]; + + if (coordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + ((float*)currentVertexBufferPointer)[2] = -((float*)currentVertexBufferPointer)[2]; + } + } + + currentVertexBufferPointer += 3 * sizeof(float); + + // Tangent + for (uint32_t j = 0; j < 4; j++) + { + ((float*)currentVertexBufferPointer)[j] = gltfTangentBuffer[i * 4 + j]; + + if (coordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + ((float*)currentVertexBufferPointer)[2] = -((float*)currentVertexBufferPointer)[2]; + } + } + + currentVertexBufferPointer += 4 * sizeof(float); + + // Texture coordinates + for (uint32_t j = 0; j < 2; j++) + { + ((float*)currentVertexBufferPointer)[j] = gltfTextureCoordinatesBuffer[i * 2 + j]; + } + + currentVertexBufferPointer += 2 * sizeof(float); + } + + return + { + .Data = { .Items = vertexBuffer.Pointer, .Length = (uint32_t)vertexBuffer.Length }, + .VertexSize = (uint32_t)realVertexSize, + .VertexCount = (uint32_t)vertexCount + }; +} + +ElemUInt32Span ConstructGltfIndexBuffer(MemoryArena memoryArena, const cgltf_primitive* gltfPrimitive, ElemSceneCoordinateSystem coordinateSystem) +{ + auto indexBuffer = SystemPushArray(memoryArena, gltfPrimitive->indices->count); + cgltf_accessor_unpack_indices(gltfPrimitive->indices, indexBuffer.Pointer, sizeof(uint32_t), indexBuffer.Length); + + if (coordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + for (uint32_t i = 0; i < indexBuffer.Length; i += 3) + { + uint32_t temp = indexBuffer[i + 1]; + indexBuffer[i + 1] = indexBuffer[i + 2]; + indexBuffer[i + 2] = temp; + } + } + + return { .Items = indexBuffer.Pointer, .Length = (uint32_t)indexBuffer.Length }; +} + +ReadOnlySpan LoadGltfMeshes(MemoryArena memoryArena, const cgltf_data* data, const ElemLoadSceneOptions* options, ToolsMessageList* messageList) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto meshes = SystemPushArray(memoryArena, data->meshes_count); + + for (uint32_t i = 0; i < data->meshes_count; i++) + { + auto mesh = &meshes[i]; + auto gltfMesh = &data->meshes[i]; + + mesh->BoundingBox = + { + .MinPoint = { FLT_MAX, FLT_MAX, FLT_MAX }, + .MaxPoint = { -FLT_MAX, -FLT_MAX, -FLT_MAX } + }; + + auto meshPrimitives = SystemPushArray(memoryArena, gltfMesh->primitives_count); + + for (uint32_t j = 0; j < gltfMesh->primitives_count; j++) + { + auto meshPrimitive = &meshPrimitives[j]; + auto gltfPrimitive = &gltfMesh->primitives[j]; + + if (gltfPrimitive->type != cgltf_primitive_type_triangles) + { + WriteToMessageList(ElemToolsMessageType_Warning, "glTF loader only support triangles for now.", messageList); + continue; + } + + if (!cgltf_find_accessor(gltfPrimitive, cgltf_attribute_type_position, 0)) + { + WriteToMessageList(ElemToolsMessageType_Warning, "The mesh primitive doesn't contains a position attribute.", messageList); + continue; + } + + meshPrimitive->BoundingBox = + { + .MinPoint = { FLT_MAX, FLT_MAX, FLT_MAX }, + .MaxPoint = { -FLT_MAX, -FLT_MAX, -FLT_MAX } + }; + + meshPrimitive->VertexBuffer = ConstructGltfVertexBuffer(memoryArena, gltfPrimitive, options->CoordinateSystem, &meshPrimitive->BoundingBox); + meshPrimitive->IndexBuffer = ConstructGltfIndexBuffer(memoryArena, gltfPrimitive, options->CoordinateSystem); + + meshPrimitive->MaterialId = gltfPrimitive->material ? cgltf_material_index(data, gltfPrimitive->material) : -1; + + AddBoundingBoxToBoundingBox(&meshPrimitive->BoundingBox, &mesh->BoundingBox); + } + + if (gltfMesh->name) + { + mesh->Name = SystemDuplicateBuffer(memoryArena, ReadOnlySpan(gltfMesh->name)).Pointer; + } + else + { + mesh->Name = SystemConcatBuffers(memoryArena, "Mesh_", SystemConvertNumberToString(stackMemoryArena, i)).Pointer; + } + + mesh->MeshPrimitives = { .Items = meshPrimitives.Pointer, .Length = (uint32_t)meshPrimitives.Length }; + } + + return meshes; +} + +ReadOnlySpan LoadGltfMaterials(MemoryArena memoryArena, const cgltf_data* data, const ElemLoadSceneOptions* options, ToolsMessageList* messageList) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto materials = SystemPushArray(memoryArena, data->materials_count); + + for (uint32_t i = 0; i < data->materials_count; i++) + { + auto material = &materials[i]; + auto gltfMaterial = &data->materials[i]; + + if (gltfMaterial->name) + { + material->Name = SystemDuplicateBuffer(memoryArena, ReadOnlySpan(gltfMaterial->name)).Pointer; + } + else + { + material->Name = SystemConcatBuffers(memoryArena, "Material_", SystemConvertNumberToString(stackMemoryArena, i)).Pointer; + } + + if (gltfMaterial->has_pbr_metallic_roughness) + { + auto gltfMaterialData = &gltfMaterial->pbr_metallic_roughness; + + if (gltfMaterialData->base_color_texture.texture) + { + material->AlbedoTexturePath = SystemDuplicateBuffer(memoryArena, gltfMaterialData->base_color_texture.texture->image->uri).Pointer; + } + + material->AlbedoFactor = + { + .X = gltfMaterialData->base_color_factor[0], + .Y = gltfMaterialData->base_color_factor[1], + .Z = gltfMaterialData->base_color_factor[2], + .W = gltfMaterialData->base_color_factor[3] + }; + } + else if (gltfMaterial->has_pbr_specular_glossiness) + { + auto gltfMaterialData = &gltfMaterial->pbr_specular_glossiness; + + if (gltfMaterialData->diffuse_texture.texture) + { + material->AlbedoTexturePath = SystemDuplicateBuffer(memoryArena, gltfMaterialData->diffuse_texture.texture->image->uri).Pointer; + } + + material->AlbedoFactor = + { + .X = gltfMaterialData->diffuse_factor[0], + .Y = gltfMaterialData->diffuse_factor[1], + .Z = gltfMaterialData->diffuse_factor[2], + .W = gltfMaterialData->diffuse_factor[3] + }; + } + else + { + WriteToMessageList(ElemToolsMessageType_Warning, "glTF loader only support metallic roughness materials for now.", messageList); + } + + if (gltfMaterial->normal_texture.texture) + { + material->NormalTexturePath = SystemDuplicateBuffer(memoryArena, gltfMaterial->normal_texture.texture->image->uri).Pointer; + } + + printf("Material: %s\n", material->Name); + } + + return materials; +} + +ReadOnlySpan LoadGltfSceneNodes(MemoryArena memoryArena, const cgltf_data* data, const ElemLoadSceneOptions* options, ToolsMessageList* messageList) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto sceneNodes = SystemPushArray(memoryArena, data->nodes_count); + auto globalTransformMatrix = CreateSceneLoaderGlobalTransformMatrix(options); + + // TODO: Children? + for (uint32_t i = 0; i < data->nodes_count; i++) + { + auto sceneNode = &sceneNodes[i]; + auto gltfNode = &data->nodes[i]; + + if (gltfNode->name) + { + sceneNode->Name = SystemDuplicateBuffer(memoryArena, ReadOnlySpan(gltfNode->name)).Pointer; + } + else + { + sceneNode->Name = SystemConcatBuffers(memoryArena, "Node_", SystemConvertNumberToString(stackMemoryArena, i)).Pointer; + } + + sceneNode->Rotation = {}; + sceneNode->Translation = {}; + + if (gltfNode->mesh) + { + sceneNode->NodeType = ElemSceneNodeType_Mesh; + sceneNode->ReferenceIndex = cgltf_mesh_index(data, gltfNode->mesh); + } + + float matrix[16]; + cgltf_node_transform_world(gltfNode, matrix); + ElemToolsMatrix4x4 nodeTransform = *(ElemToolsMatrix4x4*)matrix; + + if (options->CoordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + auto flipMatrix = ElemToolsCreateIdentityMatrix(); + flipMatrix.Rows[2].Z = -1; + + nodeTransform = ElemToolsMulMatrix4x4(flipMatrix, ElemToolsMulMatrix4x4(nodeTransform, flipMatrix)); + } + + nodeTransform = ElemToolsMulMatrix4x4(nodeTransform, globalTransformMatrix); + DecomposeTransform(nodeTransform, &sceneNode->Scale, &sceneNode->Rotation, &sceneNode->Translation); + } + + return sceneNodes; +} + +ElemLoadSceneResult LoadGltfScene(const char* path, const ElemLoadSceneOptions* options) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto sceneLoaderMemoryArena = GetSceneLoaderMemoryArena(); + + ToolsMessageList messageList = + { + .Messages = SystemPushArray(sceneLoaderMemoryArena, 1024) + }; + + cgltf_options cgltfOptions = + { + .file = + { + .read = ClgtfFileRead, + .release = ClgtfFileRelease + } + }; + + cgltf_data* data = NULL; + cgltf_result result = cgltf_parse_file(&cgltfOptions, path, &data); + + if (result != cgltf_result_success) + { + return { .Messages = ConstructErrorMessageSpan(sceneLoaderMemoryArena, "Error while reading glTF file."), .HasErrors = true }; + } + + result = cgltf_load_buffers(&cgltfOptions, data, path); + + if (result != cgltf_result_success) + { + return { .Messages = ConstructErrorMessageSpan(sceneLoaderMemoryArena, "Error while loading glTF buffers."), .HasErrors = true }; + } + + result = cgltf_validate(data); + + if (result != cgltf_result_success) + { + return { .Messages = ConstructErrorMessageSpan(sceneLoaderMemoryArena, "Error while validating glTF file."), .HasErrors = true }; + } + + auto meshes = LoadGltfMeshes(sceneLoaderMemoryArena, data, options, &messageList); + auto sceneNodes = LoadGltfSceneNodes(sceneLoaderMemoryArena, data, options, &messageList); + auto materials = LoadGltfMaterials(sceneLoaderMemoryArena, data, options, &messageList); + + cgltf_free(data); + ResetLoadFileDataMemory(); + + return + { + .SceneFormat = ElemSceneFormat_Gltf, + .CoordinateSystem = options->CoordinateSystem, + .Meshes = { .Items = (ElemSceneMesh*)meshes.Pointer, .Length = (uint32_t)meshes.Length }, + .Materials = { .Items = (ElemSceneMaterial*)materials.Pointer, .Length = (uint32_t)materials.Length }, + .Nodes = { .Items = (ElemSceneNode*)sceneNodes.Pointer, .Length = (uint32_t)sceneNodes.Length }, + .Messages = { .Items = messageList.Messages.Pointer, .Length = messageList.MessageCount }, + .HasErrors = messageList.HasErrors + }; +} diff --git a/src/ElementalTools/SceneLoading/SceneLoaderObj.cpp b/src/ElementalTools/SceneLoading/SceneLoaderObj.cpp new file mode 100644 index 00000000..d31f77b3 --- /dev/null +++ b/src/ElementalTools/SceneLoading/SceneLoaderObj.cpp @@ -0,0 +1,507 @@ +#include "SceneLoader.h" +#include "ToolsUtils.h" +#include "TangentSpaceGenerator.h" +#include "ElementalTools.h" +#include "SystemMemory.h" +#include "SystemFunctions.h" + +struct ObjLoaderFileData +{ + ReadOnlySpan FileData; + uint32_t CurrentOffset; +}; + +struct ObjMeshPrimitiveInfo +{ + uint32_t VertexOffset; + uint32_t VertexCount; + uint32_t FaceOffset; + uint32_t FaceCount; + int32_t MaterialId; +}; + +struct ObjVertex +{ + ElemToolsVector3 Position; + ElemToolsVector3 Normal; + ElemToolsVector4 Tangent; + ElemToolsVector3 Bitangent; + ElemToolsVector2 TextureCoordinates; +}; + +void* FastObjFileOpen(const char* path, void* userData) +{ + // TODO: Add support for streaming + auto objFileData = LoadFileData(path); + + if (objFileData.Length == 0) + { + return nullptr; + } + + auto objLoaderFileData = SystemPushStruct(*(MemoryArena*)userData); + objLoaderFileData->CurrentOffset = 0; + objLoaderFileData->FileData = objFileData; + + return objLoaderFileData; +} + +void FastObjFileClose(void* file, void* userData) +{ +} + +size_t FastObjFileRead(void* file, void* destination, size_t bytes, void* userData) +{ + SystemAssert(file); + + auto objLoaderFileData = (ObjLoaderFileData*)file; + auto sourceSpan = objLoaderFileData->FileData; + auto destinationSpan = Span((uint8_t*)destination, bytes); + + auto readLength = SystemMin(sourceSpan.Length - objLoaderFileData->CurrentOffset, bytes); + + if (readLength > 0) + { + SystemCopyBuffer(destinationSpan, sourceSpan.Slice(objLoaderFileData->CurrentOffset, readLength)); + } + + objLoaderFileData->CurrentOffset += readLength; + return readLength; +} + +unsigned long FastObjFileSize(void* file, void* userData) +{ + SystemAssert(file); + + auto objLoaderFileData = (ObjLoaderFileData*)file; + return objLoaderFileData->FileData.Length; +} + + +ObjVertex ReadObjVertex(fastObjIndex objVertex, const fastObjMesh* objMesh, ElemSceneCoordinateSystem coordinateSystem, bool flipVerticalTextureCoordinates) +{ + ObjVertex result = {}; + + if (objVertex.p) + { + result.Position.X = objMesh->positions[3 * objVertex.p]; + result.Position.Y = objMesh->positions[3 * objVertex.p + 1]; + result.Position.Z = objMesh->positions[3 * objVertex.p + 2]; + + if (coordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + result.Position.Z = -result.Position.Z; + } + } + + if (objVertex.n) + { + result.Normal.X = objMesh->normals[3 * objVertex.n]; + result.Normal.Y = objMesh->normals[3 * objVertex.n + 1]; + result.Normal.Z = objMesh->normals[3 * objVertex.n + 2]; + + if (coordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + result.Normal.Z = -result.Normal.Z; + } + } + + if (objVertex.t) + { + result.TextureCoordinates.X = objMesh->texcoords[2 * objVertex.t]; + result.TextureCoordinates.Y = objMesh->texcoords[2 * objVertex.t + 1]; + + if (!flipVerticalTextureCoordinates) + { + result.TextureCoordinates.Y = 1 - result.TextureCoordinates.Y; + } + } + + return result; +} + +// TODO: Pass vertex format configured by the lib +void WriteObjVertex(const ObjVertex* vertex, uint8_t** vertexBufferPointer) +{ + // TODO: Check what to include + auto currentVertexBufferPointer = *vertexBufferPointer; + + // Positions + ((float*)currentVertexBufferPointer)[0] = vertex->Position.X; + ((float*)currentVertexBufferPointer)[1] = vertex->Position.Y; + ((float*)currentVertexBufferPointer)[2] = vertex->Position.Z; + + currentVertexBufferPointer += sizeof(ElemToolsVector3); + + // Normals + ((float*)currentVertexBufferPointer)[0] = vertex->Normal.X; + ((float*)currentVertexBufferPointer)[1] = vertex->Normal.Y; + ((float*)currentVertexBufferPointer)[2] = vertex->Normal.Z; + + currentVertexBufferPointer += sizeof(ElemToolsVector3); + + // Tangents + ((float*)currentVertexBufferPointer)[0] = vertex->Tangent.X; + ((float*)currentVertexBufferPointer)[1] = vertex->Tangent.Y; + ((float*)currentVertexBufferPointer)[2] = vertex->Tangent.Z; + ((float*)currentVertexBufferPointer)[3] = vertex->Tangent.W; + + currentVertexBufferPointer += sizeof(ElemToolsVector4); + + // Texture Coordinates + ((float*)currentVertexBufferPointer)[0] = vertex->TextureCoordinates.X; + ((float*)currentVertexBufferPointer)[1] = vertex->TextureCoordinates.Y; + + currentVertexBufferPointer += sizeof(ElemToolsVector2); + + *vertexBufferPointer = currentVertexBufferPointer; +} + +ElemSceneMeshPrimitive ConstructObjMeshPrimitive(MemoryArena memoryArena, const fastObjMesh* objMesh, const ObjMeshPrimitiveInfo* meshPrimitiveInfo, const ElemLoadSceneOptions* options) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + + ElemSceneMeshPrimitive result = + { + .BoundingBox = + { + .MinPoint = { FLT_MAX, FLT_MAX, FLT_MAX }, + .MaxPoint = { FLT_MIN, FLT_MIN, FLT_MIN } + } + }; + + // TODO: Config of the vertex components to load + auto positionSize = sizeof(float) * 3; + auto normalSize = sizeof(float) * 3; + auto tangentSize = sizeof(float) * 4; + auto textureCoordinatesSize = sizeof(float) * 2; + auto maxVertexSize = positionSize + normalSize + tangentSize + textureCoordinatesSize; + + // TODO: Temporary + auto realVertexSize = maxVertexSize; + + // TODO: We can just have one loop again, it is good that we have separated the read and write + auto vertexList = SystemPushArray(stackMemoryArena, meshPrimitiveInfo->VertexCount); + + for (uint32_t i = 0; i < meshPrimitiveInfo->VertexCount; i++) + { + // TODO: Here, we can directly write the vertex data to the buffer + vertexList[i] = ReadObjVertex(objMesh->indices[meshPrimitiveInfo->VertexOffset + i], objMesh, options->CoordinateSystem, options->FlipVerticalTextureCoordinates); + AddPointToBoundingBox(vertexList[i].Position, &result.BoundingBox); + } + + auto indexCount = 0u; + + for (uint32_t i = 0; i < meshPrimitiveInfo->FaceCount; i++) + { + if (objMesh->face_vertices[meshPrimitiveInfo->FaceOffset + i] == 3) + { + indexCount += 3; + } + else + { + indexCount += 6; + } + } + + printf("IndexCount: %d\n", indexCount); + auto indexBufferData = SystemPushArray(memoryArena, indexCount); + auto currentIndex = 0u; + auto currentObjIndex = 0u; + + for (uint32_t i = 0; i < meshPrimitiveInfo->FaceCount; i++) + { + auto faceVertexCount = objMesh->face_vertices[meshPrimitiveInfo->FaceOffset + i]; + + if (faceVertexCount == 3) + { + indexBufferData[currentIndex] = currentObjIndex; + indexBufferData[currentIndex + 1] = currentObjIndex + 1; + indexBufferData[currentIndex + 2] = currentObjIndex + 2; + + if (options->CoordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + auto tmp = indexBufferData[currentIndex + 1]; + indexBufferData[currentIndex + 1] = indexBufferData[currentIndex + 2]; + indexBufferData[currentIndex + 2] = tmp; + } + + currentIndex += 3; + } + else + { + indexBufferData[currentIndex] = currentObjIndex; + indexBufferData[currentIndex + 1] = currentObjIndex + 1; + indexBufferData[currentIndex + 2] = currentObjIndex + 2; + + indexBufferData[currentIndex + 3] = currentObjIndex + 0; + indexBufferData[currentIndex + 4] = currentObjIndex + 2; + indexBufferData[currentIndex + 5] = currentObjIndex + 3; + + if (options->CoordinateSystem == ElemSceneCoordinateSystem_LeftHanded) + { + auto tmp = indexBufferData[currentIndex + 1]; + indexBufferData[currentIndex + 1] = indexBufferData[currentIndex + 2]; + indexBufferData[currentIndex + 2] = tmp; + + tmp = indexBufferData[currentIndex + 4]; + indexBufferData[currentIndex + 4] = indexBufferData[currentIndex + 5]; + indexBufferData[currentIndex + 5] = tmp; + } + + currentIndex += 6; + } + + currentObjIndex += faceVertexCount; + } + + // TODO: To replace + GenerateTangentVectorsParameters generateTangentParams = + { + .PositionPointer = &vertexList.Pointer[0].Position, + .NormalPointer = &vertexList.Pointer[0].Normal, + .TextureCoordinatesPointer = &vertexList.Pointer[0].TextureCoordinates, + .TangentPointer = &vertexList.Pointer[0].Tangent, + .VertexSize = sizeof(ObjVertex), + .IndexData = indexBufferData + }; + + AssertIfFailed(GenerateTangentVectors(&generateTangentParams)); + + auto vertexBufferData = SystemPushArray(memoryArena, meshPrimitiveInfo->VertexCount * maxVertexSize); + auto currentVertexBufferPointer = vertexBufferData.Pointer; + + // TODO: Remove that loop + for (uint32_t i = 0; i < meshPrimitiveInfo->VertexCount; i++) + { + WriteObjVertex(&vertexList[i], ¤tVertexBufferPointer); + } + + result.VertexBuffer = + { + .Data = { .Items = vertexBufferData.Pointer, .Length = (uint32_t)vertexBufferData.Length }, + .VertexSize = (uint32_t)realVertexSize, + .VertexCount = meshPrimitiveInfo->VertexCount + }; + + result.IndexBuffer = { .Items = indexBufferData.Pointer, .Length = (uint32_t)indexBufferData.Length }; + result.MaterialId = meshPrimitiveInfo->MaterialId; + + return result; +} + +void ApplyObjMeshPrimitiveInverseTranslation(ElemToolsVector3 translation, ElemVertexBuffer* vertexBuffer) +{ + for (uint32_t i = 0; i < vertexBuffer->VertexCount; i++) + { + auto position = (ElemToolsVector3*)(vertexBuffer->Data.Items + i * vertexBuffer->VertexSize); + *position = { position->X - translation.X, position->Y - translation.Y, position->Z - translation.Z }; + } +} + +void ApplyObjBoundingBoxInverseTranslation(ElemToolsVector3 translation, ElemToolsBoundingBox* boundingBox) +{ + boundingBox->MinPoint = { boundingBox->MinPoint.X - translation.X, boundingBox->MinPoint.Y - translation.Y, boundingBox->MinPoint.Z - translation.Y }; + boundingBox->MaxPoint = { boundingBox->MaxPoint.X - translation.X, boundingBox->MaxPoint.Y - translation.Y, boundingBox->MaxPoint.Z - translation.Y }; +} + +ElemLoadSceneResult LoadObjSceneAndNodes(const fastObjMesh* objFileData, const ElemLoadSceneOptions* options) +{ + auto hasErrors = false; + + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto sceneLoaderMemoryArena = GetSceneLoaderMemoryArena(); + + auto globalTransformMatrix = CreateSceneLoaderGlobalTransformMatrix(options); + + auto meshes = SystemPushArray(sceneLoaderMemoryArena, objFileData->group_count); + auto sceneNodes = SystemPushArray(sceneLoaderMemoryArena, objFileData->group_count); + auto meshPrimitiveInfos = SystemPushArray(stackMemoryArena, UINT16_MAX); + + for (uint32_t i = 0; i < objFileData->group_count; i++) + { + auto mesh = &meshes[i]; + auto sceneNode = &sceneNodes[i]; + auto groupData = &objFileData->groups[i]; + + if (groupData->face_count == 0) + { + printf("Invalid object\n"); + continue; + } + + auto vertexOffset = 0u; + + for (uint32_t j = 0; j < groupData->face_offset; j++) + { + vertexOffset += objFileData->face_vertices[j]; + } + + auto currentMaterial = objFileData->face_materials[groupData->face_offset]; + auto meshPrimitiveCount = 0u; + + auto meshPrimitiveInfo = &meshPrimitiveInfos[meshPrimitiveCount++]; + *meshPrimitiveInfo = + { + .VertexOffset = vertexOffset, + .FaceOffset = groupData->face_offset, + .MaterialId = (int32_t)currentMaterial + }; + + for (uint32_t j = 0; j < groupData->face_count; j++) + { + auto faceMaterial = objFileData->face_materials[groupData->face_offset + j]; + auto faceVertexCount = objFileData->face_vertices[groupData->face_offset + j]; + + if (faceVertexCount != 3 && faceVertexCount != 4) + { + return + { + .Messages = ConstructErrorMessageSpan(sceneLoaderMemoryArena, "Obj mesh loader only support triangles or quads."), + .HasErrors = true + }; + } + + if (faceMaterial != currentMaterial) + { + currentMaterial = faceMaterial; + printf("Material: %d\n", currentMaterial); + + auto previousVertexOffset = meshPrimitiveInfo->VertexOffset; + auto previousVertexCount = meshPrimitiveInfo->VertexCount; + auto previousFaceOffset = meshPrimitiveInfo->FaceOffset; + auto previousFaceCount = meshPrimitiveInfo->FaceCount; + + meshPrimitiveInfo = &meshPrimitiveInfos[meshPrimitiveCount++]; + *meshPrimitiveInfo = + { + .VertexOffset = previousVertexOffset + previousVertexCount, + .FaceOffset = previousFaceOffset + previousFaceCount, + .MaterialId = (int32_t)currentMaterial + }; + } + + meshPrimitiveInfo->VertexCount += faceVertexCount; + meshPrimitiveInfo->FaceCount++; + } + + mesh->BoundingBox = + { + .MinPoint = { FLT_MAX, FLT_MAX, FLT_MAX }, + .MaxPoint = { -FLT_MAX, -FLT_MAX, -FLT_MAX } + }; + + auto meshPrimitives = SystemPushArray(sceneLoaderMemoryArena, meshPrimitiveCount); + + for (uint32_t j = 0; j < meshPrimitiveCount; j++) + { + meshPrimitives[j] = ConstructObjMeshPrimitive(sceneLoaderMemoryArena, objFileData, &meshPrimitiveInfos[j], options); + AddBoundingBoxToBoundingBox(&meshPrimitives[j].BoundingBox, &mesh->BoundingBox); + } + + auto boundingBoxCenter = GetBoundingBoxCenter(&mesh->BoundingBox); + ApplyObjBoundingBoxInverseTranslation(boundingBoxCenter, &mesh->BoundingBox); + + for (uint32_t j = 0; j < meshPrimitiveCount; j++) + { + auto meshPrimitive = &meshPrimitives[j]; + + ApplyObjMeshPrimitiveInverseTranslation(boundingBoxCenter, &meshPrimitive->VertexBuffer); + ApplyObjBoundingBoxInverseTranslation(boundingBoxCenter, &meshPrimitive->BoundingBox); + } + + if (groupData->name) + { + mesh->Name = SystemDuplicateBuffer(sceneLoaderMemoryArena, ReadOnlySpan(groupData->name)).Pointer; + } + else + { + mesh->Name = "Mesh"; + } + + mesh->MeshPrimitives = { .Items = meshPrimitives.Pointer, .Length = (uint32_t)meshPrimitives.Length }; + + if (groupData->name) + { + sceneNode->Name = SystemDuplicateBuffer(sceneLoaderMemoryArena, ReadOnlySpan(groupData->name)).Pointer; + } + else + { + sceneNode->Name = "Node"; + } + + sceneNode->NodeType = ElemSceneNodeType_Mesh; + sceneNode->ReferenceIndex = i; + + auto nodeTransform = ElemToolsCreateTranslationMatrix({ boundingBoxCenter.X, boundingBoxCenter.Y, boundingBoxCenter.Z }); + + nodeTransform = ElemToolsMulMatrix4x4(nodeTransform, globalTransformMatrix); + DecomposeTransform(nodeTransform, &sceneNode->Scale, &sceneNode->Rotation, &sceneNode->Translation); + } + + return + { + .SceneFormat = ElemSceneFormat_Obj, + .CoordinateSystem = options->CoordinateSystem, + .Meshes = { .Items = meshes.Pointer, .Length = (uint32_t)meshes.Length }, + .Nodes = { .Items = sceneNodes.Pointer, .Length = (uint32_t)sceneNodes.Length }, + .HasErrors = hasErrors + }; +} + +ElemSceneMaterialSpan LoadObjMaterials(const fastObjMesh* objFileData, const ElemLoadSceneOptions* options) +{ + auto sceneLoaderMemoryArena = GetSceneLoaderMemoryArena(); + auto materials = SystemPushArray(sceneLoaderMemoryArena, objFileData->material_count); + + for (uint32_t i = 0; i < objFileData->material_count; i++) + { + auto objMaterial = &objFileData->materials[i]; + auto material = &materials[i]; + + if (objMaterial->name) + { + material->Name = SystemDuplicateBuffer(sceneLoaderMemoryArena, ReadOnlySpan(objMaterial->name)).Pointer; + } + else + { + material->Name = "Material"; + } + + if (objMaterial->map_Kd > 0) + { + material->AlbedoTexturePath = SystemDuplicateBuffer(sceneLoaderMemoryArena, ReadOnlySpan(objFileData->textures[objMaterial->map_Kd].path)).Pointer; + } + + if (objMaterial->map_bump > 0) + { + material->NormalTexturePath = SystemDuplicateBuffer(sceneLoaderMemoryArena, ReadOnlySpan(objFileData->textures[objMaterial->map_bump].path)).Pointer; + } + + material->AlbedoFactor = {{ 1.0f, 1.0f, 1.0f, 1.0f }}; + + printf("material: %s\n", objFileData->materials[i].name); + } + + return { .Items = materials.Pointer, .Length = (uint32_t)materials.Length }; +} + +ElemLoadSceneResult LoadObjScene(const char* path, const ElemLoadSceneOptions* options) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto callbacks = fastObjCallbacks + { + .file_open = FastObjFileOpen, + .file_close = FastObjFileClose, + .file_read = FastObjFileRead, + .file_size = FastObjFileSize + }; + + auto objFileData = fast_obj_read_with_callbacks(path, &callbacks, &stackMemoryArena); + + auto result = LoadObjSceneAndNodes(objFileData, options); + result.Materials = LoadObjMaterials(objFileData, options); + + return result; +} diff --git a/src/ElementalTools/SceneLoading/TangentSpaceGenerator.cpp b/src/ElementalTools/SceneLoading/TangentSpaceGenerator.cpp new file mode 100644 index 00000000..956cb8d8 --- /dev/null +++ b/src/ElementalTools/SceneLoading/TangentSpaceGenerator.cpp @@ -0,0 +1,81 @@ +#include "TangentSpaceGenerator.h" + +int32_t MikkTSpaceGetNumFaces(const SMikkTSpaceContext* context) +{ + auto parameters = (GenerateTangentVectorsParameters*)context->m_pUserData; + return parameters->IndexData.Length / 3; +} + +int32_t MikkTSpaceGetNumVerticesOfFace(const SMikkTSpaceContext* context, int32_t face) +{ + return 3; +} + +void MikkTSpaceGetPosition(const SMikkTSpaceContext* context, float* outputPosition, int32_t faceIndex, int32_t vertexIndex) +{ + auto parameters = (GenerateTangentVectorsParameters*)context->m_pUserData; + + auto vertexGlobalIndex = parameters->IndexData[faceIndex * 3 + vertexIndex]; + auto position = (ElemToolsVector3*)((uint8_t*)parameters->PositionPointer + vertexGlobalIndex * parameters->VertexSize); + + outputPosition[0] = position->X; + outputPosition[1] = position->Y; + outputPosition[2] = position->Z; +} + +void MikkTSpaceGetNormal(const SMikkTSpaceContext* context, float* outputNormal, int32_t faceIndex, int32_t vertexIndex) +{ + auto parameters = (GenerateTangentVectorsParameters*)context->m_pUserData; + + auto vertexGlobalIndex = parameters->IndexData[faceIndex * 3 + vertexIndex]; + auto normal = (ElemToolsVector3*)((uint8_t*)parameters->NormalPointer + vertexGlobalIndex * parameters->VertexSize); + + outputNormal[0] = normal->X; + outputNormal[1] = normal->Y; + outputNormal[2] = normal->Z; +} + +void MikkTSpaceGetTextureCoordinates(const SMikkTSpaceContext* context, float* outputTextureCoordinates, int32_t faceIndex, int32_t vertexIndex) +{ + auto parameters = (GenerateTangentVectorsParameters*)context->m_pUserData; + + auto vertexGlobalIndex = parameters->IndexData[faceIndex * 3 + vertexIndex]; + auto textureCoordinates = (ElemToolsVector2*)((uint8_t*)parameters->TextureCoordinatesPointer + vertexGlobalIndex * parameters->VertexSize); + + outputTextureCoordinates[0] = textureCoordinates->X; + outputTextureCoordinates[1] = textureCoordinates->Y; +} + +void MikkTSpaceSetTangent(const SMikkTSpaceContext* context, const float* tangent, float sign, int32_t faceIndex, int32_t vertexIndex) +{ + auto parameters = (GenerateTangentVectorsParameters*)context->m_pUserData; + + auto vertexGlobalIndex = parameters->IndexData[faceIndex * 3 + vertexIndex]; + auto outputTangent = (ElemToolsVector4*)((uint8_t*)parameters->TangentPointer + vertexGlobalIndex * parameters->VertexSize); + + outputTangent->X = tangent[0]; + outputTangent->Y = tangent[1]; + outputTangent->Z = tangent[2]; + outputTangent->W = sign; +} + +bool GenerateTangentVectors(const GenerateTangentVectorsParameters* parameters) +{ + SMikkTSpaceInterface mikkTSpaceInterface + { + .m_getNumFaces = MikkTSpaceGetNumFaces, + .m_getNumVerticesOfFace = MikkTSpaceGetNumVerticesOfFace, + .m_getPosition = MikkTSpaceGetPosition, + .m_getNormal = MikkTSpaceGetNormal, + .m_getTexCoord = MikkTSpaceGetTextureCoordinates, + .m_setTSpaceBasic = MikkTSpaceSetTangent + }; + + SMikkTSpaceContext mikkTSpaceContext + { + .m_pInterface = &mikkTSpaceInterface, + .m_pUserData = (void*)parameters + }; + + return genTangSpaceDefault(&mikkTSpaceContext); +} diff --git a/src/ElementalTools/SceneLoading/TangentSpaceGenerator.h b/src/ElementalTools/SceneLoading/TangentSpaceGenerator.h new file mode 100644 index 00000000..dc18b079 --- /dev/null +++ b/src/ElementalTools/SceneLoading/TangentSpaceGenerator.h @@ -0,0 +1,16 @@ +#pragma once + +#include "ElementalTools.h" +#include "SystemMemory.h" + +struct GenerateTangentVectorsParameters +{ + ElemToolsVector3* PositionPointer; + ElemToolsVector3* NormalPointer; + ElemToolsVector2* TextureCoordinatesPointer; + ElemToolsVector4* TangentPointer; + uint32_t VertexSize; + ReadOnlySpan IndexData; +}; + +bool GenerateTangentVectors(const GenerateTangentVectorsParameters* parameters); diff --git a/src/ElementalTools/Common/DirectXShaderCompiler.cpp b/src/ElementalTools/Shaders/DirectXShaderCompiler.cpp similarity index 91% rename from src/ElementalTools/Common/DirectXShaderCompiler.cpp rename to src/ElementalTools/Shaders/DirectXShaderCompiler.cpp index b082a6b4..1e8e3af7 100644 --- a/src/ElementalTools/Common/DirectXShaderCompiler.cpp +++ b/src/ElementalTools/Shaders/DirectXShaderCompiler.cpp @@ -10,8 +10,6 @@ void InitDirectXShaderCompiler() { if (!directXShaderCompilerLibrary.Handle) { - // BUG: For the moment we need to get rid of DXIL.dll in the windows SDK folder to load the correct - // dll on windows directXShaderCompilerLibrary = SystemLoadLibrary("dxcompiler"); if (directXShaderCompilerLibrary.Handle != nullptr) @@ -41,7 +39,7 @@ bool ProcessDirectXShaderCompilerLogOutput(MemoryArena memoryArena, ComPtr GetShaderTypeTarget(DxilShaderKind shaderKind) case DxilShaderKind::Compute: return "cs_6_8"; - case DxilShaderKind::Amplification: - return "as_6_8"; - case DxilShaderKind::Mesh: return "ms_6_8"; @@ -93,9 +88,6 @@ ShaderType GetShaderTypeEnum(DxilShaderKind shaderKind) case DxilShaderKind::Compute: return ShaderType_Compute; - case DxilShaderKind::Amplification: - return ShaderType_Amplification; - case DxilShaderKind::Mesh: return ShaderType_Mesh; @@ -109,6 +101,8 @@ ShaderType GetShaderTypeEnum(DxilShaderKind shaderKind) ComPtr CompileDirectXShader(ReadOnlySpan shaderCode, ReadOnlySpan target, ElemToolsGraphicsApi targetApi, ReadOnlySpan entryPoint, const ElemCompileShaderOptions* options) { + // TODO: Allow passing the pass to have a correct name in the messages + auto stackMemoryArena = SystemGetStackMemoryArena(); ComPtr compiler; @@ -132,13 +126,29 @@ ComPtr CompileDirectXShader(ReadOnlySpan shaderCode, ReadOn { parameters[parameterIndex++] = L"-spirv"; parameters[parameterIndex++] = L"-fspv-target-env=vulkan1.3"; + parameters[parameterIndex++] = L"-fvk-use-dx-layout"; + // TODO: Add an options for this + // TODO: To review + parameters[parameterIndex++] = L"-fspv-use-legacy-buffer-matrix-order"; + + parameters[parameterIndex++] = L"-fvk-bind-resource-heap"; + parameters[parameterIndex++] = L"0"; + parameters[parameterIndex++] = L"0"; + + parameters[parameterIndex++] = L"-fvk-bind-sampler-heap"; + parameters[parameterIndex++] = L"0"; + parameters[parameterIndex++] = L"1"; } parameters[parameterIndex++] = L"-HV"; - parameters[parameterIndex++] = L"2021"; + parameters[parameterIndex++] = L"202x"; + parameters[parameterIndex++] = L"-enable-16bit-types"; parameters[parameterIndex++] = DXC_ARG_ALL_RESOURCES_BOUND; + // TODO: Add an options for this + parameters[parameterIndex++] = DXC_ARG_PACK_MATRIX_ROW_MAJOR; + if (options && options->DebugMode) { parameters[parameterIndex++] = DXC_ARG_DEBUG; @@ -206,8 +216,6 @@ ElemShaderCompilationResult DirectXShaderCompilerCompileShader(MemoryArena memor ComPtr shaderByteCodeComPtr; AssertIfFailed(dxilCompileResult->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&shaderByteCodeComPtr), nullptr)); - auto shaderByteCode = Span((uint8_t*)shaderByteCodeComPtr->GetBufferPointer(), shaderByteCodeComPtr->GetBufferSize()); - ComPtr dxcUtils; AssertIfFailed(directXShaderCompilerCreateInstanceFunction(CLSID_DxcUtils, IID_PPV_ARGS(&dxcUtils))); diff --git a/src/ElementalTools/Common/DirectXShaderCompiler.h b/src/ElementalTools/Shaders/DirectXShaderCompiler.h similarity index 100% rename from src/ElementalTools/Common/DirectXShaderCompiler.h rename to src/ElementalTools/Shaders/DirectXShaderCompiler.h diff --git a/src/ElementalTools/Common/MetalShaderConverter.cpp b/src/ElementalTools/Shaders/MetalShaderConverter.cpp similarity index 97% rename from src/ElementalTools/Common/MetalShaderConverter.cpp rename to src/ElementalTools/Shaders/MetalShaderConverter.cpp index d3277c0f..e3502e35 100644 --- a/src/ElementalTools/Common/MetalShaderConverter.cpp +++ b/src/ElementalTools/Shaders/MetalShaderConverter.cpp @@ -53,9 +53,6 @@ IRShaderStage ConvertShaderTypeToMetalShaderStage(ShaderType shaderType) { switch (shaderType) { - case ShaderType_Amplification: - return IRShaderStage::IRShaderStageAmplification; - case ShaderType_Mesh: return IRShaderStage::IRShaderStageMesh; @@ -102,7 +99,7 @@ ElemShaderCompilationResult MetalShaderConverterCompileShader(MemoryArena memory } else { - IRCompilerSetMinimumDeploymentTarget(pCompiler, IROperatingSystem_macOS, "14.0.0"); + IRCompilerSetMinimumDeploymentTarget(pCompiler, IROperatingSystem_macOS, "15.0.0"); } auto compilationMessages = SystemPushArray(memoryArena, 1024); @@ -128,7 +125,7 @@ ElemShaderCompilationResult MetalShaderConverterCompileShader(MemoryArena memory { .NumParameters = 1, .pParameters = &rootParameter, - .Flags = IRRootSignatureFlags(IRRootSignatureFlagCBVSRVUAVHeapDirectlyIndexed) + .Flags = IRRootSignatureFlags(IRRootSignatureFlagCBVSRVUAVHeapDirectlyIndexed | IRRootSignatureFlagSamplerHeapDirectlyIndexed) } }; IRError* rootSignatureError = nullptr; @@ -188,6 +185,7 @@ ElemShaderCompilationResult MetalShaderConverterCompileShader(MemoryArena memory if (!result) { // TODO: Error + printf("ERRORRRRRR!\n"); } size_t metallibSize = IRMetalLibGetBytecodeSize(pMetallib); diff --git a/src/ElementalTools/Common/MetalShaderConverter.h b/src/ElementalTools/Shaders/MetalShaderConverter.h similarity index 100% rename from src/ElementalTools/Common/MetalShaderConverter.h rename to src/ElementalTools/Shaders/MetalShaderConverter.h diff --git a/src/ElementalTools/Common/ShaderCompiler.cpp b/src/ElementalTools/Shaders/ShaderCompiler.cpp similarity index 85% rename from src/ElementalTools/Common/ShaderCompiler.cpp rename to src/ElementalTools/Shaders/ShaderCompiler.cpp index 38e8ee71..277c943e 100644 --- a/src/ElementalTools/Common/ShaderCompiler.cpp +++ b/src/ElementalTools/Shaders/ShaderCompiler.cpp @@ -1,4 +1,5 @@ #include "ElementalTools.h" +#include "ToolsUtils.h" #include "DirectXShaderCompiler.h" #include "MetalShaderConverter.h" #include "SystemFunctions.h" @@ -83,6 +84,25 @@ ElemShaderLanguage GetApiTargetLanguage(ElemToolsGraphicsApi graphicsApi) } } +ElemShaderLanguage GetShaderLanguageFromPath(const char* path) +{ + auto pathSpan = ReadOnlySpan(path); + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto lastIndex = SystemLastIndexOf(path, '.'); + SystemAssert(lastIndex != -1); + + auto extension = SystemPushArray(stackMemoryArena, pathSpan.Length - lastIndex); + SystemCopyBuffer(extension, pathSpan.Slice(lastIndex + 1)); + + if (SystemFindSubString(extension, "hlsl") != -1) + { + return ElemShaderLanguage_Hlsl; + } + + return ElemShaderLanguage_Unknown; +} + void ReverseCompilerChainArray(Span steps, uint32_t count) { for (uint32_t i = 0; i < count - 1; i++) @@ -163,9 +183,9 @@ ElemToolsAPI bool ElemCanCompileShader(ElemShaderLanguage shaderLanguage, ElemTo return FindShaderCompilerChain(shaderLanguage, targetLanguage, compilerSteps, &level); } -ElemToolsAPI ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform, const ElemShaderSourceData* sourceData, const ElemCompileShaderOptions* options) +ElemToolsAPI ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraphicsApi graphicsApi, ElemToolsPlatform platform, const char* path, const ElemCompileShaderOptions* options) { - SystemAssert(sourceData); + SystemAssert(path); InitShaderCompiler(); auto stackMemoryArena = SystemGetStackMemoryArena(); @@ -176,26 +196,30 @@ ElemToolsAPI ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraph auto compilationMessages = Span(); auto compilationData = ReadOnlySpan(); - if (!FindShaderCompilerChain(sourceData->ShaderLanguage, targetLanguage, compilerSteps, &level)) - { - auto messageItem = SystemPushStruct(stackMemoryArena); - messageItem->Type = ElemToolsMessageType_Error; - messageItem->Message = "Cannot find a compatible shader compilers chain."; + auto shaderLanguage = GetShaderLanguageFromPath(path); + SystemAssert(shaderLanguage != ElemShaderLanguage_Unknown); + if (!FindShaderCompilerChain(shaderLanguage, targetLanguage, compilerSteps, &level)) + { return { - .Messages = - { - .Items = messageItem, - .Length = 1 - }, + .Messages = ConstructErrorMessageSpan(ShaderCompilerMemoryArena, "Cannot find a compatible shader compilers chain."), .HasErrors = true }; } auto hasErrors = false; - auto stepSourceData = ReadOnlySpan((uint8_t*)sourceData->Data.Items, sourceData->Data.Length); + auto stepSourceData = LoadFileData(path); + + if (stepSourceData.Length == 0) + { + return + { + .Messages = ConstructErrorMessageSpan(ShaderCompilerMemoryArena, "Cannot read input file."), + .HasErrors = true + }; + } for (uint32_t i = 0; i < level; i++) { @@ -227,6 +251,8 @@ ElemToolsAPI ElemShaderCompilationResult ElemCompileShaderLibrary(ElemToolsGraph stepSourceData = compilationData; } + ResetLoadFileDataMemory(); + return { .Data = diff --git a/src/ElementalTools/Common/ShaderCompilerUtils.cpp b/src/ElementalTools/Shaders/ShaderCompilerUtils.cpp similarity index 100% rename from src/ElementalTools/Common/ShaderCompilerUtils.cpp rename to src/ElementalTools/Shaders/ShaderCompilerUtils.cpp diff --git a/src/ElementalTools/Common/ShaderCompilerUtils.h b/src/ElementalTools/Shaders/ShaderCompilerUtils.h similarity index 81% rename from src/ElementalTools/Common/ShaderCompilerUtils.h rename to src/ElementalTools/Shaders/ShaderCompilerUtils.h index 75ee1e8e..a282e2e8 100644 --- a/src/ElementalTools/Common/ShaderCompilerUtils.h +++ b/src/ElementalTools/Shaders/ShaderCompilerUtils.h @@ -5,11 +5,10 @@ enum ShaderType { ShaderType_Unknown = 0, - ShaderType_Amplification = 1, - ShaderType_Mesh = 2, - ShaderType_Pixel = 3, - ShaderType_Compute = 4, - ShaderType_Library = 5 + ShaderType_Mesh = 1, + ShaderType_Pixel = 2, + ShaderType_Compute = 3, + ShaderType_Library = 4 }; enum ShaderMetadataType diff --git a/src/ElementalTools/Textures/TextureLoader.cpp b/src/ElementalTools/Textures/TextureLoader.cpp new file mode 100644 index 00000000..99e5aba1 --- /dev/null +++ b/src/ElementalTools/Textures/TextureLoader.cpp @@ -0,0 +1,86 @@ +#include "TextureLoader.h" +#include "SystemFunctions.h" +#include "TextureLoaderStb.cpp" +#include "TextureLoaderDds.cpp" + +// TODO: Do one for each thread +static MemoryArena TextureLoaderMemoryArena; + +void InitTextureLoaderMemoryArena() +{ + if (TextureLoaderMemoryArena.Storage == nullptr) + { + TextureLoaderMemoryArena = SystemAllocateMemoryArena(512 * 1024 * 1024); + } + + SystemClearMemoryArena(TextureLoaderMemoryArena); +} + +MemoryArena GetTextureLoaderMemoryArena() +{ + return TextureLoaderMemoryArena; +} + +ElemLoadTextureFileFormat GetLoadTextureFileFormatFromPath(const char* path) +{ + auto pathSpan = ReadOnlySpan(path); + auto stackMemoryArena = SystemGetStackMemoryArena(); + + auto lastIndex = SystemLastIndexOf(path, '.'); + SystemAssert(lastIndex != -1); + + auto extension = SystemPushArray(stackMemoryArena, pathSpan.Length - lastIndex); + SystemCopyBuffer(extension, pathSpan.Slice(lastIndex + 1)); + + if (SystemFindSubString(extension, "tga") != -1) + { + return ElemLoadTextureFileFormat_Tga; + } + else if (SystemFindSubString(extension, "jpg") != -1) + { + return ElemLoadTextureFileFormat_Jpg; + } + else if (SystemFindSubString(extension, "png") != -1) + { + return ElemLoadTextureFileFormat_Png; + } + else if (SystemFindSubString(extension, "dds") != -1) + { + return ElemLoadTextureFileFormat_Dds; + } + + return ElemLoadTextureFileFormat_Unknown; +} + +ElemToolsAPI ElemLoadTextureResult ElemLoadTexture(const char* path, const ElemLoadTextureOptions* options) +{ + InitTextureLoaderMemoryArena(); + + ElemLoadTextureOptions loadTextureOptions = {}; + + if (options) + { + loadTextureOptions = *options; + } + + auto textureFileFormat = GetLoadTextureFileFormatFromPath(path); + + // TODO: Refactor that with array entries and function pointer + switch (textureFileFormat) + { + case ElemLoadTextureFileFormat_Tga: + case ElemLoadTextureFileFormat_Jpg: + case ElemLoadTextureFileFormat_Png: + return LoadStbTexture(path, textureFileFormat, &loadTextureOptions); + + case ElemLoadTextureFileFormat_Dds: + return LoadDdsTexture(path, textureFileFormat, &loadTextureOptions); + + default: + return + { + .Messages = ConstructErrorMessageSpan(GetTextureLoaderMemoryArena(), "Texture format is not supported."), + .HasErrors = true + }; + }; +} diff --git a/src/ElementalTools/Textures/TextureLoader.h b/src/ElementalTools/Textures/TextureLoader.h new file mode 100644 index 00000000..77e30f05 --- /dev/null +++ b/src/ElementalTools/Textures/TextureLoader.h @@ -0,0 +1,5 @@ +#pragma once + +#include "SystemMemory.h" + +MemoryArena GetTextureLoaderMemoryArena(); diff --git a/src/ElementalTools/Textures/TextureLoaderDds.cpp b/src/ElementalTools/Textures/TextureLoaderDds.cpp new file mode 100644 index 00000000..ac08418c --- /dev/null +++ b/src/ElementalTools/Textures/TextureLoaderDds.cpp @@ -0,0 +1,190 @@ +#include "TextureLoader.h" +#include "ToolsUtils.h" +#include "ElementalTools.h" +#include "SystemMemory.h" +#include "SystemFunctions.h" + +#define DDSCAPS2_CUBEMAP 0x200 +#define DDSCAPS2_VOLUME 0x200000 +#define DDS_DIMENSION_TEXTURE2D 3 + +struct DdsPixelFormat +{ + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwABitMask; +}; + +struct DdsHeader +{ + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[11]; + DdsPixelFormat ddspf; + unsigned int dwCaps; + unsigned int dwCaps2; + unsigned int dwCaps3; + unsigned int dwCaps4; + unsigned int dwReserved2; +}; + +struct DdsHeaderDirectX10 +{ + unsigned int dxgiFormat; + unsigned int resourceDimension; + unsigned int miscFlag; + unsigned int arraySize; + unsigned int miscFlags2; +}; + +enum DXGI_FORMAT +{ + DXGI_FORMAT_BC1_UNORM = 71, + DXGI_FORMAT_BC1_UNORM_SRGB = 72, + DXGI_FORMAT_BC2_UNORM = 74, + DXGI_FORMAT_BC2_UNORM_SRGB = 75, + DXGI_FORMAT_BC3_UNORM = 77, + DXGI_FORMAT_BC3_UNORM_SRGB = 78, + DXGI_FORMAT_BC4_UNORM = 80, + DXGI_FORMAT_BC4_SNORM = 81, + DXGI_FORMAT_BC5_UNORM = 83, + DXGI_FORMAT_BC5_SNORM = 84, + DXGI_FORMAT_BC6H_UF16 = 95, + DXGI_FORMAT_BC6H_SF16 = 96, + DXGI_FORMAT_BC7_UNORM = 98, + DXGI_FORMAT_BC7_UNORM_SRGB = 99, +}; + +uint32_t FourCC(const char value[5]) +{ + return ((uint32_t)value[0] << 0) | ((uint32_t)value[1] << 8) | ((uint32_t)value[2] << 16) | ((uint32_t)value[3] << 24); +} + +ElemToolsGraphicsFormat GetDdsTextureFormat(const DdsHeader* ddsHeader, const DdsHeaderDirectX10* directX10Header) +{ + // TODO: Other formats + if (ddsHeader->ddspf.dwFourCC == FourCC("DX10")) + { + if (directX10Header->dxgiFormat == DXGI_FORMAT_BC7_UNORM_SRGB || directX10Header->dxgiFormat == DXGI_FORMAT_BC7_UNORM) + { + return ElemToolsGraphicsFormat_BC7; + } + } + + return ElemToolsGraphicsFormat_Unknown; +} + +uint32_t GetDdsFormatBlockSize(ElemToolsGraphicsFormat format) +{ + // TODO: other formats + if (format == ElemToolsGraphicsFormat_BC7) + { + return 16; + } + + return 8; +} + +ElemLoadTextureResult LoadDdsTexture(const char* path, ElemLoadTextureFileFormat fileFormat, const ElemLoadTextureOptions* options) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto textureLoaderMemoryArena = GetTextureLoaderMemoryArena(); + + auto messages = SystemPushArray(textureLoaderMemoryArena, 1024); + auto messageCount = 0u; + auto hasErrors = false; + + auto objFileData = LoadFileData(path); + + if (objFileData.Length == 0) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Error while reading texture file."), .HasErrors = true }; + } + + auto currentFilePointer = objFileData.Pointer; + + if (*(uint32_t*)currentFilePointer != FourCC("DDS ")) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Texture is not a DDS file."), .HasErrors = true }; + } + + currentFilePointer += 4; + + auto header = (DdsHeader*)currentFilePointer; + currentFilePointer += sizeof(DdsHeader); + + DdsHeaderDirectX10* directX10Header = nullptr; + + if (header->ddspf.dwFourCC == FourCC("DX10")) + { + directX10Header = (DdsHeaderDirectX10*)currentFilePointer; + currentFilePointer += sizeof(DdsHeaderDirectX10); + } + + if (header->dwCaps2 & (DDSCAPS2_CUBEMAP | DDSCAPS2_VOLUME)) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Cubemaps and volume textures are not supported."), .HasErrors = true }; + } + + if (header->ddspf.dwFourCC == FourCC("DX10") && directX10Header->resourceDimension != DDS_DIMENSION_TEXTURE2D) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Only Texture2D are supported."), .HasErrors = true }; + } + + auto format = GetDdsTextureFormat(header, directX10Header); + + if (format == ElemToolsGraphicsFormat_Unknown) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Unknown texture format."), .HasErrors = true }; + } + + auto width = header->dwWidth; + auto height = header->dwHeight; + auto mipLevels = header->dwMipMapCount; + auto blockSize = GetDdsFormatBlockSize(format); + + auto mipData = SystemPushArray(textureLoaderMemoryArena, mipLevels); + + for (uint32_t i = 0; i < mipLevels; i++) + { + auto mipLevelData = &mipData[i]; + printf("Processing mip level %d\n", i); + + auto mipWidth = SystemMax(1u, width >> i); + auto mipHeight = SystemMax(1u, height >> i); + + auto dataSizeInBytes = ((mipWidth + 3) / 4) * ((mipHeight + 3) / 4) * blockSize; + printf("Data size: %d\n", dataSizeInBytes); + + mipLevelData->Width = mipWidth; + mipLevelData->Height = mipHeight; + + auto mipLevelRawData = SystemDuplicateBuffer(textureLoaderMemoryArena, ReadOnlySpan((uint8_t*)currentFilePointer, dataSizeInBytes)); + mipLevelData->Data = { .Items = mipLevelRawData.Pointer, .Length = (uint32_t)mipLevelRawData.Length }; + + currentFilePointer += dataSizeInBytes; + } + + ResetLoadFileDataMemory(); + + return + { + .FileFormat = fileFormat, + .Format = format, + .Width = (uint32_t)width, + .Height = (uint32_t)height, + .MipData = { .Items = mipData.Pointer, .Length = (uint32_t)mipData.Length }, + .Messages = { .Items = messages.Pointer, .Length = messageCount }, + .HasErrors = hasErrors + }; +} diff --git a/src/ElementalTools/Textures/TextureLoaderStb.cpp b/src/ElementalTools/Textures/TextureLoaderStb.cpp new file mode 100644 index 00000000..d69d8898 --- /dev/null +++ b/src/ElementalTools/Textures/TextureLoaderStb.cpp @@ -0,0 +1,51 @@ +#include "TextureLoader.h" +#include "ToolsUtils.h" +#include "ElementalTools.h" +#include "SystemMemory.h" + +ElemLoadTextureResult LoadStbTexture(const char* path, ElemLoadTextureFileFormat fileFormat, const ElemLoadTextureOptions* options) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + auto textureLoaderMemoryArena = GetTextureLoaderMemoryArena(); + + auto messages = SystemPushArray(textureLoaderMemoryArena, 1024); + auto messageCount = 0u; + auto hasErrors = false; + + auto objFileData = LoadFileData(path); + + if (objFileData.Length == 0) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Error while reading texture file."), .HasErrors = true }; + } + + int32_t width, height, channels; + auto stbImageData = stbi_load_from_memory(objFileData.Pointer, objFileData.Length, &width, &height, &channels, STBI_rgb_alpha); + + if (!stbImageData) + { + return { .Messages = ConstructErrorMessageSpan(textureLoaderMemoryArena, "Error while loading texture file."), .HasErrors = true }; + } + + auto imageDataLength = width * height * STBI_rgb_alpha; + auto imageData = SystemDuplicateBuffer(textureLoaderMemoryArena, ReadOnlySpan(stbImageData, imageDataLength)); + + auto mipData = SystemPushStruct(textureLoaderMemoryArena); + mipData->Width = width; + mipData->Height = height; + mipData->Data = { .Items = imageData.Pointer, .Length = (uint32_t)imageData.Length }, + + stbi_image_free(stbImageData); + ResetLoadFileDataMemory(); + + return + { + .FileFormat = fileFormat, + .Format = ElemToolsGraphicsFormat_R8G8B8A8, + .Width = (uint32_t)width, + .Height = (uint32_t)height, + .MipData = { .Items = mipData, .Length = 1 }, + .Messages = { .Items = messages.Pointer, .Length = messageCount }, + .HasErrors = hasErrors + }; +} diff --git a/src/ElementalTools/Textures/TextureProcessing.cpp b/src/ElementalTools/Textures/TextureProcessing.cpp new file mode 100644 index 00000000..1cc660ee --- /dev/null +++ b/src/ElementalTools/Textures/TextureProcessing.cpp @@ -0,0 +1,162 @@ +#include "ElementalTools.h" +#include "ToolsUtils.h" +#include "SystemMemory.h" +#include "SystemFunctions.h" + +#define TEXTURE_BC_BLOCK_SIZE_IN_BYTES 16 + +// TODO: See example: https://github.com/Hork-Engine/Hork-Source/blob/97c3630480983b20bd054e06ca6c26ae2e87095a/Hork/Image/ImageEncoders.cpp +// TODO: Do one for each thread +static MemoryArena generateMipDataMemoryArena; +static MemoryArena compressMipDataMemoryArena; + +void InitGenerateMipDataMemoryArena() +{ + if (generateMipDataMemoryArena.Storage == nullptr) + { + generateMipDataMemoryArena = SystemAllocateMemoryArena(512 * 1024 * 1024); + } + + SystemClearMemoryArena(generateMipDataMemoryArena); +} + +void InitCompressMipDataMemoryArena() +{ + if (compressMipDataMemoryArena.Storage == nullptr) + { + compressMipDataMemoryArena = SystemAllocateMemoryArena(512 * 1024 * 1024); + } + + SystemClearMemoryArena(compressMipDataMemoryArena); +} + +ElemToolsAPI ElemGenerateTextureMipDataResult ElemGenerateTextureMipData(ElemToolsGraphicsFormat format, const ElemTextureMipData* baseMip, const ElemGenerateTextureMipDataOptions* options) +{ + InitGenerateMipDataMemoryArena(); + + auto messages = SystemPushArray(generateMipDataMemoryArena, 1024); + auto messageCount = 0u; + auto hasErrors = false; + + ElemGenerateTextureMipDataOptions generateMipDataOptions = {}; + + if (options) + { + generateMipDataOptions = *options; + } + + auto pixelSize = 4; // TODO: Check format first + auto maxDimension = SystemMax(baseMip->Width, baseMip->Height); + auto mipCount = (uint32_t)floorf(log2f((float)maxDimension)) + 1; + + auto mipData = SystemPushArray(generateMipDataMemoryArena, mipCount); + auto baseMipCopy = SystemDuplicateBuffer(generateMipDataMemoryArena, ReadOnlySpan(baseMip->Data.Items, baseMip->Data.Length)); + + mipData[0] = + { + .Width = baseMip->Width, + .Height = baseMip->Height, + .Data = { .Items = baseMipCopy.Pointer, .Length = (uint32_t)baseMipCopy.Length } + }; + + for (uint32_t i = 1; i < mipCount; i++) + { + auto mipLevelData = &mipData[i]; + + mipLevelData->Width = SystemMax(1u, baseMip->Width >> i); + mipLevelData->Height = SystemMax(1u, baseMip->Height >> i); + + auto mipLevelPixels = SystemPushArray(generateMipDataMemoryArena, mipLevelData->Width * mipLevelData->Height * pixelSize); + + stbir_resize_uint8_srgb(baseMip->Data.Items, baseMip->Width, baseMip->Height, 0, + mipLevelPixels.Pointer, mipLevelData->Width, mipLevelData->Height, 0, + STBIR_RGBA); + + mipLevelData->Data = { .Items = mipLevelPixels.Pointer, .Length = (uint32_t)mipLevelPixels.Length }; + } + + return + { + .MipData = { .Items = mipData.Pointer, .Length = (uint32_t)mipData.Length }, + .Messages = { .Items = messages.Pointer, .Length = messageCount }, + .HasErrors = hasErrors + }; +} + +ElemToolsAPI ElemCompressTextureMipDataResult ElemCompressTextureMipData(ElemToolsGraphicsFormat format, const ElemTextureMipData* mipData, const ElemCompressTextureMipDataOptions* options) +{ + auto stackMemoryArena = SystemGetStackMemoryArena(); + InitCompressMipDataMemoryArena(); + + auto messages = SystemPushArray(compressMipDataMemoryArena, 1024); + auto messageCount = 0u; + auto hasErrors = false; + + if (format != ElemToolsGraphicsFormat_BC7) + { + return { .Messages = ConstructErrorMessageSpan(compressMipDataMemoryArena, "The format of the mip data should be BC7 for now."), .HasErrors = true }; + } + + ElemCompressTextureMipDataOptions compressMipDataOptions = {}; + + if (options) + { + compressMipDataOptions = *options; + } + + // TODO: Fow now we don't use the GPU + bc7enc_compress_block_init(); + + bc7enc_compress_block_params bc7Params; + bc7enc_compress_block_params_init(&bc7Params); + bc7Params.m_uber_level = 4; // TODO: Do a global quality level option + //bc7Params.m_uber_level = 0; // TODO: Do a global quality level option + + auto blockWidth = (mipData->Width + 3) / 4; + auto blockHeight = (mipData->Height + 3) / 4; + auto blockCount = blockWidth * blockHeight; + + auto compressedData = SystemPushArray(compressMipDataMemoryArena, blockCount * TEXTURE_BC_BLOCK_SIZE_IN_BYTES); + auto sourceBlockData = SystemPushArray(stackMemoryArena, 4 * 4 * 4); // TODO: DEFINE + + for (uint32_t by = 0; by < blockHeight; by++) + { + for (uint32_t bx = 0; bx < blockWidth; bx++) + { + auto destinationPointer = &compressedData[(by * blockWidth + bx) * TEXTURE_BC_BLOCK_SIZE_IN_BYTES]; + + for (uint32_t row = 0; row < 4; row++) + { + auto sy = by * 4 + row; + sy = SystemMax(0u, SystemMin(sy, mipData->Height - 1)); + + for (uint32_t col = 0; col < 4; col++) + { + uint32_t sx = bx * 4 + col; + sx = SystemMax(0u, SystemMin(sx, mipData->Width - 1)); + + int srcIndex = (sy * mipData->Width + sx) * 4; + int dstIndex = (row * 4 + col) * 4; + sourceBlockData[dstIndex + 0] = mipData->Data.Items[srcIndex + 0]; + sourceBlockData[dstIndex + 1] = mipData->Data.Items[srcIndex + 1]; + sourceBlockData[dstIndex + 2] = mipData->Data.Items[srcIndex + 2]; + sourceBlockData[dstIndex + 3] = mipData->Data.Items[srcIndex + 3]; + } + } + + bc7enc_compress_block(destinationPointer, sourceBlockData.Pointer, &bc7Params); + } + } + + return + { + .MipData = + { + .Width = mipData->Width, + .Height = mipData->Height, + .Data = { .Items = compressedData.Pointer, .Length = (uint32_t)compressedData.Length } + }, + .Messages = { .Items = messages.Pointer, .Length = messageCount }, + .HasErrors = hasErrors + }; +} diff --git a/src/ElementalTools/ToolsUtils.cpp b/src/ElementalTools/ToolsUtils.cpp new file mode 100644 index 00000000..d669b3a0 --- /dev/null +++ b/src/ElementalTools/ToolsUtils.cpp @@ -0,0 +1,410 @@ +#include "ToolsUtils.h" +#include "ElementalTools.h" +#include "SystemFunctions.h" +#include "SystemMemory.h" + +// TODO: Do one for each threads +static MemoryArena FileIOMemoryArena; + +ElemToolsDataSpan DefaultFileHandler(const char* path) +{ + if (SystemFileExists(path)) + { + auto data = SystemFileReadBytes(FileIOMemoryArena, path); + return { .Items = data.Pointer, .Length = (uint32_t)data.Length }; + } + + return {}; +} + +static ElemToolsLoadFileHandlerPtr loadFileHandlerPtr = DefaultFileHandler; + +ElemToolsAPI void ElemToolsConfigureFileIO(ElemToolsLoadFileHandlerPtr loadFileHandler) +{ + SystemAssert(loadFileHandler); + loadFileHandlerPtr = loadFileHandler; +} + +void InitStorageMemoryArena() +{ + if (FileIOMemoryArena.Storage == nullptr) + { + FileIOMemoryArena = SystemAllocateMemoryArena(2u * 1024u * 1024u * 1024u); + } +} + +ReadOnlySpan LoadFileData(const char* path) +{ + InitStorageMemoryArena(); + + auto data = loadFileHandlerPtr(path); + return ReadOnlySpan(data.Items, data.Length); +} + +void ResetLoadFileDataMemory() +{ + SystemClearMemoryArena(FileIOMemoryArena); +} + +void WriteToMessageList(ElemToolsMessageType type, const char* message, ToolsMessageList* messageList) +{ + messageList->Messages[messageList->MessageCount++] = + { + .Type = type, + .Message = message + }; + + if (type == ElemToolsMessageType_Error) + { + messageList->HasErrors = true; + } +} + +ElemToolsMessageSpan ConstructErrorMessageSpan(MemoryArena memoryArena, const char* errorMessage) +{ + auto messageItem = SystemPushStruct(memoryArena); + messageItem->Type = ElemToolsMessageType_Error; + messageItem->Message = errorMessage; + + return + { + .Items = messageItem, + .Length = 1 + }; +} + +void AddPointToBoundingBox(ElemToolsVector3 point, ElemToolsBoundingBox* boundingBox) +{ + float minX = (point.X < boundingBox->MinPoint.X) ? point.X : boundingBox->MinPoint.X; + float minY = (point.Y < boundingBox->MinPoint.Y) ? point.Y : boundingBox->MinPoint.Y; + float minZ = (point.Z < boundingBox->MinPoint.Z) ? point.Z : boundingBox->MinPoint.Z; + + float maxX = (point.X > boundingBox->MaxPoint.X) ? point.X : boundingBox->MaxPoint.X; + float maxY = (point.Y > boundingBox->MaxPoint.Y) ? point.Y : boundingBox->MaxPoint.Y; + float maxZ = (point.Z > boundingBox->MaxPoint.Z) ? point.Z : boundingBox->MaxPoint.Z; + + boundingBox->MinPoint = { minX, minY, minZ }; + boundingBox->MaxPoint = { maxX, maxY, maxZ }; +} + +void AddBoundingBoxToBoundingBox(const ElemToolsBoundingBox* additional, ElemToolsBoundingBox* boundingBox) +{ + float minX = SystemMin(boundingBox->MinPoint.X, additional->MinPoint.X); + float minY = SystemMin(boundingBox->MinPoint.Y, additional->MinPoint.Y); + float minZ = SystemMin(boundingBox->MinPoint.Z, additional->MinPoint.Z); + + float maxX = SystemMax(boundingBox->MaxPoint.X, additional->MaxPoint.X); + float maxY = SystemMax(boundingBox->MaxPoint.Y, additional->MaxPoint.Y); + float maxZ = SystemMax(boundingBox->MaxPoint.Z, additional->MaxPoint.Z); + + boundingBox->MinPoint = { minX, minY, minZ }; + boundingBox->MaxPoint = { maxX, maxY, maxZ }; +} + +ElemToolsVector3 GetBoundingBoxCenter(const ElemToolsBoundingBox* boundingBox) +{ + return + { + .X = (boundingBox->MinPoint.X + boundingBox->MaxPoint.X) * 0.5f, + .Y = (boundingBox->MinPoint.Y + boundingBox->MaxPoint.Y) * 0.5f, + .Z = (boundingBox->MinPoint.Z + boundingBox->MaxPoint.Z) * 0.5f + }; +} + +ElemToolsVector2 operator +(const ElemToolsVector2& v1, const ElemToolsVector2& v2) +{ + ElemToolsVector2 result; + + result.X = v1.X + v2.X; + result.Y = v1.Y + v2.Y; + + return result; +} + +ElemToolsVector2 operator -(const ElemToolsVector2& v1, const ElemToolsVector2& v2) +{ + ElemToolsVector2 result; + + result.X = v1.X - v2.X; + result.Y = v1.Y - v2.Y; + + return result; +} + +ElemToolsVector3 operator +(const ElemToolsVector3& v1, const ElemToolsVector3& v2) +{ + ElemToolsVector3 result; + + result.X = v1.X + v2.X; + result.Y = v1.Y + v2.Y; + result.Z = v1.Z + v2.Z; + + return result; +} + +ElemToolsVector3 operator -(const ElemToolsVector3& v1, const ElemToolsVector3& v2) +{ + ElemToolsVector3 result; + + result.X = v1.X - v2.X; + result.Y = v1.Y - v2.Y; + result.Z = v1.Z - v2.Z; + + return result; +} + +ElemToolsVector3 operator *(const ElemToolsVector3& v1, const ElemToolsVector3& v2) +{ + ElemToolsVector3 result; + + result.X = v1.X * v2.X; + result.Y = v1.Y * v2.Y; + result.Z = v1.Z * v2.Z; + + return result; +} + +ElemToolsVector3 operator *(const ElemToolsVector3& v, float scalar) +{ + ElemToolsVector3 result; + + result.X = v.X * scalar; + result.Y = v.Y * scalar; + result.Z = v.Z * scalar; + + return result; +} + +ElemToolsVector3 ElemToolsMulScalarV3(ElemToolsVector3 v, float scalar) +{ + ElemToolsVector3 result; + + result.X = v.X * scalar; + result.Y = v.Y * scalar; + result.Z = v.Z * scalar; + + return result; +} + +ElemToolsVector3 ElemToolsMulV3(ElemToolsVector3 v1, ElemToolsVector3 v2) +{ + ElemToolsVector3 result; + + result.X = v1.X * v2.X; + result.Y = v1.Y * v2.Y; + result.Z = v1.Z * v2.Z; + + return result; +} + +float ElemToolsDotProductV3(ElemToolsVector3 v1, ElemToolsVector3 v2) +{ + return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; +} + +float ElemToolsMagnitudeSquaredV3(ElemToolsVector3 v) +{ + return ElemToolsDotProductV3(v, v); +} + +float ElemToolsMagnitudeV3(ElemToolsVector3 v) +{ + return sqrtf(ElemToolsMagnitudeSquaredV3(v)); +} + +ElemToolsVector3 ElemToolsNormalizeV3(ElemToolsVector3 v) +{ + float magnitude = ElemToolsMagnitudeV3(v); + + if (magnitude > 0.0f) + { + ElemToolsVector3 result = ElemToolsMulScalarV3(v, (1.0f / ElemToolsMagnitudeV3(v))); + return result; + } + + return v; +} +// TODO: CLEANUP +ElemToolsVector3 ElemToolsCrossProductV3(ElemToolsVector3 v1, ElemToolsVector3 v2) +{ + ElemToolsVector3 result; + result.X = v1.Y * v2.Z - v1.Z * v2.Y; + result.Y = v1.Z * v2.X - v1.X * v2.Z; + result.Z = v1.X * v2.Y - v1.Y * v2.X; + return result; +} + +ElemToolsVector4 ElemToolsCreateQuaternion(ElemToolsVector3 v, float w) +{ + ElemToolsVector4 result; + + result.X = v.X * sinf(w * 0.5f); + result.Y = v.Y * sinf(w * 0.5f); + result.Z = v.Z * sinf(w * 0.5f); + result.W = cosf(w * 0.5f); + + return result; +} + +float ElemToolsDotProductQuat(ElemToolsVector4 v1, ElemToolsVector4 v2) +{ + return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; +} + +ElemToolsVector4 ElemToolsMulQuat(ElemToolsVector4 q1, ElemToolsVector4 q2) +{ + float x = q2.X * q1.W + q1.X * q2.W + ElemToolsCrossProductV3(q1.XYZ, q2.XYZ).X; + float y = q2.Y * q1.W + q1.Y * q2.W + ElemToolsCrossProductV3(q1.XYZ, q2.XYZ).Y; + float z = q2.Z * q1.W + q1.Z * q2.W + ElemToolsCrossProductV3(q1.XYZ, q2.XYZ).Z; + + float w = q1.W * q2.W - ElemToolsDotProductQuat(q1, q2); + + return (ElemToolsVector4) { .X = x, .Y = y, .Z = z, .W = w }; +} + +ElemToolsMatrix4x4 ElemToolsCreateIdentityMatrix() +{ + ElemToolsMatrix4x4 result; + + result.m[0][0] = 1.0f; + result.m[0][1] = 0.0f; + result.m[0][2] = 0.0f; + result.m[0][3] = 0.0f; + + result.m[1][0] = 0.0f; + result.m[1][1] = 1.0f; + result.m[1][2] = 0.0f; + result.m[1][3] = 0.0f; + + result.m[2][0] = 0.0f; + result.m[2][1] = 0.0f; + result.m[2][2] = 1.0f; + result.m[2][3] = 0.0f; + + result.m[3][0] = 0.0f; + result.m[3][1] = 0.0f; + result.m[3][2] = 0.0f; + result.m[3][3] = 1.0f; + + return result; +} + +ElemToolsMatrix4x4 ElemToolsCreateScaleMatrix(float scale) +{ + ElemToolsMatrix4x4 result; + + result.Rows[0] = {{ scale, 0.0f, 0.0f, 0.0f }}; + result.Rows[1] = {{ 0.0f, scale, 0.0f, 0.0f }}; + result.Rows[2] = {{ 0.0f, 0.0f, scale, 0.0f }}; + result.Rows[3] = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + + return result; +} + +ElemToolsMatrix4x4 ElemToolsCreateTranslationMatrix(ElemToolsVector3 translation) +{ + ElemToolsMatrix4x4 result; + + result.Rows[0] = {{ 1.0f, 0.0f, 0.0f, 0.0f }}; + result.Rows[1] = {{ 0.0f, 1.0f, 0.0f, 0.0f }}; + result.Rows[2] = {{ 0.0f, 0.0f, 1.0f, 0.0f }}; + result.Rows[3] = {{ translation.X, translation.Y, translation.Z, 1.0f }}; + + return result; +} + +ElemToolsMatrix4x4 ElemToolsMulMatrix4x4(ElemToolsMatrix4x4 a, ElemToolsMatrix4x4 b) +{ + ElemToolsMatrix4x4 result; + + for (uint32_t i = 0; i < 4; i++) + { + for (uint32_t j = 0; j < 4; j++) + { + result.m[i][j] = a.m[i][0] * b.m[0][j] + a.m[i][1] * b.m[1][j] + a.m[i][2] * b.m[2][j] + a.m[i][3] * b.m[3][j]; + } + } + + return result; +} + +ElemToolsMatrix4x4 ElemToolsCreateRotationMatrix(ElemToolsVector4 quaternion) +{ + float xw = quaternion.X *quaternion.W , xx = quaternion.X *quaternion.X , yy = quaternion.Y *quaternion.Y , + yw = quaternion.Y *quaternion.W , xy = quaternion.X *quaternion.Y , yz = quaternion.Y *quaternion.Z , + zw = quaternion.Z *quaternion.W , xz = quaternion.X *quaternion.Z , zz = quaternion.Z *quaternion.Z ; + + ElemToolsMatrix4x4 result; + + result.Rows[0] = (ElemToolsVector4) { .X = 1-2*(yy+zz), .Y = 2*(xy+zw), .Z = 2*(xz-yw), .W = 0.0f }; + result.Rows[1] = (ElemToolsVector4) { .X = 2*(xy-zw), .Y = 1-2*(xx+zz), .Z = 2*(yz+xw), .W = 0.0f }; + result.Rows[2] = (ElemToolsVector4) { .X = 2*(xz+yw), .Y = 2*(yz-xw), .Z = 1-2*(xx+yy), .W = 0.0f }; + result.Rows[3] = (ElemToolsVector4) { .X = 0.0f, .Y = 0.0f, .Z = 0.0f, .W = 1.0f }; + + return result; +} + +ElemToolsVector3 ElemToolsTransformPointV3(ElemToolsVector3 point, ElemToolsMatrix4x4 m) +{ + ElemToolsVector3 result; + + result.X = m.m[0][0] * point.X + m.m[0][1] * point.Y + m.m[0][2] * point.Z + m.m[0][3]; + result.Y = m.m[1][0] * point.X + m.m[1][1] * point.Y + m.m[1][2] * point.Z + m.m[1][3]; + result.Z = m.m[2][0] * point.X + m.m[2][1] * point.Y + m.m[2][2] * point.Z + m.m[2][3]; + + return result; +} + +void DecomposeTransform(ElemToolsMatrix4x4 transform, float* scale, ElemToolsVector4* rotationQuaternion, ElemToolsVector3* translation) +{ + translation->X = transform.Rows[3].X; + translation->Y = transform.Rows[3].Y; + translation->Z = transform.Rows[3].Z; + + // compute determinant to determine handedness + float det = + transform.m[0][0] * (transform.m[1][1] * transform.m[2][2] - transform.m[2][1] * transform.m[1][2]) - + transform.m[0][1] * (transform.m[1][0] * transform.m[2][2] - transform.m[1][2] * transform.m[2][0]) + + transform.m[0][2] * (transform.m[1][0] * transform.m[2][1] - transform.m[1][1] * transform.m[2][0]); + + float sign = (det < 0.f) ? -1.f : 1.f; + + // recover scale from axis lengths + float scales[3]; + + scales[0] = sqrtf(transform.m[0][0] * transform.m[0][0] + transform.m[0][1] * transform.m[0][1] + transform.m[0][2] * transform.m[0][2]) * sign; + scales[1] = sqrtf(transform.m[1][0] * transform.m[1][0] + transform.m[1][1] * transform.m[1][1] + transform.m[1][2] * transform.m[1][2]) * sign; + scales[2] = sqrtf(transform.m[2][0] * transform.m[2][0] + transform.m[2][1] * transform.m[2][1] + transform.m[2][2] * transform.m[2][2]) * sign; + + *scale = scales[0]; + + // normalize axes to get a pure rotation matrix + float rsx = (scales[0] == 0.f) ? 0.f : 1.f / scales[0]; + float rsy = (scales[1] == 0.f) ? 0.f : 1.f / scales[1]; + float rsz = (scales[2] == 0.f) ? 0.f : 1.f / scales[2]; + + float r00 = transform.m[0][0] * rsx, r10 = transform.m[1][0] * rsy, r20 = transform.m[2][0] * rsz; + float r01 = transform.m[0][1] * rsx, r11 = transform.m[1][1] * rsy, r21 = transform.m[2][1] * rsz; + float r02 = transform.m[0][2] * rsx, r12 = transform.m[1][2] * rsy, r22 = transform.m[2][2] * rsz; + + // "branchless" version of Mike Day's matrix to quaternion conversion + int qc = r22 < 0 ? (r00 > r11 ? 0 : 1) : (r00 < -r11 ? 2 : 3); + float qs1 = qc & 2 ? -1.f : 1.f; + float qs2 = qc & 1 ? -1.f : 1.f; + float qs3 = (qc - 1) & 2 ? -1.f : 1.f; + + float qt = 1.f - qs3 * r00 - qs2 * r11 - qs1 * r22; + float qs = 0.5f / sqrtf(qt); + + float* rotation = (float*)rotationQuaternion; + + rotation[qc ^ 0] = qs * qt; + rotation[qc ^ 1] = qs * (r01 + qs1 * r10); + rotation[qc ^ 2] = qs * (r20 + qs2 * r02); + rotation[qc ^ 3] = qs * (r12 + qs3 * r21); + + rotationQuaternion->X = rotation[0]; + rotationQuaternion->Y = rotation[1]; + rotationQuaternion->Z = rotation[2]; + rotationQuaternion->W = rotation[3]; +} diff --git a/src/ElementalTools/ToolsUtils.h b/src/ElementalTools/ToolsUtils.h new file mode 100644 index 00000000..a52fd907 --- /dev/null +++ b/src/ElementalTools/ToolsUtils.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ElementalTools.h" +#include "SystemMemory.h" +#include "SystemSpan.h" + +struct ToolsMessageList +{ + Span Messages; + uint32_t MessageCount; + bool HasErrors; +}; + +ReadOnlySpan LoadFileData(const char* path); +void ResetLoadFileDataMemory(); + +void WriteToMessageList(ElemToolsMessageType type, const char* message, ToolsMessageList* messageList); + +ElemToolsMessageSpan ConstructErrorMessageSpan(MemoryArena memoryArena, const char* errorMessage); + +void AddPointToBoundingBox(ElemToolsVector3 point, ElemToolsBoundingBox* boundingBox); +void AddBoundingBoxToBoundingBox(const ElemToolsBoundingBox* additional, ElemToolsBoundingBox* boundingBox); +ElemToolsVector3 GetBoundingBoxCenter(const ElemToolsBoundingBox* boundingBox); + +ElemToolsVector3 ElemToolsMulScalarV3(ElemToolsVector3 v, float scalar); +ElemToolsVector3 ElemToolsMulV3(ElemToolsVector3 v1, ElemToolsVector3 v2); +float ElemToolsDotProductV3(ElemToolsVector3 v1, ElemToolsVector3 v2); +float ElemToolsMagnitudeSquaredV3(ElemToolsVector3 v); +float ElemToolsMagnitudeV3(ElemToolsVector3 v); +ElemToolsVector3 ElemToolsNormalizeV3(ElemToolsVector3 v); +ElemToolsVector3 ElemToolsCrossProductV3(ElemToolsVector3 v1, ElemToolsVector3 v2); + +ElemToolsVector2 operator +(const ElemToolsVector2& v1, const ElemToolsVector2& v2); +ElemToolsVector2 operator -(const ElemToolsVector2& v1, const ElemToolsVector2& v2); + +ElemToolsVector3 operator +(const ElemToolsVector3& v1, const ElemToolsVector3& v2); +ElemToolsVector3 operator -(const ElemToolsVector3& v1, const ElemToolsVector3& v2); +ElemToolsVector3 operator *(const ElemToolsVector3& v1, const ElemToolsVector3& v2); +ElemToolsVector3 operator *(const ElemToolsVector3& v1, float scalar); + +ElemToolsVector4 ElemToolsCreateQuaternion(ElemToolsVector3 v, float w); +ElemToolsVector4 ElemToolsMulQuat(ElemToolsVector4 q1, ElemToolsVector4 q2); +ElemToolsMatrix4x4 ElemToolsCreateRotationMatrix(ElemToolsVector4 quaternion); + +ElemToolsMatrix4x4 ElemToolsCreateIdentityMatrix(); +ElemToolsMatrix4x4 ElemToolsCreateScaleMatrix(float scale); +ElemToolsMatrix4x4 ElemToolsCreateTranslationMatrix(ElemToolsVector3 translation); +ElemToolsMatrix4x4 ElemToolsMulMatrix4x4(ElemToolsMatrix4x4 a, ElemToolsMatrix4x4 b); + +void DecomposeTransform(ElemToolsMatrix4x4 transform, float* scale, ElemToolsVector4* rotationQuaternion, ElemToolsVector3* translation); diff --git a/src/ElementalTools/UnityBuild.cpp b/src/ElementalTools/UnityBuild.cpp new file mode 100644 index 00000000..a93bc425 --- /dev/null +++ b/src/ElementalTools/UnityBuild.cpp @@ -0,0 +1,49 @@ +#include "ToolsUtils.cpp" +#include "Shaders/ShaderCompiler.cpp" +#include "Shaders/ShaderCompilerUtils.cpp" +#include "Shaders/DirectXShaderCompiler.cpp" + +#ifndef __linux__ +#include "Shaders/MetalShaderConverter.cpp" +#endif + +#include "SceneLoading/TangentSpaceGenerator.cpp" + +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" +#include "fast_obj.c" +#include "SceneLoading/SceneLoader.cpp" + +#include "Meshes/MeshletBuilder.cpp" + +#ifdef _WIN32 +#define STBI_WINDOWS_UTF8 +#endif + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc99-extensions" +#pragma clang diagnostic ignored "-Wmacro-redefined" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize2.h" +#pragma clang diagnostic pop + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-but-set-variable" +#include "mikktspace.c" +#pragma clang diagnostic pop + +#include "bc7enc.cpp" + +#include "Textures/TextureLoader.cpp" +#include "Textures/TextureProcessing.cpp" + +#include "SystemFunctions.cpp" +#include "SystemDictionary.cpp" +#include "SystemMemory.cpp" +#include "SystemPlatformFunctions.cpp" + +#ifndef _WIN32 +#include "PosixPlatformFunctions.cpp" +#endif diff --git a/tests/GraphicsTests/CMakeLists.txt b/tests/GraphicsTests/CMakeLists.txt index 6133a8f2..a02bdb6d 100644 --- a/tests/GraphicsTests/CMakeLists.txt +++ b/tests/GraphicsTests/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(GraphicsTests UnityBuild.cpp Shaders) +add_executable(GraphicsTests UnityBuild.cpp Data) target_link_libraries(GraphicsTests PRIVATE utest) target_link_libraries(GraphicsTests PRIVATE ElementalInterface) diff --git a/tests/GraphicsTests/CommandListTests.cpp b/tests/GraphicsTests/CommandListTests.cpp index 52e719f4..c8342e90 100644 --- a/tests/GraphicsTests/CommandListTests.cpp +++ b/tests/GraphicsTests/CommandListTests.cpp @@ -122,7 +122,7 @@ UTEST(CommandList, ExecuteCommandListWaitForFence) // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readBufferDataPipelineState); ElemFreePipelineState(writeBufferDataPipelineState); diff --git a/tests/GraphicsTests/Data/Assert.hlsl b/tests/GraphicsTests/Data/Assert.hlsl new file mode 100644 index 00000000..67ddadf3 --- /dev/null +++ b/tests/GraphicsTests/Data/Assert.hlsl @@ -0,0 +1,45 @@ +struct Parameters +{ + uint SourceBufferIndex; + uint DestinationBufferIndex; + uint MipLevel; +}; + +[[vk::push_constant]] +Parameters parameters : register(b0); + +[shader("compute")] +[numthreads(8, 8, 1)] +void CopyTexture(uint2 threadId: SV_DispatchThreadID) +{ + Texture2D sourceTexture = ResourceDescriptorHeap[parameters.SourceBufferIndex]; + RWStructuredBuffer destinationBuffer = ResourceDescriptorHeap[parameters.DestinationBufferIndex]; + + float width; + float height; + float mipLevelCount; + sourceTexture.GetDimensions(parameters.MipLevel, width, height, mipLevelCount); + + if (threadId.x < width && threadId.y < height) + { + destinationBuffer[threadId.y * width + threadId.x] = sourceTexture.Load(uint3(threadId, parameters.MipLevel)); + } +} + +[shader("compute")] +[numthreads(8, 8, 1)] +void CopyTextureFloat(uint2 threadId: SV_DispatchThreadID) +{ + Texture2D sourceTexture = ResourceDescriptorHeap[parameters.SourceBufferIndex]; + RWStructuredBuffer destinationBuffer = ResourceDescriptorHeap[parameters.DestinationBufferIndex]; + + float width; + float height; + float mipLevelCount; + sourceTexture.GetDimensions(parameters.MipLevel, width, height, mipLevelCount); + + if (threadId.x < width && threadId.y < height) + { + destinationBuffer[threadId.y * width + threadId.x] = sourceTexture.Load(uint3(threadId, parameters.MipLevel)); + } +} diff --git a/tests/GraphicsTests/Shaders/CommandListTests.hlsl b/tests/GraphicsTests/Data/CommandListTests.hlsl similarity index 100% rename from tests/GraphicsTests/Shaders/CommandListTests.hlsl rename to tests/GraphicsTests/Data/CommandListTests.hlsl diff --git a/tests/GraphicsTests/Data/RenderingTests.hlsl b/tests/GraphicsTests/Data/RenderingTests.hlsl new file mode 100644 index 00000000..558827ce --- /dev/null +++ b/tests/GraphicsTests/Data/RenderingTests.hlsl @@ -0,0 +1,67 @@ +struct TestParameters +{ + uint ShouldDraw; +}; + +[[vk::push_constant]] +TestParameters parameters : register(b0); + +struct VertexOutput +{ + float4 Position: SV_Position; +}; + +static float3 rectangleVertices[] = +{ + float3(-1.0, 1.0, 0.0), + float3(1.0, 1.0, 0.0), + float3(-1.0, -1.0, 0.0), + float3(1.0, -1.0, 0.0) +}; + +static uint3 rectanglePrimitives[] = +{ + uint3(0, 1, 2), + uint3(2, 1, 3) +}; + +struct AmplificationPayload +{ + float Test; +}; + +groupshared AmplificationPayload sharedPayload; + +[shader("amplification")] +[numthreads(1, 1, 1)] +void AmplificationShader(in uint3 groupID : SV_GroupID) +{ + DispatchMesh(parameters.ShouldDraw, 1, 1, sharedPayload); +} + +[shader("mesh")] +[OutputTopology("triangle")] +[NumThreads(4, 1, 1)] +void MeshShader(in uint groupThreadId : SV_GroupThreadID, in payload AmplificationPayload payload, out vertices VertexOutput vertices[4], out indices uint3 indices[2]) +{ + const uint meshVertexCount = 4; + const uint meshPrimitiveCount = 2; + + SetMeshOutputCounts(meshVertexCount, meshPrimitiveCount); + + if (groupThreadId < meshVertexCount) + { + vertices[groupThreadId].Position = float4(rectangleVertices[groupThreadId], 1); + } + + if (groupThreadId < meshPrimitiveCount) + { + indices[groupThreadId] = rectanglePrimitives[groupThreadId]; + } +} + +[shader("pixel")] +float4 PixelShader(const VertexOutput input) : SV_Target0 +{ + return float4(1.0, 1.0, 0.0, 1.0); +} diff --git a/tests/GraphicsTests/Shaders/ResourceBarrierTests.hlsl b/tests/GraphicsTests/Data/ResourceBarrierTests.hlsl similarity index 100% rename from tests/GraphicsTests/Shaders/ResourceBarrierTests.hlsl rename to tests/GraphicsTests/Data/ResourceBarrierTests.hlsl diff --git a/tests/GraphicsTests/Shaders/RenderingTests.hlsl b/tests/GraphicsTests/Data/ShaderTests.hlsl similarity index 62% rename from tests/GraphicsTests/Shaders/RenderingTests.hlsl rename to tests/GraphicsTests/Data/ShaderTests.hlsl index 56197453..8f01a2dd 100644 --- a/tests/GraphicsTests/Shaders/RenderingTests.hlsl +++ b/tests/GraphicsTests/Data/ShaderTests.hlsl @@ -1,21 +1,20 @@ -// TODO: Move copy texture to assert.hlsl and rename -struct Parameters +struct TestParameters { - uint SourceBufferIndex; - uint DestinationBufferIndex; + uint TestDescriptor; + float Depth; + uint2 Reserved; + float4 Color; }; [[vk::push_constant]] -Parameters parameters : register(b0); +TestParameters parameters : register(b0); [shader("compute")] -[numthreads(16, 16, 1)] -void CopyTexture(uint2 threadId: SV_DispatchThreadID) +[numthreads(16, 1, 1)] +void TestCompute(uint2 threadId: SV_DispatchThreadID) { - Texture2D sourceTexture = ResourceDescriptorHeap[parameters.SourceBufferIndex]; - RWStructuredBuffer destinationBuffer = ResourceDescriptorHeap[parameters.DestinationBufferIndex]; - - destinationBuffer[threadId.y * 16 + threadId.x] = sourceTexture.Load(uint3(threadId, 0)); + RWStructuredBuffer testBuffer = ResourceDescriptorHeap[parameters.TestDescriptor]; + testBuffer[threadId.x] = threadId.x; } struct VertexOutput @@ -49,7 +48,7 @@ void MeshShader(in uint groupThreadId : SV_GroupThreadID, out vertices VertexOut if (groupThreadId < meshVertexCount) { - vertices[groupThreadId].Position = float4(rectangleVertices[groupThreadId], 1); + vertices[groupThreadId].Position = float4(rectangleVertices[groupThreadId].xy, parameters.Depth, 1); } if (groupThreadId < meshPrimitiveCount) @@ -61,5 +60,5 @@ void MeshShader(in uint groupThreadId : SV_GroupThreadID, out vertices VertexOut [shader("pixel")] float4 PixelShader(const VertexOutput input) : SV_Target0 { - return float4(1.0, 1.0, 0.0, 1.0); + return float4(parameters.Color.rgba); } diff --git a/tests/GraphicsTests/GraphicsTests.cpp b/tests/GraphicsTests/GraphicsTests.cpp index 0724bccb..47fa9bef 100644 --- a/tests/GraphicsTests/GraphicsTests.cpp +++ b/tests/GraphicsTests/GraphicsTests.cpp @@ -6,27 +6,49 @@ bool testPrintLogs = true; bool testForceVulkanApi = false; bool workingTestHasLogErrors = false; -char workingTestErrorLogs[TESTLOG_LENGTH]; +char* workingTestErrorLogs; uint32_t currentTestErrorLogsIndex; -char testDebugLogs[TESTLOG_LENGTH]; +char* testDebugLogs; uint32_t currentTestDebugLogsIndex = 0; bool testHasLogErrors = false; -char testErrorLogs[TESTLOG_LENGTH]; +char* testErrorLogs; + +ElemGraphicsOptions startOptions; + +void InitLogBuffers() +{ + workingTestErrorLogs = (char*)malloc(TESTLOG_LENGTH); + testDebugLogs = (char*)malloc(TESTLOG_LENGTH); + testErrorLogs = (char*)malloc(TESTLOG_LENGTH); +} + +void EnableBarriersLog() +{ + ElemGraphicsOptions options = startOptions; + options.EnableDebugBarrierInfo = true; + + ElemSetGraphicsOptions(&options); +} + +void DisableBarriersLog() +{ + ElemGraphicsOptions options = startOptions; + options.EnableDebugBarrierInfo = false; + + ElemSetGraphicsOptions(&options); +} uint64_t TestMegaBytesToBytes(uint64_t value) { return value * 1024 * 1024; } +// TODO: Remove this function void CopyString(char* destination, uint32_t destinationLength, const char* source, uint32_t sourceLength) { - #ifdef _WIN32 - strncpy_s(destination, destinationLength, source, sourceLength); - #else strncpy(destination, source, sourceLength); - #endif } void GetFullPath(char* destination, const char* path) @@ -52,7 +74,6 @@ void GetFullPath(char* destination, const char* path) CopyString(pointer, pointer - destination, folderPrefix, strlen(folderPrefix)); pointer = pointer + strlen(folderPrefix); - CopyString(pointer, pointer - destination, path, strlen(path)); } @@ -163,7 +184,7 @@ const char* ReplaceShaderFilenameForVulkan(const char* original) } // Copy the base part - strncpy(newString, original, baseLength); + CopyString(newString, newLength, original, baseLength); newString[baseLength] = '\0'; // Null-terminate the base part // Append the "_vulkan" string @@ -238,13 +259,16 @@ void TestLogHandler(ElemLogMessageType messageType, ElemLogMessageCategory categ } else if (messageType == ElemLogMessageType_Debug) { - char tmpMessage[TESTLOG_LENGTH]; + char* tmpMessage = (char*)malloc(TESTLOG_LENGTH + 1); snprintf(tmpMessage, TESTLOG_LENGTH, "%s\n", message); auto tmpMessageLength = strlen(tmpMessage); char* logCopyDestination = testDebugLogs + currentTestDebugLogsIndex; + //strncpy(logCopyDestination, tmpMessage, tmpMessageLength + 1); CopyString(logCopyDestination, TESTLOG_LENGTH - currentTestDebugLogsIndex, tmpMessage, tmpMessageLength + 1); currentTestDebugLogsIndex += tmpMessageLength; + + free(tmpMessage); } } @@ -299,17 +323,15 @@ ElemPipelineState TestOpenComputeShader(ElemGraphicsDevice graphicsDevice, const return pipelineState; } -ElemPipelineState TestOpenMeshShader(ElemGraphicsDevice graphicsDevice, const char* shader, const char* meshShaderFunction, const char* pixelShaderFunction, ElemGraphicsFormat renderTargetFormat) +ElemPipelineState TestOpenMeshShader(ElemGraphicsDevice graphicsDevice, const char* shader, const char* meshShaderFunction, const char* pixelShaderFunction, const ElemGraphicsPipelineStateParameters* baseParameters) { auto shaderLibrary = TestOpenShader(graphicsDevice, shader); - ElemGraphicsPipelineStateParameters pipelineStateParameters = - { - .ShaderLibrary = shaderLibrary, - .MeshShaderFunction = meshShaderFunction, - .PixelShaderFunction = pixelShaderFunction, - .TextureFormats = { .Items = &renderTargetFormat, .Length = 1 } - }; + ElemGraphicsPipelineStateParameters pipelineStateParameters = *baseParameters; + + pipelineStateParameters.ShaderLibrary = shaderLibrary; + pipelineStateParameters.MeshShaderFunction = meshShaderFunction; + pipelineStateParameters.PixelShaderFunction = pixelShaderFunction; auto pipelineState = ElemCompileGraphicsPipelineState(graphicsDevice, &pipelineStateParameters); @@ -329,9 +351,9 @@ TestGpuBuffer TestCreateGpuBuffer(ElemGraphicsDevice graphicsDevice, uint32_t si .HeapType = heapType }; - auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, sizeInBytes, &heapOptions); - auto gpuBufferInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, sizeInBytes, ElemGraphicsResourceUsage_Write, nullptr); + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, gpuBufferInfo.SizeInBytes, &heapOptions); auto gpuBuffer = ElemCreateGraphicsResource(graphicsHeap, 0, &gpuBufferInfo); auto gpuBufferReadDescriptor = ElemCreateGraphicsResourceDescriptor(gpuBuffer, ElemGraphicsResourceDescriptorUsage_Read, nullptr); auto gpuBufferWriteDescriptor = ElemCreateGraphicsResourceDescriptor(gpuBuffer, ElemGraphicsResourceDescriptorUsage_Write, nullptr); @@ -353,13 +375,24 @@ void TestFreeGpuBuffer(TestGpuBuffer gpuBuffer) ElemFreeGraphicsHeap(gpuBuffer.GraphicsHeap); } -TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, ElemGraphicsFormat format) +TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, ElemGraphicsFormat format, ElemGraphicsResourceUsage usage) { - auto textureInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, width, height, 1, format, (ElemGraphicsResourceUsage)(ElemGraphicsResourceUsage_Write | ElemGraphicsResourceUsage_RenderTarget), nullptr); + return TestCreateGpuTexture(graphicsDevice, width, height, 1, format, usage); +} + +TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, uint32_t mipLevels, ElemGraphicsFormat format, ElemGraphicsResourceUsage usage) +{ + auto textureInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, width, height, mipLevels, format, usage, nullptr); auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, textureInfo.SizeInBytes, nullptr); auto texture = ElemCreateGraphicsResource(graphicsHeap, 0, &textureInfo); auto textureReadDescriptor = ElemCreateGraphicsResourceDescriptor(texture, ElemGraphicsResourceDescriptorUsage_Read, nullptr); - auto textureWriteDescriptor = ElemCreateGraphicsResourceDescriptor(texture, ElemGraphicsResourceDescriptorUsage_Write, nullptr); + + auto textureWriteDescriptor = -1; + + if (usage & ElemGraphicsResourceUsage_Write) + { + textureWriteDescriptor = ElemCreateGraphicsResourceDescriptor(texture, ElemGraphicsResourceDescriptorUsage_Write, nullptr); + } return { @@ -374,7 +407,12 @@ TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t void TestFreeGpuTexture(TestGpuTexture texture) { ElemFreeGraphicsResourceDescriptor(texture.ReadDescriptor, nullptr); - ElemFreeGraphicsResourceDescriptor(texture.WriteDescriptor, nullptr); + + if (texture.WriteDescriptor != -1) + { + ElemFreeGraphicsResourceDescriptor(texture.WriteDescriptor, nullptr); + } + ElemFreeGraphicsResource(texture.Texture, nullptr); ElemFreeGraphicsHeap(texture.GraphicsHeap); } @@ -389,7 +427,7 @@ void TestDispatchCompute(ElemCommandList commandList, ElemPipelineState pipeline } template -void TestDispatchComputeForReadbackBuffer(ElemGraphicsDevice graphicsDevice, ElemCommandQueue commandQueue, const char* shaderName, const char* function, uint32_t threadGroupSizeX, uint32_t threadGroupSizeY, uint32_t threadGroupSizeZ, const T* parameters) +void TestDispatchComputeForShader(ElemGraphicsDevice graphicsDevice, ElemCommandQueue commandQueue, const char* shaderName, const char* function, uint32_t threadGroupSizeX, uint32_t threadGroupSizeY, uint32_t threadGroupSizeZ, const T* parameters) { auto pipelineState = TestOpenComputeShader(graphicsDevice, shaderName, function); @@ -407,28 +445,6 @@ void TestDispatchComputeForReadbackBuffer(ElemGraphicsDevice graphicsDevice, Ele ElemFreePipelineState(pipelineState); } -void TestBeginClearRenderPass(ElemCommandList commandList, ElemGraphicsResource renderTarget, ElemColor clearColor) -{ - ElemRenderPassRenderTarget renderPassRenderTarget = - { - .RenderTarget = renderTarget, - .ClearColor = clearColor, - .LoadAction = ElemRenderPassLoadAction_Clear - }; - - ElemBeginRenderPassParameters parameters = - { - .RenderTargets = - { - .Items = &renderPassRenderTarget, - .Length = 1 - } - }; - - // Act - ElemBeginRenderPass(commandList, ¶meters); -} - void TestBarrierCheckSyncTypeToString(char* destination, ElemGraphicsResourceBarrierSyncType syncType) { switch (syncType) diff --git a/tests/GraphicsTests/GraphicsTests.h b/tests/GraphicsTests/GraphicsTests.h index 1c22e2bf..4d47d221 100644 --- a/tests/GraphicsTests/GraphicsTests.h +++ b/tests/GraphicsTests/GraphicsTests.h @@ -1,19 +1,80 @@ #pragma once #include "Elemental.h" +#include #include -#define TESTLOG_LENGTH 4096 +#define TESTLOG_LENGTH 4 * 1024 * 1024 #ifndef _WIN32 #define MAX_PATH 255 #define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a))) #endif +extern ElemGraphicsOptions startOptions; + +void InitLogBuffers(); +void EnableBarriersLog(); +void DisableBarriersLog(); + +#define Max(a,b) ((a) > (b) ? (a) : (b)) +#define Min(a,b) ((a) < (b) ? (a) : (b)) + +#define PRINT_COLOR_BUFFER(buffer, totalWidth) \ + { \ + auto floatData = (float*)buffer.Items; \ + \ + for (uint32_t i = 0; i < bufferData.Length / sizeof(float); i += 4) \ + { \ + auto pixelX = (i / 4) % totalWidth;\ + auto pixelY = (i / 4) / totalWidth;\ + printf("Pixel %d [%d, %d]: %f %f %f %f\n", i / 4, pixelX, pixelY, floatData[i], floatData[i + 1], floatData[i + 2], floatData[i + 3]); \ + } \ + } + #define ASSERT_LOG_NOERROR() { TestInitLog(); ASSERT_FALSE_MSG(testHasLogErrors, testErrorLogs); } #define ASSERT_LOG_MESSAGE(message) { TestInitLog(); ASSERT_TRUE_MSG(strstr(testErrorLogs, message) != NULL, message); } #define ASSERT_LOG_MESSAGE_DEBUG(message) { TestInitLog(); ASSERT_TRUE_MSG(strstr(testDebugLogs, message) != NULL, message); } +#define ASSERT_COLOR_BUFFER(buffer, red, green, blue, alpha) \ + { \ + auto floatData = (float*)buffer.Items; \ + \ + for (uint32_t i = 0; i < bufferData.Length / sizeof(float); i += 4) \ + { \ + ASSERT_EQ_MSG(floatData[i], red, "Red channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 1], green, "Green channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 2], blue, "Blue channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 3], alpha, "Alpha channel data is invalid."); \ + } \ + } + +#define ASSERT_COLOR_BUFFER_RECTANGLE(buffer, totalWidth, x, y, width, height, red, green, blue, alpha, backgroundRed, backgroundGreen, backgroundBlue, backgroundAlpha) \ + { \ + auto floatData = (float*)buffer.Items;\ + \ + for (uint32_t i = 0; i < bufferData.Length / sizeof(float); i += 4) \ + { \ + auto pixelX = (i / 4) % totalWidth;\ + auto pixelY = (i / 4) / totalWidth;\ + \ + if (pixelX >= x && pixelX < (x + width) && pixelY >= y && pixelY < (y + height)) \ + { \ + ASSERT_EQ_MSG(floatData[i], red, "Red channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 1], green, "Green channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 2], blue, "Blue channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 3], alpha, "Alpha channel data is invalid."); \ + } \ + else \ + { \ + ASSERT_EQ_MSG(floatData[i], backgroundRed, "Background Red channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 1], backgroundGreen, "Background Green channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 2], backgroundBlue, "Background Blue channel data is invalid."); \ + ASSERT_EQ_MSG(floatData[i + 3], backgroundAlpha, "Background Alpha channel data is invalid."); \ + } \ + } \ + } + #define BUFFER_BARRIER(resource, syncBefore, syncAfter, accessBefore, accessAfter) \ { \ .Resource = resource, \ @@ -114,7 +175,7 @@ struct TestBarrierCheck extern bool testPrintLogs; extern bool testForceVulkanApi; extern bool testHasLogErrors; -extern char testErrorLogs[TESTLOG_LENGTH]; +extern char* testErrorLogs; extern uint32_t currentTestErrorLogsIndex; uint64_t TestMegaBytesToBytes(uint64_t value); @@ -128,21 +189,20 @@ void TestInitLog(); ElemShaderLibrary TestOpenShader(ElemGraphicsDevice graphicsDevice, const char* shader); ElemPipelineState TestOpenComputeShader(ElemGraphicsDevice graphicsDevice, const char* shader, const char* function); -ElemPipelineState TestOpenMeshShader(ElemGraphicsDevice graphicsDevice, const char* shader, const char* meshShaderFunction, const char* pixelShaderFunction, ElemGraphicsFormat renderTargetFormat); +ElemPipelineState TestOpenMeshShader(ElemGraphicsDevice graphicsDevice, const char* shader, const char* meshShaderFunction, const char* pixelShaderFunction, const ElemGraphicsPipelineStateParameters* baseParameters); -TestGpuBuffer TestCreateGpuBuffer(ElemGraphicsDevice graphicsDevice, uint32_t sizeInBytes, ElemGraphicsHeapType heapType = ElemGraphicsHeapType_Gpu); +TestGpuBuffer TestCreateGpuBuffer(ElemGraphicsDevice graphicsDevice, uint32_t sizeInBytes, ElemGraphicsHeapType heapType = ElemGraphicsHeapType_GpuUpload); void TestFreeGpuBuffer(TestGpuBuffer gpuBuffer); -TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, ElemGraphicsFormat format); +TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, ElemGraphicsFormat format, ElemGraphicsResourceUsage usage); +TestGpuTexture TestCreateGpuTexture(ElemGraphicsDevice graphicsDevice, uint32_t width, uint32_t height, uint32_t mipLevels, ElemGraphicsFormat format, ElemGraphicsResourceUsage usage); void TestFreeGpuTexture(TestGpuTexture texture); template void TestDispatchCompute(ElemCommandList commandList, ElemPipelineState pipelineState, uint32_t threadGroupSizeX, uint32_t threadGroupSizeY, uint32_t threadGroupSizeZ, std::initializer_list parameters); template -void TestDispatchComputeForReadbackBuffer(ElemGraphicsDevice graphicsDevice, ElemCommandQueue commandQueue, const char* shaderName, const char* function, uint32_t threadGroupSizeX, uint32_t threadGroupSizeY, uint32_t threadGroupSizeZ, const T* parameters); - -void TestBeginClearRenderPass(ElemCommandList commandList, ElemGraphicsResource renderTarget, ElemColor clearColor); +void TestDispatchComputeForShader(ElemGraphicsDevice graphicsDevice, ElemCommandQueue commandQueue, const char* shaderName, const char* function, uint32_t threadGroupSizeX, uint32_t threadGroupSizeY, uint32_t threadGroupSizeZ, const T* parameters); void TestBarrierCheckSyncTypeToString(char* destination, ElemGraphicsResourceBarrierSyncType syncType); void TestBarrierCheckAccessTypeToString(char* destination, ElemGraphicsResourceBarrierAccessType accessType); diff --git a/tests/GraphicsTests/RenderingTests.cpp b/tests/GraphicsTests/RenderingTests.cpp index d939a807..8daa4eff 100644 --- a/tests/GraphicsTests/RenderingTests.cpp +++ b/tests/GraphicsTests/RenderingTests.cpp @@ -2,11 +2,11 @@ #include "GraphicsTests.h" #include "utest.h" -// TODO: Test Viewports -// TODO: Render on a render target texture +// TODO: Test Multiple render targets +// TODO: Test Multiple Viewports +// TODO: Test Multiple ScissorRectangles // TODO: Check command list type when dispatch mesh // TODO: Multiple config for rendering -// TODO: Test Barrier log UTEST(Rendering, RenderPassClearRenderTarget) { @@ -15,10 +15,27 @@ UTEST(Rendering, RenderPassClearRenderTarget) auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); auto commandList = ElemGetCommandList(commandQueue, nullptr); - auto renderTarget = TestCreateGpuTexture(graphicsDevice, 16, 16, ElemGraphicsFormat_R32G32B32A32_FLOAT); + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); // Act - TestBeginClearRenderPass(commandList, renderTarget.Texture, { .Red = 1.0f, .Green = 0.5f, .Blue = 0.25f, .Alpha = 0.95f }); + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 1.0f, .Green = 0.5f, .Blue = 0.25f, .Alpha = 0.95f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); ElemEndRenderPass(commandList); // Assert @@ -26,26 +43,66 @@ UTEST(Rendering, RenderPassClearRenderTarget) auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); ElemWaitForFenceOnCpu(fence); - auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, 16 * 16 * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; - TestDispatchComputeForReadbackBuffer(graphicsDevice, commandQueue, "RenderingTests.shader", "CopyTexture", 1, 1, 1, &resourceIdList); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); TestFreeGpuBuffer(readbackBuffer); TestFreeGpuTexture(renderTarget); ElemFreeCommandQueue(commandQueue); ElemFreeGraphicsDevice(graphicsDevice); + ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER(bufferData, 1.0f, 0.5f, 0.25f, 0.95f); +} + +UTEST(Rendering, RenderPassClearDepthBuffer) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto depthBuffer = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_D32_FLOAT, ElemGraphicsResourceUsage_DepthStencil); + + // Act + ElemBeginRenderPassParameters parameters = + { + .DepthStencil = + { + .DepthStencil = depthBuffer.Texture, + .DepthClearValue = 0.5f, + .DepthLoadAction = ElemRenderPassLoadAction_Clear + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)depthBuffer.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTextureFloat", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(depthBuffer); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + ASSERT_LOG_NOERROR(); auto floatData = (float*)bufferData.Items; - for (uint32_t i = 0; i < bufferData.Length / 4; i += 4) + for (uint32_t i = 0; i < bufferData.Length / sizeof(float); i++) { - ASSERT_EQ_MSG(floatData[i], 1.0f, "Red channel data is invalid."); - ASSERT_EQ_MSG(floatData[i + 1], 0.5f, "Green channel data is invalid."); - ASSERT_EQ_MSG(floatData[i + 2], 0.25f, "Blue channel data is invalid."); - ASSERT_EQ_MSG(floatData[i + 3], 0.95f, "Alpha channel data is invalid."); + ASSERT_EQ_MSG(floatData[i], 0.5f, "Depth data is invalid."); } } @@ -56,11 +113,35 @@ UTEST(Rendering, DispatchMesh) auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); auto commandList = ElemGetCommandList(commandQueue, nullptr); - auto renderTarget = TestCreateGpuTexture(graphicsDevice, 16, 16, ElemGraphicsFormat_R32G32B32A32_FLOAT); - auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "RenderingTests.shader", "MeshShader", "PixelShader", renderTarget.Format); + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget = { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 } + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "RenderingTests.shader", "MeshShader", "PixelShader", &psoParameters); // Act - TestBeginClearRenderPass(commandList, renderTarget.Texture, { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }); + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); ElemBindPipelineState(commandList, meshShaderPipeline); ElemDispatchMesh(commandList, 1, 1, 1); ElemEndRenderPass(commandList); @@ -70,10 +151,10 @@ UTEST(Rendering, DispatchMesh) auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); ElemWaitForFenceOnCpu(fence); - auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, 16 * 16 * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; - TestDispatchComputeForReadbackBuffer(graphicsDevice, commandQueue, "RenderingTests.shader", "CopyTexture", 1, 1, 1, &resourceIdList); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); TestFreeGpuBuffer(readbackBuffer); TestFreeGpuTexture(renderTarget); @@ -82,14 +163,301 @@ UTEST(Rendering, DispatchMesh) ElemFreeGraphicsDevice(graphicsDevice); ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER(bufferData, 1.0f, 1.0f, 0.0f, 1.0f); +} - auto floatData = (float*)bufferData.Items; +UTEST(Rendering, BeginRenderPass_SetViewport) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); - for (uint32_t i = 0; i < bufferData.Length / 4; i += 4) + ElemGraphicsPipelineStateRenderTarget psoRenderTarget = { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = { - ASSERT_EQ_MSG(floatData[i], 1.0f, "Red channel data is invalid."); - ASSERT_EQ_MSG(floatData[i + 1], 1.0f, "Green channel data is invalid."); - ASSERT_EQ_MSG(floatData[i + 2], 0.0f, "Blue channel data is invalid."); - ASSERT_EQ_MSG(floatData[i + 3], 1.0f, "Alpha channel data is invalid."); - } + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 } + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "RenderingTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemViewport renderPassViewport = + { + .X = 2, + .Y = 2, + .Width = 4, + .Height = 4 + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + }, + .Viewports = + { + .Items = &renderPassViewport, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + ElemDispatchMesh(commandList, 1, 1, 1); + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER_RECTANGLE(bufferData, textureSize, + 2, 2, 4, 4, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f); +} + +UTEST(Rendering, BeginRenderPass_SetScissorRectangle) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget = { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 } + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "RenderingTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemRectangle renderPassScissorRectangle = + { + .X = 2, + .Y = 2, + .Width = 4, + .Height = 4 + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + }, + .ScissorRectangles = + { + .Items = &renderPassScissorRectangle, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + ElemDispatchMesh(commandList, 1, 1, 1); + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER_RECTANGLE(bufferData, textureSize, + 2, 2, 4, 4, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f); +} + +UTEST(Rendering, SetViewport) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget = { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 } + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "RenderingTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemViewport viewport = + { + .X = 2, + .Y = 2, + .Width = 4, + .Height = 4 + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + ElemSetViewport(commandList, &viewport); + ElemDispatchMesh(commandList, 1, 1, 1); + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER_RECTANGLE(bufferData, textureSize, + 2, 2, 4, 4, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f); +} + +UTEST(Rendering, SetScissorRectangle) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget = { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 } + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "RenderingTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemRectangle scissorRectangle = + { + .X = 2, + .Y = 2, + .Width = 4, + .Height = 4 + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + ElemSetScissorRectangle(commandList, &scissorRectangle); + ElemDispatchMesh(commandList, 1, 1, 1); + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER_RECTANGLE(bufferData, textureSize, + 2, 2, 4, 4, + 1.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, 1.0f); } diff --git a/tests/GraphicsTests/ResourceBarrierTests.cpp b/tests/GraphicsTests/ResourceBarrierTests.cpp index 9c77b06d..1b6029bf 100644 --- a/tests/GraphicsTests/ResourceBarrierTests.cpp +++ b/tests/GraphicsTests/ResourceBarrierTests.cpp @@ -48,7 +48,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_BufferReadAfterWrite) // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readBufferDataPipelineState); ElemFreePipelineState(writeBufferDataPipelineState); @@ -122,7 +122,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_BufferWriteAfterWrite) // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readBufferDataPipelineState); ElemFreePipelineState(writeAddBufferDataPipelineState); @@ -201,7 +201,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_DifferentBuffers) // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readBufferDataPipelineState); ElemFreePipelineState(writeBufferDataPipelineState); @@ -235,7 +235,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureReadAfterWrite) auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); - auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT); + auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT, (ElemGraphicsResourceUsage)(ElemGraphicsResourceUsage_RenderTarget | ElemGraphicsResourceUsage_Write)); auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, width * height * 4 * sizeof(float), ElemGraphicsHeapType_Readback); auto writeTextureDataPipelineState = TestOpenComputeShader(graphicsDevice, "ResourceBarrierTests.shader", "TestWriteTextureData"); @@ -269,7 +269,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureReadAfterWrite) // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readTextureDataPipelineState); ElemFreePipelineState(writeTextureDataPipelineState); @@ -309,12 +309,19 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureRenderTargetAfterWrite) auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); - auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT); + auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT, (ElemGraphicsResourceUsage)(ElemGraphicsResourceUsage_RenderTarget | ElemGraphicsResourceUsage_Write)); auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, width * height * 4 * sizeof(float), ElemGraphicsHeapType_Readback); auto writeTextureDataPipelineState = TestOpenComputeShader(graphicsDevice, "ResourceBarrierTests.shader", "TestWriteTextureData"); auto readTextureDataPipelineState = TestOpenComputeShader(graphicsDevice, "ResourceBarrierTests.shader", "TestReadTextureData"); - auto meshShaderPipelineState = TestOpenMeshShader(graphicsDevice, "ResourceBarrierTests.shader", "TestMeshShader", "TestPixelShader", gpuTexture.Format); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget { .Format = gpuTexture.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 } + }; + + auto meshShaderPipelineState = TestOpenMeshShader(graphicsDevice, "ResourceBarrierTests.shader", "TestMeshShader", "TestPixelShader", &psoParameters); // Act auto commandList = ElemGetCommandList(commandQueue, nullptr); @@ -329,7 +336,23 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureRenderTargetAfterWrite) ElemGraphicsResourceBarrierLayoutType_Undefined, ElemGraphicsResourceBarrierLayoutType_Write) )); - TestBeginClearRenderPass(commandList, gpuTexture.Texture, { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }); + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = gpuTexture.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); ElemBindPipelineState(commandList, meshShaderPipelineState); ElemDispatchMesh(commandList, 1, 1, 1); ElemEndRenderPass(commandList); @@ -356,7 +379,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureRenderTargetAfterWrite) // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readTextureDataPipelineState); ElemFreePipelineState(writeTextureDataPipelineState); @@ -387,6 +410,8 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureRenderTargetAfterWrite) } } +// TODO: DepthBuffer + UTEST(ResourceBarrier, GraphicsResourceBarrier_BufferReadAfterWriteWithCustomBeforeSyncAndAccess) { // Arrange @@ -518,7 +543,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureReadAfterWriteWithCustomBe auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); - auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT); + auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT, (ElemGraphicsResourceUsage)(ElemGraphicsResourceUsage_RenderTarget | ElemGraphicsResourceUsage_Write)); auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, width * height * 4 * sizeof(float), ElemGraphicsHeapType_Readback); auto writeTextureDataPipelineState = TestOpenComputeShader(graphicsDevice, "ResourceBarrierTests.shader", "TestWriteTextureData"); @@ -590,7 +615,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_TextureReadAfterWriteWithCustomAf auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); - auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT); + auto gpuTexture = TestCreateGpuTexture(graphicsDevice, width, height, ElemGraphicsFormat_R16G16B16A16_FLOAT, (ElemGraphicsResourceUsage)(ElemGraphicsResourceUsage_RenderTarget | ElemGraphicsResourceUsage_Write)); auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, width * height * 4 * sizeof(float), ElemGraphicsHeapType_Readback); auto writeTextureDataPipelineState = TestOpenComputeShader(graphicsDevice, "ResourceBarrierTests.shader", "TestWriteTextureData"); @@ -696,7 +721,7 @@ UTEST(ResourceBarrier, GraphicsResourceBarrier_BufferReadAfterWriteMultiCommandL // Assert ElemWaitForFenceOnCpu(fence); - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); ElemFreePipelineState(readBufferDataPipelineState); ElemFreePipelineState(writeBufferDataPipelineState); diff --git a/tests/GraphicsTests/ResourceIOTests.cpp b/tests/GraphicsTests/ResourceIOTests.cpp new file mode 100644 index 00000000..16d6a702 --- /dev/null +++ b/tests/GraphicsTests/ResourceIOTests.cpp @@ -0,0 +1,529 @@ +#include "Elemental.h" +#include "GraphicsTests.h" +#include "utest.h" + +ElemVector3 TestMipColors[] = +{ + { 1.0f, 0.0f, 0.0f }, + { 1.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 1.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, +}; + +// TODO: File source +// TODO: Big source (so that the upload heap can grow) +// TODO: Test buffer offset and length if too much + +UTEST(ResourceIO, UploadGraphicsBufferData_WithBuffer) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_GpuUpload + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + uint8_t data[] = { 1, 2, 3, 4 }; + + + // Act + ElemUploadGraphicsBufferData(resource, 0, { .Items = data, .Length = ARRAYSIZE(data) }); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); +} + +UTEST(ResourceIO, UploadGraphicsBufferData_WithNoGpuUploadHeap) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_Gpu + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + uint8_t data[] = { 1, 2, 3, 4 }; + + // Act + ElemUploadGraphicsBufferData(resource, 0, { .Items = data, .Length = ARRAYSIZE(data) }); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("UploadGraphicsBufferData only works with graphics buffers allocated in a GpuUpload heap."); +} + +UTEST(ResourceIO, UploadGraphicsBufferData_WithBufferOffsetSize) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_GpuUpload + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + uint8_t data[] = { 1, 2, 3, 4 }; + ElemUploadGraphicsBufferData(resource, 2, { .Items = data, .Length = 2 }); + + // Act + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, nullptr); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + ASSERT_TRUE_MSG(resourceDataSpan.Items != nullptr, "Resource dataspan pointer should not be null."); + ASSERT_EQ_MSG(resourceDataSpan.Items[0], 0, "Resource dataspan element 0 should be equal to 0."); + ASSERT_EQ_MSG(resourceDataSpan.Items[1], 0, "Resource dataspan element 1 should be equal to 0."); + ASSERT_EQ_MSG(resourceDataSpan.Items[2], data[0], "Resource dataspan element 2 should be equal to test data at offset 0."); + ASSERT_EQ_MSG(resourceDataSpan.Items[3], data[1], "Resource dataspan element 3 should be equal to test data at offset 1."); + ASSERT_EQ_MSG(resourceDataSpan.Items[4], 0, "Resource dataspan element 2 should be equal to 0."); +} + +UTEST(ResourceIO, UploadGraphicsBufferData_WithTexture2D) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + auto resourceInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, 256, 256, 1, ElemGraphicsFormat_B8G8R8A8_SRGB, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + uint8_t data[] = { 1, 2, 3, 4 }; + + // Act + ElemUploadGraphicsBufferData(resource, 0, { .Items = data, .Length = ARRAYSIZE(data) }); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("UploadGraphicsBufferData only works with graphics buffers."); +} + +UTEST(ResourceIO, DownloadGraphicsBufferData_WithBuffer) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_GpuUpload + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Act + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, nullptr); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + ASSERT_TRUE_MSG(resourceDataSpan.Items != nullptr, "Resource dataspan pointer should not be null."); + ASSERT_EQ_MSG(resourceDataSpan.Length, 1024u, "Resource dataspan length should be equals to buffer size."); +} + +UTEST(ResourceIO, DownloadGraphicsBufferData_WithNoReadbackHeap) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_Gpu + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Act + ElemDownloadGraphicsBufferData(resource, nullptr); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("DownloadGraphicsBufferData only works with graphics buffers allocated in a Readback heap or GpuUpload heap."); +} + +UTEST(ResourceIO, DownloadGraphicsBufferData_WithBufferOffsetAndSize) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_GpuUpload + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + uint8_t data[] = { 1, 2, 3, 4 }; + ElemUploadGraphicsBufferData(resource, 0, { .Items = data, .Length = ARRAYSIZE(data) }); + + // Act + ElemDownloadGraphicsBufferDataOptions downloadOptions = + { + .Offset = 2, + .SizeInBytes = 2 + }; + + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, &downloadOptions); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + ASSERT_TRUE_MSG(resourceDataSpan.Items != nullptr, "Resource dataspan pointer should not be null."); + ASSERT_EQ_MSG(resourceDataSpan.Length, 2u, "Resource dataspan length should be equals to specified SizeInBytes."); + ASSERT_EQ_MSG(resourceDataSpan.Items[0], data[2], "Resource dataspan element 0 should be equal to test data at offset 2."); +} + +UTEST(ResourceIO, DownloadGraphicsBufferData_WithTexture2D) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + auto resourceInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, 256, 256, 1, ElemGraphicsFormat_B8G8R8A8_SRGB, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Act + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, nullptr); + + // Assert + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("ElemDownloadGraphicsBufferData only works with graphics buffers."); + + ASSERT_TRUE_MSG(resourceDataSpan.Items == nullptr, "Resource dataspan pointer should be null."); + ASSERT_EQ_MSG(resourceDataSpan.Length, 0u, "Resource dataspan length should be 0."); +} + +UTEST(ResourceIO, CopyDataToGraphicsResource_WithBuffer) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_GpuUpload + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + uint8_t data[] = { 1, 2, 3, 4 }; + + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + // Act + ElemCopyDataToGraphicsResourceParameters parameters = + { + .Resource = resource, + .BufferOffset = 2, + .SourceType = ElemCopyDataSourceType_Memory, + .SourceMemoryData = { .Items = data, .Length = 2 } + }; + + ElemCopyDataToGraphicsResource(commandList, ¶meters); + + // Assert + ElemCommitCommandList(commandList); + + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, nullptr); + + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + ASSERT_TRUE_MSG(resourceDataSpan.Items != nullptr, "Resource dataspan pointer should not be null."); + + ASSERT_EQ_MSG(resourceDataSpan.Items[0], 0, "Resource dataspan element 0 should be equal to 0."); + ASSERT_EQ_MSG(resourceDataSpan.Items[1], 0, "Resource dataspan element 1 should be equal to 0."); + ASSERT_EQ_MSG(resourceDataSpan.Items[2], data[0], "Resource dataspan element 2 should be equal to test data at offset 0."); + ASSERT_EQ_MSG(resourceDataSpan.Items[3], data[1], "Resource dataspan element 3 should be equal to test data at offset 1."); + ASSERT_EQ_MSG(resourceDataSpan.Items[4], 0, "Resource dataspan element 2 should be equal to 0."); +} + +// TODO: Do the same test but with the copy operations spread accross multiple frames +// TODO: Bigger item count to check the error +UTEST(ResourceIO, CopyDataToGraphicsResource_WithBufferMultiCopies) +{ + // Arrange + DisableBarriersLog(); + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_Readback + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(256), &options); + + auto itemCount = 20000u; + auto resourceSize = itemCount * sizeof(uint32_t); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, resourceSize, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + auto data = (uint32_t*)malloc(resourceSize); + + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + // Act + for (uint32_t i = 0; i < itemCount; i++) + { + for (uint32_t j = 0; j < itemCount; j++) + { + data[j] = i; + } + + ElemCopyDataToGraphicsResourceParameters parameters = + { + .Resource = resource, + .BufferOffset = (uint32_t)(i * sizeof(uint32_t)), + .SourceType = ElemCopyDataSourceType_Memory, + .SourceMemoryData = { .Items = (uint8_t*)data, .Length = sizeof(uint32_t) } + }; + + ElemCopyDataToGraphicsResource(commandList, ¶meters); + } + + // Assert + ElemCommitCommandList(commandList); + + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, nullptr); + + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + auto bufferValues = (uint32_t*)resourceDataSpan.Items; + + for (uint32_t i = 0; i < itemCount; i++) + { + ASSERT_EQ_MSG(bufferValues[i], i, "Resource dataspan element should be equal to initial data."); + } + + free(data); + EnableBarriersLog(); +} + +UTEST(ResourceIO, CopyDataToGraphicsResource_WithBufferMultiCopiesAndCommandLists) +{ + // Arrange + DisableBarriersLog(); + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + ElemGraphicsHeapOptions options = + { + .HeapType = ElemGraphicsHeapType_Readback + }; + + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(256), &options); + + auto itemCount = 50000u; + auto resourceSize = itemCount * sizeof(uint32_t); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, resourceSize, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + auto data = (uint32_t*)malloc(resourceSize); + + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + + // Act + ElemFence fence; + + for (uint32_t i = 0; i < itemCount; i++) + { + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + for (uint32_t j = 0; j < itemCount; j++) + { + data[j] = i; + } + + ElemCopyDataToGraphicsResourceParameters parameters = + { + .Resource = resource, + .BufferOffset = (uint32_t)(i * sizeof(uint32_t)), + .SourceType = ElemCopyDataSourceType_Memory, + .SourceMemoryData = { .Items = (uint8_t*)data, .Length = sizeof(uint32_t) } + }; + + ElemCopyDataToGraphicsResource(commandList, ¶meters); + ElemCommitCommandList(commandList); + fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + + ElemWaitForFenceOnCpu(fence); + } + + // Assert + ElemWaitForFenceOnCpu(fence); + + auto resourceDataSpan = ElemDownloadGraphicsBufferData(resource, nullptr); + + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + auto bufferValues = (uint32_t*)resourceDataSpan.Items; + + for (uint32_t i = 0; i < itemCount; i++) + { + ASSERT_EQ_MSG(bufferValues[i], i, "Resource dataspan element should be equal to initial data."); + } + + free(data); + EnableBarriersLog(); +} + +UTEST(ResourceIO, CopyDataToGraphicsResource_WithTexture) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + + const auto width = 1024u; + const auto height = 1024u; + const auto mipLevelCount = 11u; + + auto texture = TestCreateGpuTexture(graphicsDevice, width, height, mipLevelCount, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_Read); + + uint8_t* mipData[mipLevelCount]; + + for (uint32_t i = 0; i < mipLevelCount; i++) + { + auto currentWidth = Max(1u, width >> i); + auto currentHeight = Max(1u, height >> i); + + mipData[i] = (uint8_t*)malloc(currentWidth * currentHeight * 16); + auto testColor = TestMipColors[i % ARRAYSIZE(TestMipColors)]; + + for (uint32_t j = 0; j < currentWidth * currentHeight * 4; j += 4) + { + ((float*)mipData[i])[j] = testColor.X; + ((float*)mipData[i])[j + 1] = testColor.Y; + ((float*)mipData[i])[j + 2] = testColor.Z; + ((float*)mipData[i])[j + 3] = 1.0f; + } + } + + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + // Act + for (uint32_t i = 0; i < mipLevelCount; i++) + { + auto currentWidth = Max(1u, width >> i); + auto currentHeight = Max(1u, height >> i); + + ElemCopyDataToGraphicsResourceParameters parameters = + { + .Resource = texture.Texture, + .TextureMipLevel = i, + .SourceType = ElemCopyDataSourceType_Memory, + .SourceMemoryData = { .Items = mipData[i], .Length = currentWidth * currentHeight * 4 } + }; + + ElemCopyDataToGraphicsResource(commandList, ¶meters); + } + + // Assert + ElemCommitCommandList(commandList); + + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + ElemDataSpan outputMipData[mipLevelCount]; + + // BUG: Problem in vulkan with last mip level + for (uint32_t i = 0; i < mipLevelCount; i++) + { + auto currentWidth = Max(1u, width >> i); + auto currentHeight = Max(1u, height >> i); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, currentWidth * currentHeight * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)texture.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor, i }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", (currentWidth + 7) / 8, (currentHeight + 7) / 8, 1, &resourceIdList); + + auto readbackBufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + outputMipData[i].Items = (uint8_t*)malloc(readbackBufferData.Length); + outputMipData[i].Length = readbackBufferData.Length; + memcpy(outputMipData[i].Items, readbackBufferData.Items, readbackBufferData.Length); + + TestFreeGpuBuffer(readbackBuffer); + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); + } + + TestFreeGpuTexture(texture); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + ASSERT_LOG_NOERROR(); + + for (uint32_t i = 0; i < mipLevelCount; i++) + { + auto outputMipDataItem = outputMipData[i]; + + for (uint32_t j = 0; j < outputMipDataItem.Length; j++) + { + ASSERT_EQ_MSG(outputMipDataItem.Items[j], mipData[i][j], "TestMipData"); + } + + free(mipData[i]); + free(outputMipData[i].Items); + } +} diff --git a/tests/GraphicsTests/ResourceTests.cpp b/tests/GraphicsTests/ResourceTests.cpp index 722f7af2..da05671b 100644 --- a/tests/GraphicsTests/ResourceTests.cpp +++ b/tests/GraphicsTests/ResourceTests.cpp @@ -4,6 +4,8 @@ // TODO: Add a test for texture2D uav + rendertarget // TODO: Add a test for gpupload heap can only be used for buffers +// TODO: Add a test for GetResourcePointer only for GpuUpload +// TODO: Test for structured buffer descriptors // TODO: Test all formats // TODO: Test Texture format required // TODO: Test Format SRGB not allowed to Texture UAV @@ -113,6 +115,30 @@ UTEST(Resource, CreateBufferGraphicsResource_UsageRenderTargetAndWrite) ASSERT_EQ_MSG(resource, ELEM_HANDLE_NULL, "Handle should be null."); } +UTEST(Resource, CreateBufferGraphicsResource_UsageDepthStencil) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + + ElemGraphicsResourceInfo resourceInfo = + { + .Type = ElemGraphicsResourceType_Buffer, + .Width = 64, + .Usage = ElemGraphicsResourceUsage_DepthStencil + }; + + // Act + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Assert + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("GraphicsBuffer usage should not be equals to DepthStencil."); + ASSERT_EQ_MSG(resource, ELEM_HANDLE_NULL, "Handle should be null."); +} + UTEST(Resource, CreateGraphicsBufferResourceInfo) { // Arrange @@ -179,6 +205,150 @@ UTEST(Resource, CreateTexture2DGraphicsResource) ASSERT_EQ_MSG(resourceInfo.Usage, ElemGraphicsResourceUsage_Read, "Usage should match the creation usage."); } +UTEST(Resource, CreateTexture2DGraphicsResource_UsageRenderTarget) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto width = 128u; + auto height = 256u; + auto format = ElemGraphicsFormat_B8G8R8A8_SRGB; + auto mipLevels = 3u; + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + + ElemGraphicsResourceInfo resourceInfo = + { + .Type = ElemGraphicsResourceType_Texture2D, + .Width = width, + .Height = height, + .MipLevels = mipLevels, + .Format = format, + .Usage = ElemGraphicsResourceUsage_RenderTarget + }; + + // Act + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Assert + resourceInfo = ElemGetGraphicsResourceInfo(resource); + + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_NE_MSG(resource, ELEM_HANDLE_NULL, "Handle should not be null."); + + ASSERT_EQ_MSG(resourceInfo.Type, ElemGraphicsResourceType_Texture2D, "Resource Type should be a Texture2D."); + ASSERT_EQ_MSG(resourceInfo.Width, width, "Width should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.Height, height, "Height should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.MipLevels, mipLevels, "MipLevels should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.Format, format, "Format should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.Usage, ElemGraphicsResourceUsage_RenderTarget, "Usage should match the creation usage."); +} + +UTEST(Resource, CreateTexture2DGraphicsResource_UsageDepthStencil) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto width = 128u; + auto height = 256u; + auto format = ElemGraphicsFormat_D32_FLOAT; + auto mipLevels = 3u; + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + + ElemGraphicsResourceInfo resourceInfo = + { + .Type = ElemGraphicsResourceType_Texture2D, + .Width = width, + .Height = height, + .MipLevels = mipLevels, + .Format = format, + .Usage = ElemGraphicsResourceUsage_DepthStencil + }; + + // Act + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Assert + resourceInfo = ElemGetGraphicsResourceInfo(resource); + + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_NE_MSG(resource, ELEM_HANDLE_NULL, "Handle should not be null."); + + ASSERT_EQ_MSG(resourceInfo.Type, ElemGraphicsResourceType_Texture2D, "Resource Type should be a Texture2D."); + ASSERT_EQ_MSG(resourceInfo.Width, width, "Width should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.Height, height, "Height should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.MipLevels, mipLevels, "MipLevels should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.Format, format, "Format should be equals to creation."); + ASSERT_EQ_MSG(resourceInfo.Usage, ElemGraphicsResourceUsage_DepthStencil, "Usage should match the creation usage."); +} + +UTEST(Resource, CreateTexture2DGraphicsResource_UsageDepthStencil_WrongFormat) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto width = 128u; + auto height = 256u; + auto format = ElemGraphicsFormat_B8G8R8A8_SRGB; + auto mipLevels = 3u; + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + + ElemGraphicsResourceInfo resourceInfo = + { + .Type = ElemGraphicsResourceType_Texture2D, + .Width = width, + .Height = height, + .MipLevels = mipLevels, + .Format = format, + .Usage = ElemGraphicsResourceUsage_DepthStencil + }; + + // Act + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Assert + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("Texture2D with usage DepthStencil should use a compatible format."); + ASSERT_EQ_MSG(resource, ELEM_HANDLE_NULL, "Handle should be null."); +} + +UTEST(Resource, CreateTexture2DGraphicsResource_UsageRenderTargetDepthStencil) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto width = 128u; + auto height = 256u; + auto format = ElemGraphicsFormat_B8G8R8A8_SRGB; + auto mipLevels = 3u; + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + + ElemGraphicsResourceInfo resourceInfo = + { + .Type = ElemGraphicsResourceType_Texture2D, + .Width = width, + .Height = height, + .MipLevels = mipLevels, + .Format = format, + .Usage = (ElemGraphicsResourceUsage)(ElemGraphicsResourceUsage_RenderTarget | ElemGraphicsResourceUsage_DepthStencil) + }; + + // Act + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Assert + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_MESSAGE("Texture2D with usage RenderTarget and DepthStencil should not be used together."); + ASSERT_EQ_MSG(resource, ELEM_HANDLE_NULL, "Handle should be null."); +} + UTEST(Resource, CreateTexture2DGraphicsResource_WidthZero) { // Arrange @@ -287,7 +457,7 @@ UTEST(Resource, CreateTexture2DResourceInfo_RenderTargetWrite) auto width = 128u; auto height = 256u; auto mipLevels = 5u; - auto format = ElemGraphicsFormat_B8G8R8A8_UNORM; + auto format = ElemGraphicsFormat_B8G8R8A8; ElemGraphicsResourceInfoOptions options = { .DebugName = "TestTexture2D" }; // Act @@ -309,56 +479,6 @@ UTEST(Resource, CreateTexture2DResourceInfo_RenderTargetWrite) ASSERT_STREQ_MSG(resourceInfo.DebugName, "TestTexture2D", "Debug name should match the creation usage."); } -UTEST(Resource, GetGraphicsResourceDataSpan_WithBuffer) -{ - // Arrange - auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); - - ElemGraphicsHeapOptions options = - { - .HeapType = ElemGraphicsHeapType_GpuUpload - }; - - auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), &options); - auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); - auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); - - // Act - auto resourceDataSpan = ElemGetGraphicsResourceDataSpan(resource); - - // Assert - ElemFreeGraphicsResource(resource, nullptr); - ElemFreeGraphicsHeap(graphicsHeap); - ElemFreeGraphicsDevice(graphicsDevice); - - ASSERT_LOG_NOERROR(); - - ASSERT_TRUE_MSG(resourceDataSpan.Items != nullptr, "Resource dataspan pointer should not be null."); - ASSERT_EQ_MSG(resourceDataSpan.Length, 1024u, "Resource dataspan length should be equals to buffer size."); -} - -UTEST(Resource, GetGraphicsResourceDataSpan_WithTexture2D) -{ - // Arrange - auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); - auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); - auto resourceInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, 256, 256, 1, ElemGraphicsFormat_B8G8R8A8_SRGB, ElemGraphicsResourceUsage_Read, nullptr); - auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); - - // Act - auto resourceDataSpan = ElemGetGraphicsResourceDataSpan(resource); - - // Assert - ElemFreeGraphicsResource(resource, nullptr); - ElemFreeGraphicsHeap(graphicsHeap); - ElemFreeGraphicsDevice(graphicsDevice); - - ASSERT_LOG_MESSAGE("GetGraphicsResourceDataSpan only works with graphics buffers."); - - ASSERT_TRUE_MSG(resourceDataSpan.Items == nullptr, "Resource dataspan pointer should be null."); - ASSERT_EQ_MSG(resourceDataSpan.Length, 0u, "Resource dataspan length should be 0."); -} - UTEST(Resource, FreeGraphicsResource) { // Arrange @@ -394,7 +514,7 @@ UTEST(Resource, FreeGraphicsResource_WithFenceNotExecuted) ElemFreeGraphicsResource(resource, &options); // Assert - ElemProcessGraphicsResourceDeleteQueue(); + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); auto afterFreeResourceInfo = ElemGetGraphicsResourceInfo(resource); ElemFreeGraphicsResource(resource, nullptr); @@ -426,7 +546,7 @@ UTEST(Resource, FreeGraphicsResource_WithFenceExecuted) // Assert ElemWaitForFenceOnCpu(fence); - ElemProcessGraphicsResourceDeleteQueue(); + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); auto afterFreeResourceInfo = ElemGetGraphicsResourceInfo(resource); ElemFreeGraphicsHeap(graphicsHeap); @@ -461,6 +581,35 @@ UTEST(Resource, CreateGraphicsResourceDescriptor_ReadWithBuffer) ASSERT_EQ_MSG(descriptorInfo.Usage, ElemGraphicsResourceDescriptorUsage_Read, "Usage should be equals to the one used during creation."); } +UTEST(Resource, CreateGraphicsResourceDescriptor_BigAmount) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); + auto resourceInfo = ElemCreateGraphicsBufferResourceInfo(graphicsDevice, 1024u, ElemGraphicsResourceUsage_Read, nullptr); + auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); + + // Act + ElemGraphicsResourceDescriptor descriptor; + + for (uint32_t i = 0; i < 100000; i++) + { + descriptor = ElemCreateGraphicsResourceDescriptor(resource, ElemGraphicsResourceDescriptorUsage_Read, nullptr); + } + + // Assert + auto descriptorInfo = ElemGetGraphicsResourceDescriptorInfo(descriptor); + + ElemFreeGraphicsResourceDescriptor(descriptor, nullptr); + ElemFreeGraphicsResource(resource, nullptr); + ElemFreeGraphicsHeap(graphicsHeap); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_EQ_MSG(descriptorInfo.Resource, resource, "Resource should be equals to the one used during creation."); + ASSERT_EQ_MSG(descriptorInfo.Usage, ElemGraphicsResourceDescriptorUsage_Read, "Usage should be equals to the one used during creation."); +} + UTEST(Resource, CreateGraphicsResourceDescriptor_WriteWithBufferWrite) { // Arrange @@ -534,7 +683,7 @@ UTEST(Resource, CreateGraphicsResourceDescriptor_WriteWithTexture2DWrite) // Arrange auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); auto graphicsHeap = ElemCreateGraphicsHeap(graphicsDevice, TestMegaBytesToBytes(1), nullptr); - auto resourceInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, 256, 256, 1, ElemGraphicsFormat_B8G8R8A8_UNORM, ElemGraphicsResourceUsage_Write, nullptr); + auto resourceInfo = ElemCreateTexture2DResourceInfo(graphicsDevice, 256, 256, 1, ElemGraphicsFormat_B8G8R8A8, ElemGraphicsResourceUsage_Write, nullptr); auto resource = ElemCreateGraphicsResource(graphicsHeap, 0, &resourceInfo); // Act @@ -613,7 +762,7 @@ UTEST(Resource, FreeGraphicsResourceDescriptor_WithFenceNotExecuted) ElemFreeGraphicsResourceDescriptor(descriptor, &options); // Assert - ElemProcessGraphicsResourceDeleteQueue(); + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); auto descriptorInfo = ElemGetGraphicsResourceDescriptorInfo(descriptor); ElemFreeGraphicsResourceDescriptor(descriptor, nullptr); @@ -647,7 +796,7 @@ UTEST(Resource, FreeGraphicsResourceDescriptor_WithFenceExecuted) // Assert ElemWaitForFenceOnCpu(fence); - ElemProcessGraphicsResourceDeleteQueue(); + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); auto descriptorInfo = ElemGetGraphicsResourceDescriptorInfo(descriptor); ElemFreeGraphicsResource(resource, nullptr); @@ -683,3 +832,139 @@ UTEST(Resource, GetGraphicsResourceDescriptorInfo_WithInvalidDescriptor) ASSERT_LOG_MESSAGE("Resource Descriptor is invalid."); ASSERT_EQ_MSG(descriptorInfo.Resource, 0u, "Resource should be equals to 0."); } + +// TODO: Add validation tests +// TODO: Validation MaxAnisotropy 16 + +UTEST(Resource, CreateGraphicsSampler_WithDefaultValues) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + ElemGraphicsSamplerInfo samplerInfo = {}; + + // Act + auto sampler = ElemCreateGraphicsSampler(graphicsDevice, &samplerInfo); + + // Assert + auto resultSamplerInfo = ElemGetGraphicsSamplerInfo(sampler); + + ElemFreeGraphicsSampler(sampler, nullptr); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_EQ_MSG(resultSamplerInfo.MinFilter, ElemGraphicsSamplerFilter_Nearest, "MinFilter should be equals to Nearest."); + ASSERT_EQ_MSG(resultSamplerInfo.MagFilter, ElemGraphicsSamplerFilter_Nearest, "MagFilter should be equals to Nearest."); + ASSERT_EQ_MSG(resultSamplerInfo.MipFilter, ElemGraphicsSamplerFilter_Nearest, "MipFilter should be equals to Nearest."); + ASSERT_EQ_MSG(resultSamplerInfo.AddressU, ElemGraphicsSamplerAddressMode_Repeat, "AddressU should be equals to Repeat."); + ASSERT_EQ_MSG(resultSamplerInfo.AddressV, ElemGraphicsSamplerAddressMode_Repeat, "AddressV should be equals to Repeat."); + ASSERT_EQ_MSG(resultSamplerInfo.AddressW, ElemGraphicsSamplerAddressMode_Repeat, "AddressW should be equals to Repeat."); + ASSERT_EQ_MSG(resultSamplerInfo.MaxAnisotropy, 1u, "Max Anisotropy should equals to 1."); + ASSERT_EQ_MSG(resultSamplerInfo.CompareFunction, ElemGraphicsCompareFunction_Never, "CompareFunction should equals to Never."); + ASSERT_EQ_MSG(resultSamplerInfo.BorderColor.Red, 0.0f, "Border Red Color should be 0."); + ASSERT_EQ_MSG(resultSamplerInfo.BorderColor.Green, 0.0f, "Border Green Color should be 0."); + ASSERT_EQ_MSG(resultSamplerInfo.BorderColor.Blue, 0.0f, "Border Blue Color should be 0."); + ASSERT_EQ_MSG(resultSamplerInfo.BorderColor.Alpha, 0.0f, "Border Alpha Color should be 0."); + ASSERT_EQ_MSG(resultSamplerInfo.MinLod, 0.0f, "Min Lod should be 0."); + ASSERT_EQ_MSG(resultSamplerInfo.MaxLod, 0.0f, "Max Lod should be 0."); +} + +UTEST(Resource, FreeGraphicsSampler) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + ElemGraphicsSamplerInfo samplerInfo = { .MipFilter = ElemGraphicsSamplerFilter_Linear }; + auto sampler = ElemCreateGraphicsSampler(graphicsDevice, &samplerInfo); + + // Act + ElemFreeGraphicsSampler(sampler, nullptr); + + // Assert + auto resultSamplerInfo = ElemGetGraphicsSamplerInfo(sampler); + + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_EQ_MSG(resultSamplerInfo.MaxAnisotropy, 0u, "MaxAnisotropy should be equals to 0."); +} + +UTEST(Resource, FreeGraphicsSampler_WithFenceNotExecuted) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + + ElemGraphicsSamplerInfo samplerInfo = { .MipFilter = ElemGraphicsSamplerFilter_Linear }; + auto sampler = ElemCreateGraphicsSampler(graphicsDevice, &samplerInfo); + + ElemFence fence = { .CommandQueue = commandQueue, .FenceValue = UINT64_MAX }; + ElemFreeGraphicsSamplerOptions options = { .FencesToWait = { .Items = &fence, .Length = 1 } }; + + // Act + ElemFreeGraphicsSampler(sampler, &options); + + // Assert + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); + auto resultSamplerInfo = ElemGetGraphicsSamplerInfo(sampler); + + ElemFreeGraphicsSampler(sampler, nullptr); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_EQ_MSG(resultSamplerInfo.MaxAnisotropy, 1u, "MaxAnisotropy should be equals to 1."); +} + +UTEST(Resource, FreeGraphicsSampler_WithFenceExecuted) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + + ElemGraphicsSamplerInfo samplerInfo = { .MipFilter = ElemGraphicsSamplerFilter_Linear }; + auto sampler = ElemCreateGraphicsSampler(graphicsDevice, &samplerInfo); + + auto commandList = ElemGetCommandList(commandQueue, nullptr); + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + + ElemFreeGraphicsSamplerOptions options = { .FencesToWait = { .Items = &fence, .Length = 1 } }; + + // Act + ElemFreeGraphicsSampler(sampler, &options); + + // Assert + ElemWaitForFenceOnCpu(fence); + ElemProcessGraphicsResourceDeleteQueue(graphicsDevice); + auto resultSamplerInfo = ElemGetGraphicsSamplerInfo(sampler); + + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_EQ_MSG(resultSamplerInfo.MaxAnisotropy, 0u, "MaxAnisotropy should be equals to 0."); +} + +UTEST(Resource, FreeGraphicsSampler_WithInvalidDescriptor) +{ + // Arrange + ElemGraphicsSampler sampler = -1; + + // Act + ElemFreeGraphicsSampler(sampler, nullptr); + + // Assert + ASSERT_LOG_MESSAGE("Sampler is invalid."); +} + +UTEST(Resource, GetGraphicsSamplerInfo_WithInvalidDescriptor) +{ + // Arrange + ElemGraphicsSampler sampler = -1; + + // Act + auto samplerInfo = ElemGetGraphicsSamplerInfo(sampler); + + // Assert + ASSERT_LOG_MESSAGE("Sampler is invalid."); + ASSERT_EQ_MSG(samplerInfo.MaxAnisotropy, 0u, "MaxAnisotropy should be equals to 0."); +} diff --git a/tests/GraphicsTests/ShaderTests.cpp b/tests/GraphicsTests/ShaderTests.cpp index 0af9ab23..d0d7aaa7 100644 --- a/tests/GraphicsTests/ShaderTests.cpp +++ b/tests/GraphicsTests/ShaderTests.cpp @@ -4,6 +4,8 @@ // TODO: Validate dispatch thread group count // TODO: Cannot push constant before binding pso +// TODO: PSO Multi render targets (Test Blend States too) +// TODO: Check depth stencil format if comparaison function set UTEST(Shader, CompileComputePipelineState) { @@ -82,10 +84,10 @@ UTEST(Shader, DispatchCompute) auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, 64 * sizeof(uint32_t), ElemGraphicsHeapType_Readback); // Act - TestDispatchComputeForReadbackBuffer(graphicsDevice, commandQueue, "ShaderTests.shader", "TestCompute", 1, 1, 1, &readbackBuffer.WriteDescriptor); + TestDispatchComputeForShader(graphicsDevice, commandQueue, "ShaderTests.shader", "TestCompute", 1, 1, 1, &readbackBuffer.WriteDescriptor); // Assert - auto bufferData = ElemGetGraphicsResourceDataSpan(readbackBuffer.Buffer); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); TestFreeGpuBuffer(readbackBuffer); ElemFreeCommandQueue(commandQueue); @@ -99,3 +101,1050 @@ UTEST(Shader, DispatchCompute) ASSERT_EQ_MSG(uintData[i], i < 16 ? i : 0u, "Compute shader data is invalid."); } } + +struct Shader_CompileGraphicsPipelineStateFillAndCullMode +{ + ElemGraphicsFillMode FillMode; + ElemGraphicsCullMode CullMode; + bool TwoColors; + float Color[3]; + float Color2[3]; +}; + +UTEST_F_SETUP(Shader_CompileGraphicsPipelineStateFillAndCullMode) +{ +} + +UTEST_F_TEARDOWN(Shader_CompileGraphicsPipelineStateFillAndCullMode) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 }, + .FillMode = utest_fixture->FillMode, + .CullMode = utest_fixture->CullMode + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "ShaderTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 1.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + + float shaderParameters[] = { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + ElemPushPipelineStateConstants(commandList, 0, { .Items = (uint8_t*)shaderParameters, .Length = sizeof(float) * 8 }); + ElemDispatchMesh(commandList, 1, 1, 1); + + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + auto floatData = (float*)bufferData.Items; + bool colorMatch[2] = {}; + + for (uint32_t i = 0; i < bufferData.Length / sizeof(float); i += 4) + { + if (!utest_fixture->TwoColors) + { + ASSERT_EQ_MSG(floatData[i], utest_fixture->Color[0], "Red channel data is invalid."); + ASSERT_EQ_MSG(floatData[i + 1], utest_fixture->Color[1], "Green channel data is invalid."); + ASSERT_EQ_MSG(floatData[i + 2], utest_fixture->Color[2], "Blue channel data is invalid."); + ASSERT_EQ_MSG(floatData[i + 3], 1.0f, "Alpha channel data is invalid."); + } + else + { + if (floatData[i] == utest_fixture->Color[0] && + floatData[i + 1] == utest_fixture->Color[1] && + floatData[i + 2] == utest_fixture->Color[2]) + { + colorMatch[0] = true; + } + else if(floatData[i] == utest_fixture->Color2[0] && + floatData[i + 1] == utest_fixture->Color2[1] && + floatData[i + 2] == utest_fixture->Color2[2]) + { + colorMatch[1] = true; + } + } + } + + if (utest_fixture->TwoColors) + { + ASSERT_TRUE_MSG(colorMatch[0], "First color must be present."); + ASSERT_TRUE_MSG(colorMatch[1], "Second color must be present."); + } +} + +UTEST_F(Shader_CompileGraphicsPipelineStateFillAndCullMode, FillModeSolid) +{ + utest_fixture->FillMode = ElemGraphicsFillMode_Solid; + utest_fixture->Color[0] = 1.0f; + utest_fixture->Color[1] = 1.0f; + utest_fixture->Color[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateFillAndCullMode, FillModeWireframe) +{ + utest_fixture->FillMode = ElemGraphicsFillMode_Wireframe; + + utest_fixture->TwoColors = true; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 1.0f; + + utest_fixture->Color2[0] = 1.0f; + utest_fixture->Color2[1] = 1.0f; + utest_fixture->Color2[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateFillAndCullMode, CullModeBackface) +{ + utest_fixture->CullMode = ElemGraphicsCullMode_BackFace; + utest_fixture->Color[0] = 1.0f; + utest_fixture->Color[1] = 1.0f; + utest_fixture->Color[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateFillAndCullMode, CullModeFrontFace) +{ + utest_fixture->CullMode = ElemGraphicsCullMode_FrontFace; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 1.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateFillAndCullMode, CullModeNone) +{ + utest_fixture->CullMode = ElemGraphicsCullMode_None; + utest_fixture->Color[0] = 1.0f; + utest_fixture->Color[1] = 1.0f; + utest_fixture->Color[2] = 0.0f; +} + +struct Shader_CompileGraphicsPipelineStateDepthCompare +{ + float ClearDepthValue; + ElemGraphicsCompareFunction CompareFunction; + float Color[3]; +}; + +UTEST_F_SETUP(Shader_CompileGraphicsPipelineStateDepthCompare) +{ +} + +UTEST_F_TEARDOWN(Shader_CompileGraphicsPipelineStateDepthCompare) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + auto depthBuffer = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_D32_FLOAT, ElemGraphicsResourceUsage_DepthStencil); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget { .Format = renderTarget.Format }; + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 }, + .DepthStencil = + { + .Format = depthBuffer.Format, + .DepthCompareFunction = utest_fixture->CompareFunction + } + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "ShaderTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = { .Red = 0.0f, .Green = 0.0f, .Blue = 0.0f, .Alpha = 1.0f }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + }, + .DepthStencil = + { + .DepthStencil = depthBuffer.Texture, + .DepthClearValue = utest_fixture->ClearDepthValue + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + + float shaderParameters[] = { 0.0f, 0.25f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; + ElemPushPipelineStateConstants(commandList, 0, { .Items = (uint8_t*)shaderParameters, .Length = sizeof(float) * 8 }); + ElemDispatchMesh(commandList, 1, 1, 1); + + float shaderParameters2[] = { 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f }; + ElemPushPipelineStateConstants(commandList, 0, { .Items = (uint8_t*)shaderParameters2, .Length = sizeof(float) * 8 }); + ElemDispatchMesh(commandList, 1, 1, 1); + + float shaderParameters3[] = { 0.0f, 0.75f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f }; + ElemPushPipelineStateConstants(commandList, 0, { .Items = (uint8_t*)shaderParameters3, .Length = sizeof(float) * 8 }); + ElemDispatchMesh(commandList, 1, 1, 1); + + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + TestFreeGpuTexture(depthBuffer); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + ASSERT_COLOR_BUFFER(bufferData, utest_fixture->Color[0], utest_fixture->Color[1], utest_fixture->Color[2], 1.0f); +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, Never) +{ + utest_fixture->ClearDepthValue = 0.5f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_Never; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, Less) +{ + utest_fixture->ClearDepthValue = 1.0f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_Less; + utest_fixture->Color[0] = 1.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, LessEqual) +{ + utest_fixture->ClearDepthValue = 0.25f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_LessEqual; + utest_fixture->Color[0] = 1.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, Greater) +{ + utest_fixture->ClearDepthValue = 0.0f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_Greater; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 1.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, GreaterEqual) +{ + utest_fixture->ClearDepthValue = 0.75f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_GreaterEqual; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 1.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, Equal) +{ + utest_fixture->ClearDepthValue = 0.5f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_Equal; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 1.0f; + utest_fixture->Color[2] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateDepthCompare, Always) +{ + utest_fixture->ClearDepthValue = 1.0f; + utest_fixture->CompareFunction = ElemGraphicsCompareFunction_Always; + utest_fixture->Color[0] = 0.0f; + utest_fixture->Color[1] = 0.0f; + utest_fixture->Color[2] = 1.0f; +} + +struct Shader_CompileGraphicsPipelineStateBlendState +{ + ElemGraphicsBlendOperation BlendOperation; + ElemGraphicsBlendFactor SourceBlendFactor; + ElemGraphicsBlendFactor DestinationBlendFactor; + ElemGraphicsBlendOperation BlendOperationAlpha; + ElemGraphicsBlendFactor SourceBlendFactorAlpha; + ElemGraphicsBlendFactor DestinationBlendFactorAlpha; + float DestinationColor[4]; + float SourceColor[4]; + float ResultColor[4]; +}; + +UTEST_F_SETUP(Shader_CompileGraphicsPipelineStateBlendState) +{ +} + +UTEST_F_TEARDOWN(Shader_CompileGraphicsPipelineStateBlendState) +{ + // Arrange + auto graphicsDevice = ElemCreateGraphicsDevice(nullptr); + auto commandQueue = ElemCreateCommandQueue(graphicsDevice, ElemCommandQueueType_Graphics, nullptr); + auto commandList = ElemGetCommandList(commandQueue, nullptr); + + auto textureSize = 16u; + auto renderTarget = TestCreateGpuTexture(graphicsDevice, textureSize, textureSize, ElemGraphicsFormat_R32G32B32A32_FLOAT, ElemGraphicsResourceUsage_RenderTarget); + + ElemGraphicsPipelineStateRenderTarget psoRenderTarget = + { + .Format = renderTarget.Format, + .BlendOperation = utest_fixture->BlendOperation, + .SourceBlendFactor = utest_fixture->SourceBlendFactor, + .DestinationBlendFactor = utest_fixture->DestinationBlendFactor, + .BlendOperationAlpha = utest_fixture->BlendOperationAlpha, + .SourceBlendFactorAlpha = utest_fixture->SourceBlendFactorAlpha, + .DestinationBlendFactorAlpha = utest_fixture->DestinationBlendFactorAlpha + }; + + ElemGraphicsPipelineStateParameters psoParameters = + { + .RenderTargets = { .Items = &psoRenderTarget, .Length = 1 }, + }; + + auto meshShaderPipeline = TestOpenMeshShader(graphicsDevice, "ShaderTests.shader", "MeshShader", "PixelShader", &psoParameters); + + // Act + ElemRenderPassRenderTarget renderPassRenderTarget = + { + .RenderTarget = renderTarget.Texture, + .ClearColor = + { + .Red = utest_fixture->DestinationColor[0], + .Green = utest_fixture->DestinationColor[1], + .Blue = utest_fixture->DestinationColor[2], + .Alpha = utest_fixture->DestinationColor[3] + }, + .LoadAction = ElemRenderPassLoadAction_Clear + }; + + ElemBeginRenderPassParameters parameters = + { + .RenderTargets = + { + .Items = &renderPassRenderTarget, + .Length = 1 + } + }; + + ElemBeginRenderPass(commandList, ¶meters); + ElemBindPipelineState(commandList, meshShaderPipeline); + + float shaderParameters[] = { 0.0f, 0.0f, 0.0f, 0.0f, utest_fixture->SourceColor[0], utest_fixture->SourceColor[1], utest_fixture->SourceColor[2], utest_fixture->SourceColor[3] }; + ElemPushPipelineStateConstants(commandList, 0, { .Items = (uint8_t*)shaderParameters, .Length = sizeof(float) * 8 }); + ElemDispatchMesh(commandList, 1, 1, 1); + + ElemEndRenderPass(commandList); + + // Assert + ElemCommitCommandList(commandList); + auto fence = ElemExecuteCommandList(commandQueue, commandList, nullptr); + ElemWaitForFenceOnCpu(fence); + + auto readbackBuffer = TestCreateGpuBuffer(graphicsDevice, textureSize * textureSize * 4 * sizeof(float), ElemGraphicsHeapType_Readback); + uint32_t resourceIdList[] = { (uint32_t)renderTarget.ReadDescriptor, (uint32_t)readbackBuffer.WriteDescriptor }; + TestDispatchComputeForShader(graphicsDevice, commandQueue, "Assert.shader", "CopyTexture", textureSize / 8, textureSize / 8, 1, &resourceIdList); + auto bufferData = ElemDownloadGraphicsBufferData(readbackBuffer.Buffer, nullptr); + + TestFreeGpuBuffer(readbackBuffer); + TestFreeGpuTexture(renderTarget); + ElemFreePipelineState(meshShaderPipeline); + ElemFreeCommandQueue(commandQueue); + ElemFreeGraphicsDevice(graphicsDevice); + + ASSERT_LOG_NOERROR(); + + auto floatData = (float*)bufferData.Items; + + for (uint32_t i = 0; i < bufferData.Length / sizeof(float); i += 4) + { + ASSERT_EQ_MSG(floatData[i], utest_fixture->ResultColor[0], "Red channel data is invalid."); + ASSERT_EQ_MSG(floatData[i + 1], utest_fixture->ResultColor[1], "Green channel data is invalid."); + ASSERT_EQ_MSG(floatData[i + 2], utest_fixture->ResultColor[2], "Blue channel data is invalid."); + ASSERT_EQ_MSG(floatData[i + 3], utest_fixture->ResultColor[3], "Alpha channel data is invalid."); + } +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Defaults) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 0.5f; + utest_fixture->ResultColor[2] = 1.0f; + utest_fixture->ResultColor[3] = 1.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_Zero_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 1.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_One_Zero) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 0.5f; + utest_fixture->ResultColor[2] = 1.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 1.5f; + utest_fixture->ResultColor[2] = 1.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AddAlpha_Zero_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_One; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 0.5f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.5f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AddAlpha_One_Zero) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 0.5f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 1.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AddAlpha_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_One; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 0.5f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 1.5f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_SourceColor_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_SourceColor; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 1.25f; + utest_fixture->ResultColor[2] = 1.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_InverseSourceColor_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_InverseSourceColor; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.25f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 1.1875f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_SourceAlpha_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_SourceAlpha; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.25f; + utest_fixture->SourceColor[2] = 0.8; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.5f; + utest_fixture->ResultColor[1] = 1.125f; + utest_fixture->ResultColor[2] = 0.4f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_InverseSourceAlpha_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_InverseSourceAlpha; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.8; + utest_fixture->SourceColor[3] = 0.75f; + + utest_fixture->ResultColor[0] = 0.25f; + utest_fixture->ResultColor[1] = 1.125f; + utest_fixture->ResultColor[2] = 0.2f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_DestinationColor_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_DestinationColor; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.5f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 1.5f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_InverseDestinationColor_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_InverseDestinationColor; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.5f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.25f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 1.0f; + utest_fixture->ResultColor[2] = 1.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_DestinationAlpha_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_DestinationAlpha; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.5f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.25f; + utest_fixture->SourceColor[2] = 0.8; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 1.5f; + utest_fixture->ResultColor[1] = 1.25; + utest_fixture->ResultColor[2] = 0.8f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_InverseDestinationAlpha_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_InverseDestinationAlpha; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.5f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.8; + utest_fixture->SourceColor[3] = 0.75f; + + utest_fixture->ResultColor[0] = 0.5f; + utest_fixture->ResultColor[1] = 1.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Add_SourceAlphaSaturated_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_SourceAlphaSaturated; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 0.0f; + utest_fixture->DestinationColor[3] = 0.75f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 0.25f; + utest_fixture->SourceColor[2] = 0.8; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.25f; + utest_fixture->ResultColor[1] = 1.0625f; + utest_fixture->ResultColor[2] = 0.2f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Subtract_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Subtract; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 0.25f; + utest_fixture->DestinationColor[1] = 0.5f; + utest_fixture->DestinationColor[2] = 0.75f; + utest_fixture->DestinationColor[3] = 0.5f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 1.0f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.75f; + utest_fixture->ResultColor[1] = 0.5f; + utest_fixture->ResultColor[2] = 0.25f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, ReverseSubtract_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_ReverseSubtract; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 1.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 1.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 0.25f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.75f; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.75f; + utest_fixture->ResultColor[1] = 0.5f; + utest_fixture->ResultColor[2] = 0.25f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Min_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Min; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 1.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 1.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 0.25f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.75f; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.25f; + utest_fixture->ResultColor[1] = 0.5f; + utest_fixture->ResultColor[2] = 0.75f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, Max_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Max; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_One; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_Zero; + + utest_fixture->DestinationColor[0] = 1.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 1.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 0.25f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.75f; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 1.0f; + utest_fixture->ResultColor[1] = 1.0f; + utest_fixture->ResultColor[2] = 1.0f; + utest_fixture->ResultColor[3] = 0.0f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AlphaSubtract_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Subtract; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_One; + + utest_fixture->DestinationColor[0] = 0.25f; + utest_fixture->DestinationColor[1] = 0.5f; + utest_fixture->DestinationColor[2] = 0.75f; + utest_fixture->DestinationColor[3] = 0.5f; + + utest_fixture->SourceColor[0] = 1.0f; + utest_fixture->SourceColor[1] = 1.0f; + utest_fixture->SourceColor[2] = 1.0f; + utest_fixture->SourceColor[3] = 1.0f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.5f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AlphaReverseSubtract_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_ReverseSubtract; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_One; + + utest_fixture->DestinationColor[0] = 1.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 1.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 0.25f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.75f; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.5f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AlphaMin_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Min; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_One; + + utest_fixture->DestinationColor[0] = 1.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 1.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 0.25f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.75f; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 0.5f; +} + +UTEST_F(Shader_CompileGraphicsPipelineStateBlendState, AlphaMax_One_One) +{ + utest_fixture->BlendOperation = ElemGraphicsBlendOperation_Add; + utest_fixture->SourceBlendFactor = ElemGraphicsBlendFactor_Zero; + utest_fixture->DestinationBlendFactor = ElemGraphicsBlendFactor_Zero; + + utest_fixture->BlendOperationAlpha = ElemGraphicsBlendOperation_Max; + utest_fixture->SourceBlendFactorAlpha = ElemGraphicsBlendFactor_One; + utest_fixture->DestinationBlendFactorAlpha = ElemGraphicsBlendFactor_One; + + utest_fixture->DestinationColor[0] = 1.0f; + utest_fixture->DestinationColor[1] = 1.0f; + utest_fixture->DestinationColor[2] = 1.0f; + utest_fixture->DestinationColor[3] = 1.0f; + + utest_fixture->SourceColor[0] = 0.25f; + utest_fixture->SourceColor[1] = 0.5f; + utest_fixture->SourceColor[2] = 0.75f; + utest_fixture->SourceColor[3] = 0.5f; + + utest_fixture->ResultColor[0] = 0.0f; + utest_fixture->ResultColor[1] = 0.0f; + utest_fixture->ResultColor[2] = 0.0f; + utest_fixture->ResultColor[3] = 1.0f; +} diff --git a/tests/GraphicsTests/Shaders/ShaderTests.hlsl b/tests/GraphicsTests/Shaders/ShaderTests.hlsl deleted file mode 100644 index 31785334..00000000 --- a/tests/GraphicsTests/Shaders/ShaderTests.hlsl +++ /dev/null @@ -1,15 +0,0 @@ -struct TestParameters -{ - uint TestDescriptor; -}; - -[[vk::push_constant]] -TestParameters parameters : register(b0); - -[shader("compute")] -[numthreads(16, 1, 1)] -void TestCompute(uint2 threadId: SV_DispatchThreadID) -{ - RWStructuredBuffer testBuffer = ResourceDescriptorHeap[parameters.TestDescriptor]; - testBuffer[threadId.x] = threadId.x; -} diff --git a/tests/GraphicsTests/UnityBuild.cpp b/tests/GraphicsTests/UnityBuild.cpp index 8fbf0fc8..1d3e265d 100644 --- a/tests/GraphicsTests/UnityBuild.cpp +++ b/tests/GraphicsTests/UnityBuild.cpp @@ -4,6 +4,7 @@ #include "ShaderTests.cpp" #include "SwapChainTests.cpp" #include "ResourceTests.cpp" +#include "ResourceIOTests.cpp" #include "ResourceBarrierTests.cpp" #include "RenderingTests.cpp" #include "utest.h" @@ -29,8 +30,11 @@ void ApplicationTestInitFunction(void* payload) options.EnableGpuValidation = false; options.EnableDebugBarrierInfo = true; + startOptions = options; + ElemConfigureLogHandler(TestLogHandler); ElemSetGraphicsOptions(&options); + InitLogBuffers(); auto result = utest_main(applicationTestPayload->argc, applicationTestPayload->argv); diff --git a/tests/ToolsTests/MeshBuilderTests.cpp b/tests/ToolsTests/MeshBuilderTests.cpp new file mode 100644 index 00000000..7c2123c5 --- /dev/null +++ b/tests/ToolsTests/MeshBuilderTests.cpp @@ -0,0 +1,195 @@ +#include "ToolsTests.h" +#include "utest.h" + +// TODO: Add check when passing only vertex buffer for mod 3 +// TODO: Test cone and bounding box and spheres + +struct TestMeshVector2 +{ + float X, Y; +}; + +struct TestMeshVector3 +{ + float X, Y, Z; +}; + +struct TestMeshVertex +{ + TestMeshVector3 Position; + TestMeshVector3 Normal; + TestMeshVector2 TextureCoordinates; +}; + +ElemVertexBuffer TestBuildVertexBuffer(TestMeshVertex* destination, uint32_t count) +{ + float globalVertexCounter = 0u; + + for (uint32_t i = 0; i < count; i++) + { + destination[i] = + { + .Position = { globalVertexCounter++, globalVertexCounter++, globalVertexCounter++ }, + .Normal = { globalVertexCounter++, globalVertexCounter++, globalVertexCounter++ }, + .TextureCoordinates = { globalVertexCounter++, globalVertexCounter++ } + }; + } + + return + { + .Data = { .Items = (uint8_t*)destination, .Length = (uint32_t)(count * sizeof(TestMeshVertex)) }, + .VertexSize = sizeof(TestMeshVertex), + .VertexCount = count + }; +} + +ElemUInt32Span TestBuildIndexBuffer(uint32_t* destination, uint32_t count) +{ + for (uint32_t i = 0; i < count; i++) + { + destination[i] = i; + } + + return + { + .Items = destination, .Length = count + }; +} + +UTEST(MeshBuilder, CheckVertexBuffer) +{ + // Arrange + const uint32_t vertexCount = 210; + + TestMeshVertex vertexList[vertexCount]; + uint32_t indexList[vertexCount]; + + auto vertexBuffer = TestBuildVertexBuffer(vertexList, vertexCount); + auto indexBuffer = TestBuildIndexBuffer(indexList, vertexCount); + + // Act + auto result = ElemBuildMeshlets(vertexBuffer, indexBuffer, NULL); + + // Assert + ASSERT_FALSE(result.HasErrors); + ASSERT_EQ_MSG(result.VertexBuffer.Data.Length / result.VertexBuffer.VertexSize, vertexCount, "Vertex Count is not correct."); + + for (uint32_t i = 0; i < vertexCount; i++) + { + auto found = false; + auto testVertex = vertexList[i]; + + for (uint32_t j = 0; j < result.VertexBuffer.Data.Length / result.VertexBuffer.VertexSize; j++) + { + auto resultVertex = (TestMeshVertex*)&result.VertexBuffer.Data.Items[j * result.VertexBuffer.VertexSize]; + + if (testVertex.Position.X == resultVertex->Position.X && + testVertex.Position.Y == resultVertex->Position.Y && + testVertex.Position.Z == resultVertex->Position.Z && + testVertex.Normal.X == resultVertex->Normal.X && + testVertex.Normal.Y == resultVertex->Normal.Y && + testVertex.Normal.Z == resultVertex->Normal.Z && + testVertex.TextureCoordinates.X == resultVertex->TextureCoordinates.X && + testVertex.TextureCoordinates.Y == resultVertex->TextureCoordinates.Y) + { + found = true; + break; + } + } + + ASSERT_TRUE(found); + } +} + +UTEST(MeshBuilder, CheckVertexBuffer_NoDuplicates) +{ + // Arrange + const uint32_t vertexCount = 210; + + TestMeshVertex vertexList[vertexCount]; + uint32_t indexList[vertexCount]; + + auto vertexBuffer = TestBuildVertexBuffer(vertexList, vertexCount - 3); + auto indexBuffer = TestBuildIndexBuffer(indexList, vertexCount - 3); + + for (uint32_t i = 0; i < 3; i++) + { + vertexList[vertexCount - 3 + i] = vertexList[vertexCount - 6 + i]; + } + + vertexBuffer.Data.Length += vertexBuffer.VertexSize * 3; + + // Act + auto result = ElemBuildMeshlets(vertexBuffer, indexBuffer, NULL); + + // Assert + ASSERT_FALSE(result.HasErrors); + ASSERT_EQ_MSG(result.VertexBuffer.Data.Length / result.VertexBuffer.VertexSize, vertexCount - 3, "Vertex Count from vertexbuffer length is not correct."); + ASSERT_EQ_MSG(result.VertexBuffer.VertexCount, vertexCount - 3, "Vertex Count is not correct."); +} + +UTEST(MeshBuilder, CheckMeshletVertexIndexBuffer) +{ + // Arrange + const uint32_t vertexCount = 210; + + TestMeshVertex vertexList[vertexCount]; + uint32_t indexList[vertexCount]; + + auto vertexBuffer = TestBuildVertexBuffer(vertexList, vertexCount - 3); + auto indexBuffer = TestBuildIndexBuffer(indexList, vertexCount - 3); + + // Act + auto result = ElemBuildMeshlets(vertexBuffer, indexBuffer, NULL); + + // Assert + ASSERT_FALSE(result.HasErrors); + + for (uint32_t i = 0; i < result.Meshlets.Length; i++) + { + ElemMeshlet meshlet = result.Meshlets.Items[i]; + + if (i < result.Meshlets.Length - 1) + { + ElemMeshlet nextMeshlet = result.Meshlets.Items[i + 1]; + ASSERT_NE_MSG(meshlet.VertexIndexOffset + meshlet.VertexIndexCount - 1, nextMeshlet.VertexIndexOffset, "Error Not Last"); + } + else + { + ASSERT_NE_MSG(meshlet.VertexIndexOffset + meshlet.VertexIndexCount - 1, result.MeshletVertexIndexBuffer.Length, "Error"); + } + } +} + +UTEST(MeshBuilder, CheckMeshletTriangleIndexBuffer) +{ + // Arrange + const uint32_t vertexCount = 210; + + TestMeshVertex vertexList[vertexCount]; + uint32_t indexList[vertexCount]; + + auto vertexBuffer = TestBuildVertexBuffer(vertexList, vertexCount - 3); + auto indexBuffer = TestBuildIndexBuffer(indexList, vertexCount - 3); + + // Act + auto result = ElemBuildMeshlets(vertexBuffer, indexBuffer, NULL); + + // Assert + ASSERT_FALSE(result.HasErrors); + + for (uint32_t i = 0; i < result.Meshlets.Length; i++) + { + ElemMeshlet meshlet = result.Meshlets.Items[i]; + + if (i < result.Meshlets.Length - 1) + { + ElemMeshlet nextMeshlet = result.Meshlets.Items[i + 1]; + ASSERT_NE_MSG(meshlet.TriangleOffset + meshlet.TriangleCount - 1, nextMeshlet.TriangleOffset, "Error Not Last"); + } + else + { + ASSERT_NE_MSG(meshlet.TriangleOffset + meshlet.TriangleCount - 1, result.MeshletTriangleIndexBuffer.Length, "Error"); + } + } +} diff --git a/tests/ToolsTests/SceneLoaderTests.cpp b/tests/ToolsTests/SceneLoaderTests.cpp new file mode 100644 index 00000000..3f9d01a0 --- /dev/null +++ b/tests/ToolsTests/SceneLoaderTests.cpp @@ -0,0 +1,109 @@ +#include "ToolsTests.h" +#include "utest.h" + +// TODO: Add real test files on disk? + +auto cubeObjSceneSource = R"(o Cube + v 1.000000 -1.000000 -1.000000 + v 1.000000 -1.000000 1.000000 + v -1.000000 -1.000000 1.000000 + v -1.000000 -1.000000 -1.000000 + v 1.000000 1.000000 -0.999999 + v 0.999999 1.000000 1.000001 + v -1.000000 1.000000 1.000000 + v -1.000000 1.000000 -1.000000 + vt 1.000000 0.333333 + vt 1.000000 0.666667 + vt 0.666667 0.666667 + vt 0.666667 0.333333 + vt 0.666667 0.000000 + vt 0.000000 0.333333 + vt 0.000000 0.000000 + vt 0.333333 0.000000 + vt 0.333333 1.000000 + vt 0.000000 1.000000 + vt 0.000000 0.666667 + vt 0.333333 0.333333 + vt 0.333333 0.666667 + vt 1.000000 0.000000 + vn 0.000000 -1.000000 0.000000 + vn 0.000000 1.000000 0.000000 + vn 1.000000 0.000000 0.000000 + vn -0.000000 0.000000 1.000000 + vn -1.000000 -0.000000 -0.000000 + vn 0.000000 0.000000 -1.000000 + s off + f 2/1/1 3/2/1 4/3/1 + f 8/1/2 7/4/2 6/5/2 + f 5/6/3 6/7/3 2/8/3 + f 6/8/4 7/5/4 3/4/4 + f 3/9/5 7/10/5 8/11/5 + f 1/12/6 4/13/6 8/11/6 + f 1/4/1 2/1/1 4/3/1 + f 5/14/2 8/1/2 6/5/2 + f 1/12/3 5/6/3 2/8/3 + f 2/12/4 6/8/4 3/4/4 + f 4/13/5 3/9/5 8/11/5 + f 5/6/6 1/12/6 8/11/6)"; + +struct SceneLoader_LoadScene +{ + const char* Path; + ElemSceneFormat SceneFormat; + uint32_t ExpectedNodeCount; + uint32_t ExpectedMeshCount; + uint32_t ExpectedVertexCount; +}; + +UTEST_F_SETUP(SceneLoader_LoadScene) +{ + AddTestFile("Cube.obj", { .Items = (uint8_t*)cubeObjSceneSource, .Length = (uint32_t)strlen(cubeObjSceneSource) }); +} + +UTEST_F_TEARDOWN(SceneLoader_LoadScene) +{ + // Act + auto result = ElemLoadScene(utest_fixture->Path, NULL); + + // Assert + ASSERT_FALSE(result.HasErrors); + + ASSERT_EQ_MSG(result.SceneFormat, utest_fixture->SceneFormat, "Scene format is invalid."); + + // TODO: + ASSERT_EQ_MSG(result.Nodes.Length, utest_fixture->ExpectedNodeCount, "Node count is not correct."); + ASSERT_EQ_MSG(result.Meshes.Length, utest_fixture->ExpectedMeshCount, "Mesh count is not correct."); + + ElemSceneMesh mesh = result.Meshes.Items[0]; + ElemSceneMeshPrimitive meshPart = mesh.MeshPrimitives.Items[0]; + + ASSERT_EQ_MSG(meshPart.VertexBuffer.VertexCount, utest_fixture->ExpectedVertexCount, "MeshPrimitive vertex count is not correct."); + ASSERT_GT_MSG(meshPart.VertexBuffer.VertexSize, 0u, "Vertex size must be greater than 0."); + ASSERT_GT_MSG(meshPart.VertexBuffer.Data.Length, 0u, "VertexBuffer size must be greater than 0."); + + bool isEmpty = true; + + for (uint32_t i = 0; i < meshPart.VertexBuffer.Data.Length; i++) + { + if (meshPart.VertexBuffer.Data.Items[i] != 0) + { + isEmpty = false; + break; + } + } + + ASSERT_FALSE_MSG(isEmpty, "VertexBuffer must contains data."); + + // TODO: Add more data checks based on the format +} + +UTEST_F(SceneLoader_LoadScene, Obj) +{ + utest_fixture->Path = "Cube.obj"; + utest_fixture->SceneFormat = ElemSceneFormat_Obj; + utest_fixture->ExpectedMeshCount = 1; + utest_fixture->ExpectedNodeCount = 1; + + // TODO: To change + utest_fixture->ExpectedVertexCount = 36; +} diff --git a/tests/ToolsTests/ShaderCompilerTests.cpp b/tests/ToolsTests/ShaderCompilerTests.cpp index ccffc9d7..6439d3f1 100644 --- a/tests/ToolsTests/ShaderCompilerTests.cpp +++ b/tests/ToolsTests/ShaderCompilerTests.cpp @@ -4,11 +4,10 @@ enum ShaderType { ShaderType_Unknown = 0, - ShaderType_Amplification = 1, - ShaderType_Mesh = 2, - ShaderType_Pixel = 3, - ShaderType_Compute = 4, - ShaderType_Library = 5 + ShaderType_Mesh = 1, + ShaderType_Pixel = 2, + ShaderType_Compute = 3, + ShaderType_Library = 4 }; enum ShaderMetadataType @@ -24,20 +23,6 @@ auto hlslTestSource = R"( { } - struct AmplificationPayload - { - float Test; - }; - - groupshared AmplificationPayload sharedPayload; - - [shader("amplification")] - [numthreads(1, 1, 1)] - void TestAmplification(in uint3 groupID : SV_GroupID) - { - DispatchMesh(1, 1, 1, sharedPayload); - } - struct VertexOutput { float4 Position: SV_Position; @@ -77,6 +62,7 @@ auto hlslTestSourceError = R"( } )"; + UTEST(ShaderCompiler, CanCompileShaderTest_HlslToDirectX12) { // Act @@ -122,7 +108,7 @@ struct ShaderInfo struct ShaderCompiler_CompileShader { - ElemShaderLanguage SourceLanguage; + const char* ShaderPath; ElemToolsGraphicsApi TargetGraphicsApi; ElemToolsPlatform TargetPlatform; ShaderInfo ExpectedShaders[16]; @@ -146,18 +132,6 @@ UTEST_F_SETUP(ShaderCompiler_CompileShader) .MetaDataCount = 1 }; - utest_fixture->ExpectedShaders[utest_fixture->ExpectedShaderCount++] = - { - .Type = ShaderType_Amplification, - .Name = "TestAmplification", - .Metadata = { - { - .Type = ShaderMetadataType_ThreadGroupSize, - .Value = { 1u, 1u, 1u, 0u } - }}, - .MetaDataCount = 1 - }; - utest_fixture->ExpectedShaders[utest_fixture->ExpectedShaderCount++] = { .Type = ShaderType_Mesh, @@ -181,6 +155,9 @@ UTEST_F_SETUP(ShaderCompiler_CompileShader) }}, .MetaDataCount = 1 }; + + AddTestFile("HlslTestSource.hlsl", { .Items = (uint8_t*)hlslTestSource, .Length = (uint32_t)strlen(hlslTestSource) }); + AddTestFile("HlslTestSourceError.hlsl", { .Items = (uint8_t*)hlslTestSourceError, .Length = (uint32_t)strlen(hlslTestSourceError) }); } void AssertCompilationErrors(int32_t* utest_result, ShaderCompiler_CompileShader* utest_fixture, const ElemShaderCompilationResult* compilationResult) @@ -296,21 +273,8 @@ void AssertShaderData(int32_t* utest_result, ShaderCompiler_CompileShader* utest UTEST_F_TEARDOWN(ShaderCompiler_CompileShader) { - // Arrange - auto sourceCode = !utest_fixture->WithError ? hlslTestSource : hlslTestSourceError; - - ElemShaderSourceData sourceData = - { - .ShaderLanguage = utest_fixture->SourceLanguage, - .Data = - { - .Items = (uint8_t*)sourceCode, - .Length = (uint32_t)strlen(sourceCode) - } - }; - // Act - auto compilationResult = ElemCompileShaderLibrary(utest_fixture->TargetGraphicsApi, utest_fixture->TargetPlatform, &sourceData, nullptr); + auto compilationResult = ElemCompileShaderLibrary(utest_fixture->TargetGraphicsApi, utest_fixture->TargetPlatform, utest_fixture->ShaderPath, nullptr); // Assert AssertCompilationErrors(utest_result, utest_fixture, &compilationResult); @@ -319,7 +283,7 @@ UTEST_F_TEARDOWN(ShaderCompiler_CompileShader) UTEST_F(ShaderCompiler_CompileShader, Hlsl_DirectX12_Windows) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSource.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_DirectX12; utest_fixture->TargetPlatform = ElemToolsPlatform_Windows; utest_fixture->WithError = false; @@ -327,7 +291,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_DirectX12_Windows) UTEST_F(ShaderCompiler_CompileShader, Hlsl_DirectX12_Windows_Error) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSourceError.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_DirectX12; utest_fixture->TargetPlatform = ElemToolsPlatform_Windows; utest_fixture->WithError = true; @@ -335,7 +299,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_DirectX12_Windows_Error) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Windows) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSource.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Vulkan; utest_fixture->TargetPlatform = ElemToolsPlatform_Windows; utest_fixture->WithError = false; @@ -343,7 +307,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Windows) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Windows_Error) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSourceError.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Vulkan; utest_fixture->TargetPlatform = ElemToolsPlatform_Windows; utest_fixture->WithError = true; @@ -351,7 +315,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Windows_Error) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Linux) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSource.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Vulkan; utest_fixture->TargetPlatform = ElemToolsPlatform_Linux; utest_fixture->WithError = false; @@ -359,7 +323,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Linux) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Linux_Error) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSourceError.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Vulkan; utest_fixture->TargetPlatform = ElemToolsPlatform_Linux; utest_fixture->WithError = true; @@ -368,7 +332,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Vulkan_Linux_Error) #ifndef __linux__ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_MacOS) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSource.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Metal; utest_fixture->TargetPlatform = ElemToolsPlatform_MacOS; utest_fixture->WithError = false; @@ -376,7 +340,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_MacOS) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_MacOS_Error) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSourceError.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Metal; utest_fixture->TargetPlatform = ElemToolsPlatform_MacOS; utest_fixture->WithError = true; @@ -384,7 +348,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_MacOS_Error) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_iOS) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSource.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Metal; utest_fixture->TargetPlatform = ElemToolsPlatform_iOS; utest_fixture->WithError = false; @@ -392,7 +356,7 @@ UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_iOS) UTEST_F(ShaderCompiler_CompileShader, Hlsl_Metal_iOS_Error) { - utest_fixture->SourceLanguage = ElemShaderLanguage_Hlsl; + utest_fixture->ShaderPath = "HlslTestSourceError.hlsl"; utest_fixture->TargetGraphicsApi = ElemToolsGraphicsApi_Metal; utest_fixture->TargetPlatform = ElemToolsPlatform_iOS; utest_fixture->WithError = true; diff --git a/tests/ToolsTests/ToolsTests.cpp b/tests/ToolsTests/ToolsTests.cpp new file mode 100644 index 00000000..88a6a569 --- /dev/null +++ b/tests/ToolsTests/ToolsTests.cpp @@ -0,0 +1,44 @@ +#include "ToolsTests.h" + +#define MAX_TEST_FILES 256 + +struct TestFileEntry +{ + char Path[256]; + ElemToolsDataSpan Data; +}; + +static TestFileEntry GlobalTestFiles[MAX_TEST_FILES]; +static uint32_t GlobalTestFilesCount; + +ElemToolsDataSpan LoadTestFilesHandler(const char* path) +{ + for (uint32_t i = 0; i < GlobalTestFilesCount; i++) + { + auto testFile = GlobalTestFiles[i]; + + if (strstr(path, testFile.Path)) + { + return testFile.Data; + } + } + + return {}; +} + +void ConfigureTestFileIO() +{ + ElemToolsConfigureFileIO(LoadTestFilesHandler); +} + +void AddTestFile(const char* path, ElemToolsDataSpan data) +{ + TestFileEntry fileEntry + { + .Data = data + }; + + strncpy(fileEntry.Path, path, strlen(path)); + + GlobalTestFiles[GlobalTestFilesCount++] = fileEntry; +} diff --git a/tests/ToolsTests/ToolsTests.h b/tests/ToolsTests/ToolsTests.h index c7b4ce81..1081db26 100644 --- a/tests/ToolsTests/ToolsTests.h +++ b/tests/ToolsTests/ToolsTests.h @@ -1,5 +1,7 @@ #pragma once #include "ElementalTools.h" +#include "utest.h" -// TODO: Add Utils functions +void ConfigureTestFileIO(); +void AddTestFile(const char* path, ElemToolsDataSpan data); diff --git a/tests/ToolsTests/UnityBuild.cpp b/tests/ToolsTests/UnityBuild.cpp index f29003d4..465d3353 100644 --- a/tests/ToolsTests/UnityBuild.cpp +++ b/tests/ToolsTests/UnityBuild.cpp @@ -1,4 +1,12 @@ +#include "ToolsTests.cpp" #include "ShaderCompilerTests.cpp" -#include "utest.h" +#include "SceneLoaderTests.cpp" +#include "MeshBuilderTests.cpp" -UTEST_MAIN(); +UTEST_STATE(); + +int main(int argc, const char* argv[]) +{ + ConfigureTestFileIO(); + return utest_main(argc, argv); +} diff --git a/utilities/CodeGenerator/CLoaderCodeGenerator.cs b/utilities/CodeGenerator/CLoaderCodeGenerator.cs index 0a739943..b4f9ad8d 100644 --- a/utilities/CodeGenerator/CLoaderCodeGenerator.cs +++ b/utilities/CodeGenerator/CLoaderCodeGenerator.cs @@ -274,9 +274,16 @@ public void GenerateCode(CppCompilation compilation, string source, string input parameterValuesBuilder.Append(", "); } - builder.Append($"{parameter.Type.GetDisplayName()} {parameter.Name}"); - functionsStringBuilder.Append($"{parameter.Type.GetDisplayName()}"); - functionsDeclarationStringBuilder.Append($"{parameter.Type.GetDisplayName()}"); + var parameterType = parameter.Type.GetDisplayName(); + + if (parameterType == "unsigned long long") + { + parameterType = "uint64_t"; + } + + builder.Append($"{parameterType} {parameter.Name}"); + functionsStringBuilder.Append($"{parameterType}"); + functionsDeclarationStringBuilder.Append($"{parameterType}"); parameterValuesBuilder.Append($"{parameter.Name}"); } } diff --git a/utilities/CodeGenerator/CodeGenerator.csproj b/utilities/CodeGenerator/CodeGenerator.csproj index 01617ecd..eb5cd5c8 100644 --- a/utilities/CodeGenerator/CodeGenerator.csproj +++ b/utilities/CodeGenerator/CodeGenerator.csproj @@ -13,7 +13,7 @@ - + diff --git a/utilities/CodeGenerator/DotnetCodeGenerator.cs b/utilities/CodeGenerator/DotnetCodeGenerator.cs index 151d9e7c..6dad9ef2 100644 --- a/utilities/CodeGenerator/DotnetCodeGenerator.cs +++ b/utilities/CodeGenerator/DotnetCodeGenerator.cs @@ -233,7 +233,9 @@ private bool GenerateServiceFunction(CppCompilation compilation, string serviceN if (structType != null) { - if (NeedCustomMarshaller(compilation, structType)) + var structGenerationInfo = ComputeStructGenerationInfo(compilation, structType); + + if (structGenerationInfo.NeedsCustomMarshaller) { customMarshallerTypes.Add(parameter.Name, structType); continue; @@ -249,11 +251,16 @@ private bool GenerateServiceFunction(CppCompilation compilation, string serviceN } } + // TODO: Refactor utf 8 marshalling with result.ApplicationPath = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(resultUnsafe.ApplicationPath); + + var returnNeedsMarshalling = false; CppType? returnTypeMarshalling = null; if (function.ReturnType.GetDisplayName() != "void") { + var structGenerationInfo = ComputeStructGenerationInfo(compilation, function.ReturnType); + if (function.ReturnType.GetDisplayName().Contains("Span")) { var returnName = function.ReturnType.GetDisplayName().Replace("Elem", string.Empty); @@ -268,7 +275,7 @@ private bool GenerateServiceFunction(CppCompilation compilation, string serviceN returnNeedsMarshalling = true; } - else if (NeedCustomMarshaller(compilation, function.ReturnType)) + else if (structGenerationInfo.NeedsCustomMarshaller) { returnNeedsMarshalling = true; returnTypeMarshalling = function.ReturnType; @@ -553,10 +560,10 @@ private void GenerateInterop(string name, string outputPath, CppCompilation comp Indent(stringBuilder); stringBuilder.AppendLine("[UnmanagedCallConv(CallConvs = new[] { typeof(CallConvCdecl) })]"); - var returnNeedsMarshalling = NeedCustomMarshaller(compilation, function.ReturnType); + var returnStructGenerationInfo = ComputeStructGenerationInfo(compilation, function.ReturnType); Indent(stringBuilder); - stringBuilder.Append($"internal static partial {MapType(compilation, function.ReturnType, forInterop: true, isReturnValue: true)}{(returnNeedsMarshalling ? "Unsafe" : "")} {functionName}("); + stringBuilder.Append($"internal static partial {MapType(compilation, function.ReturnType, forInterop: true, isReturnValue: true)}{(returnStructGenerationInfo.NeedsCustomMarshaller ? "Unsafe" : "")} {functionName}("); var firstParameter = true; @@ -584,7 +591,9 @@ private void GenerateInterop(string name, string outputPath, CppCompilation comp if (structType != null) { - if (NeedCustomMarshaller(compilation, structType)) + var structGenerationInfo = ComputeStructGenerationInfo(compilation, structType); + + if (structGenerationInfo.NeedsCustomMarshaller) { stringBuilder.Append($"{MapType(compilation, parameter.Type)}Unsafe {parameter.Name}"); continue; @@ -649,29 +658,20 @@ private void GenerateDelegate(CppCompilation compilation, CppType canonicalType, Console.WriteLine($"Generate Delegate: {canonicalType.GetDisplayName()}"); - var needMarshaller = false; var result = FunctionPointerRegex().Match(canonicalType.GetDisplayName()); if (result.Success) { - stringBuilder.Append("##MARSHALLER##"); - stringBuilder.AppendLine("[UnmanagedFunctionPointer(CallingConvention.Cdecl)]"); stringBuilder.Append($"public delegate {result.Groups[1].Value.Trim()} {typeName}("); var parameterResults = FunctionPointerParameterRegex().Matches(result.Groups[2].Value); var firstParameter = true; - var parametersDeclaration = new StringBuilder(); - var parametersValue = new StringBuilder(); - var customBeforeCode = new StringBuilder(); - var parameterChecks = new StringBuilder(); foreach (Match parameterResult in parameterResults) { if (!firstParameter) { stringBuilder.Append(", "); - parametersDeclaration.Append(", "); - parametersValue.Append(", "); } else { @@ -691,47 +691,10 @@ private void GenerateDelegate(CppCompilation compilation, CppType canonicalType, var parameterName = parameterResult.Groups[2].Value.Trim(); - if (parameterType.Contains("Span")) - { - needMarshaller = true; - - customBeforeCode.AppendLine(); - customBeforeCode.AppendLine($$""" - var {{parameterName}}Counter = 0; - var {{parameterName}}Pointer = (byte*){{parameterName}}; - - while ({{parameterName}}Pointer[{{parameterName}}Counter] != 0) - { - {{parameterName}}Counter++; - } - - {{parameterName}}Counter++; - """); - } - stringBuilder.Append($"{parameterType} {parameterName}"); - parametersDeclaration.Append($"{MapTypeToUnmanaged(parameterType)} {parameterName}"); - parametersValue.Append($"{MapValueToUnmanaged(parameterType, parameterName)}"); - parameterChecks.Append($" || {parameterName} == null"); } stringBuilder.AppendLine(");"); - - if (!needMarshaller) - { - stringBuilder.Replace("##MARSHALLER##", string.Empty); - } - else - { - stringBuilder.Replace("##MARSHALLER##", $"[NativeMarshalling(typeof({typeName}Marshaller))]\n"); - stringBuilder.AppendLine(); - stringBuilder.AppendLine(HandlerMarshallerTemplate); - stringBuilder.Replace("##TYPENAME##", typeName); - stringBuilder.Replace("##PARAMETERS_DECLARATION##", parametersDeclaration.ToString()); - stringBuilder.Replace("##PARAMETERS_VALUE##", parametersValue.ToString()); - stringBuilder.Replace("##CUSTOM_BEFORE_CODE##", customBeforeCode.ToString()); - stringBuilder.Replace("##PARAMETER_CHECKS##", parameterChecks.ToString()); - } } } @@ -838,8 +801,17 @@ private void GenerateEnum(CppType type, string typeName, StringBuilder stringBui stringBuilder.AppendLine("}"); } - private bool NeedCustomMarshaller(CppCompilation compilation, CppType type) + public readonly record struct StructGenerationInfo + { + public bool NeedsCustomMarshaller { get; init; } + public bool UseGenerics { get; init; } + } + + private StructGenerationInfo ComputeStructGenerationInfo(CppCompilation compilation, CppType type) { + bool needsCutomMarshaller = false; + bool useGenerics = false; + CppClass? structType = null; if (type is CppClass classType) @@ -852,7 +824,7 @@ private bool NeedCustomMarshaller(CppCompilation compilation, CppType type) if (qualifiedType == null) { - return false; + return new StructGenerationInfo {}; } var classTypeTemp = qualifiedType.ElementType as CppClass; @@ -865,18 +837,26 @@ private bool NeedCustomMarshaller(CppCompilation compilation, CppType type) if (structType == null) { - return false; + return new StructGenerationInfo {}; } foreach (var field in structType.Fields) { if (FieldNeedCustomMarshaller(compilation, field.Type)) { - return true; + needsCutomMarshaller = true; + } + else if(FieldIsGeneric(compilation, field.Type)) + { + useGenerics = true; } } - return false; + return new StructGenerationInfo + { + NeedsCustomMarshaller = needsCutomMarshaller, + UseGenerics = useGenerics + }; } private bool FieldNeedCustomMarshaller(CppCompilation compilation, CppType type) @@ -891,6 +871,18 @@ private bool FieldNeedCustomMarshaller(CppCompilation compilation, CppType type) return false; } + private bool FieldIsGeneric(CppCompilation compilation, CppType type) + { + var mappedType = MapType(compilation, type); + + if (mappedType.Contains("T")) + { + return true; + } + + return false; + } + private void GenerateStruct(CppCompilation compilation, CppType type, string typeName, StringBuilder stringBuilder) { var structType = type as CppClass; @@ -905,11 +897,11 @@ private void GenerateStruct(CppCompilation compilation, CppType type, string typ GenerateComment(stringBuilder, structType.Comment, 0); } - var needsCustomMarshaller = NeedCustomMarshaller(compilation, structType); + var structGenerationInfo = ComputeStructGenerationInfo(compilation, structType); - if (typeName.Contains("Options") || typeName.Contains("Parameters")) + if (structGenerationInfo.UseGenerics) { - stringBuilder.AppendLine($"public ref struct {typeName}"); + stringBuilder.AppendLine($"public ref struct {typeName} where T : unmanaged"); } else { @@ -942,7 +934,7 @@ private void GenerateStruct(CppCompilation compilation, CppType type, string typ stringBuilder.AppendLine("}"); - if (needsCustomMarshaller) + if (structGenerationInfo.NeedsCustomMarshaller) { stringBuilder.AppendLine(); stringBuilder.AppendLine($"internal unsafe struct {typeName}Unsafe"); @@ -1112,7 +1104,8 @@ private string GetSpanUnsafe(CppCompilation compilation, string value) if (cppType != null) { - needsCustomMarshalling = NeedCustomMarshaller(compilation, cppType); + var structGenerationInfo = ComputeStructGenerationInfo(compilation, cppType); + needsCustomMarshalling = structGenerationInfo.NeedsCustomMarshaller; } return $"SpanUnsafe<{value}{(needsCustomMarshalling ? "Unsafe" : "")}>"; @@ -1135,6 +1128,8 @@ string value when value.Contains("Span") && isUnsafe => $"{value.Substring(0, va string value when value.Contains("Span") && forInterop => GetSpanUnsafe(compilation, value.Substring(0, value.IndexOf("Span"))), string value when value.Contains("Span") && isReturnValue => $"ReadOnlySpan<{value.Substring(0, value.IndexOf("Span"))}>", string value when value.Contains("Span") => $"ReadOnlySpan<{value.Substring(0, value.IndexOf("Span"))}>", + string value when value == "void *" && isUnsafe => "nint", + string value when value == "void *" => "T", string value when ConstStructPointerRegex().IsMatch(value) => "in " + ConstStructPointerRegex().Match(value).Groups[1].Value, string value when value.Contains("HandlerPtr") => typeName.Replace("Ptr", string.Empty), _ => typeName diff --git a/utilities/cmake/AppPackaging.cmake b/utilities/cmake/AppPackaging.cmake index 96d9eab7..baa0a5bb 100644 --- a/utilities/cmake/AppPackaging.cmake +++ b/utilities/cmake/AppPackaging.cmake @@ -1,97 +1,219 @@ -function(configure_resource_compilation target_name resource_list) +function(get_compiler_bin_path binary_name out_var) if(BUILD_FOR_IOS) - set(SHADER_COMPILER_BIN "${CMAKE_SOURCE_DIR}/build/bin/ShaderCompiler.app/Contents/MacOS/ShaderCompiler") - set(DEFAULT_SHADER_COMPILER_OPTIONS "--target-platform" "iOS") + set(bin_path "${CMAKE_SOURCE_DIR}/build/bin/${binary_name}.app/Contents/MacOS/${binary_name}") else() if(CMAKE_GENERATOR STREQUAL "Ninja") - if (APPLE) - set(SHADER_COMPILER_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ShaderCompiler.app/Contents/MacOS/ShaderCompiler") + if(APPLE) + set(bin_path "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${binary_name}.app/Contents/MacOS/${binary_name}") else() - set(SHADER_COMPILER_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ShaderCompiler/ShaderCompiler") + set(bin_path "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${binary_name}/${binary_name}") endif() else() - set(SHADER_COMPILER_BIN "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/ShaderCompiler") + set(bin_path "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/${binary_name}") endif() - set(DEFAULT_SHADER_COMPILER_OPTIONS "") - add_dependencies(${target_name} ShaderCompiler) - endif() - - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - list(APPEND DEFAULT_SHADER_COMPILER_OPTIONS "--debug") endif() + set(${out_var} "${bin_path}" PARENT_SCOPE) +endfunction() + - # TODO: Take into account other type of resources - set(hlsl_files_list "") - set(compiled_shaders "") +function(configure_resources_for_compiler + target_name # e.g. MyApp + result_var # e.g. compiled_files_for_X + name # e.g. HLSL + binary_name # e.g. ShaderCompiler + source_exts # ".hlsl" + dest_ext # ".shader" + resource_root # the folder from resource_list if it is a directory + ) + # Convert commas to semicolons if present + string(REPLACE "," ";" source_exts "${source_exts}") get_target_property(target_sources ${target_name} SOURCES) - foreach(source IN LISTS target_sources) - if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${source}) - file(GLOB_RECURSE directory_hlsl_files "${CMAKE_CURRENT_SOURCE_DIR}/${source}/*.hlsl") - list(APPEND hlsl_files_list ${directory_hlsl_files}) + set(resource_files "") + foreach(src IN LISTS target_sources) + # Combine the "folder or file" from resource_root with the target source + # if your calling code is using it that way. (This part might vary depending on your usage.) + set(abs_src "${CMAKE_CURRENT_SOURCE_DIR}/${src}") + + # If we are dealing with a directory in resource_root or if 'src' *is* that directory, + # then GLOB_RECURSE subfiles + if(IS_DIRECTORY "${abs_src}") + foreach(ext IN LISTS source_exts) + file(GLOB_RECURSE matched_files "${abs_src}/*${ext}") + list(APPEND resource_files ${matched_files}) + endforeach() else() - get_filename_component(ext ${source} EXT) - if("${ext}" STREQUAL ".hlsl") - list(APPEND hlsl_files_list ${CMAKE_CURRENT_SOURCE_DIR}/${source}) - endif() + # It's a single file—check extension + get_filename_component(this_ext "${abs_src}" EXT) + foreach(ext IN LISTS source_exts) + if("${this_ext}" STREQUAL "${ext}") + list(APPEND resource_files "${abs_src}") + break() + endif() + endforeach() endif() endforeach() - foreach(hlsl_file IN LISTS hlsl_files_list) - get_filename_component(file_path ${hlsl_file} ABSOLUTE) - get_filename_component(file_dir ${file_path} DIRECTORY) - get_filename_component(file_name ${hlsl_file} NAME_WE) + if(resource_files STREQUAL "") + set(${result_var} "" PARENT_SCOPE) + return() + endif() - file(RELATIVE_PATH relative_dir ${CMAKE_CURRENT_SOURCE_DIR} ${file_dir}) - set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/${relative_dir}") - file(MAKE_DIRECTORY ${output_dir}) + # Use the new param as the "root" for the relative subfolder structure + # If resource_root is empty or not a directory, you might choose a fallback: + if(NOT IS_DIRECTORY "${resource_root}") + # fallback if needed + set(resource_root "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + # Get path to the compiler binary + get_compiler_bin_path(${binary_name} compiler_bin) + + # Grab any remaining ARGN as default parameters + # (If you’re using ARGN for something else, adapt accordingly.) + set(args_to_parse ${ARGN}) + set(default_params "") + foreach(param IN LISTS args_to_parse) + if(NOT param STREQUAL "") + list(APPEND default_params ${param}) + endif() + endforeach() + + set(compiled_files "") - set(compiled_shader "${output_dir}/${file_name}.shader") + foreach(file IN LISTS resource_files) + get_filename_component(file_path "${file}" ABSOLUTE) + get_filename_component(file_dir "${file_path}" DIRECTORY) + get_filename_component(file_name "${file_path}" NAME_WE) - SET(SHADER_COMPILER_OPTIONS ${DEFAULT_SHADER_COMPILER_OPTIONS}) + # Compute the relative subfolder structure from 'resource_root' + file(RELATIVE_PATH relative_dir "${resource_root}" "${file_dir}") - add_custom_command(OUTPUT ${compiled_shader} - COMMAND ${SHADER_COMPILER_BIN} ${SHADER_COMPILER_OPTIONS} ${hlsl_file} ${compiled_shader} - DEPENDS ${hlsl_file} - COMMENT "Compiling HLSL shader: ${hlsl_file}" - WORKING_DIRECTORY ${file_dir} + # Reproduce that structure inside the build folder + set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/${relative_dir}") + file(MAKE_DIRECTORY "${output_dir}") + + set(output_file "${output_dir}/${file_name}${dest_ext}") + + add_custom_command( + OUTPUT "${output_file}" + COMMAND "${compiler_bin}" ${default_params} "${file}" "${output_file}" + DEPENDS "${file}" + COMMENT "Compiling ${name}: ${file}" + WORKING_DIRECTORY "${file_dir}" ) - set_source_files_properties(${compiled_shader} PROPERTIES GENERATED TRUE) - target_sources(${target_name} PRIVATE ${compiled_shader}) + set_source_files_properties("${output_file}" PROPERTIES GENERATED TRUE) + target_sources("${target_name}" PRIVATE "${output_file}") + list(APPEND compiled_files "${output_file}") - list(APPEND compiled_shaders ${compiled_shader}) - - if (WIN32) - set(compiled_shader_vulkan "${output_dir}/${file_name}_vulkan.shader") - list(APPEND SHADER_COMPILER_OPTIONS "--target-api" "vulkan") - - add_custom_command(OUTPUT ${compiled_shader_vulkan} - COMMAND ${SHADER_COMPILER_BIN} ${SHADER_COMPILER_OPTIONS} ${hlsl_file} ${compiled_shader_vulkan} - DEPENDS ${hlsl_file} - COMMENT "Compiling HLSL shader for vulkan: ${hlsl_file}" - WORKING_DIRECTORY ${file_dir} + # Extra Vulkan variant for HLSL on Windows + if("${name}" STREQUAL "HLSL" AND WIN32) + set(vulkan_output_file "${output_dir}/${file_name}_vulkan${dest_ext}") + add_custom_command( + OUTPUT "${vulkan_output_file}" + COMMAND "${compiler_bin}" ${default_params} --target-api vulkan "${file}" "${vulkan_output_file}" + DEPENDS "${file}" + COMMENT "Compiling ${name} (Vulkan): ${file}" + WORKING_DIRECTORY "${file_dir}" ) + set_source_files_properties("${vulkan_output_file}" PROPERTIES GENERATED TRUE) + target_sources("${target_name}" PRIVATE "${vulkan_output_file}") + list(APPEND compiled_files "${vulkan_output_file}") + endif() + endforeach() + + set(${result_var} "${compiled_files}" PARENT_SCOPE) +endfunction() - set_source_files_properties(${compiled_shader_vulkan} PROPERTIES GENERATED TRUE) - target_sources(${target_name} PRIVATE ${compiled_shader_vulkan}) - list(APPEND compiled_shaders ${compiled_shader_vulkan}) +function(configure_resource_compilation target_name resource_list) + # iOS, debug logic, etc. + if(BUILD_FOR_IOS) + set(SHADER_COMPILER_DEFAULT_OPTIONS "--target-platform iOS") + else() + set(SHADER_COMPILER_DEFAULT_OPTIONS "") + add_dependencies(${target_name} ShaderCompiler) + endif() + + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(NOT "${SHADER_COMPILER_DEFAULT_OPTIONS}" STREQUAL "") + set(SHADER_COMPILER_DEFAULT_OPTIONS "${SHADER_COMPILER_DEFAULT_OPTIONS} --debug") + else() + set(SHADER_COMPILER_DEFAULT_OPTIONS "--debug") endif() + endif() + + set(MESH_COMPILER_DEFAULT_OPTIONS "") + set(TEXTURE_COMPILER_DEFAULT_OPTIONS "") + + # Use '|' as delimiters + set(COMPILERS_LIST + "HLSL|ShaderCompiler|.hlsl|.shader|${SHADER_COMPILER_DEFAULT_OPTIONS}" + "MESH|SceneCompiler|.obj|.scene|${MESH_COMPILER_DEFAULT_OPTIONS}" + "MESH|SceneCompiler|.gltf|.scene|${MESH_COMPILER_DEFAULT_OPTIONS}" + "TEXTURE|TextureCompiler|.tga|.texture|${TEXTURE_COMPILER_DEFAULT_OPTIONS}" + "TEXTURE|TextureCompiler|.jpg|.texture|${TEXTURE_COMPILER_DEFAULT_OPTIONS}" + "TEXTURE|TextureCompiler|.png|.texture|${TEXTURE_COMPILER_DEFAULT_OPTIONS}" + "TEXTURE|TextureCompiler|.dds|.texture|${TEXTURE_COMPILER_DEFAULT_OPTIONS}" + ) + + set(all_compiled_resources "") + foreach(compiler_entry IN LISTS COMPILERS_LIST) + string(REPLACE "|" ";" fields_string "${compiler_entry}") + set(fields ${fields_string}) + list(LENGTH fields fields_length) + if(fields_length LESS 4) + message(FATAL_ERROR "Invalid compiler definition: ${compiler_entry}") + endif() + + list(GET fields 0 name) + list(GET fields 1 binary_name) + list(GET fields 2 source_exts) + list(GET fields 3 dest_ext) + + if(fields_length GREATER 4) + list(SUBLIST fields 4 -1 default_params) + else() + set(default_params "") + endif() + + #----------------------------------------------------------------- + # Here we call our new configure_resources_for_compiler. + # The last param is the folder from resource_list if you want + # to treat it as the "root" for preserving subfolders. + # + # You could pass `resource_list` directly. If your `resource_list` + # has multiple entries, you might loop them or parse them individually. + #----------------------------------------------------------------- + configure_resources_for_compiler( + "${target_name}" + compiled_files_for_${name} + "${name}" + "${binary_name}" + "${source_exts}" + "${dest_ext}" + "${resource_list}" # TODO: We can remove that parameter + ${default_params} + ) + + list(APPEND all_compiled_resources ${compiled_files_for_${name}}) endforeach() - set(${resource_list} "${compiled_shaders}" PARENT_SCOPE) + set(${resource_list} "${all_compiled_resources}" PARENT_SCOPE) endfunction() + + function(configure_project_package target_name install_folder) cmake_parse_arguments(ARG "" "" "DEPENDENCIES;RESOURCES" ${ARGV}) list(LENGTH ARG_DEPENDENCIES dependencies_length) - message("Number of Dependencies: ${dependencies_length}") + # message("Number of Dependencies: ${dependencies_length}") list(LENGTH ARG_RESOURCES resources_length) - message("Number of resources: ${resources_length}") + # message("Number of resources: ${resources_length}") if(APPLE) set(APPFOLDER_EXTENSION ".app") @@ -100,12 +222,6 @@ function(configure_project_package target_name install_folder) MACOSX_BUNDLE "TRUE" ) - if(NOT resources_length EQUAL 0) - set_target_properties(${target_name} PROPERTIES - RESOURCE "${ARG_RESOURCES}" - ) - endif() - # TODO: Use dependencies variable instead if(CMAKE_GENERATOR STREQUAL "Xcode") set(ELEMENTAL_PATH "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/Elemental.framework") @@ -115,6 +231,23 @@ function(configure_project_package target_name install_folder) XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "YES" XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES" ) + + foreach(res IN LISTS ARG_RESOURCES) + get_filename_component(file_name "${res}" NAME) + get_filename_component(full_path "${res}" ABSOLUTE) + get_filename_component(file_dir "${full_path}" DIRECTORY) + + set(resource_root "${CMAKE_CURRENT_BINARY_DIR}/Data") + + file(RELATIVE_PATH relative_dir "${resource_root}" "${file_dir}") + + set_source_files_properties("${full_path}" + PROPERTIES MACOSX_PACKAGE_LOCATION "${relative_dir}" + ) + + # Attach file to the target so Xcode knows to include it + target_sources(${target_name} PRIVATE "${full_path}") + endforeach() else() add_custom_target(CopyFrameworkFolder${target_name} ALL) @@ -135,6 +268,38 @@ function(configure_project_package target_name install_folder) COMMENT "Copying ${dependency} framework folder to destination" ) endforeach() + + if(NOT resources_length EQUAL 0) + set(output_folder "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target_name}.app/Contents/") + + foreach(file IN LISTS ARG_RESOURCES) + get_filename_component(file_name "${file}" NAME) + get_filename_component(full_path "${file}" ABSOLUTE) + get_filename_component(file_dir "${full_path}" DIRECTORY) + + set(resource_root "${CMAKE_CURRENT_BINARY_DIR}/Data") + + file(RELATIVE_PATH relative_dir "${resource_root}" "${file_dir}") + set(output_subdir "${output_folder}/Resources/${relative_dir}") + file(MAKE_DIRECTORY "${output_subdir}") + + set(output_file "${output_subdir}/${file_name}") + + add_custom_command( + OUTPUT "${output_file}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${output_folder}/Resources" + COMMAND ${CMAKE_COMMAND} -E copy "${full_path}" "${output_file}" + DEPENDS "${full_path}" + COMMENT "Copying and checking resource file ${file_name}" + ) + + list(APPEND output_files "${output_file}") + endforeach() + + add_custom_target(CopyResources${target_name} ALL DEPENDS ${output_files}) + add_dependencies(CopyResources${target_name} ShaderCompiler) + add_dependencies(${target_name} CopyResources${target_name}) + endif() add_dependencies(${target_name} CopyFrameworkFolder${target_name}) endif() @@ -170,7 +335,6 @@ function(configure_project_package target_name install_folder) ) foreach(dependency IN LISTS ARG_DEPENDENCIES) - message("Dep: ${dependency}") add_dependencies(CopyApplicationFolder${target_name} ${dependency}) add_custom_command(TARGET CopyApplicationFolder${target_name} POST_BUILD @@ -183,7 +347,15 @@ function(configure_project_package target_name install_folder) foreach(file IN LISTS ARG_RESOURCES) get_filename_component(file_name "${file}" NAME) get_filename_component(full_path "${file}" ABSOLUTE) - set(output_file "${output_folder}/Data/${file_name}") + get_filename_component(file_dir "${full_path}" DIRECTORY) + + set(resource_root "${CMAKE_CURRENT_BINARY_DIR}/Data") + + file(RELATIVE_PATH relative_dir "${resource_root}" "${file_dir}") + set(output_subdir "${output_folder}/Data/${relative_dir}") + file(MAKE_DIRECTORY "${output_subdir}") + + set(output_file "${output_subdir}/${file_name}") add_custom_command( OUTPUT "${output_file}"