From 7db5b60c651746f6667e57a4e08a8f73fb74629e Mon Sep 17 00:00:00 2001 From: Gareth Morgan Date: Wed, 16 Nov 2022 15:00:47 -0500 Subject: [PATCH] Create main (#45) Add Aurora source code to main branch. --- .clang-format | 32 + .clang-tidy | 11 + .gitattributes | 23 + .github/cla.yml | 42 + .gitignore | 8 + .gitmodules | 0 Applications/CMakeLists.txt | 2 + Applications/Plasma/CMakeLists.txt | 81 + Applications/Plasma/Camera.cpp | 269 ++ Applications/Plasma/Camera.h | 77 + Applications/Plasma/Libraries.cpp | 33 + Applications/Plasma/Loaders.h | 82 + Applications/Plasma/OBJLoader.cpp | 315 ++ Applications/Plasma/PerformanceMonitor.h | 147 + Applications/Plasma/Plasma.cpp | 1661 +++++++ Applications/Plasma/Plasma.h | 176 + Applications/Plasma/SceneContents.cpp | 26 + Applications/Plasma/SceneContents.h | 58 + Applications/Plasma/glTFLoader.cpp | 39 + Applications/Plasma/pch.h | 133 + Applications/Plasma/resource.h | 26 + CMakeLists.txt | 72 + CODE_OF_CONDUCT.md | 82 + CONTRIBUTING.md | 39 + .../ADSKFormCorpContribAgmtforOpenSource.docx | 3 + .../ADSKFormIndContribAgmtforOpenSource.docx | 3 + Doc/CodingStandards.md | 504 ++ Doc/HdAurora.md | 35 + Doc/Plasma.jpg | 3 + Doc/Plasma.md | 65 + Doc/RendererMenu.jpg | 3 + Doc/USDView.JPG | 3 + Doc/sample.jpg | 3 + LICENSE.md | 482 +- Libraries/Aurora/API/Aurora/Aurora.h | 1087 +++++ Libraries/Aurora/API/Aurora/AuroraNames.h | 123 + Libraries/Aurora/CMakeLists.txt | 422 ++ Libraries/Aurora/Source/AliasMap.cpp | 170 + Libraries/Aurora/Source/AliasMap.h | 40 + Libraries/Aurora/Source/AssetManager.cpp | 289 ++ Libraries/Aurora/Source/AssetManager.h | 79 + Libraries/Aurora/Source/Aurora.cpp | 71 + Libraries/Aurora/Source/AuroraNames.cpp | 61 + Libraries/Aurora/Source/DLL.cpp | 55 + .../Aurora/Source/DirectX/DirectXHeaders.h | 80 + .../Source/DirectX/DirectXHeaders/d3dx12.h | 4044 +++++++++++++++++ Libraries/Aurora/Source/DirectX/MemoryPool.h | 210 + Libraries/Aurora/Source/DirectX/PTDevice.cpp | 186 + Libraries/Aurora/Source/DirectX/PTDevice.h | 95 + .../Aurora/Source/DirectX/PTEnvironment.cpp | 78 + .../Aurora/Source/DirectX/PTEnvironment.h | 54 + .../Aurora/Source/DirectX/PTGeometry.cpp | 220 + Libraries/Aurora/Source/DirectX/PTGeometry.h | 90 + .../Aurora/Source/DirectX/PTGroundPlane.cpp | 90 + .../Aurora/Source/DirectX/PTGroundPlane.h | 66 + Libraries/Aurora/Source/DirectX/PTImage.cpp | 160 + Libraries/Aurora/Source/DirectX/PTImage.h | 76 + .../Aurora/Source/DirectX/PTMaterial.cpp | 240 + Libraries/Aurora/Source/DirectX/PTMaterial.h | 81 + .../Aurora/Source/DirectX/PTRenderer.cpp | 1457 ++++++ Libraries/Aurora/Source/DirectX/PTRenderer.h | 231 + Libraries/Aurora/Source/DirectX/PTSampler.cpp | 81 + Libraries/Aurora/Source/DirectX/PTSampler.h | 46 + Libraries/Aurora/Source/DirectX/PTScene.cpp | 844 ++++ Libraries/Aurora/Source/DirectX/PTScene.h | 223 + .../Aurora/Source/DirectX/PTShaderLibrary.cpp | 1088 +++++ .../Aurora/Source/DirectX/PTShaderLibrary.h | 320 ++ Libraries/Aurora/Source/DirectX/PTTarget.cpp | 328 ++ Libraries/Aurora/Source/DirectX/PTTarget.h | 174 + .../Source/DirectX/Shaders/Accumulation.hlsl | 106 + .../Source/DirectX/Shaders/Colors.hlsli | 55 + .../DirectX/Shaders/PostProcessing.hlsl | 166 + Libraries/Aurora/Source/EnvironmentBase.cpp | 73 + Libraries/Aurora/Source/EnvironmentBase.h | 54 + Libraries/Aurora/Source/GeometryBase.cpp | 125 + Libraries/Aurora/Source/GeometryBase.h | 55 + .../Aurora/Source/HGI/HGIEnvironment.cpp | 57 + Libraries/Aurora/Source/HGI/HGIEnvironment.h | 42 + Libraries/Aurora/Source/HGI/HGIGeometry.cpp | 148 + Libraries/Aurora/Source/HGI/HGIGeometry.h | 52 + .../Aurora/Source/HGI/HGIGroundPlane.cpp | 51 + Libraries/Aurora/Source/HGI/HGIGroundPlane.h | 36 + .../Aurora/Source/HGI/HGIHandleWrapper.cpp | 83 + .../Aurora/Source/HGI/HGIHandleWrapper.h | 88 + Libraries/Aurora/Source/HGI/HGIImage.cpp | 67 + Libraries/Aurora/Source/HGI/HGIImage.h | 38 + Libraries/Aurora/Source/HGI/HGIMaterial.cpp | 55 + Libraries/Aurora/Source/HGI/HGIMaterial.h | 42 + .../Aurora/Source/HGI/HGIRenderBuffer.cpp | 78 + Libraries/Aurora/Source/HGI/HGIRenderBuffer.h | 53 + Libraries/Aurora/Source/HGI/HGIRenderer.cpp | 472 ++ Libraries/Aurora/Source/HGI/HGIRenderer.h | 107 + Libraries/Aurora/Source/HGI/HGIScene.cpp | 660 +++ Libraries/Aurora/Source/HGI/HGIScene.h | 138 + Libraries/Aurora/Source/HGI/HGIWindow.cpp | 27 + Libraries/Aurora/Source/HGI/HGIWindow.h | 34 + .../Source/HGI/Shaders/Accumulation.glsl | 39 + .../Source/HGI/Shaders/InstanceData.glsl | 102 + .../Source/HGI/Shaders/PostProcessing.glsl | 73 + Libraries/Aurora/Source/ImageBase.h | 32 + Libraries/Aurora/Source/MaterialBase.cpp | 231 + Libraries/Aurora/Source/MaterialBase.h | 140 + .../Source/MaterialX/BSDFCodeGenerator.cpp | 949 ++++ .../Source/MaterialX/BSDFCodeGenerator.h | 182 + .../Source/MaterialX/MaterialGenerator.cpp | 561 +++ .../Source/MaterialX/MaterialGenerator.h | 52 + Libraries/Aurora/Source/Properties.h | 535 +++ Libraries/Aurora/Source/RendererBase.cpp | 250 + Libraries/Aurora/Source/RendererBase.h | 186 + Libraries/Aurora/Source/ResourceStub.cpp | 459 ++ Libraries/Aurora/Source/ResourceStub.h | 333 ++ Libraries/Aurora/Source/ResourceTracker.h | 291 ++ Libraries/Aurora/Source/Resources.cpp | 322 ++ Libraries/Aurora/Source/Resources.h | 217 + Libraries/Aurora/Source/SceneBase.cpp | 438 ++ Libraries/Aurora/Source/SceneBase.h | 131 + Libraries/Aurora/Source/Shaders/BSDF.slang | 5 + .../Aurora/Source/Shaders/BSDFCommon.slang | 363 ++ .../Source/Shaders/BackgroundMissShader.slang | 36 + .../ClosestHitEntryPointTemplate.slang | 200 + Libraries/Aurora/Source/Shaders/Colors.slang | 55 + .../Aurora/Source/Shaders/Environment.slang | 220 + Libraries/Aurora/Source/Shaders/Frame.slang | 95 + .../Aurora/Source/Shaders/GLSLToHLSL.slang | 84 + .../Aurora/Source/Shaders/Geometry.slang | 151 + Libraries/Aurora/Source/Shaders/Globals.slang | 77 + .../Aurora/Source/Shaders/GroundPlane.slang | 169 + .../InitializeDefaultMaterialType.slang | 23 + .../LayerShaderEntryPointTemplate.slang | 147 + .../Aurora/Source/Shaders/Material.slang | 314 ++ .../Source/Shaders/MaterialXCommon.slang | 20 + .../Source/Shaders/PathTracingCommon.slang | 201 + .../Source/Shaders/RadianceMissShader.slang | 39 + Libraries/Aurora/Source/Shaders/Random.slang | 148 + .../Aurora/Source/Shaders/RayGenShader.slang | 108 + .../Aurora/Source/Shaders/RayTrace.slang | 355 ++ .../Aurora/Source/Shaders/ReferenceBSDF.slang | 372 ++ .../Aurora/Source/Shaders/Sampling.slang | 109 + .../Source/Shaders/ShadeFunctions.slang | 240 + .../Shaders/ShadowHitEntryPointTemplate.slang | 80 + .../Source/Shaders/ShadowMissShader.slang | 21 + .../Source/Shaders/StandardSurfaceBSDF.slang | 1293 ++++++ Libraries/Aurora/Source/Transpiler.cpp | 238 + Libraries/Aurora/Source/Transpiler.h | 54 + Libraries/Aurora/Source/WindowsHeaders.h | 116 + Libraries/Aurora/Source/pch.h | 142 + Libraries/CMakeLists.txt | 3 + .../API/Aurora/Foundation/BoundingBox.h | 148 + .../API/Aurora/Foundation/Frustum.h | 141 + .../Foundation/API/Aurora/Foundation/Log.h | 331 ++ .../Foundation/API/Aurora/Foundation/Plane.h | 120 + .../Foundation/API/Aurora/Foundation/Timer.h | 289 ++ .../API/Aurora/Foundation/Utilities.h | 144 + Libraries/Foundation/CMakeLists.txt | 27 + Libraries/Foundation/Source/Log.cpp | 74 + Libraries/Foundation/Source/Utilities.cpp | 106 + Libraries/HdAurora/CMakeLists.txt | 67 + Libraries/HdAurora/DLL.cpp | 52 + Libraries/HdAurora/HdAuroraCamera.cpp | 108 + Libraries/HdAurora/HdAuroraCamera.h | 31 + Libraries/HdAurora/HdAuroraImageCache.cpp | 133 + Libraries/HdAurora/HdAuroraImageCache.h | 43 + Libraries/HdAurora/HdAuroraInstancer.cpp | 188 + Libraries/HdAurora/HdAuroraInstancer.h | 32 + Libraries/HdAurora/HdAuroraLight.cpp | 157 + Libraries/HdAurora/HdAuroraLight.h | 50 + Libraries/HdAurora/HdAuroraMaterial.cpp | 661 +++ Libraries/HdAurora/HdAuroraMaterial.h | 80 + Libraries/HdAurora/HdAuroraMesh.cpp | 804 ++++ Libraries/HdAurora/HdAuroraMesh.h | 61 + Libraries/HdAurora/HdAuroraPlugin.cpp | 82 + Libraries/HdAurora/HdAuroraPlugin.h | 33 + Libraries/HdAurora/HdAuroraRenderBuffer.cpp | 514 +++ Libraries/HdAurora/HdAuroraRenderBuffer.h | 82 + Libraries/HdAurora/HdAuroraRenderDelegate.cpp | 559 +++ Libraries/HdAurora/HdAuroraRenderDelegate.h | 164 + Libraries/HdAurora/HdAuroraRenderPass.cpp | 177 + Libraries/HdAurora/HdAuroraRenderPass.h | 45 + Libraries/HdAurora/HdAuroraTokens.h | 92 + Libraries/HdAurora/pch.h | 87 + Libraries/HdAurora/resources/plugInfo.json | 22 + README.md | 209 +- Scripts/cmake/externals.cmake | 85 + Scripts/cmake/modules/FindD3D12.cmake | 107 + Scripts/cmake/modules/FindNRD.cmake | 94 + Scripts/cmake/modules/FindNRI.cmake | 62 + Scripts/cmake/modules/FindSlang.cmake | 68 + Scripts/cmake/modules/Findstb.cmake | 36 + Scripts/cmake/modules/Findtinyobjloader.cmake | 55 + Scripts/cmake/toolbox.cmake | 43 + Scripts/deployHdAurora.py | 126 + Scripts/installExternals.py | 1479 ++++++ Scripts/minifyShaders.py | 111 + Scripts/minifyShadersFolder.py | 148 + Scripts/runCLangFormat.py | 15 + .../Materials/Decals/test_decal_mask.mtlx | 57 + .../Decals/test_decal_nottransparent.mtlx | 28 + .../Decals/test_decal_transparent.mtlx | 49 + Tests/Assets/Materials/FishScale.mtlx | 33 + Tests/Assets/Materials/HdAuroraTest.mtlx | 22 + Tests/Assets/Materials/NormalMapTest.mtlx | 31 + Tests/Assets/Materials/TestBMPTexture.mtlx | 28 + Tests/Assets/Materials/TestMaterial.mtlx | 12 + Tests/Assets/Materials/TestTexture.mtlx | 28 + Tests/Assets/Textures/CoatOfArms.bmp | Bin 0 -> 196662 bytes Tests/Assets/Textures/Hieroglyphs.jpg | 3 + Tests/Assets/Textures/Mandrill.png | 3 + Tests/Assets/Textures/Mr._Smiley_Face.png | 3 + Tests/Assets/Textures/Triangle.png | 3 + Tests/Assets/Textures/fishscale_basecolor.jpg | 3 + Tests/Assets/Textures/fishscale_normal.png | 3 + Tests/Assets/Textures/fishscale_roughness.png | 3 + Tests/Aurora/AuroraMain.cpp | 33 + .../Images/TestGammaImage_0.DirectX.png | 3 + .../Images/TestGammaImage_1.HGI.png | 3 + .../Images/TestImageDefault_0.DirectX.png | 3 + .../Images/TestImageDefault_1.HGI.png | 3 + .../Images/TestImageOpacity_0.DirectX.png | 3 + .../Images/TestImageOpacity_1.HGI.png | 3 + .../TestImageSamplers_0.DirectXClamp.png | 3 + .../TestImageSamplers_0.DirectXMirror.png | 3 + .../Images/TestImageSamplers_1.HGIClamp.png | 3 + .../Images/TestImageSamplers_1.HGIMirror.png | 3 + .../Images/TestNormalMapImage_0.DirectX.png | 3 + .../Images/TestNormalMapImage_1.HGI.png | 3 + .../TestLightEnvTextureMIS_0.DirectX.png | 3 + .../Light/TestLightEnvTextureMIS_1.HGI.png | 3 + .../Light/TestLightEnvTexture_0.DirectX.png | 3 + .../Light/TestLightEnvTexture_1.HGI.png | 3 + ...AuroraMaterialX_0.DirectX_HdAuroraMtlX.png | 3 + ...stHdAuroraMaterialX_1.HGI_HdAuroraMtlX.png | 3 + ...perties_0.DirectX_CoatAffect_Reference.png | 3 + ...s_0.DirectX_CoatAffect_StandardSurface.png | 3 + ...ialProperties_0.DirectX_Coat_Reference.png | 3 + ...perties_0.DirectX_Coat_StandardSurface.png | 3 + ...alProperties_0.DirectX_Sheen_Reference.png | 3 + ...erties_0.DirectX_Sheen_StandardSurface.png | 3 + ...perties_0.DirectX_Subsurface_Reference.png | 3 + ...s_0.DirectX_Subsurface_StandardSurface.png | 3 + ...rties_0.DirectX_Transmission_Reference.png | 3 + ...0.DirectX_Transmission_StandardSurface.png | 3 + ...lProperties_1.HGI_CoatAffect_Reference.png | 3 + ...rties_1.HGI_CoatAffect_StandardSurface.png | 3 + ...aterialProperties_1.HGI_Coat_Reference.png | 3 + ...lProperties_1.HGI_Coat_StandardSurface.png | 3 + ...terialProperties_1.HGI_Sheen_Reference.png | 3 + ...Properties_1.HGI_Sheen_StandardSurface.png | 3 + ...lProperties_1.HGI_Subsurface_Reference.png | 3 + ...rties_1.HGI_Subsurface_StandardSurface.png | 3 + ...roperties_1.HGI_Transmission_Reference.png | 3 + ...ies_1.HGI_Transmission_StandardSurface.png | 3 + ...ialProperties_0.DirectX_Base_Reference.png | 3 + ...perties_0.DirectX_Base_StandardSurface.png | 3 + ...operties_0.DirectX_Metalness_Reference.png | 3 + ...es_0.DirectX_Metalness_StandardSurface.png | 3 + ...operties_0.DirectX_Roughness_Reference.png | 3 + ...es_0.DirectX_Roughness_StandardSurface.png | 3 + ....DirectX_Roughness_TinyMetal_Reference.png | 3 + ...tX_Roughness_TinyMetal_StandardSurface.png | 3 + ...roperties_0.DirectX_Specular_Reference.png | 3 + ...ies_0.DirectX_Specular_StandardSurface.png | 3 + ...aterialProperties_1.HGI_Base_Reference.png | 3 + ...lProperties_1.HGI_Base_StandardSurface.png | 3 + ...alProperties_1.HGI_Metalness_Reference.png | 3 + ...erties_1.HGI_Metalness_StandardSurface.png | 3 + ...alProperties_1.HGI_Roughness_Reference.png | 3 + ...erties_1.HGI_Roughness_StandardSurface.png | 3 + ...es_1.HGI_Roughness_TinyMetal_Reference.png | 3 + ...GI_Roughness_TinyMetal_StandardSurface.png | 3 + ...ialProperties_1.HGI_Specular_Reference.png | 3 + ...perties_1.HGI_Specular_StandardSurface.png | 3 + ...rMaterialProperty_0.DirectX_AfterClear.png | 3 + ...MaterialProperty_0.DirectX_BeforeClear.png | 3 + ...ClearMaterialProperty_1.HGI_AfterClear.png | 3 + ...learMaterialProperty_1.HGI_BeforeClear.png | 3 + ...terialDuplicateMaterialTypes_0.DirectX.png | 3 + ...tMaterialTransparency_0.DirectXOpacity.png | 3 + ...terialTransparency_0.DirectXThinWalled.png | 3 + ...rialTransparency_0.DirectXTransmission.png | 3 + .../TestMaterialTransparency_1.HGIOpacity.png | 3 + ...stMaterialTransparency_1.HGIThinWalled.png | 3 + ...MaterialTransparency_1.HGITransmission.png | 3 + .../Materials/TestMaterialTypes_0.DirectX.png | 3 + .../Materials/TestMaterialXBMP_0.DirectX.png | 3 + ...tMaterialXFlipImageY_0.DirectX_Flipped.png | 3 + ...terialXFlipImageY_0.DirectX_NotFlipped.png | 3 + .../TestMaterialX_0.DirectX_FromString.png | 3 + ...tMaterialX_0.DirectX_ParameterOverride.png | 3 + .../Materials/TestMtlXSamplers_0.DirectX.png | 3 + .../Materials/TestMtlXSamplers_1.HGI.png | 3 + .../Paths/TestPathDefault_0.DirectX0.png | 3 + .../Paths/TestPathDefault_0.DirectX1.png | 3 + .../Paths/TestPathDefault_0.DirectX2.png | 3 + .../Paths/TestPathDefault_1.HGI0.png | 3 + .../Paths/TestPathDefault_1.HGI1.png | 3 + .../Paths/TestPathDefault_1.HGI2.png | 3 + .../TestMaterialMaterialXLayers_0.DirectX.png | 3 + ...erialMaterialXLayers_0.DirectX_Removed.png | 3 + .../TestRendererAlpha_0.DirectX.png | 3 + ...TestRendererBackFaceLighting_0.DirectX.png | 3 + .../TestRendererBackFaceLighting_1.HGI.png | 3 + ...rCreateDestroyThenRenderFull_0.DirectX.png | 3 + ...dererCreateDestroyThenRenderFull_1.HGI.png | 3 + ...stRendererInstanceProperties_0.DirectX.png | 3 + .../TestRendererInstanceProperties_1.HGI.png | 3 + .../TestRendererMaterialLayers_0.DirectX.png | 3 + .../TestRendererMaterialLayers_1.HGI.png | 3 + ...ndererMultipleMaterialLayers_0.DirectX.png | 3 + .../TestRendererNonIndexedGeom_0.DirectX.png | 3 + .../TestRendererNonIndexedGeom_1.HGI.png | 3 + ...ndererOrthographicProjection_0.DirectX.png | 3 + ...stRendererOrthographicProjection_1.HGI.png | 3 + ...stRendererRemoveInstance_0.DirectXBoth.png | 3 + ...rRemoveInstance_0.DirectXTeapotRemoved.png | 3 + .../TestRendererRemoveInstance_1.HGIBoth.png | 3 + ...dererRemoveInstance_1.HGITeapotRemoved.png | 3 + Tests/Aurora/CMakeLists.txt | 95 + Tests/Aurora/Tests/TestBenchmarks.cpp | 174 + Tests/Aurora/Tests/TestImage.cpp | 354 ++ Tests/Aurora/Tests/TestLight.cpp | 255 ++ Tests/Aurora/Tests/TestMaterial.cpp | 1307 ++++++ Tests/Aurora/Tests/TestPaths.cpp | 195 + Tests/Aurora/Tests/TestRenderer.cpp | 1208 +++++ Tests/AuroraInternals/AuroraInternalsMain.cpp | 33 + Tests/AuroraInternals/CMakeLists.txt | 91 + .../Common/TestAssetManager.cpp | 71 + .../AuroraInternals/Common/TestProperties.cpp | 115 + .../AuroraInternals/Common/TestResources.cpp | 362 ++ .../MaterialX/TestBSDFGenerator.cpp | 254 ++ Tests/CMakeLists.txt | 61 + Tests/Foundation/CMakeLists.txt | 57 + Tests/Foundation/FoundationMain.cpp | 33 + Tests/Foundation/Tests/TestDispatch.cpp | 230 + Tests/Foundation/Tests/TestLogger.cpp | 182 + Tests/Foundation/Tests/TestMath.cpp | 151 + Tests/Foundation/Tests/TestUtilities.cpp | 67 + Tests/Helpers/AuroraTestHelpers.cpp | 753 +++ Tests/Helpers/AuroraTestHelpers.h | 304 ++ Tests/Helpers/BaselineImageHelpers.cpp | 262 ++ Tests/Helpers/BaselineImageHelpers.h | 106 + Tests/Helpers/RendererTestHelpers.cpp | 71 + Tests/Helpers/RendererTestHelpers.h | 115 + Tests/Helpers/TeapotModel.h | 362 ++ Tests/Helpers/TestHelpers.cpp | 240 + Tests/Helpers/TestHelpers.h | 191 + sample.jpg | Bin 225279 -> 0 bytes 346 files changed, 47668 insertions(+), 484 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitattributes create mode 100644 .github/cla.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Applications/CMakeLists.txt create mode 100644 Applications/Plasma/CMakeLists.txt create mode 100644 Applications/Plasma/Camera.cpp create mode 100644 Applications/Plasma/Camera.h create mode 100644 Applications/Plasma/Libraries.cpp create mode 100644 Applications/Plasma/Loaders.h create mode 100644 Applications/Plasma/OBJLoader.cpp create mode 100644 Applications/Plasma/PerformanceMonitor.h create mode 100644 Applications/Plasma/Plasma.cpp create mode 100644 Applications/Plasma/Plasma.h create mode 100644 Applications/Plasma/SceneContents.cpp create mode 100644 Applications/Plasma/SceneContents.h create mode 100644 Applications/Plasma/glTFLoader.cpp create mode 100644 Applications/Plasma/pch.h create mode 100644 Applications/Plasma/resource.h create mode 100644 CMakeLists.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Doc/CLA/ADSKFormCorpContribAgmtforOpenSource.docx create mode 100644 Doc/CLA/ADSKFormIndContribAgmtforOpenSource.docx create mode 100644 Doc/CodingStandards.md create mode 100644 Doc/HdAurora.md create mode 100644 Doc/Plasma.jpg create mode 100644 Doc/Plasma.md create mode 100644 Doc/RendererMenu.jpg create mode 100644 Doc/USDView.JPG create mode 100644 Doc/sample.jpg create mode 100644 Libraries/Aurora/API/Aurora/Aurora.h create mode 100644 Libraries/Aurora/API/Aurora/AuroraNames.h create mode 100644 Libraries/Aurora/CMakeLists.txt create mode 100644 Libraries/Aurora/Source/AliasMap.cpp create mode 100644 Libraries/Aurora/Source/AliasMap.h create mode 100644 Libraries/Aurora/Source/AssetManager.cpp create mode 100644 Libraries/Aurora/Source/AssetManager.h create mode 100644 Libraries/Aurora/Source/Aurora.cpp create mode 100644 Libraries/Aurora/Source/AuroraNames.cpp create mode 100644 Libraries/Aurora/Source/DLL.cpp create mode 100644 Libraries/Aurora/Source/DirectX/DirectXHeaders.h create mode 100644 Libraries/Aurora/Source/DirectX/DirectXHeaders/d3dx12.h create mode 100644 Libraries/Aurora/Source/DirectX/MemoryPool.h create mode 100644 Libraries/Aurora/Source/DirectX/PTDevice.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTDevice.h create mode 100644 Libraries/Aurora/Source/DirectX/PTEnvironment.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTEnvironment.h create mode 100644 Libraries/Aurora/Source/DirectX/PTGeometry.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTGeometry.h create mode 100644 Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTGroundPlane.h create mode 100644 Libraries/Aurora/Source/DirectX/PTImage.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTImage.h create mode 100644 Libraries/Aurora/Source/DirectX/PTMaterial.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTMaterial.h create mode 100644 Libraries/Aurora/Source/DirectX/PTRenderer.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTRenderer.h create mode 100644 Libraries/Aurora/Source/DirectX/PTSampler.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTSampler.h create mode 100644 Libraries/Aurora/Source/DirectX/PTScene.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTScene.h create mode 100644 Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTShaderLibrary.h create mode 100644 Libraries/Aurora/Source/DirectX/PTTarget.cpp create mode 100644 Libraries/Aurora/Source/DirectX/PTTarget.h create mode 100644 Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl create mode 100644 Libraries/Aurora/Source/DirectX/Shaders/Colors.hlsli create mode 100644 Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl create mode 100644 Libraries/Aurora/Source/EnvironmentBase.cpp create mode 100644 Libraries/Aurora/Source/EnvironmentBase.h create mode 100644 Libraries/Aurora/Source/GeometryBase.cpp create mode 100644 Libraries/Aurora/Source/GeometryBase.h create mode 100644 Libraries/Aurora/Source/HGI/HGIEnvironment.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIEnvironment.h create mode 100644 Libraries/Aurora/Source/HGI/HGIGeometry.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIGeometry.h create mode 100644 Libraries/Aurora/Source/HGI/HGIGroundPlane.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIGroundPlane.h create mode 100644 Libraries/Aurora/Source/HGI/HGIHandleWrapper.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIHandleWrapper.h create mode 100644 Libraries/Aurora/Source/HGI/HGIImage.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIImage.h create mode 100644 Libraries/Aurora/Source/HGI/HGIMaterial.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIMaterial.h create mode 100644 Libraries/Aurora/Source/HGI/HGIRenderBuffer.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIRenderBuffer.h create mode 100644 Libraries/Aurora/Source/HGI/HGIRenderer.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIRenderer.h create mode 100644 Libraries/Aurora/Source/HGI/HGIScene.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIScene.h create mode 100644 Libraries/Aurora/Source/HGI/HGIWindow.cpp create mode 100644 Libraries/Aurora/Source/HGI/HGIWindow.h create mode 100644 Libraries/Aurora/Source/HGI/Shaders/Accumulation.glsl create mode 100644 Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl create mode 100644 Libraries/Aurora/Source/HGI/Shaders/PostProcessing.glsl create mode 100644 Libraries/Aurora/Source/ImageBase.h create mode 100644 Libraries/Aurora/Source/MaterialBase.cpp create mode 100644 Libraries/Aurora/Source/MaterialBase.h create mode 100644 Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp create mode 100644 Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h create mode 100644 Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp create mode 100644 Libraries/Aurora/Source/MaterialX/MaterialGenerator.h create mode 100644 Libraries/Aurora/Source/Properties.h create mode 100644 Libraries/Aurora/Source/RendererBase.cpp create mode 100644 Libraries/Aurora/Source/RendererBase.h create mode 100644 Libraries/Aurora/Source/ResourceStub.cpp create mode 100644 Libraries/Aurora/Source/ResourceStub.h create mode 100644 Libraries/Aurora/Source/ResourceTracker.h create mode 100644 Libraries/Aurora/Source/Resources.cpp create mode 100644 Libraries/Aurora/Source/Resources.h create mode 100644 Libraries/Aurora/Source/SceneBase.cpp create mode 100644 Libraries/Aurora/Source/SceneBase.h create mode 100644 Libraries/Aurora/Source/Shaders/BSDF.slang create mode 100644 Libraries/Aurora/Source/Shaders/BSDFCommon.slang create mode 100644 Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang create mode 100644 Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang create mode 100644 Libraries/Aurora/Source/Shaders/Colors.slang create mode 100644 Libraries/Aurora/Source/Shaders/Environment.slang create mode 100644 Libraries/Aurora/Source/Shaders/Frame.slang create mode 100644 Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang create mode 100644 Libraries/Aurora/Source/Shaders/Geometry.slang create mode 100644 Libraries/Aurora/Source/Shaders/Globals.slang create mode 100644 Libraries/Aurora/Source/Shaders/GroundPlane.slang create mode 100644 Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang create mode 100644 Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang create mode 100644 Libraries/Aurora/Source/Shaders/Material.slang create mode 100644 Libraries/Aurora/Source/Shaders/MaterialXCommon.slang create mode 100644 Libraries/Aurora/Source/Shaders/PathTracingCommon.slang create mode 100644 Libraries/Aurora/Source/Shaders/RadianceMissShader.slang create mode 100644 Libraries/Aurora/Source/Shaders/Random.slang create mode 100644 Libraries/Aurora/Source/Shaders/RayGenShader.slang create mode 100644 Libraries/Aurora/Source/Shaders/RayTrace.slang create mode 100644 Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang create mode 100644 Libraries/Aurora/Source/Shaders/Sampling.slang create mode 100644 Libraries/Aurora/Source/Shaders/ShadeFunctions.slang create mode 100644 Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang create mode 100644 Libraries/Aurora/Source/Shaders/ShadowMissShader.slang create mode 100644 Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang create mode 100644 Libraries/Aurora/Source/Transpiler.cpp create mode 100644 Libraries/Aurora/Source/Transpiler.h create mode 100644 Libraries/Aurora/Source/WindowsHeaders.h create mode 100644 Libraries/Aurora/Source/pch.h create mode 100644 Libraries/CMakeLists.txt create mode 100644 Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h create mode 100644 Libraries/Foundation/API/Aurora/Foundation/Frustum.h create mode 100644 Libraries/Foundation/API/Aurora/Foundation/Log.h create mode 100644 Libraries/Foundation/API/Aurora/Foundation/Plane.h create mode 100644 Libraries/Foundation/API/Aurora/Foundation/Timer.h create mode 100644 Libraries/Foundation/API/Aurora/Foundation/Utilities.h create mode 100644 Libraries/Foundation/CMakeLists.txt create mode 100644 Libraries/Foundation/Source/Log.cpp create mode 100644 Libraries/Foundation/Source/Utilities.cpp create mode 100644 Libraries/HdAurora/CMakeLists.txt create mode 100644 Libraries/HdAurora/DLL.cpp create mode 100644 Libraries/HdAurora/HdAuroraCamera.cpp create mode 100644 Libraries/HdAurora/HdAuroraCamera.h create mode 100644 Libraries/HdAurora/HdAuroraImageCache.cpp create mode 100644 Libraries/HdAurora/HdAuroraImageCache.h create mode 100644 Libraries/HdAurora/HdAuroraInstancer.cpp create mode 100644 Libraries/HdAurora/HdAuroraInstancer.h create mode 100644 Libraries/HdAurora/HdAuroraLight.cpp create mode 100644 Libraries/HdAurora/HdAuroraLight.h create mode 100644 Libraries/HdAurora/HdAuroraMaterial.cpp create mode 100644 Libraries/HdAurora/HdAuroraMaterial.h create mode 100644 Libraries/HdAurora/HdAuroraMesh.cpp create mode 100644 Libraries/HdAurora/HdAuroraMesh.h create mode 100644 Libraries/HdAurora/HdAuroraPlugin.cpp create mode 100644 Libraries/HdAurora/HdAuroraPlugin.h create mode 100644 Libraries/HdAurora/HdAuroraRenderBuffer.cpp create mode 100644 Libraries/HdAurora/HdAuroraRenderBuffer.h create mode 100644 Libraries/HdAurora/HdAuroraRenderDelegate.cpp create mode 100644 Libraries/HdAurora/HdAuroraRenderDelegate.h create mode 100644 Libraries/HdAurora/HdAuroraRenderPass.cpp create mode 100644 Libraries/HdAurora/HdAuroraRenderPass.h create mode 100644 Libraries/HdAurora/HdAuroraTokens.h create mode 100644 Libraries/HdAurora/pch.h create mode 100644 Libraries/HdAurora/resources/plugInfo.json create mode 100644 Scripts/cmake/externals.cmake create mode 100644 Scripts/cmake/modules/FindD3D12.cmake create mode 100644 Scripts/cmake/modules/FindNRD.cmake create mode 100644 Scripts/cmake/modules/FindNRI.cmake create mode 100644 Scripts/cmake/modules/FindSlang.cmake create mode 100644 Scripts/cmake/modules/Findstb.cmake create mode 100644 Scripts/cmake/modules/Findtinyobjloader.cmake create mode 100644 Scripts/cmake/toolbox.cmake create mode 100644 Scripts/deployHdAurora.py create mode 100644 Scripts/installExternals.py create mode 100644 Scripts/minifyShaders.py create mode 100644 Scripts/minifyShadersFolder.py create mode 100644 Scripts/runCLangFormat.py create mode 100644 Tests/Assets/Materials/Decals/test_decal_mask.mtlx create mode 100644 Tests/Assets/Materials/Decals/test_decal_nottransparent.mtlx create mode 100644 Tests/Assets/Materials/Decals/test_decal_transparent.mtlx create mode 100644 Tests/Assets/Materials/FishScale.mtlx create mode 100644 Tests/Assets/Materials/HdAuroraTest.mtlx create mode 100644 Tests/Assets/Materials/NormalMapTest.mtlx create mode 100644 Tests/Assets/Materials/TestBMPTexture.mtlx create mode 100644 Tests/Assets/Materials/TestMaterial.mtlx create mode 100644 Tests/Assets/Materials/TestTexture.mtlx create mode 100644 Tests/Assets/Textures/CoatOfArms.bmp create mode 100644 Tests/Assets/Textures/Hieroglyphs.jpg create mode 100644 Tests/Assets/Textures/Mandrill.png create mode 100644 Tests/Assets/Textures/Mr._Smiley_Face.png create mode 100644 Tests/Assets/Textures/Triangle.png create mode 100644 Tests/Assets/Textures/fishscale_basecolor.jpg create mode 100644 Tests/Assets/Textures/fishscale_normal.png create mode 100644 Tests/Assets/Textures/fishscale_roughness.png create mode 100644 Tests/Aurora/AuroraMain.cpp create mode 100644 Tests/Aurora/BaselineImages/Images/TestGammaImage_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestGammaImage_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageDefault_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageDefault_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageOpacity_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageOpacity_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageSamplers_0.DirectXClamp.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageSamplers_0.DirectXMirror.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageSamplers_1.HGIClamp.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestImageSamplers_1.HGIMirror.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestNormalMapImage_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Images/TestNormalMapImage_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Light/TestLightEnvTextureMIS_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Light/TestLightEnvTextureMIS_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Light/TestLightEnvTexture_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Light/TestLightEnvTexture_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestHdAuroraMaterialX_0.DirectX_HdAuroraMtlX.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestHdAuroraMaterialX_1.HGI_HdAuroraMtlX.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_CoatAffect_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_CoatAffect_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Coat_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Coat_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Sheen_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Sheen_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Subsurface_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Subsurface_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Transmission_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_0.DirectX_Transmission_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_CoatAffect_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_CoatAffect_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Coat_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Coat_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Sheen_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Sheen_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Subsurface_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Subsurface_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Transmission_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialAdvancedMaterialProperties_1.HGI_Transmission_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Base_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Base_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Metalness_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Metalness_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Roughness_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Roughness_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Roughness_TinyMetal_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Roughness_TinyMetal_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Specular_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_0.DirectX_Specular_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Base_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Base_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Metalness_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Metalness_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Roughness_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Roughness_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Roughness_TinyMetal_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Roughness_TinyMetal_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Specular_Reference.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialBasicMaterialProperties_1.HGI_Specular_StandardSurface.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialClearMaterialProperty_0.DirectX_AfterClear.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialClearMaterialProperty_0.DirectX_BeforeClear.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialClearMaterialProperty_1.HGI_AfterClear.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialClearMaterialProperty_1.HGI_BeforeClear.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialDuplicateMaterialTypes_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTransparency_0.DirectXOpacity.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTransparency_0.DirectXThinWalled.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTransparency_0.DirectXTransmission.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTransparency_1.HGIOpacity.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTransparency_1.HGIThinWalled.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTransparency_1.HGITransmission.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialTypes_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialXBMP_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialXFlipImageY_0.DirectX_Flipped.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialXFlipImageY_0.DirectX_NotFlipped.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialX_0.DirectX_FromString.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMaterialX_0.DirectX_ParameterOverride.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMtlXSamplers_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/Materials/TestMtlXSamplers_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/Paths/TestPathDefault_0.DirectX0.png create mode 100644 Tests/Aurora/BaselineImages/Paths/TestPathDefault_0.DirectX1.png create mode 100644 Tests/Aurora/BaselineImages/Paths/TestPathDefault_0.DirectX2.png create mode 100644 Tests/Aurora/BaselineImages/Paths/TestPathDefault_1.HGI0.png create mode 100644 Tests/Aurora/BaselineImages/Paths/TestPathDefault_1.HGI1.png create mode 100644 Tests/Aurora/BaselineImages/Paths/TestPathDefault_1.HGI2.png create mode 100644 Tests/Aurora/BaselineImages/TestMaterialMaterialXLayers_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestMaterialMaterialXLayers_0.DirectX_Removed.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererAlpha_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererBackFaceLighting_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererBackFaceLighting_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererCreateDestroyThenRenderFull_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererCreateDestroyThenRenderFull_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererInstanceProperties_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererInstanceProperties_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererMaterialLayers_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererMaterialLayers_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererMultipleMaterialLayers_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererNonIndexedGeom_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererNonIndexedGeom_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererOrthographicProjection_0.DirectX.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererOrthographicProjection_1.HGI.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererRemoveInstance_0.DirectXBoth.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererRemoveInstance_0.DirectXTeapotRemoved.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererRemoveInstance_1.HGIBoth.png create mode 100644 Tests/Aurora/BaselineImages/TestRendererRemoveInstance_1.HGITeapotRemoved.png create mode 100644 Tests/Aurora/CMakeLists.txt create mode 100644 Tests/Aurora/Tests/TestBenchmarks.cpp create mode 100644 Tests/Aurora/Tests/TestImage.cpp create mode 100644 Tests/Aurora/Tests/TestLight.cpp create mode 100644 Tests/Aurora/Tests/TestMaterial.cpp create mode 100644 Tests/Aurora/Tests/TestPaths.cpp create mode 100644 Tests/Aurora/Tests/TestRenderer.cpp create mode 100644 Tests/AuroraInternals/AuroraInternalsMain.cpp create mode 100644 Tests/AuroraInternals/CMakeLists.txt create mode 100644 Tests/AuroraInternals/Common/TestAssetManager.cpp create mode 100644 Tests/AuroraInternals/Common/TestProperties.cpp create mode 100644 Tests/AuroraInternals/Common/TestResources.cpp create mode 100644 Tests/AuroraInternals/MaterialX/TestBSDFGenerator.cpp create mode 100644 Tests/CMakeLists.txt create mode 100644 Tests/Foundation/CMakeLists.txt create mode 100644 Tests/Foundation/FoundationMain.cpp create mode 100644 Tests/Foundation/Tests/TestDispatch.cpp create mode 100644 Tests/Foundation/Tests/TestLogger.cpp create mode 100644 Tests/Foundation/Tests/TestMath.cpp create mode 100644 Tests/Foundation/Tests/TestUtilities.cpp create mode 100644 Tests/Helpers/AuroraTestHelpers.cpp create mode 100644 Tests/Helpers/AuroraTestHelpers.h create mode 100644 Tests/Helpers/BaselineImageHelpers.cpp create mode 100644 Tests/Helpers/BaselineImageHelpers.h create mode 100644 Tests/Helpers/RendererTestHelpers.cpp create mode 100644 Tests/Helpers/RendererTestHelpers.h create mode 100644 Tests/Helpers/TeapotModel.h create mode 100644 Tests/Helpers/TestHelpers.cpp create mode 100644 Tests/Helpers/TestHelpers.h delete mode 100644 sample.jpg diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..fe5a601 --- /dev/null +++ b/.clang-format @@ -0,0 +1,32 @@ +--- +# "Cpp11" includes C++14 in the version of clang-format (9) used in Visual Studio. +BasedOnStyle: Microsoft +Standard: Cpp11 + +# The following options override those from the Microsoft style. +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: true +AllowAllParametersOfDeclarationOnNextLine: true +AlignOperands: false +AllowShortFunctionsOnASingleLine : Inline +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + AfterCaseLabel: true + AfterUnion: true + # Enable this when when clang-format version in Visual Studio supports it. + # BeforeLambdaBody: true +BreakConstructorInitializers: AfterColon +ColumnLimit: 100 +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +Cpp11BracedListStyle: false +PointerBindsToType: true +SpaceBeforeCpp11BracedList: true + +# Do NOT disable SortIncludes like this. If you get a compile error after sorting includes, then +# use blank lines to separate the includes into blocks and include a comment to explain it. This +# should happen rarely. +# SortIncludes: false +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..9f62a15 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,11 @@ +--- +Checks: > + clang-analyzer-*, + clang-diagnostic-*, + modernize-avoid-c-arrays, + modernize-loop-convert, + modernize-make-shared, + modernize-make-unique, + modernize-use-auto, + modernize-use-default-member-init +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fc6f87e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Track large files with Git LFS. +############################################################################### +*.[Bb][Ii][Nn] filter=lfs diff=lfs merge=lfs -text +*.[Dd][Oo][Cc][Xx] filter=lfs diff=lfs merge=lfs -text +*.[Ee][Xx][Ee] filter=lfs diff=lfs merge=lfs -text +*.[Ee][Xx][Rr] filter=lfs diff=lfs merge=lfs -text +*.[Gg][Ll][Bb] filter=lfs diff=lfs merge=lfs -text +*.[Gg][Ll][Tt][Ff] filter=lfs diff=lfs merge=lfs -text +*.[Hh][Dd][Rr] filter=lfs diff=lfs merge=lfs -text +*.[Ii][Cc][Oo] filter=lfs diff=lfs merge=lfs -text +*.[Jj][Pp][Ee][Gg] filter=lfs diff=lfs merge=lfs -text +*.[Jj][Pp][Gg] filter=lfs diff=lfs merge=lfs -text +*.[Kk][Tt][Xx] filter=lfs diff=lfs merge=lfs -text +*.[Pp][Nn][Gg] filter=lfs diff=lfs merge=lfs -text +*.[Pp][Pp][Tt][Xx] filter=lfs diff=lfs merge=lfs -text +*.[Rr][Cc] filter=lfs diff=lfs merge=lfs -text +*.[Uu][Ss][Dd] filter=lfs diff=lfs merge=lfs -text diff --git a/.github/cla.yml b/.github/cla.yml new file mode 100644 index 0000000..f22ac08 --- /dev/null +++ b/.github/cla.yml @@ -0,0 +1,42 @@ +name: "CLA Assistant" +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened,closed,synchronize] + +jobs: + CLA-Assistant: + runs-on: ubuntu-latest + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + # Beta Release + uses: contributor-assistant/github-action@v2.1.3-beta + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # the below token should have repo scope and must be manually added by you in the repository's secret + PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_BOT_SECRET }} + with: + remote-repository-name: 'CLA-Signatures' + remote-organization-name: 'Autodesk' + path-to-signatures: 'signatures/Autodesk/Individual/Aurora.json' + path-to-document: 'https://github.com/Autodesk/CLA-Assistant-Test-Signatures/blob/master/CLA.md' # e.g. a CLA or a DCO document + # branch should not be protected + branch: 'main' + allowlist: user1,bot* + + #below are the optional inputs - If the optional inputs are not given, then default values will be taken + #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) + #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) + #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' + #signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo' + custom-notsigned-prcomment: | + Thank you for your submission, we really appreciate it. We ask that you sign our Contributor License Agreement before we can accept your contribution. + + If you are contributing on behalf of your employer you must fill out our **Corporate Contributor License Agreement** which can be found [here](https://github.com/Autodesk/autodesk.github.io/releases/download/1.0/ADSK.Form.Corp.Contrib.Agmt.for.Open.Source.docx). + If you are contributing on behalf of yourself you must agree to our **Individual Contributor License Agreement** by reviewing [this document](https://github.com/Autodesk/autodesk.github.io/releases/download/1.0/ADSK.Form.Ind.Contrib.Agmt.for.Open.Source.docx) and signing it or by replying below a with a comment containing the following text: + #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA' + #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.' + #lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true) + #use-dco-flag: true - If you are using DCO instead of CLA \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be4b543 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Ignore build directories. +Build/ +_build/ + +# Ignore IDE cache folders +.vs/ +.vscode/ +.idea/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/Applications/CMakeLists.txt b/Applications/CMakeLists.txt new file mode 100644 index 0000000..0f6c74f --- /dev/null +++ b/Applications/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(Plasma) + diff --git a/Applications/Plasma/CMakeLists.txt b/Applications/Plasma/CMakeLists.txt new file mode 100644 index 0000000..1ba0279 --- /dev/null +++ b/Applications/Plasma/CMakeLists.txt @@ -0,0 +1,81 @@ +project(Plasma) + +if(WIN32) + option(ENABLE_INTERACTIVE_PLASMA "Build Plasma as an interactive viewer." ON) +else() + set(ENABLE_INTERACTIVE_PLASMA OFF) +endif() + +# Find packages used by Plasma application. +find_package(glm REQUIRED) # Find the GLM vector maths package. +find_package(cxxopts REQUIRED) +find_package(stb REQUIRED) +find_package(TinyGLTF REQUIRED) +find_package(tinyobjloader REQUIRED) + +# Add test executable with all source files. +add_executable(${PROJECT_NAME} + "Camera.cpp" + "Camera.h" + "Plasma.cpp" + "Plasma.h" + "glTFLoader.cpp" + "Libraries.cpp" + "Loaders.h" + "OBJLoader.cpp" + "pch.h" + "PerformanceMonitor.h" + "resource.h" + "SceneContents.cpp" + "SceneContents.h" +) + +# Set custom ouput properties. +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER "Applications" + RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" +) + +if(WIN32 AND ENABLE_INTERACTIVE_PLASMA) + set(WINSDK_LIB Pathcch.lib Shlwapi.lib) +else() + set(WINSDK_LIB "") +endif() + +# Add dependencies. +target_link_libraries(${PROJECT_NAME} +PRIVATE + glm::glm + cxxopts::cxxopts + stb::stb + tinyobjloader::tinyobjloader + Foundation + Aurora + ${WINSDK_LIB} + ${CMAKE_DL_LIBS} +) + +target_include_directories(${PROJECT_NAME} +PRIVATE + "${TinyGLTF_INCLUDE_DIR}" +) + +if(WIN32 AND ENABLE_INTERACTIVE_PLASMA) + # set windows-specific properties including WIN32 executable (gui app) + set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE) +endif() + +# Add default compile definitions (set in root CMakefile) +if(ENABLE_INTERACTIVE_PLASMA) + set(PLASMA_DEFINITIONS INTERACTIVE_PLASMA) +else() + set(PLASMA_DEFINITIONS "") +endif() +target_compile_definitions(${PROJECT_NAME} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} + ${PLASMA_DEFINITIONS} +) diff --git a/Applications/Plasma/Camera.cpp b/Applications/Plasma/Camera.cpp new file mode 100644 index 0000000..da59add --- /dev/null +++ b/Applications/Plasma/Camera.cpp @@ -0,0 +1,269 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "Camera.h" + +// The canonical direction vectors. +static constexpr vec3 kForward = vec3(0.0f, 0.0f, -1.0f); +static constexpr vec3 kRight = vec3(1.0f, 0.0f, 0.0f); +static constexpr vec3 kUp = vec3(0.0f, 1.0f, 0.0f); + +const mat4& Camera::viewMatrix() +{ + // Create a view matrix if it needs to be updated. + if (_isViewDirty) + { + _viewMatrix = lookAt(_eye, _target, _up); + _isViewDirty = false; + } + + return _viewMatrix; +} + +const mat4& Camera::projMatrix() +{ + // NOTE: The near / far clip distances are not relevant for ray tracing. In order to support + // rasterization, the clip distances should be computed from the scene bounds and the current + // camera position. See Fit() for how the view Z bounds can be determined. + + // Do nothing if the projection matrix is not dirty. + if (!_isProjDirty) + { + return _projMatrix; + } + + // Compute the proper orthographic or perspective projection matrix. + if (_isOrtho) + { + // Compute the size of the view at the target distance. This ensures that orthographic and + // perspective projections will have the same "zoom" at the target point. + // NOTE: Any changes to the target distance must invalidate the projection matrix. + vec2 size; + size.y = length(_target - _eye) * tan(_fov * 0.5f); + size.x = aspectRatio() * size.y; + + // Create a (right-hand) orthographic projection matrix with the computed view size. + _projMatrix = orthoRH(-size.x, size.x, -size.y, size.y, _near, _far); + } + else + { + // Compute the perspective projection matrix. + _projMatrix = perspective(_fov, aspectRatio(), _near, _far); + } + + _isProjDirty = false; + + return _projMatrix; +} + +void Camera::setIsOrtho(bool value) +{ + _isOrtho = value; + _isProjDirty = true; +} + +void Camera::setView(const vec3& eye, const vec3& target) +{ + // Set the view properties. + _eye = eye; + _target = target; + _up = kUp; + + // Compute a valid perpendicular up vector from the direction and (provided, rough) up vectors. + vec3 forward = forwardDir(); + _up = cross(rightDir(), forward); + + // Compute the azimuth and elevation angles from the forward direction. + _azimuth = atan2(forward.x, -forward.z); + _elevation = asin(forward.y); + + // Set the view and projection matrices as dirty. + // NOTE: The projection matrix is dirty because the target distance may have changed here. + _isViewDirty = true; + _isProjDirty = true; +} + +void Camera::setProjection(float fov, float nearClip, float farClip) +{ + assert(fov > 0.0f && nearClip > 0.0f && farClip > 0.0f); + + // Set the projection properties. + _fov = fov; + _near = nearClip; + _far = farClip; + + _isProjDirty = true; +} + +void Camera::setDimensions(const ivec2& dimensions) +{ + assert(dimensions.x > 0 && dimensions.y > 0); + + // Set the dimensions. + _dimensions = dimensions; + + _isProjDirty = true; +} + +void Camera::fit(const Foundation::BoundingBox& bounds) +{ + fit(bounds, forwardDir()); +} + +void Camera::fit(const Foundation::BoundingBox& bounds, const vec3& direction) +{ + // Set the target to the center of the bounding box, and temporarily set an eye position an + // arbitrary distance from the new target, using the specified direction. + vec3 target = bounds.center(); + vec3 eye = target - direction; + + // Transform the bounding box corners to view space, and create a new view space bounding box + // from them. This results in a conservative box. + setView(eye, target); + Foundation::BoundingBox viewBox = bounds.transform(viewMatrix()); + + // Compute half of the camera FOV in radians, using the vertical or horizontal FOV depending + // on the relative aspect ratios of the camera and the camera box. Also compute the width or + // height to cover in the view, again based on the relative aspect ratios. + // NOTE: In GLM, the FOV value covers the *full* vertical view, but *half* is needed here for + // trigonometric calculations. + vec3 boxDims = viewBox.dimensions(); + float boxAspect = boxDims.x / boxDims.y; + float viewAspect = aspectRatio(); + float halfFOV = boxAspect > viewAspect ? atan(tan(_fov * 0.5f) * viewAspect) : _fov * 0.5f; + float size = boxAspect > viewAspect ? boxDims.x : boxDims.y; + + // Compute the distance to keep the camera away from the box and have the box fill the computed + // FOV. This includes half the box depth (Z), since this is the distance from the target. Set + // the eye position as the computed distance away from the target. + float distance = size / (2.0f * tan(halfFOV)) + boxDims.z / 2.0f; + _eye = target - distance * direction; + + // Set the view and projection matrices as dirty. + // NOTE: The projection matrix is dirty because the target distance may have changed here. + _isViewDirty = true; + _isProjDirty = true; +} + +void Camera::mouseMove(int xPos, int yPos, const Inputs& inputs) +{ + // Handle wheel input immediately by dollying the camera, then returning. + if (inputs.Wheel) + { + // For each wheel "delta" (1 or -1), treat that as dollying 10%. + dolly(vec2(0.0f, -yPos / 10.0f)); + return; + } + + // If an update is not in progress, and a mouse button is pressed, start updating. Stop further + // processing regardless of whether a mouse button is pressed, i.e. wait for the next event. + if (!_isUpdating) + { + if (inputs.LeftButton || inputs.MiddleButton || inputs.RightButton) + { + _isUpdating = true; + _lastMouse = vec2(xPos, yPos); + } + + return; + } + + // Get the change in mouse position, as a fraction of the viewport dimensions. + vec2 currentMouse = vec2(xPos, yPos); + vec2 delta = currentMouse - _lastMouse; + delta /= _dimensions; + _lastMouse = currentMouse; + + // Update the camera based on the pressed mouse button and the change in mouse position. If + // no button is pressed, stop updating. + if (inputs.LeftButton) + { + orbit(delta); + } + else if (inputs.RightButton) + { + pan(delta); + } + else if (inputs.MiddleButton) + { + dolly(delta); + } + else + { + _isUpdating = false; + } +} + +void Camera::orbit(const vec2& delta) +{ + // Increment and clamp the azimuth and elevation angles from the provided delta. The azimuth + // angle is in [0, 360] wrapped, and the elevation angle is in [-89, 89] clamped. + constexpr float kOrbitRate = radians(360.0f); + constexpr float kAzimuthMax = radians(360.0f); + constexpr vec2 kElevationRange = vec2(radians(-89.0f), radians(89.0f)); + _azimuth -= delta.x * kOrbitRate; + _elevation -= delta.y * kOrbitRate; + _azimuth = fmod(fmod(_azimuth, kAzimuthMax) + kAzimuthMax, kAzimuthMax); + _elevation = glm::clamp(_elevation, kElevationRange.x, kElevationRange.y); + + // Create rotation matrices: rotate around the canonical right axis using the elevation, and + // around the canonical up axis using the azimuth angle. Then apply both rotations to the + // canonical forward vector, to create a direction vector. + mat4 rotateAzimuth = rotate(_azimuth, kUp); + mat4 rotateElevation = rotate(_elevation, kRight); + vec3 direction = rotateAzimuth * rotateElevation * vec4(kForward, 0.0f); + + // Set the eye position at the existing distance away from the target, in the new direction. + // Also update the up vector. + _eye = _target - direction * targetDistance(); + vec3 right = cross(direction, kUp); + _up = cross(right, direction); + _isViewDirty = true; +} + +void Camera::pan(const vec2& delta) +{ + // Compute the dimensions of the world covered by the camera at the target distance. + float halfTanFOVY = tan(_fov * 0.5f); + vec2 size(halfTanFOVY * aspectRatio(), halfTanFOVY); + size *= targetDistance() * 2.0f; + + // Compute the amount to shift the camera: the right vector scaled by the X delta (a fraction + // of the world size) and X world size, plus the up vector scaled by the X delta and world size. + // This way the pan exactly follows the mouse, at the target distance. + vec3 translate = rightDir() * -delta.x * size.x + _up * delta.y * size.y; + + // Shift both the eye and target positions. + _eye += translate; + _target += translate; + _isViewDirty = true; +} + +void Camera::dolly(const vec2& delta) +{ + // Scale the distance between the eye and target by the change in Y, e.g. a Y value of 0.1 + // means increasing the distance by 1.1x (dolly out), and a Y value of -0.1 means scaling by + // 0.9x (dolly in). + float distance = targetDistance(); + distance *= 1.0f + glm::max(-0.9f, delta.y); + + // Set the eye position to the computed distance away from the target. + _eye = _target - forwardDir() * distance; + + // Set the view and projection matrices as dirty. + // NOTE: The projection matrix is dirty because the target distance may have changed here. + _isViewDirty = true; + _isProjDirty = true; +} \ No newline at end of file diff --git a/Applications/Plasma/Camera.h b/Applications/Plasma/Camera.h new file mode 100644 index 0000000..3484aa4 --- /dev/null +++ b/Applications/Plasma/Camera.h @@ -0,0 +1,77 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// A class representing a virtual camera that can be updated with mouse / keyboard input, and +// supplies the corresponding view and projection matrices. +class Camera +{ +public: + /*** Types ***/ + + struct Inputs + { + bool LeftButton; + bool MiddleButton; + bool RightButton; + bool Wheel; + }; + + /*** Functions ***/ + + const vec3& eye() const { return _eye; } + const vec3& target() const { return _target; } + const vec3& upDir() const { return _up; } + vec3 forwardDir() const { return normalize(_target - _eye); } + vec3 rightDir() const { return cross(forwardDir(), _up); } + float targetDistance() const { return length(_target - _eye); } + float aspectRatio() const { return static_cast(_dimensions.x) / _dimensions.y; } + const mat4& viewMatrix(); + const mat4& projMatrix(); + const ivec2& dimensions() const { return _dimensions; } + bool isDirty() { return _isViewDirty || _isProjDirty; } + void setIsOrtho(bool value); + void setView(const vec3& eye, const vec3& target); + void setProjection(float fov, float nearClip, float farClip); + void setDimensions(const ivec2& dimensions); + void fit(const Foundation::BoundingBox& bounds); + void fit(const Foundation::BoundingBox& bounds, const vec3& direction); + void mouseMove(int xPos, int yPos, const Inputs& inputs); + +private: + /*** Private Functions ***/ + + void orbit(const vec2& delta); + void pan(const vec2& delta); + void dolly(const vec2& delta); + + /*** Private Variables ***/ + + bool _isUpdating = false; + vec2 _lastMouse; + bool _isViewDirty = true; + bool _isProjDirty = true; + bool _isOrtho = false; + float _fov = radians(45.0f); + float _near = 0.1f; + float _far = 1.0f; + float _azimuth = 0.0f; + float _elevation = 0.0f; + vec3 _eye = vec3(0.0f, 0.0f, 1.0f); + vec3 _target = vec3(0.0f, 0.0f, 0.0f); + vec3 _up = vec3(0.0f, 1.0f, 0.0f); + ivec2 _dimensions = ivec2(100, 100); + mat4 _viewMatrix; + mat4 _projMatrix; +}; \ No newline at end of file diff --git a/Applications/Plasma/Libraries.cpp b/Applications/Plasma/Libraries.cpp new file mode 100644 index 0000000..3ca3304 --- /dev/null +++ b/Applications/Plasma/Libraries.cpp @@ -0,0 +1,33 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#if defined(INTERACTIVE_PLASMA) +// Window global variable from pch.h. +HWND gWindow = nullptr; +#endif + +// Prepare the implementations of these header-only libraries. +// NOTE: This must be done in a *single* source file; other source files can just include the +// header(s). +#define TINYOBJLOADER_IMPLEMENTATION +#include +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION // needed for TinyGLTF's use of stb_image. +#include +#pragma warning(push) +#pragma warning(disable : 4996) +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include +#pragma warning(pop) \ No newline at end of file diff --git a/Applications/Plasma/Loaders.h b/Applications/Plasma/Loaders.h new file mode 100644 index 0000000..2c56bc7 --- /dev/null +++ b/Applications/Plasma/Loaders.h @@ -0,0 +1,82 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +struct SceneContents; + +// A cache of Aurora images that are loaded from disk as needed. +class ImageCache +{ +public: + // Gets an image from the image cache with the specified file path, or creates a new one if it + // doesn't exist in the cache. + // Will linearize from sRGB if linearize parameter is true. + Aurora::Path getImage(string filePath, Aurora::IScene* pScene, bool linearize) + { + // Return empty path if the file path is not specified. + if (filePath.empty()) + { + return ""; + } + Aurora::Path auroraImagePath = + "PlasmaImage/" + filePath + ":linearize:" + to_string(linearize); + + pScene->setImageFromFilePath(auroraImagePath, filePath, linearize); + return auroraImagePath; + } + +private: + using CacheType = + unordered_map, Aurora::ImageDescriptor>>; + CacheType _cache; + static uint32_t whitePixels[1]; +}; + +// A type for scene loading function. +using LoadSceneFunc = function; + +// Loads a Wavefront OBJ (.obj) file into the specified renderer and scene, from the specified file +// path. +bool loadOBJFile(Aurora::IRenderer* pRenderer, Aurora::IScene* pScene, const string& filePath, + SceneContents& sceneContents); + +// Loads a glTF file (.gltf ASCII or .glb binary) into the specified renderer and scene, from the +// specified file path. +bool loadglTFFile(Aurora::IRenderer* pRenderer, Aurora::IScene* pScene, const string& filePath, + SceneContents& SceneContents); + +// Returns the scene loading function for the specified scene file path. +inline LoadSceneFunc getLoadSceneFunc(const string& filePath) +{ + // Get the file extension. + string extension = filesystem::path { filePath }.extension().string(); + + Foundation::sLower(extension); + + // Return the glTF load function for .gltf (ASCII) and .glb (binary). + if (extension.compare(".gltf") == 0 || extension.compare(".glb") == 0) + { + return ::loadglTFFile; + } + + // Return the OBJ load function for .obj. + else if (extension.compare(".obj") == 0) + { + return ::loadOBJFile; + } + + // Return no function (unrecognized extension). + return LoadSceneFunc(); +} diff --git a/Applications/Plasma/OBJLoader.cpp b/Applications/Plasma/OBJLoader.cpp new file mode 100644 index 0000000..c2f37aa --- /dev/null +++ b/Applications/Plasma/OBJLoader.cpp @@ -0,0 +1,315 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "Loaders.h" +#include "SceneContents.h" + +uint32_t ImageCache::whitePixels[1] = { 0xFFFFFFFF }; + +// A simple structure describing an OBJ index, which has sub-indices for vertex channels: position, +// normal, and texture coordinates. +struct OBJIndex +{ + // Constructor, from a tinyobj::index_t. + OBJIndex(const tinyobj::index_t& index) : + Position(index.vertex_index), Normal(index.normal_index), TexCoord(index.texcoord_index) + { + } + + // Equality operator, for use in a hash map. + bool operator==(const OBJIndex& other) const + { + return Position == other.Position && Normal == other.Normal && TexCoord == other.TexCoord; + } + + // Indices, same as tinyobj::index_t. + int Position; + int Normal; + int TexCoord; +}; + +// A hash function object for OBJIndex. +struct hashOBJIndex +{ + size_t operator()(const OBJIndex& index) const + { + // Get hash values for the individual indices... + hash hasher; + size_t h1 = hasher(index.Position); + size_t h2 = hasher(index.Normal); + size_t h3 = hasher(index.TexCoord); + + // ... and combine them in one recommended way. + return ((h1 ^ (h2 << 1)) >> 1) ^ (h3 << 1); + } +}; + +// An unordered (hash) map from an OBJIndex (with vertex channel indices) to a single index. +using OBJIndexMap = unordered_map; + +// Loads a Wavefront OBJ file into the specified renderer and scene, from the specified file path. +bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const string& filePath, + SceneContents& sceneContents) +{ + sceneContents.reset(); + + // Start a timer for reading the OBJ file. + Foundation::CPUTimer timer; + ::infoMessage("Reading OBJ file \"" + filePath + "\"..."); + + // Load the OBJ file with tinyobjloader. If the load is not successful or has no shapes, do + // nothing else. + // NOTE: tinyobjloader currently does not support wide strings on Windows, so files with wide + // non-ASCII characters in their names will fail to load. + tinyobj::attrib_t attrib; + vector shapes; + vector objMaterials; + string sWarnings; + string sErrors; + uint32_t objectCount = 0; + bool result = + tinyobj::LoadObj(&attrib, &shapes, &objMaterials, &sWarnings, &sErrors, filePath.c_str()); + if (!result || shapes.empty()) + { + return false; + } + + // Report the file read time. + ::infoMessage("... completed in " + to_string(static_cast(timer.elapsed())) + " ms."); + + // Start a timer for translating the OBJ scene data. + timer.reset(); + ::infoMessage("Translating OBJ scene data..."); + + // Parse the OBJ materials into a corresponding array of Aurora materials. + vector lstMaterials; + lstMaterials.reserve(objMaterials.size()); + ImageCache imageCache; + int mtlCount = 0; + for (auto& objMaterial : objMaterials) + { + // Collect material properties. + // NOTE: This includes support for some of the properties in the PBR extension here: + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + // > We use the Standard Surface IOR default of 1.5 when no IOR is specified in the file, + // which is parsed as 1.0. + // > Transmission is derived from the "dissolve" property. This is normally intended for + // what Standard Surface calls "opacity" but transmission is more interesting. + // > The default transmittance is black, which is a useless tint color, so that value is + // converted to white. + vec3 baseColor = ::sRGBToLinear(make_vec3(objMaterial.diffuse)); + float metalness = objMaterial.metallic; + vec3 specularColor = ::sRGBToLinear(make_vec3(objMaterial.specular)); + float specularRoughness = objMaterial.roughness; + float specularIOR = objMaterial.ior == 1.0f ? 1.5f : objMaterial.ior; + float transmission = 1.0f - objMaterial.dissolve; + vec3 transmissionColor = ::sRGBToLinear(make_vec3(objMaterial.transmittance)); + if (length(transmissionColor) == 0.0f) + { + transmissionColor = vec3(1.0f); + } + vec3 opacity = vec3(1.0f); // objMaterial.dissolve (see NOTE above) + + // Load the base color image file from the "map_Kd" file path. + // NOTE: Set the linearize flag, as these images typically use the sRGB color space. + Aurora::Path baseColorImage = + imageCache.getImage(objMaterial.diffuse_texname, pScene, true); + + // Load the specular roughness image file from the "map_Pr" file path. + // NOTE: Don't set the linearize flag, as these images typically use the linear color space. + Aurora::Path specularRoughnessImage = + imageCache.getImage(objMaterial.roughness_texname, pScene, false); + + // Load the opacity image file from the "map_d" file path. + // NOTE: Don't set the linearize flag, as these images typically use the linear color space. + Aurora::Path opacityImage = imageCache.getImage(objMaterial.alpha_texname, pScene, false); + + // Load the normal image file from either the "bump" or "norm" file path in the material. + // The file could be specified in either property depending on the exporter. + // NOTE: Don't set the linearize flag, as these images typically use the linear color space. + // Only normal maps (not height maps) are supported here. + string normalFilePath = objMaterial.normal_texname.empty() ? objMaterial.bump_texname + : objMaterial.normal_texname; + Aurora::Path normalImage = imageCache.getImage(normalFilePath, pScene, false); + + Aurora::Path materialPath = filePath + ":OBJFileMaterial-" + to_string(mtlCount++); + + // Create an Aurora material, assign the properties, and add the material to the list. + pScene->setMaterialProperties(materialPath, + { { "base_color", (baseColor) }, { "metalness", metalness }, + { "specular_color", (specularColor) }, { "specular_roughness", specularRoughness }, + { "specular_IOR", specularIOR }, { "transmission", transmission }, + { "transmission_color", (transmissionColor) }, { "opacity", (opacity) }, + { "base_color_image", baseColorImage }, + { "specular_roughness_image", specularRoughnessImage }, + { "opacity_image", opacityImage }, { "normal_image", normalImage } }); + lstMaterials.push_back(materialPath); + }; + + // Get the data arrays from the tinyobj::attrib_t object. + const auto& srcPositions = attrib.vertices; + const auto& srcNormals = attrib.normals; + const auto& srcTexCoords = attrib.texcoords; + + // Iterate the shapes, creating an Aurora geometry for each one and adding an instance of it + // to the scene. + bool hasMesh = false; + + for (const auto& shape : shapes) + { + + // Skip shapes that don't have a mesh (e.g. lines or points). + auto indexCount = static_cast(shape.mesh.indices.size()); + if (indexCount == 0) + { + continue; + } + hasMesh = true; + + Aurora::Path sceneInstancePath = filePath + ":OBJFileInstance-" + to_string(objectCount); + Aurora::Path geomPath = filePath + ":OBJFileGeom-" + to_string(objectCount); + objectCount++; + + SceneGeometryData& geometryData = sceneContents.addGeometry(geomPath); + auto& positions = geometryData.positions; + auto& normals = geometryData.normals; + auto& tex_coords = geometryData.texCoords; + auto& indices = geometryData.indices; + indices.reserve(indexCount); + + bool bHasNormals = shape.mesh.indices[0].normal_index >= 0; + bool bHasTexCoords = shape.mesh.indices[0].texcoord_index >= 0; + + // Iterate the "OBJ indices" in the shape, which consist of three sub-indices for individual + // position, normal, and texture coordinate values. These are used to define unique vertices + // (i.e. identical combinations of the three sub-indices) and populate the data arrays. + uint32_t nextIndex = 0; + OBJIndexMap objIndexMap; + for (const auto& objIndex : shape.mesh.indices) + { + // If this OBJ index is already in the map, add the corresponding unique index to the + // index array. The OBJIndex struct supports hashing to allow this. + if (objIndexMap.find(objIndex) != objIndexMap.end()) + { + indices.push_back(objIndexMap[objIndex]); + } + + // Else: Add the corresponding position, normal, and texture coordinate values to the + // their respective arrays. Also add the next index value to the index array and the OBJ + // index map. + else + { + // Add the position (XYZ) value, including updating the bounding box if needed. + const float* position = &srcPositions[objIndex.vertex_index * 3]; + sceneContents.bounds.add(position); + positions.push_back(*position++); + positions.push_back(*position++); + positions.push_back(*position); + + // Add the normal (XYZ) value, if needed. + if (bHasNormals) + { + // Normalize the normal, in case the source data has bad normals. + glm::vec3 normal = + glm::normalize(make_vec3(&srcNormals[objIndex.normal_index * 3])); + + // Add the normal. + normals.push_back(normal.x); + normals.push_back(normal.y); + normals.push_back(normal.z); + } + + // Add the texture coordinate (UV) value, if needed. + if (bHasTexCoords) + { + const float* tex_coord = &srcTexCoords[objIndex.texcoord_index * 2]; + tex_coords.push_back(*tex_coord++); + tex_coords.push_back(1.0f - *tex_coord); // flip V + } + + // Add the next index to the index array and the OBJ index map, for the current OBJ + // index. + indices.push_back(nextIndex); + objIndexMap[objIndex] = nextIndex; + nextIndex++; + } + } + + Aurora::GeometryDescriptor& geomDesc = geometryData.descriptor; + geomDesc.type = Aurora::PrimitiveType::Triangles; + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kPosition] = + Aurora::AttributeFormat::Float3; + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kNormal] = + Aurora::AttributeFormat::Float3; + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTexCoord0] = + Aurora::AttributeFormat::Float2; + geomDesc.vertexDesc.count = static_cast(positions.size()) / 3; + geomDesc.indexCount = indexCount; + geomDesc.getAttributeData = [geomPath, &sceneContents](Aurora::AttributeDataMap& buffers, + size_t /* firstVertex*/, size_t /* vertexCount*/, + size_t /* firstIndex*/, size_t /* indexCount*/) { + SceneGeometryData& geometryData = sceneContents.geometry[geomPath]; + + buffers[Aurora::Names::VertexAttributes::kPosition].address = + geometryData.positions.data(); + buffers[Aurora::Names::VertexAttributes::kPosition].size = + geometryData.positions.size() * sizeof(float); + buffers[Aurora::Names::VertexAttributes::kPosition].stride = sizeof(vec3); + buffers[Aurora::Names::VertexAttributes::kNormal].address = geometryData.normals.data(); + buffers[Aurora::Names::VertexAttributes::kNormal].size = + geometryData.normals.size() * sizeof(float); + buffers[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(vec3); + buffers[Aurora::Names::VertexAttributes::kTexCoord0].address = + geometryData.texCoords.data(); + buffers[Aurora::Names::VertexAttributes::kTexCoord0].size = + geometryData.texCoords.size() * sizeof(float); + buffers[Aurora::Names::VertexAttributes::kTexCoord0].stride = sizeof(vec2); + buffers[Aurora::Names::VertexAttributes::kIndices].address = + geometryData.indices.data(); + buffers[Aurora::Names::VertexAttributes::kIndices].size = + geometryData.indices.size() * sizeof(int); + buffers[Aurora::Names::VertexAttributes::kIndices].stride = sizeof(int); + + return true; + }; + + pScene->setGeometryDescriptor(geomPath, geomDesc); + + // Create an Aurora geometry object and get the associated material. Add an instance with + // that geometry and material to the scene. + // NOTE: An OBJ file can have different materials for each face (triangle) in a mesh. Here + // we only use the material assigned to the *first* face for the entire mesh. If no material + // is specified, use a null pointer which indicates Aurora should use a default material. + int material_id = shape.mesh.material_ids[0]; + + // Add instance to the scene. + Aurora::InstanceDefinition instDef = { sceneInstancePath, + { { Aurora::Names::InstanceProperties::kTransform, mat4() } } }; + + if (material_id >= 0) + instDef.properties[Aurora::Names::InstanceProperties::kMaterial] = + lstMaterials[material_id]; + pScene->addInstance(sceneInstancePath, geomPath, instDef.properties); + + sceneContents.instances.push_back({ instDef, geomPath }); + sceneContents.vertexCount += static_cast(geomDesc.vertexDesc.count); + sceneContents.triangleCount += static_cast(geomDesc.indexCount / 3); + } + + // Report the translation time. + ::infoMessage("... completed in " + to_string(static_cast(timer.elapsed())) + " ms."); + + return hasMesh; +} diff --git a/Applications/Plasma/PerformanceMonitor.h b/Applications/Plasma/PerformanceMonitor.h new file mode 100644 index 0000000..4e4f425 --- /dev/null +++ b/Applications/Plasma/PerformanceMonitor.h @@ -0,0 +1,147 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// A class to track rendering performance statistics and provide periodic status updates. +class PerformanceMonitor +{ +public: + // Constructor, with a parameter to specify the status update interval. + PerformanceMonitor(float statusInterval = 500.0f) : _statusInterval(statusInterval) + { + assert(statusInterval > 0.0f); + } + + // Sets a callback function that may be called with a status message when a frame is completed, + // through endFrame(). + using StatusFunction = function; + void setStatusFunction(StatusFunction statusFunction) { _statusFunction = statusFunction; } + + // Set the rendering dimensions. + void setDimensions(const ivec2& dimensions) + { + assert(dimensions.x > 0 && dimensions.y > 0); + + _dimensions = dimensions; + } + + // Notifies the performance monitor that rendering of a frame is going to begin. The parameter + // indicates whether rendering will be restarting from the first sample with this frame. + void beginFrame(bool isRestarting) + { + // If rendering is restarting, reset the variables tracking total work. + if (isRestarting) + { + _totalTimer.reset(); + _totalFrames = 0; + _totalSamples = 0; + } + } + + // Notifies the performance monitor that rendering of a frame is done. The parameters indicate + // whether rendering was completed (converged) with this frame and the number of samples + // rendered in the frame. + // NOTE: It is possible (though unlikely) for rendering to be restarted _and_ completed in a + // single frame. The order of the operations in this function accounts for that. + void endFrame(bool isComplete, int sampleCount) + { + // If rendering was *previously* complete, this frame is likely to be happening long after + // the previous status update, i.e. the application was idle. In that case, reset the + // variables tracking status updates. + if (_isComplete) + { + _statusTimer.reset(); + _statusFrames = 0; + _statusSamples = 0; + _statusLastTime = 0.0f; + } + _isComplete = isComplete; + + // Increment the frame and sample counters. + _totalFrames++; + _totalSamples += sampleCount; + _statusFrames++; + _statusSamples += sampleCount; + + // Record the elapsed time since the last status update. Return if the status update + // interval has not yet elapsed *and* rendering has not been completed. + float statusTime = _statusTimer.elapsed(); + float statusDuration = statusTime - _statusLastTime; + if (statusDuration < _statusInterval && !_isComplete) + { + return; + } + + // Start building a status report string, starting with the current completed samples. + stringstream report; + report.imbue(locale("")); // thousands separator for integers + report << setprecision(1) << fixed // single decimal place, no scientific notation + << " | " << _totalSamples << " SPP"; + + // Append to the report string, with information about either: + // - The total work for converged rendering (if complete). + // - Work done since the last status update (otherwise). + float megaraysPerSample = _dimensions.x * _dimensions.y / 1.0e6f; + if (_isComplete) + { + // Collect performance stats for the total work performed to complete rendering. + float durationInSeconds = _totalTimer.elapsed() / 1000.0f; + + // Add them to the report string. + report << " | " << durationInSeconds << " s"; + } + else + { + // Collect performance stats since the last status update. + float durationInSeconds = statusDuration / 1000.0f; + float timePerFrame = statusDuration / _statusFrames; + float timePerSample = statusDuration / _statusSamples; + float framesPerSecond = _statusFrames / durationInSeconds; + float megaraysPerSecond = megaraysPerSample * _statusSamples / durationInSeconds; + + // Add them to the report string. + report << " | " << timePerFrame << " ms/frame" + << " (" << framesPerSecond << " FPS)" + << " | " << timePerSample << " ms/sample" + << " (" << megaraysPerSecond << " MRPS)"; + } + + // Call the status update function. + if (_statusFunction) + { + _statusFunction(report.str()); + } + // A status update was performed, so update the last status update time and frame count. + _statusFrames = 0; + _statusSamples = 0; + _statusLastTime = statusTime; + } + +private: + ivec2 _dimensions = ivec2(1280, 720); + bool _isComplete = true; + + // Variables that track total work performed from the first sample to the last sample. + Foundation::CPUTimer _totalTimer; + int _totalFrames = 0; + int _totalSamples = 0; + + // Variables that track work performed during a single status update interval. + StatusFunction _statusFunction; + Foundation::CPUTimer _statusTimer; + float _statusInterval = 500.0f; + int _statusFrames = 0; + int _statusSamples = 0; + float _statusLastTime = 0.0f; +}; \ No newline at end of file diff --git a/Applications/Plasma/Plasma.cpp b/Applications/Plasma/Plasma.cpp new file mode 100644 index 0000000..ad466ba --- /dev/null +++ b/Applications/Plasma/Plasma.cpp @@ -0,0 +1,1661 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "Plasma.h" + +#include "Loaders.h" + +// A global pointer to the application object, for the static WndProc function. +Plasma* gpApp = nullptr; +#if defined(INTERACTIVE_PLASMA) +static const char* kAppName = "Plasma"; +#endif +// The maximum number of samples to render when converging. +constexpr uint32_t kMaxSamples = 1000; +constexpr uint32_t kDenoisingSamples = 50; + +#if defined(INTERACTIVE_PLASMA) +// Application constructor. +Plasma::Plasma(HINSTANCE hInstance, unsigned int width, unsigned int height) +{ + // Initialize member variables. + _hInstance = hInstance; + _dimensions.x = width > 0 ? width : _dimensions.x; + _dimensions.y = height > 0 ? height : _dimensions.y; + + // Initialize the sample counter. + _sampleCounter.setMaxSamples(kMaxSamples); + _sampleCounter.reset(); + + // Initialize the file loading functions, based on file extension. + _loadFileFunctions[".hdr"] = bind(&Plasma::loadEnvironmentImageFile, this, placeholders::_1); + _loadFileFunctions[".mtlx"] = bind(&Plasma::applyMaterialXFile, this, placeholders::_1); + _loadFileFunctions[".obj"] = bind(&Plasma::loadSceneFile, this, placeholders::_1); + + // Set a status update function on the performance monitor. + _performanceMonitor.setStatusFunction([this](const string& message) { + // Start building a report string with scene information and performance stats. + stringstream report; + report.imbue(locale("")); // thousands separator for integers + report << kAppName; + if (_sceneContents.instances.size() > 0) + { + report << " | " << _sceneContents.vertexCount << " vertices / " + << _sceneContents.triangleCount << " triangles / " + << _sceneContents.instances.size() << " instance(s)"; + } + + // Add the status report from the performance monitor. + report << message; + + // Set the complete report as the window title. + ::SetWindowTextW(_hwnd, Foundation::s2w(report.str()).c_str()); + }); +} +#else //! INTERACTIVE_PLASMA +// Application constructor. +Plasma::Plasma(unsigned int width, unsigned int height) +{ + // Initialize member variables. + _dimensions.x = width > 0 ? width : _dimensions.x; + _dimensions.y = height > 0 ? height : _dimensions.y; + + // Initialize the sample counter. + _sampleCounter.setMaxSamples(kMaxSamples); + _sampleCounter.reset(); + + // Initialize the file loading functions, based on file extension. + _loadFileFunctions[".hdr"] = bind(&Plasma::loadEnvironmentImageFile, this, placeholders::_1); + _loadFileFunctions[".mtlx"] = bind(&Plasma::applyMaterialXFile, this, placeholders::_1); + _loadFileFunctions[".obj"] = bind(&Plasma::loadSceneFile, this, placeholders::_1); +} +#endif // INTERACTIVE_PLASMA + +// Application destructor. +Plasma::~Plasma() +{ + // These hold a shared pointer to the scene object, need to clear them before renderer dtor + // triggered. + _sceneContents.reset(); +} + +#if defined(INTERACTIVE_PLASMA) +// Runs the application message loop until the user quits the application. +bool Plasma::run() +{ + // Parse the command line arguments as options. + // NOTE: This will exit the application if the help argument is supplied. + parseOptions(); + + // Initialize the application, and return immediately if that does not succeed. + if (!initialize()) + { + ::errorMessage("Failed to initialize Plasma."); + + return false; + } + + // Show the window. + ::ShowWindow(_hwnd, SW_SHOWNORMAL); + + // Process system messages until a WM_QUIT message is received. + MSG msg = {}; + while (::GetMessage(&msg, nullptr, 0, 0) != 0) + { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + return true; +} + +// The static window callback function. +LRESULT __stdcall Plasma::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + // Process the message. + LRESULT result = gpApp->processMessage(message, wParam, lParam); + + // Return the result if valid, otherwise use default message processing. + return result == -1 ? DefWindowProc(hWnd, message, wParam, lParam) : result; +} +#else +// Runs the application as one-pass command line program +bool Plasma::run(int argc, char* argv[]) +{ + // Parse the command line arguments as options. + // NOTE: This will exit the application if the help argument is supplied. + parseOptions(argc, argv); + + // Initialize the application, and return immediately if that does not succeed. + if (!initialize()) // TODO single image rendering starts and ends inside initialize(). + { + ::errorMessage("Failed to initialize Plasma."); + + return false; + } + + return true; +} +#endif + +// Creates a sample scene. +Aurora::IScenePtr Plasma::createSampleScene(Aurora::IRenderer* pRenderer, SceneContents& contents) +{ + // Clear the result. + contents.reset(); + + // Create an Aurora scene. + Aurora::IScenePtr pScene = pRenderer->createScene(); + if (!pScene) + { + return nullptr; + } + + // Create sample geometry, a single triangle. + const Aurora::Path kGeomPath = "PlasmaDefaultSceneGeometry"; + + contents.geometry[kGeomPath] = SceneGeometryData(); + SceneGeometryData& geometryData = contents.geometry[kGeomPath]; + + // clang-format off + geometryData.positions = { + 0.0f, 0.0f, 0.0f, + 0.25f, -0.5f, 0.0f, + -0.25f, -0.5f, 0.0f, + }; + geometryData.normals = { + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + }; + // clang-format on + + Aurora::GeometryDescriptor& geomDesc = geometryData.descriptor; + geomDesc.type = Aurora::PrimitiveType::Triangles; + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kPosition] = + Aurora::AttributeFormat::Float3; + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kNormal] = + Aurora::AttributeFormat::Float3; + geomDesc.vertexDesc.count = 3; + geomDesc.indexCount = 0; + geomDesc.getAttributeData = [kGeomPath, &contents](Aurora::AttributeDataMap& buffers, + size_t /* firstVertex*/, size_t /* vertexCount*/, + size_t /* firstIndex*/, size_t /* indexCount*/) { + SceneGeometryData& geometryData = contents.geometry[kGeomPath]; + + buffers[Aurora::Names::VertexAttributes::kPosition].address = geometryData.positions.data(); + buffers[Aurora::Names::VertexAttributes::kPosition].size = + geometryData.positions.size() * sizeof(float); + buffers[Aurora::Names::VertexAttributes::kPosition].stride = sizeof(vec3); + buffers[Aurora::Names::VertexAttributes::kNormal].address = geometryData.normals.data(); + buffers[Aurora::Names::VertexAttributes::kNormal].size = + geometryData.normals.size() * sizeof(float); + buffers[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(vec3); + + return true; + }; + + pScene->setGeometryDescriptor(kGeomPath, geomDesc); + + // Create several materials with varying base colors. + const int kNumLevels = 5; + const int kNumChannels = 4; + const int kNumMaterials = kNumLevels * kNumChannels; + vector lstMaterials; + int mtlIdx = 0; + lstMaterials.reserve(kNumMaterials); + for (int channel = 0; channel < kNumChannels; channel++) + { + for (int level = 1; level <= kNumLevels; level++) + { + Aurora::Path mtlPath = "PlasmaDefaultSceneMaterial:" + to_string(mtlIdx++); + + vec3 color; + float channel_value = static_cast(level) / kNumLevels; + if (channel == 3) + { + color = vec3(channel_value); // grayscale + } + else + { + color[channel] = channel_value; // single channel + } + pScene->setMaterialProperties(mtlPath, { { "base_color", ::sRGBToLinear(color) } }); + lstMaterials.push_back(mtlPath); + } + } + + // Add several transformed instances of the sample geometry to the scene. + constexpr float kDepth = 10.0f; + constexpr size_t kNumInstances = 200; + constexpr float kFullAngle = 8.0f * radians(360.0f); + constexpr vec3 kRotateAxis = vec3(0.0f, 0.0f, 1.0f); + constexpr vec3 kOffset = vec3(0.0f, -0.5f, 0.0); + constexpr float kAngleOffset = kFullAngle / kNumInstances; + constexpr float kDepthOffset = -kDepth / (kNumInstances - 1); + + // Retain a list of transforms for use in the callback function below. + Aurora::InstanceDefinitions instanceData; + instanceData.resize(kNumInstances); + contents.instances.clear(); + for (size_t i = 0; i < kNumInstances; ++i) + { + instanceData[i] = { "DefaultSceneInstance" + to_string(i), + { { Aurora::Names::InstanceProperties::kMaterial, lstMaterials[i % kNumMaterials] }, + { Aurora::Names::InstanceProperties::kTransform, + translate(rotate(mat4(), i * kAngleOffset, kRotateAxis), + kOffset + vec3(0.0f, 0.0f, i * kDepthOffset)) } } }; + + contents.instances.push_back({ instanceData[i], kGeomPath }); + } + + // Add all the instances with material and transform variations. + auto instancePaths = pScene->addInstances(kGeomPath, instanceData); + + // Specify a bounding box for the scene. + vec3 min(-1.0f, -1.0f, -kDepth - 0.01f); + vec3 max(1.0f, 1.0f, 0.01f); + contents.bounds = Foundation::BoundingBox(min, max); + + return pScene; +} + +bool Plasma::getFloat3Option(const string& name, glm::vec3& value) const +{ + // Get a reference to the parsed options result. + cxxopts::ParseResult& arguments = *_pArguments; + + // Return false if no argument was supplied for the option. + if (arguments.count(name) == 0) + { + return false; + } + + // Get the argument for the specified option as a float vector. Return false if it doesn't + // have three values. + vector floatVector = arguments[name].as>(); + if (floatVector.size() != 3) + { + return false; + } + + // Convert the float vector to vec3. + value = glm::make_vec3(floatVector.data()); + + return true; +} + +#if defined(INTERACTIVE_PLASMA) +void Plasma::parseOptions() +{ + // Command line arguments. + int numArgs = 0; + + // Parse the command line string into argument pointers, and convert the arguments to narrow + // strings so they can be parsed by cxxopts. + LPWSTR* pArgs = ::CommandLineToArgvW(::GetCommandLine(), &numArgs); + + // Pre-allocate vectors for the arguments. + vector argsNarrow(numArgs); + vector argsNarrowPtr(numArgs); + + for (int i = 0; i < numArgs; i++) + { + // Convert the argument to a UTF-8 string, and collect a non-const char pointer for it. + // NOTE: The first array must be maintained for the duration, as the second array has + // pointers to it. + argsNarrow[i] = Foundation::w2s(pArgs[i]); + argsNarrowPtr[i] = const_cast(argsNarrow[i].c_str()); + } + + // Free the command line argument pointers. + ::LocalFree(pArgs); + pArgs = nullptr; + + // Get char pointer-to-pointer to pass to cxxopts. + char** pArgv = argsNarrowPtr.data(); +#else +void Plasma::parseOptions(int argc, char* argv[]) +{ + // Command line arguments. + int numArgs = argc; + char** pArgv = argv; + +#endif + // Initialize cxxopts options with application name. + cxxopts::Options options("Plasma", "Plasma: Aurora example application."); + + // Set the help string to display for positional arguments. + options.positional_help(""); + options.show_positional_help(); + + // Initialize the cxxopts command line parser with the available options. + // NOTE: The first option is the (optional) positional argument. + // clang-format off + options.add_options() + ("scene", "Scene file path to load (Wavefront OBJ format)",cxxopts::value()) + ("reference", "Use reference BSDF", cxxopts::value()) + ("denoise", "Enable denoising", cxxopts::value()) + ("renderer", "Renderer type ('dx for DirectX, hgi for HGI.)", cxxopts::value()) + ("e,eye", "Camera eye position as comma-separated 3D vector (e.g. 1,2,3)", cxxopts::value>()) + ("t,target", "Camera target position as comma-separated 3D vector (e.g. 1,2,3)", cxxopts::value>()) + ("u,up", "Camera up vector as comma-separated 3D vector (e.g. 1,2,3)", cxxopts::value>()) + ("lightDir", "Directional light initial direction as comma-separated 3D vector (e.g. 1,2,3)", cxxopts::value>()) + ("lightColor", "Directional light color as comma-separated 3D vector (e.g. 1,2,3)", cxxopts::value>()) + ("lightIntensity", "Directional light intensity", cxxopts::value()) + ("output", "Output image file (if set will render once and exit)", cxxopts::value()) + ("dims", "Window dimensions", cxxopts::value>()) + ("fov", "Camera field of view in degrees.", cxxopts::value()) + ("env", "Environment map path to load (lat-long format .HDR file)", cxxopts::value()) + ("mtlx", "MaterialX document path to apply.",cxxopts::value()) + ("h,help", "Print help"); + // clang-format on + + // Parse positional arguments, and then the remaining arguments. This will decrement + // numArgs. NOTE: The stored ParseResult is assigned to a unique_ptr because the class does + // not have a default constructor or assignment operator. + options.parse_positional({ "scene" }); + _pArguments = make_unique(options.parse(numArgs, pArgv)); + + // If the help argument is supplied, display the print the usage message immediately exit. + if (_pArguments->count("help")) + { + stringstream message; + message << options.help(); +#if defined(INTERACTIVE_PLASMA) + wstring messageWide = Foundation::s2w(message.str()); + ::MessageBox(nullptr, messageWide.c_str(), L"Command line options", MB_OK); +#else + cout << message.str() << endl; +#endif + + exit(0); + } +} + +// Initializes the application. +bool Plasma::initialize() +{ + cxxopts::ParseResult& arguments = *_pArguments; + + // Set the window dimensions based on dims argument. + if (arguments.count("dims")) + { + vector dims = arguments["dims"].as>(); + if (dims.size() == 2) + { + _dimensions.x = dims[0]; + _dimensions.y = dims[1]; + } + } + +#if defined(INTERACTIVE_PLASMA) + // Create the application window. + _hwnd = createWindow(_dimensions); + ::setMessageWindow(_hwnd); +#endif + + // Parse the backend type from the argument for the "renderer" parameter: "dx" or "hgi". + if (_pArguments->count("renderer")) + { + string renderArg = arguments["renderer"].as(); + if (renderArg.compare("dx") == 0) + { + _rendererType = Aurora::IRenderer::Backend::DirectX; + } + else if (renderArg.compare("hgi") == 0) + { + _rendererType = Aurora::IRenderer::Backend::HGI; + } + else + { + ::errorMessage("Unknown renderer argument: " + renderArg); + } + } + + // Create an Aurora renderer. + _pRenderer = Aurora::createRenderer(_rendererType); + + // Ensure images not flipped. + _pRenderer->options().setBoolean("isFlipImageYEnabled", false); + + // Return false (failed initialization) if a renderer could not be created. + if (!_pRenderer) + { + return false; + } + + // Setup asset search paths. Including the path to the ProteinX test folder (if running within + // the Github repo). + // TODO: A more reliable solution would be to set based on the loaded materialX file path. + _assetPaths = { "", + Foundation::getModulePath() + + "../../../Renderers/Tests/Data/Materials/mtlx_ProteinSubset/" }; + + // Setup the resource loading function to use asset search paths. + auto loadResourceFunc = [this](const string& uri, vector* pBufferOut, + string* pFileNameOut) { + // Iterate through all search paths. + for (size_t i = 0; i < _assetPaths.size(); i++) + { + // Prefix the current search path to URI. + string currPath = _assetPaths[i] + uri; + + // Open file. + ifstream is(currPath, ifstream::binary); + + // If file could not be opened, continue. + if (!is) + continue; + + // Read contents to buffer. + is.seekg(0, is.end); + size_t length = is.tellg(); + is.seekg(0, is.beg); + pBufferOut->resize(length); + is.read((char*)&(*pBufferOut)[0], length); + + // Copy the URI to file name with no manipulation. + *pFileNameOut = currPath; + + // Return indicating success. + return true; + } + + // Return indicating failure. + return false; + }; + _pRenderer->setLoadResourceFunction(loadResourceFunc); + + // Set reference flag. + if (_pArguments->count("reference") > 0) + { + _pRenderer->options().setBoolean("isReferenceBSDFEnabled", true); + } + + if (_pArguments->count("denoise") > 0) + { + _isDenoisingEnabled = arguments["denoise"].as(); + } + _camera.setDimensions(_dimensions); + + // Create an Aurora environment and ground plane. + _environmentPath = "PlasmaEnvironment"; + + _pGroundPlane = _pRenderer->createGroundPlanePointer(); + _pGroundPlane->values().setBoolean("enabled", false); + + // We will set the camera view if any of the camera properties were specified as arguments. + vec3 eye(0.0f, 0.0f, 1.0f); + vec3 target(0.0f, 0.0f, 0.0f); + bool shouldSetCamera = false; + + // Get the camera properties, if they were supplied as arguments. + if (getFloat3Option("eye", eye)) + { + shouldSetCamera = true; + } + if (getFloat3Option("target", target)) + { + shouldSetCamera = true; + } + + // Get light command line options. + getFloat3Option("lightDir", _lightStartDirection); + getFloat3Option("lightColor", _lightColor); + if (arguments.count("lightIntensity")) + _lightIntensity = arguments["lightIntensity"].as(); + + // Get the scene file path from the scene positional argument. + bool bFileLoaded = false; + if (arguments.count("scene")) + { + string filePath = arguments["scene"].as(); + bFileLoaded = loadSceneFile(filePath); + } + + // Get the MaterialX file path from the mtlx argument. + if (arguments.count("mtlx")) + { + string mtlxPath = arguments["mtlx"].as(); + loadMaterialXFile(mtlxPath); + } + + // If a file was not loaded, create a sample scene. + if (!bFileLoaded) + { + // Create the sample scene, and return if it not successful. + _pScene = createSampleScene(_pRenderer.get(), _sceneContents); + if (!_pScene) + { + return false; + } + + // Create empty layer array for each instance. + _instanceLayers = vector(_sceneContents.instances.size()); + + // Update for the new scene. + updateNewScene(); + + // Fit the camera to the scene bounds, with a special direction for this scene. + static const vec3 kDefaultDirection = normalize(vec3(0.0f, 0.0f, -1.0f)); + _camera.fit(_sceneContents.bounds, kDefaultDirection); + } + + // Get the environment map file path from the env argument. + if (arguments.count("env")) + { + string envPath = arguments["env"].as(); + loadEnvironmentImageFile(envPath); + } + + // Set the camera view if needed. + if (shouldSetCamera) + { + _camera.setView(eye, target); + } + + // Set the FOV if needed. + if (arguments.count("fov")) + { + float fovDeg = arguments["fov"].as(); + _camera.setProjection(radians(fovDeg), 0.1f, 1.0f); + } + + // If output is set render single frame then exit. + if (_pArguments->count("output")) + { + string outputFile = arguments["output"].as(); + saveImage(Foundation::s2w(outputFile), uvec2(_dimensions.x, _dimensions.y)); + AU_INFO("Output command line option is set. Rendered one image to %s, now exiting.", + outputFile.c_str()); + +#if defined(INTERACTIVE_PLASMA) + PostMessage(_hwnd, WM_CLOSE, 0, 0); +#endif + } +#if defined(INTERACTIVE_PLASMA) + else + { + // Create an Aurora window, which can be used to render into the application window. + _pWindow = _pRenderer->createWindow(_hwnd, _dimensions.x, _dimensions.y); + if (!_pWindow) + { + return false; + } + _pRenderer->setTargets({ { Aurora::AOV::kFinal, _pWindow } }); + } +#endif + return true; +} + +bool Plasma::addDecal(const string& decalMtlXPath) +{ + // Load the materialX file for decal. + auto materialPath = loadMaterialXFile(decalMtlXPath); + if (materialPath.empty()) + return false; + + // Set the current decal path. + _decalMaterialXFilePath = decalMtlXPath; + + // Get the view matrix. + const float* viewArray = value_ptr(_camera.viewMatrix()); + mat4 view = glm::make_mat4(viewArray); + + // Create geometry for each instance with new UV set based on camera view. + int instanceIndex = 0; + for (size_t i = 0; i < _sceneContents.instances.size(); i++) + { + auto& inst = _sceneContents.instances[i]; + auto& geom = _sceneContents.geometry[inst.geometryPath]; + int layerIndex = (int)_instanceLayers[instanceIndex].size(); + Aurora::Path layerGeomPath = inst.def.path + ":LayerGeom-" + to_string(layerIndex); + _instanceLayers[instanceIndex].push_back({}); + auto& layer = _instanceLayers[instanceIndex][layerIndex]; + layer.geomPath = layerGeomPath; + layer.mtlPath = materialPath; + for (size_t j = 0; j < geom.descriptor.vertexDesc.count; j++) + { + // Transform position into view space. + vec4 pos(geom.positions[j * 3 + 0], geom.positions[j * 3 + 1], + geom.positions[j * 3 + 2], 1.0f); + mat4 xform = + inst.def.properties[Aurora::Names::InstanceProperties::kTransform].asMatrix4(); + pos = pos * xform; + vec4 projPos = view * pos; + + // Create UV based scaled and offet view coordinate. + vec2 uv(projPos.x, projPos.y); + uv *= 0.1; + uv += 0.5; + layer.uvs.push_back(uv); + } + + // Create the Aurora geometry object. + Aurora::GeometryDescriptor& geomDesc = layer.geomDesc; + geomDesc.type = Aurora::PrimitiveType::Triangles; + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTexCoord0] = + Aurora::AttributeFormat::Float2; + geomDesc.vertexDesc.count = geom.descriptor.vertexDesc.count; + geomDesc.indexCount = 0; + geomDesc.getAttributeData = [this, instanceIndex, layerIndex]( + Aurora::AttributeDataMap& buffers, size_t /* firstVertex*/, + size_t /* vertexCount*/, size_t /* firstIndex*/, + size_t /* indexCount*/) { + auto& layer = _instanceLayers[instanceIndex][layerIndex]; + buffers[Aurora::Names::VertexAttributes::kTexCoord0].address = layer.uvs.data(); + buffers[Aurora::Names::VertexAttributes::kTexCoord0].size = + layer.uvs.size() * sizeof(vec2); + buffers[Aurora::Names::VertexAttributes::kTexCoord0].stride = sizeof(vec2); + return true; + }; + + // Build array of geometry and material paths for layers. + vector layerGeomPaths; + vector layerMtlPaths; + for (size_t j = 0; j < _instanceLayers[i].size(); j++) + { + layerGeomPaths.push_back(_instanceLayers[i][j].geomPath); + layerMtlPaths.push_back(_instanceLayers[i][j].mtlPath); + } + + // Create the geometry object with give path. + _pScene->setGeometryDescriptor(layerGeomPath, geomDesc); + + // Remove the old instance. + _pScene->removeInstance(inst.def.path); + + // The layer properties. + Aurora::Properties newProp = inst.def.properties; + if (!_materialXFilePath.empty()) + newProp[Aurora::Names::InstanceProperties::kMaterial] = + "MaterialX:" + _materialXFilePath; + newProp[Aurora::Names::InstanceProperties::kMaterialLayers] = layerMtlPaths; + newProp[Aurora::Names::InstanceProperties::kGeometryLayers] = layerGeomPaths; + + // Create the instance. + _pScene->addInstance(inst.def.path, inst.geometryPath, newProp); + instanceIndex++; + } + + _shouldRestart = true; + return true; +} + +void Plasma::updateNewScene() +{ + // Reset the sample counter, as the new scene may have very different complexity. Also reset + // the animation timer and frame number. + _sampleCounter.reset(); + _animationTimer.reset(!_isAnimating); + _frameNumber = 0; + + // Set the scene on the renderer, and assign the environment and ground plane. Set the ground + // plane position to the bottom corner, of the scene bounds. + _pRenderer->setScene(_pScene); + + // Setup environment for new scene. + _pScene->setEnvironmentProperties( + _environmentPath, { { Aurora::Names::EnvironmentProperties::kBackgroundUseScreen, true } }); + _pScene->setEnvironment(_environmentPath); + + _pScene->setGroundPlanePointer(_pGroundPlane); + _pGroundPlane->values().setFloat3("position", value_ptr(_sceneContents.bounds.min())); + + // Request a history reset for the next render if the path tracing renderer is being used, since + // any retained history is probably invalid with a new scene. + _pRenderer->options().setBoolean("isResetHistoryEnabled", true); + + // Set the bounds on the scene. + const vec3& min = _sceneContents.bounds.min(); + const vec3& max = _sceneContents.bounds.max(); + _pScene->setBounds(value_ptr(min), value_ptr(max)); + + // Fit the camera to the scene bounds. + static const vec3 kDefaultDirection = normalize(vec3(0.0f, -0.5f, -1.0f)); + _camera.fit(_sceneContents.bounds, kDefaultDirection); +} + +void Plasma::updateLighting() +{ + // Prepare directional light properties. + float lightIntensity = _isDirectionalLightEnabled ? _lightIntensity : 0.0f; + const vec3 lightStartDirection = normalize(_lightStartDirection); + const vec3 lightColor(::sRGBToLinear(_lightColor)); + + // Get the animation elapsed time, in seconds. + float elapsed = _animationTimer.elapsed() / 1000.0f; + + // Create a transformation matrix to rotate around the Y axis, from the animation time. + static const float kSpinRate = 9.0f; + static const vec3 kSpinAxis = vec3(0.0f, 1.0f, 0.0f); + mat4 transform = rotate(radians(kSpinRate) * elapsed, kSpinAxis); + + // Compute a transformed light direction. + _lightDirection = transform * vec4(lightStartDirection, 0.0f); + + // Update the environment light and background transforms. + _pScene->setEnvironmentProperties(_environmentPath, + { { Aurora::Names::EnvironmentProperties::kLightTransform, transform }, + { Aurora::Names::EnvironmentProperties::kBackgroundTransform, transform } }); + + // Update the directional light. + _pScene->setLight(lightIntensity, value_ptr(lightColor), value_ptr(_lightDirection)); +} + +void Plasma::updateGroundPlane() +{ + // Set the relevant ground plane properties. + Aurora::IValues& values = _pGroundPlane->values(); + values.setBoolean("enabled", _isGroundPlaneShadowEnabled || _isGroundPlaneReflectionEnabled); + values.setFloat("shadow_opacity", _isGroundPlaneShadowEnabled ? 1.0f : 0.0f); + values.setFloat("reflection_opacity", _isGroundPlaneReflectionEnabled ? 0.5f : 0.0f); +} + +void Plasma::updateSampleCount() +{ + constexpr unsigned int kDebugModeErrors = 1; + constexpr unsigned int kDebugModeDenoising = 7; + + // Set the maximum number of samples based on the debug mode and denoising state: + // - If denoising is enabled with a debug mode that shows denoising results, use the denoising + // sample count. + // - Otherwise if the debug mode doesn't use the output (beauty) AOV, use a single sample. + // - Otherwise use the maximum number of samples, i.e. for full path tracing. + bool isDenoisingDebugMode = _isDenoisingEnabled && + (_debugMode <= kDebugModeErrors || _debugMode >= kDebugModeDenoising); + uint32_t sampleCount = isDenoisingDebugMode ? kDenoisingSamples + : (_debugMode > kDebugModeErrors ? 1 : kMaxSamples); + _sampleCounter.setMaxSamples(sampleCount); + _sampleCounter.reset(); +} + +// Updates the window. +void Plasma::update() +{ + // Prepare the performance monitor for the frame. + _performanceMonitor.beginFrame(_shouldRestart); + + // Update lighting properties, which may have changed. + updateLighting(); + + // Get the view and projection matrices from the camera as float arrays, and set them on the + // renderer as the camera (view). + const float* viewArray = value_ptr(_camera.viewMatrix()); + const float* projArray = value_ptr(_camera.projMatrix()); + _pRenderer->setCamera(viewArray, projArray); + + // Render the scene, accumulating as many frames as possible within a target time, as + // determined by the sample counter. This provides better visual results without making the + // user wait too long, and is most effective on simple scenes. + Foundation::CPUTimer firstFrameTimer; + uint32_t sampleStart = 0; + uint32_t sampleCount = _sampleCounter.update(sampleStart, _shouldRestart); + if (sampleCount > 0) + { + _pRenderer->render(sampleStart, sampleCount); + } + + // Report the time to render the first frame. This includes the first scene update, which + // performs acceleration structure building and other potentially time-consuming work. + if (_frameNumber == 0) + { + _pRenderer->waitForTask(); + ::infoMessage("First frame completed in " + + to_string(static_cast(firstFrameTimer.elapsed())) + " ms."); + } + + // Increment the frame counter and clear the restart flag. + _frameNumber++; + _shouldRestart = false; + + // Update the performance monitor, which may emit a status message. If rendering is complete, + // wait for the last task to finish, so that the performance monitor timing is accurate. + bool isComplete = _sampleCounter.isComplete(); + if (isComplete) + { + _pRenderer->waitForTask(); + } + _performanceMonitor.endFrame(isComplete, sampleCount); +} + +#if defined(INTERACTIVE_PLASMA) +// Requests a display update, which ensures a paint message will be sent and processed. The +// parameter indicates whether to restart rendering from the first sample, e.g. when the camera has +// changed. +void Plasma::requestUpdate(bool shouldRestart) +{ + _shouldRestart = _shouldRestart || shouldRestart; + + // Invalidate the window to ensure that a paint message is sent. + ::InvalidateRect(_hwnd, nullptr, FALSE); +} + +// Toggles the animating state of the application. +void Plasma::toggleAnimation() +{ + // Toggle the flag and resume or suspend the animation timer as needed. + _isAnimating = !_isAnimating; + if (_isAnimating) + { + _animationTimer.resume(); + } + else + { + _animationTimer.suspend(); + } + + // Request an update. + requestUpdate(); +} + +// Toggles the application between a window and full screen. +void Plasma::toggleFullScreen() +{ + // Toggle the full screen state. + _isFullScreenEnabled = !_isFullScreenEnabled; + + LONG windowStyle = 0; + HWND windowZ = nullptr; + UINT windowShowState = SW_SHOWNORMAL; + RECT windowRect = {}; + + // Determine the desired window style, z placement, and location / dimensions, for a + // windowed or full screen state. NOTE: For full screen display, this sets the window to a + // full screen borderless window, rather than using a full screen exclusive mode. + if (_isFullScreenEnabled) + { + // Store the current window location / dimensions and show state, i.e. the "placement." + ::GetWindowPlacement(_hwnd, &_prevWindowPlacement); + + // Set the window style to have no border, and put the window on top. + windowStyle = WS_OVERLAPPED; + windowZ = HWND_TOP; + + // Get the location / dimensions of the monitor that the window most occupies. + HMONITOR hMonitor = ::MonitorFromWindow(_hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFO); + ::GetMonitorInfo(hMonitor, &monitorInfo); + windowRect = monitorInfo.rcMonitor; + } + else + { + // Set the original window style and placement, and default Z. + windowStyle = WS_OVERLAPPEDWINDOW; + windowZ = HWND_NOTOPMOST; + windowShowState = _prevWindowPlacement.showCmd; + windowRect = _prevWindowPlacement.rcNormalPosition; + } + + // Set the computed window style, z placement, and location / dimensions on the window. + ::SetWindowLong(_hwnd, GWL_STYLE, windowStyle); + ::SetWindowPos(_hwnd, windowZ, windowRect.left, windowRect.top, + windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, + SWP_FRAMECHANGED | SWP_NOACTIVATE); + + // Show the window (again) so that changes take effect, with a show state. + ::ShowWindow(_hwnd, windowShowState); +} + +// Toggles vsync (vertical sync). +void Plasma::toggleVSync() +{ + // Toggle the flag and set it on the Aurora window. + _isVSyncEnabled = !_isVSyncEnabled; + _pWindow->setVSyncEnabled(_isVSyncEnabled); +} + +// Select a different unit. +void Plasma::adjustUnit(int increment) +{ + // Increment the unit. + _currentUnitIndex = + Foundation::iwrap(_currentUnitIndex + increment, static_cast(_units.size())); + + // Set the units option. + _pRenderer->options().setString("units", _units[_currentUnitIndex]); + + // Request an update. + requestUpdate(); +} + +// Adjust the renderer brightness multiplier based on an exposure value. +void Plasma::adjustExposure(float increment) +{ + // Apply the increment to the exposure value and use that to compute a brightness value for + // the renderer. Specifically, the brightness is 2^exposure. + _exposure += increment; + vec3 brightness(pow(2.0f, _exposure)); + _pRenderer->options().setFloat3("brightness", value_ptr(brightness)); + + // Request an update. + requestUpdate(); +} + +// Adjust the renderer max luminance based on an exposure value. +void Plasma::adjustMaxLuminanceExposure(float increment) +{ + // Apply the increment to the exposure value for max luminance and use that and the base max + // luminance (hardcoded) to compute a max luminance value for the renderer. Specifically, + // the max luminance is base * 2^exposure. + constexpr float kBaseMaxLuminance = 1000.0f; + _maxLuminanceExposure += increment; + float maxLuminance = kBaseMaxLuminance * pow(2.0f, _maxLuminanceExposure); + _pRenderer->options().setFloat("maxLuminance", maxLuminance); + + // Request an update. + requestUpdate(); +} + +// Displays a dialog for selecting a file to load, and loads it using the specified load +// function. +void Plasma::selectFile( + const string& extension, const wchar_t* pFilters, const LoadFileFunction& loadFunc) +{ + // Prepare a structure for displaying a file open dialog. + array filePath = { '\0' }; // must be initialized for GetOpenFileName() + OPENFILENAME desc = {}; + desc.lStructSize = sizeof(OPENFILENAME); + desc.hwndOwner = _hwnd; + desc.lpstrFilter = pFilters; + desc.lpstrFile = filePath.data(); + desc.nMaxFile = MAX_PATH; + desc.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + // Display a dialog for a user to select a file. + if (::GetOpenFileName(&desc) == FALSE) + { + return; + } + + // Get the extension of the file. If it is not valid, return. + string foundExtension = Foundation::w2s(::PathFindExtension(filePath.data())); + Foundation::sLower(foundExtension); + if (extension.compare(foundExtension) != 0) + { + ::errorMessage("Invalid file extension; " + extension + " expected."); + + return; + } + + // Display a wait cursor. + // NOTE: The cursor is restored as soon as the next system message is received. So this only + // works as desired here because the load blocks the application. + ::SetCursor(::LoadCursor(nullptr, IDC_WAIT)); + + // Load the file. + loadFunc(Foundation::w2s(filePath.data())); +} + +#endif + +// Loads an environment image file with the specified path. +bool Plasma::loadEnvironmentImageFile(const string& filePath) +{ + // Create environment image using setImageFromFilePath; + Path imagePath = "PlasmaEnvironmentImage/" + filePath; + _pScene->setImageFromFilePath(imagePath, filePath, false, true); + + // Set light and background image in environment properties to the new image. + _pScene->setEnvironmentProperties(_environmentPath, + { + { Names::EnvironmentProperties::kLightImage, imagePath }, + { Names::EnvironmentProperties::kBackgroundImage, imagePath }, + { Names::EnvironmentProperties::kBackgroundUseScreen, false }, + }); + +#if defined(INTERACTIVE_PLASMA) + // Request an update. + requestUpdate(); +#endif + + return true; +} + +// Loads a scene file with the specified path. +bool Plasma::loadSceneFile(const string& filePath) +{ + Foundation::CPUTimer loadTimer; + + // Get the appropriate function to load the specified scene file. If there is no appropriate + // scene load function, return false. + LoadSceneFunc loadSceneFunc = ::getLoadSceneFunc(filePath); + if (!loadSceneFunc) + { + ::errorMessage("The file extension is not recognized."); + + return false; + } + + // Get the directory from the file path, and set it as the current directory. This is often + // necessary for the loaders to find adjacent material and image files. + auto directory = filesystem::path(filePath).parent_path(); + filesystem::current_path(directory); + + // Create a new scene and load the scene file into it. If that fails, return immediately. + _sceneContents.reset(); + Aurora::IScenePtr pScene = _pRenderer->createScene(); + if (!loadSceneFunc(_pRenderer.get(), pScene.get(), filePath, _sceneContents)) + { + ::errorMessage("Unable to load the specified scene file: \"" + filePath + "\""); + + return false; + } + _instanceLayers = vector(_sceneContents.instances.size()); + + // Record the new scene and update application state for the new scene. + _pScene = pScene; + updateNewScene(); + + // Report the load time. + ::infoMessage("Loaded scene file \"" + filePath + "\" in " + + to_string(static_cast(loadTimer.elapsed())) + " ms."); + +#if defined(INTERACTIVE_PLASMA) + // Request an update. + requestUpdate(); +#endif + + return true; +} + +void Plasma::saveImage(const wstring& filePath, const uvec2& dimensions) +{ + assert(dimensions.x > 0 && dimensions.y > 0); + + updateLighting(); + + // Get the view and projection matrices from the camera as float arrays, and set them on the + // renderer as the camera (view). + auto* viewArray = reinterpret_cast(&_camera.viewMatrix()); + auto* projArray = reinterpret_cast(&_camera.projMatrix()); + _pRenderer->setCamera(viewArray, projArray); + + // Create a temporary render buffer. + Aurora::IRenderBufferPtr pRenderBuffer = _pRenderer->createRenderBuffer( + dimensions.x, dimensions.y, Aurora::ImageFormat::Integer_RGBA); + + // Render with the render buffer, then restore the window as the renderer's final target. + _pRenderer->setTargets({ { Aurora::AOV::kFinal, pRenderBuffer } }); + _pRenderer->render(0, 100); + _pRenderer->setTargets({ { Aurora::AOV::kFinal, _pWindow } }); + + // Get the data from the render buffer, and save it to a PNG file with the specified path. + size_t stride = 0; + const void* pData = pRenderBuffer->data(stride); + int res = ::stbi_write_png(Foundation::w2s(filePath).c_str(), dimensions.x, dimensions.y, 4, + pData, static_cast(stride)); + AU_ASSERT(res, "Failed to write PNG: %s", Foundation::w2s(filePath).c_str()); +} + +Aurora::Path Plasma::loadMaterialXFile(const string& filePath) +{ + Aurora::Path materialPath = "MaterialX:" + filePath; + // Load the MaterialX document into a string. + ifstream mtlXStream(filePath); + if (mtlXStream.fail()) + return ""; + + string mtlXString((istreambuf_iterator(mtlXStream)), istreambuf_iterator()); + + // Work out Autodesk Material Library path relative to Platform root. + const string kSourceRoot = TOSTRING(PLATFORM_ROOT_PATH); + const string mtlLibPath = + kSourceRoot + "/Renderers/Tests/Data/Materials/AutodeskMaterialLibrary/"; + + // Work out MaterialX resource path relative to Externals root. + const string kExternalsRoot = TOSTRING(EXTERNALS_ROOT_PATH); + const string mtlxResourcesPath = kExternalsRoot + "/git/materialx/resources"; + + // Replace the Autodesk Material Library in the loaded document. + // In the ProteinX files this is referenced as C:/Program Files/Common Files/Autodesk Shared. + // TODO: Should have file load callback in Aurora to avoid this. + string processedMtlXString = regex_replace( + mtlXString, regex("C:.Program Files.+Common Files.Autodesk Shared."), mtlLibPath); + + // Replace the MaterialX resource path in the loaded document. + // In the MaterialX sample document this is referenced as ../../.. + // TODO: Should have file load callback in Aurora to avoid this. + processedMtlXString = + regex_replace(processedMtlXString, regex(R"(\.\.\/\.\.\/\.\.)"), mtlxResourcesPath); + + // Create a new material from the materialX document. + _pScene->setMaterialType( + materialPath, Aurora::Names::MaterialTypes::kMaterialX, processedMtlXString); + + return materialPath; +} +bool Plasma::applyMaterialXFile(const string& filePath) +{ + // Create a new material from the materialX document. + Aurora::Path materialPath = loadMaterialXFile(filePath); + if (materialPath.empty()) + { + ::errorMessage("Failed to load MaterialX file: \"" + filePath + "\""); + return false; + } + + // Store the path used, so we can reload later. + _materialXFilePath = filePath; + + // Apply the material to all the instances in the scene. + for (size_t i = 0; i < _sceneContents.instances.size(); i++) + { + _pScene->setInstanceProperties(_sceneContents.instances[i].def.path, + { { + Aurora::Names::InstanceProperties::kMaterial, + materialPath, + } }); + } + +#if defined(INTERACTIVE_PLASMA) + // Request an update. + requestUpdate(); +#endif + + return true; +} + +void Plasma::resetMaterials() +{ + // Reset the materials to their original values as loaded. + for (size_t i = 0; i < _sceneContents.instances.size(); i++) + { + _pScene->setInstanceProperties(_sceneContents.instances[i].def.path, + { { + Aurora::Names::InstanceProperties::kMaterial, + _sceneContents.instances[i] + .def.properties[Aurora::Names::InstanceProperties::kMaterial], + } }); + } + + _materialXFilePath.clear(); + +#if defined(INTERACTIVE_PLASMA) + // Request an update. + requestUpdate(); +#endif +} + +#if defined(INTERACTIVE_PLASMA) +// Processes a message for the application window. +LRESULT Plasma::processMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + // Quit when the window is destroyed. + case WM_DESTROY: + PostQuitMessage(0); + _hwnd = nullptr; + ::setMessageWindow(nullptr); + return 0; + + // Handle dropping files on the window. + case WM_DROPFILES: + onFilesDropped(reinterpret_cast(wParam)); + return 0; + + // Handle key presses while the window has focus. + case WM_KEYUP: + onKeyPressed(wParam); + return 0; + + // Handle mouse moving over the window. + case WM_MOUSEMOVE: + onMouseMoved(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), GET_KEYSTATE_WPARAM(wParam)); + return 0; + + // Handle mouse wheel rotation. + case WM_MOUSEWHEEL: + onMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA, GET_KEYSTATE_WPARAM(wParam)); + return 0; + + // Handle window updates. + // NOTE: This message will only be received when something relevant has changed, i.e. that + // requires a new render to be performed. + case WM_PAINT: + // Update the window (render) and validate the window as updated (part of Begin/EndPaint()). + PAINTSTRUCT ps; + ::BeginPaint(_hwnd, &ps); + update(); + ::EndPaint(_hwnd, &ps); + + // Request another update if rendering is not converged or animation is being performed. + // For the latter, also request a restart, i.e. start rendering from the first sample. + if (!_sampleCounter.isComplete() || _isAnimating) + { + requestUpdate(_isAnimating); + } + + return 0; + + // Handle window size changes. + case WM_SIZE: + { + RECT clientRect = {}; + ::GetClientRect(_hwnd, &clientRect); + onSizeChanged(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); + return 0; + } + + // Handle pressing ALT-ENTER, which would otherwise produce a sound. + // NOTE: This is here only to prevent the sound, not to prevent setting a full screen state. + // Aurora already ensures that default handling of ALT-ENTER is disabled. + case WM_SYSCHAR: + if (wParam == VK_RETURN && ((lParam & (1 << 29)) != 0)) + { + return 0; + } + } + + return -1; +} + +// Creates a window for the application. +HWND Plasma::createWindow(const uvec2& dimensions) +{ + // Define and register a window class. + const wstring appName = Foundation::s2w(kAppName); + const WCHAR* appNameW = appName.c_str(); + WNDCLASS wc = {}; + wc.lpfnWndProc = wndProc; + wc.hInstance = _hInstance; + wc.lpszClassName = appNameW; + wc.hIcon = ::LoadIcon(_hInstance, MAKEINTRESOURCE(IDI_ENIGMA)); + wc.hCursor = ::LoadCursor(nullptr, IDC_ARROW); + ::RegisterClass(&wc); + + // Specify window styles, and get the size of window with the specified client area + // dimensions, accounting for the extra space for the window frame. + DWORD style = WS_OVERLAPPEDWINDOW; + RECT rect { 0, 0, static_cast(dimensions.x), static_cast(dimensions.y) }; + ::AdjustWindowRect(&rect, style, false); + int windowWidth = rect.right - rect.left; + int windowHeight = rect.bottom - rect.top; + + // Create the window. + HWND hwnd = ::CreateWindowEx(0, appNameW, appNameW, style, CW_USEDEFAULT, CW_USEDEFAULT, + windowWidth, windowHeight, nullptr, nullptr, _hInstance, nullptr); + + // Set the window to accept dropped files. + ::DragAcceptFiles(hwnd, TRUE); + + return hwnd; +} + +// Handles dropping of files on to the window. +void Plasma::onFilesDropped(HDROP hDrop) +{ + // Get the path of the first file dropped. + array filePathW; + ::DragQueryFile(hDrop, 0, filePathW.data(), MAX_PATH); + ::DragFinish(hDrop); + + // Display a wait cursor. + // NOTE: The cursor is restored as soon as the next system message is received. So this only + // works as desired here because the load blocks the application. + ::SetCursor(::LoadCursor(nullptr, IDC_WAIT)); + + // Get the extension of the file, and use the appropriate file load function. + string filePath = Foundation::w2s(filePathW.data()); + string foundExtension = Foundation::w2s(::PathFindExtension(filePathW.data())); + Foundation::sLower(foundExtension); + auto funcIt = _loadFileFunctions.find(foundExtension); + if (funcIt == _loadFileFunctions.end()) + { + ::errorMessage("Unknown file extension: " + foundExtension); + } + else + { + (*funcIt).second(filePath); + } +} + +// Handles key presses. +void Plasma::onKeyPressed(WPARAM keyCode) +{ + // Get the renderer options object. + Aurora::IValues& options = _pRenderer->options(); + + // For sequential keys starting with 0, enable the corresponding debug mode, e.g. 2 means + // debug mode 2 (show the view depth AOV). + constexpr int kTildeKey = 0xC0; + constexpr int kZeroKey = 0x30; + constexpr int kMaxDebugMode = 10; + if (keyCode == kTildeKey || (keyCode >= kZeroKey && keyCode < kZeroKey + kMaxDebugMode)) + { + // Determine and set the debug mode based on the key code: + // - Tilde maps to mode 0. + // - 0 key maps to mode 10. + // - Other number keys map to modes 1 - 9. + _debugMode = keyCode == kTildeKey + ? 0 + : (keyCode == kZeroKey ? kMaxDebugMode : static_cast(keyCode) - kZeroKey); + options.setInt("debugMode", _debugMode); + + // Update the desired sample count as that is affected by the debug mode, and request an + // update. + updateSampleCount(); + requestUpdate(); + + return; + } + + switch (keyCode) + { + // ESC: Destroy the main window. + case VK_ESCAPE: + DestroyWindow(_hwnd); + break; + + // F11: Toggle full screen display. + case VK_F11: + toggleFullScreen(); + break; + + // Space: Toggle animation. + case VK_SPACE: + toggleAnimation(); + break; + + // B: Toggle reference BSDF. + case 0x42: + _isReferenceBSDFEnabled = !_isReferenceBSDFEnabled; + _pRenderer->options().setBoolean("isReferenceBSDFEnabled", _isReferenceBSDFEnabled); + requestUpdate(); + break; + + // C: Toggle orthographic projection. + case 0x43: + _isOrthoProjection = !_isOrthoProjection; + _camera.setIsOrtho(_isOrthoProjection); + requestUpdate(); + break; + + // D: Toggle diffuse only. + // SHIFT-D: Toggle denoising. + case 0x44: + if (::GetAsyncKeyState(VK_SHIFT) != 0) + { + _isDenoisingEnabled = !_isDenoisingEnabled; + options.setBoolean("isDenoisingEnabled", _isDenoisingEnabled); + updateSampleCount(); + } + else + { + _isDiffuseOnlyEnabled = !_isDiffuseOnlyEnabled; + options.setBoolean("isDiffuseOnlyEnabled", _isDiffuseOnlyEnabled); + } + requestUpdate(); + break; + + // CTRL-E: Select an environment image file to load. + case 0x45: + if (::GetAsyncKeyState(VK_CONTROL) != 0) + { + LoadFileFunction func = _loadFileFunctions[".hdr"]; + selectFile(".hdr", L"HDR File\0*.hdr\0All Files\0*.*\0\0", func); + } + break; + + // F: Fit the view to the scene, retaining the current direction and up vectors. + case 0x46: + _camera.fit(_sceneContents.bounds); + requestUpdate(); + break; + + // G: Toggle ground plane shadow. + // SHIFT-G: Toggle ground plane reflection. + case 0x47: + if (::GetAsyncKeyState(VK_SHIFT) != 0) + { + _isGroundPlaneReflectionEnabled = !_isGroundPlaneReflectionEnabled; + } + else + { + _isGroundPlaneShadowEnabled = !_isGroundPlaneShadowEnabled; + } + updateGroundPlane(); + requestUpdate(); + break; + + // I: Cycle through the importance sampling modes: + // - 0: BSDF sampling. + // - 1: Environment light sampling. + // - 2: Multiple importance sampling (MIS). + case 0x49: + _importanceSamplingMode = (_importanceSamplingMode + 1) % 3; + _pRenderer->options().setInt("importanceSamplingMode", _importanceSamplingMode); + requestUpdate(); + break; + + // L: Toggle the directional light. + case 0x4c: + _isDirectionalLightEnabled = !_isDirectionalLightEnabled; + requestUpdate(); + break; + + // CTRL-M: Select a MaterialX file to load, and apply it to instances. + // M: Reload the previously loaded MaterialX document. + case 0x4d: + { + if (::GetAsyncKeyState(VK_CONTROL) != 0) + { + LoadFileFunction func = _loadFileFunctions[".mtlx"]; + selectFile(".mtlx", L"MaterialX File\0*.mtlx\0All Files\0*.*\0\0", func); + } + else if (!_materialXFilePath.empty()) + { + loadMaterialXFile(_materialXFilePath); + } + } + break; + + // CTRL-O: Select a scene file to load. + // NOTE: Currently this only supports OBJ files, but it may be expanded to other file types. + // O: Toggle whether to treat all objects as opaque for shadows, as a performance optimization. + case 0x4f: + if (::GetAsyncKeyState(VK_CONTROL) != 0) + { + LoadFileFunction func = _loadFileFunctions[".obj"]; + selectFile(".obj", L"OBJ File\0*.obj\0All Files\0*.*\0\0", func); + } + else + { + _isOpaqueShadowsEnabled = !_isOpaqueShadowsEnabled; + options.setBoolean("isOpaqueShadowsEnabled", _isOpaqueShadowsEnabled); + requestUpdate(); + } + break; + + // R: Reset materials to original ones. + case 0x52: + resetMaterials(); + break; + + // S: Save the current image to a file. + case 0x53: + saveImage(L"capture.png", uvec2(1920, 1080)); + break; + + // T: Toggle tone mapping. + case 0x54: + _isToneMappingEnabled = !_isToneMappingEnabled; + options.setBoolean("isToneMappingEnabled", _isToneMappingEnabled); + requestUpdate(); + break; + + // V: Toggle v-sync. + case 0x56: + toggleVSync(); + break; + + // U: Increment units. + case 0x55: + adjustUnit(+1); + break; + + // W: Add decal from materialX file. + case 0x57: + if (::GetAsyncKeyState(VK_CONTROL) != 0) + { + LoadFileFunction func = bind(&Plasma::addDecal, this, placeholders::_1); + selectFile(".mtlx", L"MaterialX File\0*.mtlx\0All Files\0*.*\0\0", func); + } + else if (!_decalMaterialXFilePath.empty()) + { + addDecal(_decalMaterialXFilePath); + } + break; + + // Y: Decrement units + case 0x59: + adjustUnit(-1); + break; + + // +: Increase exposure. + // CTRL+: Increase max luminance exposure. + case 0x6b: + case 0xbb: + if (::GetAsyncKeyState(VK_CONTROL) != 0) + { + adjustMaxLuminanceExposure(1.0f); + } + else + { + adjustExposure(0.5f); + } + break; + + // -: Decrease exposure. + // CTRL-: Decrease max luminance exposure. + case 0x6d: + case 0xbd: + if (::GetAsyncKeyState(VK_CONTROL) != 0) + { + adjustMaxLuminanceExposure(-1.0f); + } + else + { + adjustExposure(-0.5f); + } + break; + + // [: Decrease max trace depth. + case 0xdb: + _traceDepth = glm::max(1, _traceDepth - 1); + options.setInt("traceDepth", _traceDepth); + requestUpdate(); + break; + + // ]: Increase max trace depth. + case 0xdd: + _traceDepth = glm::min(10, _traceDepth + 1); + options.setInt("traceDepth", _traceDepth); + requestUpdate(); + break; + } +} + +// Handles mouse moves. +void Plasma::onMouseMoved(int xPos, int yPos, WPARAM buttons) +{ + // Update the camera. + Camera::Inputs inputs = {}; + inputs.LeftButton = (buttons & MK_LBUTTON) != 0; + inputs.MiddleButton = (buttons & MK_MBUTTON) != 0; + inputs.RightButton = (buttons & MK_RBUTTON) != 0; + _camera.mouseMove(xPos, yPos, inputs); + + // Request an update if the camera is dirty. + if (_camera.isDirty()) + { + requestUpdate(); + } +} + +// Handle mouse wheel input. +void Plasma::onMouseWheel(int delta, WPARAM /*buttons*/) +{ + // Update the camera. + Camera::Inputs inputs = {}; + inputs.Wheel = true; + _camera.mouseMove(0, delta, inputs); + + // Request an update if the camera is dirty. + if (_camera.isDirty()) + { + requestUpdate(); + } +} + +// Handles window size changes. +void Plasma::onSizeChanged(UINT clientWidth, UINT clientHeight) +{ + // Do nothing if the dimensions have not changed. + if (clientHeight == _dimensions.y && clientWidth == _dimensions.x) + { + return; + } + + // If the window has a zero dimension (e.g. it is minimized), validate the window so that no + // further paint messages are received (thus preventing unnecessary rendering) and return. + // NOTE: Restoring the window will invalidate the window and restart the paint messages. + if (clientWidth == 0 || clientHeight == 0) + { + ::ValidateRect(_hwnd, nullptr); + return; + } + + // Resize the Aurora window and update the camera. + _dimensions = uvec2(clientWidth, clientHeight); + _pWindow->resize(clientWidth, clientHeight); + _camera.setDimensions(_dimensions); + _performanceMonitor.setDimensions(_dimensions); + + // Request an update. + requestUpdate(); +} + +// Application entry point. +int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE /*hPrevInstance*/, + _In_ PWSTR /*lpCmdLine*/, _In_ int /*nCmdShow*/) +{ + // Initialize memory leak detection. + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + // _crtBreakAlloc = XXX; <- set a memory allocation breakpoint + + // Create an application object on the stack, and run it. The Run() function returns when + // the user quits the application, and it is destroyed. + Plasma app(hInstance); + gpApp = &app; + bool result = app.run(); + + return result ? 0 : -1; +} +#else //! INTERACTIVE_PLASMA +int main(int argc, char* argv[]) +{ + // Create an application object on the stack, and run it. The run() function returns when + // the single image rendering is finished, and the appllication object is then destroyed. + Plasma app; + gpApp = &app; + bool result = app.run(argc, argv); + + return result ? 0 : -1; +} +#endif // INTERACTIVE_PLASMA \ No newline at end of file diff --git a/Applications/Plasma/Plasma.h b/Applications/Plasma/Plasma.h new file mode 100644 index 0000000..7a3ee80 --- /dev/null +++ b/Applications/Plasma/Plasma.h @@ -0,0 +1,176 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Camera.h" +#include "Loaders.h" +#include "PerformanceMonitor.h" +#include "SceneContents.h" + +// Structure representing layer geometry and material. +struct Layer +{ + Aurora::GeometryDescriptor geomDesc; + Aurora::Path geomPath; + Aurora::Path mtlPath; + vector uvs; +}; + +// Vector of layers +using Layers = vector; + +class Plasma +{ +public: + /*** Lifetime Management ***/ + +#if defined(INTERACTIVE_PLASMA) + explicit Plasma(HINSTANCE hInstance, unsigned int width = 1280, unsigned int height = 720); +#else + explicit Plasma(unsigned int width = 1280, unsigned int height = 720); +#endif + ~Plasma(); + + /*** Functions **/ +#if defined(INTERACTIVE_PLASMA) + bool run(); +#else + bool run(int argc, char* argv[]); +#endif + +private: + /*** Private Types ***/ + + using LoadFileFunction = function; + using LoadFileFunctionMap = unordered_map; + + /*** Private Static Functions ***/ +#if defined(INTERACTIVE_PLASMA) + static LRESULT __stdcall wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +#endif + static Aurora::IScenePtr createSampleScene( + Aurora::IRenderer* pRenderer, SceneContents& contentsOut); + + /*** Private Functions ***/ + +#if defined(INTERACTIVE_PLASMA) + LRESULT processMessage(UINT message, WPARAM wParam, LPARAM lParam); + HWND createWindow(const uvec2& dimensions); +#endif + bool getFloat3Option(const string& name, glm::vec3& value) const; +#if defined(INTERACTIVE_PLASMA) + void parseOptions(); +#else + void parseOptions(int argc, char* argv[]); +#endif + bool initialize(); + void updateNewScene(); + void updateLighting(); + void updateGroundPlane(); + void updateSampleCount(); + void update(); +#if defined(INTERACTIVE_PLASMA) + void requestUpdate(bool shouldRestart = true); + void toggleAnimation(); + void toggleFullScreen(); + void toggleVSync(); + void adjustUnit(int increment); + void adjustExposure(float increment); + void adjustMaxLuminanceExposure(float increment); + void selectFile( + const string& extension, const wchar_t* pFilters, const LoadFileFunction& loadFunc); +#endif + bool loadEnvironmentImageFile(const string& filePath); + bool loadSceneFile(const string& filePath); + void saveImage(const wstring& filePath, const uvec2& dimensions); + bool applyMaterialXFile(const string& mtlxPath); + Aurora::Path loadMaterialXFile(const string& filePath); + + void resetMaterials(); + bool addDecal(const string& decalMtlXPath); + +#if defined(INTERACTIVE_PLASMA) + /*** Private Event Handlers ***/ + + void onFilesDropped(HDROP hDrop); + void onKeyPressed(WPARAM keyCode); + void onMouseMoved(int xPos, int yPos, WPARAM buttons); + void onMouseWheel(int delta, WPARAM buttons); + void onSizeChanged(UINT width, UINT height); +#endif + /*** Private Variables ***/ + +#if defined(INTERACTIVE_PLASMA) + HINSTANCE _hInstance = nullptr; + HWND _hwnd = nullptr; + WINDOWPLACEMENT _prevWindowPlacement = {}; +#endif + uvec2 _dimensions = uvec2(1280, 720); + LoadFileFunctionMap _loadFileFunctions; + unique_ptr _pArguments; + Camera _camera; + Foundation::SampleCounter _sampleCounter; + PerformanceMonitor _performanceMonitor; + unsigned int _debugMode = 0; + SceneContents _sceneContents; + unsigned int _frameNumber = 0; + bool _isAnimating = false; +#if defined(INTERACTIVE_PLASMA) + bool _isFullScreenEnabled = false; + bool _isVSyncEnabled = false; + bool _isOrthoProjection = false; +#endif + bool _isDirectionalLightEnabled = true; +#if defined(INTERACTIVE_PLASMA) + unsigned int _importanceSamplingMode = 2; // Importance sampling mode 2 == MIS + bool _isReferenceBSDFEnabled = false; +#endif + vec3 _lightDirection = normalize(vec3(1.0f, -0.5f, 0.0f)); + bool _isDenoisingEnabled = false; +#if defined(INTERACTIVE_PLASMA) + bool _isDiffuseOnlyEnabled = false; + bool _isOpaqueShadowsEnabled = false; + bool _isToneMappingEnabled = false; +#endif + bool _isGroundPlaneShadowEnabled = false; + bool _isGroundPlaneReflectionEnabled = false; +#if defined(INTERACTIVE_PLASMA) + int _traceDepth = 5; + float _exposure = 0.0f; + float _maxLuminanceExposure = 0.0f; +#endif + bool _shouldRestart = true; + vector _units = { "millimeter", "centimeter", "inch", "foot", "yard" }; +#if defined(INTERACTIVE_PLASMA) + int _currentUnitIndex = 1; +#endif + + vec3 _lightStartDirection = vec3(1.0f, -0.5f, 0.0f); + vec3 _lightColor = vec3(1.0f, 1.0f, 1.0f); + float _lightIntensity = 2.0f; + + Foundation::CPUTimer _animationTimer; + string _materialXFilePath; + string _decalMaterialXFilePath; + vector _instanceLayers; + + Aurora::Path _environmentPath; + + Aurora::IRenderer::Backend _rendererType = Aurora::IRenderer::Backend::Default; + Aurora::IRendererPtr _pRenderer; + Aurora::IGroundPlanePtr _pGroundPlane; + Aurora::IScenePtr _pScene; + Aurora::IWindowPtr _pWindow; + vector _assetPaths; +}; diff --git a/Applications/Plasma/SceneContents.cpp b/Applications/Plasma/SceneContents.cpp new file mode 100644 index 0000000..f2d7143 --- /dev/null +++ b/Applications/Plasma/SceneContents.cpp @@ -0,0 +1,26 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "SceneContents.h" + +// Clear the contents all the loaded values instance data. +void SceneContents::reset() +{ + vertexCount = 0; + triangleCount = 0; + bounds.reset(); + instances.clear(); + geometry.clear(); +} diff --git a/Applications/Plasma/SceneContents.h b/Applications/Plasma/SceneContents.h new file mode 100644 index 0000000..7ebd2b4 --- /dev/null +++ b/Applications/Plasma/SceneContents.h @@ -0,0 +1,58 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// A structure used to keep a copy of all the vertex and index data used to create instance. +struct SceneGeometryData +{ + std::vector positions; + std::vector normals; + std::vector texCoords; + + std::vector indices; + Aurora::GeometryDescriptor descriptor; +}; + +struct SceneInstanceData +{ + Aurora::InstanceDefinition def; + Aurora::Path geometryPath; +}; + +// Contents of a loaded scene. +struct SceneContents +{ + // Description of instances in the scene. + std::vector instances; + + std::map geometry; + + SceneGeometryData& addGeometry(Aurora::Path path) + { + geometry[path] = SceneGeometryData(); + return geometry[path]; + } + + // Number of vertices in the loaded scene. + uint32_t vertexCount = 0; + + // Number of triangles in the loaded scene. + uint32_t triangleCount = 0; + + // The world space bounds of the loaded scene. + Foundation::BoundingBox bounds; + + // Clear the contents all the loaded values instance data. + void reset(); +}; \ No newline at end of file diff --git a/Applications/Plasma/glTFLoader.cpp b/Applications/Plasma/glTFLoader.cpp new file mode 100644 index 0000000..b964d38 --- /dev/null +++ b/Applications/Plasma/glTFLoader.cpp @@ -0,0 +1,39 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "Loaders.h" + +// Loads a glTF file into the specified renderer and scene, from the specified file path. +bool loadglTFFile(Aurora::IRenderer* /* pRenderer */, Aurora::IScene* /* pScene */, + const string& filePath, SceneContents& /* sceneContents */) +{ + tinygltf::TinyGLTF loader; + tinygltf::Model model; + string warnings, errors; + + // Attempt to load the file as a binary glTF file, which is (quickly) identified from the file + // header. If that fails, instead try to load it as an ASCII file. + bool result = loader.LoadBinaryFromFile(&model, &errors, &warnings, filePath); + if (!result) + { + result = loader.LoadASCIIFromFile(&model, &errors, &errors, filePath); + } + + // glTF is not yet supported, so for now simply return false. + // TODO: Load the model into Aurora data structures, similar to the OBJ loader. Also include + // ".gtlf" and ".glb" in the list of file selection filters. + + return false; +} \ No newline at end of file diff --git a/Applications/Plasma/pch.h b/Applications/Plasma/pch.h new file mode 100644 index 0000000..5b759e7 --- /dev/null +++ b/Applications/Plasma/pch.h @@ -0,0 +1,133 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// Memory leak detection. +#if defined(INTERACTIVE_PLASMA) +#define _CRTDBG_MAP_ALLOC +#include +#endif +#include + +#if defined(INTERACTIVE_PLASMA) +// Windows headers. +#define WIN32_LEAN_AND_MEAN +#include +// NOTE: Must come after Windows.h. This comment ensures clang-format does not reorder. +#include +#include +#include +#include +#include +#endif + +// STL headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +// GLM - OpenGL Mathematics +// NOTE: This is a math library, and not specific to OpenGL. +#define GLM_FORCE_CTOR_INIT +#pragma warning(push) +#pragma warning(disable : 4127) // nameless struct/union +#pragma warning(disable : 4201) // conditional expression is not constant +#include +#include +#include +#include +#pragma warning(pop) +using namespace glm; + +// STB libraries. +#pragma warning(push) +#pragma warning(disable : 4996) +#include +#pragma warning(pop) + +// 3D model file loaders. +#include +#include + +// cxxopts for command line argument parsing. +#include + +// Aurora. +#include +#include +#include +#include +using namespace Aurora; + +// Module resources. +#include "resource.h" + +#if defined(INTERACTIVE_PLASMA) +// TODO move those windows implementations to a utility class +// Set the window to use for messages. +extern HWND gWindow; +inline void setMessageWindow(HWND hwnd) +{ + gWindow = hwnd; +} +#endif + +// Displays an information message. +inline void infoMessage(const string& message) +{ + AU_INFO(message); +} + +// Displays an error message. +inline void errorMessage(const string& message) +{ + AU_ERROR(message); +#if defined(INTERACTIVE_PLASMA) + ::MessageBoxW(gWindow, Foundation::s2w(message).c_str(), L"Error", MB_OK); +#endif +} + +// Linearizes a single sRGB color component and returns it. +inline float sRGBToLinear(float value) +{ + return value * (value * (value * 0.305306011f + 0.682171111f) + 0.012522878f); +} + +// Linearizes an sRGB color and returns it. +inline vec3 sRGBToLinear(const vec3& color) +{ + vec3 result; + result.r = sRGBToLinear(color.r); + result.g = sRGBToLinear(color.g); + result.b = sRGBToLinear(color.b); + + return result; +} + +// Utility macro to convert preprocessor value to string. +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) diff --git a/Applications/Plasma/resource.h b/Applications/Plasma/resource.h new file mode 100644 index 0000000..97cef29 --- /dev/null +++ b/Applications/Plasma/resource.h @@ -0,0 +1,26 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#define IDI_ICON1 101 +#define IDI_ENIGMA 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cfac487 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.21) + +# Forbid the in-source build +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR “In-source build is not allowed!”) +endif() + +# Set the project name and project variables +project(Aurora VERSION 0.0.0.1) + +set(AURORA_ROOT_DIR ${PROJECT_SOURCE_DIR}) + +# Set the default output directories +set(RUNTIME_OUTPUT_DIR "${PROJECT_BINARY_DIR}/bin/$") +set(LIBRARY_OUTPUT_DIR "${PROJECT_BINARY_DIR}/lib/$") + +# Set scripts folder. +set(SCRIPTS_DIR ${AURORA_ROOT_DIR}/Scripts) + +# Add the search path of CMake modules +list(APPEND CMAKE_MODULE_PATH + "${SCRIPTS_DIR}/cmake" + "${SCRIPTS_DIR}/cmake/modules" +) + +# Import the cmake utility functions +include(toolbox) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Default build type: Release" FORCE) +endif() + +# The default compile definition used by all packages. +set(DEFAULT_COMPILE_DEFINITIONS NOMINMAX UNICODE _USE_MATH_DEFINES _HAS_STD_BYTE=0) + +# Enable folders for project code organisation in IDE. +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + + +if(MSVC) + # Add warning level 4 and warnings as errors + add_compile_options(/W4 /WX -wd4068) # -wd4068 disables unknown-pragmas warnings + # Enable multiple-processor compilation + add_compile_options(/MP) +else() + # Enables strict standard conformance and warning as errors + add_compile_options(-Wall -Wextra -Wpedantic -Werror -Wno-unknown-pragmas -Wno-gnu-zero-variadic-macro-arguments) +endif() + +include(externals) + +#Setup the backend flags used by Aurora libray and tests. +if(WIN32) + option(ENABLE_DIRECTX_BACKEND "Build with DirectX renderer backend." ON) + option(ENABLE_HGI_BACKEND "Build with HGI renderer backend (requires Vulkan SDK)." OFF) +else() + # Always disable DirectX backend on non-Windows platforms + set(ENABLE_DIRECTX_BACKEND OFF) + # Always enable HGI backend on non-Windows platforms + set(ENABLE_HGI_BACKEND ON) +endif() + +add_subdirectory(Libraries) +add_subdirectory(Tests) +add_subdirectory(Applications) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8bf07a5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,82 @@ +# Contributor Covenant + +In addition to the below contributor covenant, Autodesk employees are bound by the [Autodesk Code of Business Conduct](https://www.autodesk.com/company/legal-notices-trademarks/compliance#conduct). + +------ + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a safe, inclusive and harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive and healthy community. + +## Our Standards + +Our Open Source Community works to: + +- Be kind towards other people which enables us to be empathic to each other +- Be respectful of differing opinions, viewpoints, and experiences +- Give and gracefully accept constructive feedback +- Accept responsibility and apologize to those affected by our mistakes, and learning from the experience +- Focus on what is best not just for us as individuals, but for the overall community + +We will not tolerate the following behaviors: + +- Violent threats or language +- The use of sexualized language or imagery, and sexual attention or advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or email address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement using the [Autodesk Open Source email](mailto:opensource@autodesk.com). All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla’s code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..eab08a5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing to Aurora + +## Contributor License Agreement # +Before contributing code to this project, we ask that you sign the appropriate Contributor License Agreement (CLA): + ++ [ADSKFormCorpContribAgmtForOpenSource.docx](Doc/CLA/ADSKFormCorpContribAgmtforOpenSource.docx?raw=1): please sign this one for corporate use. ++ [ADSKFormIndContribAgmtForOpenSource.docx](Doc/CLA/ADSKFormIndContribAgmtforOpenSource.docx?raw=1): please sign this one if you're an individual contributor + +The documents include instructions on where to send the completed forms. Once a signed form has been received you will be able to submit pull requests. + +Contributors are expected to follow the [Contributor Covenant](CODE_OF_CONDUCT.md), which describes the code of conduct for Autodesk open source projects like this one. + +## Logging Issues + +### Suggestions + +The Aurora project is meant to evolve with feedback - the project and its users greatly appreciate any thoughts on ways to improve the design or features. Please use the `enhancement` tag to specifically denote issues that are suggestions, as it helps us triage and respond appropriately. + +### Bugs + +As with all pieces of software, you may end up running into bugs. Please submit bugs as regular issues on GitHub. Aurora developers are regularly monitoring issues and will prioritize and schedule fixes. + +The best bug reports include the following: + +- Detailed steps to reliably reproduce the issue. +- A description of what the expected and the actual results are. +- Any screenshots or associated sample files to aid in reproducing the issue. +- What software versions and hardware is being used. +- Any other supplemental information that maybe useful in reproducing and diagnosing the issue. + +## Contributing Code + +The Aurora project accepts and greatly appreciates contributions. The project follows the [fork & pull](https://help.github.com/articles/using-pull-requests/#fork--pull) model for accepting contributions. + +When contributing code, please also include appropriate tests as part of the pull request, and follow the published [coding standards](Doc/CodingStandards.md). In particular, use the same style as the code you are modifying. + +All development should happen against the "main" (default) branch of the repository. Please make sure the base branch of your pull request is set to the "main" branch when filing your pull request. + +It is highly recommended that an issue be logged on GitHub before any work is started. This will allow for early feedback from other developers and will help avoid duplicate effort. diff --git a/Doc/CLA/ADSKFormCorpContribAgmtforOpenSource.docx b/Doc/CLA/ADSKFormCorpContribAgmtforOpenSource.docx new file mode 100644 index 0000000..48cc380 --- /dev/null +++ b/Doc/CLA/ADSKFormCorpContribAgmtforOpenSource.docx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b47880b98be4b761e84e225b3c9da6d11ba035633417dffd38f22dda56359eaa +size 33428 diff --git a/Doc/CLA/ADSKFormIndContribAgmtforOpenSource.docx b/Doc/CLA/ADSKFormIndContribAgmtforOpenSource.docx new file mode 100644 index 0000000..5771a1a --- /dev/null +++ b/Doc/CLA/ADSKFormIndContribAgmtforOpenSource.docx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:068fffc911026ed8a6fb22136ba141316cb2e72ea80dd1e68c44b785d54b13e6 +size 29025 diff --git a/Doc/CodingStandards.md b/Doc/CodingStandards.md new file mode 100644 index 0000000..b5e910a --- /dev/null +++ b/Doc/CodingStandards.md @@ -0,0 +1,504 @@ +> Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. +> +> — John F. Woods + +Please observe the following coding standards when contributing to Aurora. + +## Table of Contents + +- [Rule Zero: No Warnings or Memory Leaks](#rule-zero-no-warnings-or-memory-leaks) +- [Rule One: Implementation Comments](#rule-one-implementation-comments) +- [Doc Comments](#doc-comments) +- [Linting and Formatting Tools](#linting-and-formatting-tools) + - [clang-tidy](#clang-tidy) + - [clang-format](#clang-format) +- [Secure Coding Guidelines](#secure-coding-guidelines) +- [C++ Features](#c-features) + - [Overview](#overview) + - [Arrays](#arrays) + - [Auto](#auto) + - [Callback Functions](#callback-functions) + - [Casting](#casting) + - [Conditional Statements](#conditional-statements) + - [Enumerations](#enumerations) + - [Files and Paths](#files-and-paths) + - [Integer Types](#integer-types) + - [Loops](#loops) + - [Macros](#macros) + - [Member Initialization](#member-initialization) + - [Namespaces](#namespaces) + - [Numbers](#numbers) + - [Override Keyword](#override-keyword) + - [Smart Pointers](#smart-pointers) + - [Strings and Unicode](#strings-and-unicode) + - [Type Aliases](#type-aliases) +- [Formatting](#formatting) + - [Accessors](#accessors) + - [Complexity](#complexity) + - [File and Directory Names](#file-and-directory-names) + - [Identifiers](#identifiers) + - [Indenting](#indenting) + - [Line Length](#line-length) + - [Spacing Inside Expressions](#spacing-inside-expressions) + - [Whitespace](#whitespace) +- [Header Files](#header-files) + - [Including Header Files](#including-header-files) + - [Including Header Files - Example](#including-header-files---example) + - [Member Order](#member-order) +- [Logging](#logging) +- [Error Handling](#error-handling) +- [CMake](#cmake) +- [Binaries](#binaries) + +## Rule Zero: No Warnings or Memory Leaks + +* **NO linter / compiler warnings or memory leaks.** + +Why zero? It is easy to spot one new warning or memory leak when you don't have any to begin with, but difficult when you already have many of them. + +**Warnings:** You can use warning or error overrides (e.g. `pragma warning disable`) if the underlying issue can't be fixed, but such overrides _must_ be commented. This may be unavoidable for third-party libraries. Overrides should be added in the code at the specific points where they are needed, and not in the CMake scripts. Note that Aurora is built with a high warning level (e.g. [`/W4` in Visual C++](https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=vs-2019)), and with warnings treated as errors. + +**Memory Leaks:** You must not introduce obvious memory leaks, i.e. from launching and exiting a test application with simple data. Memory leaks are usually reported on application shutdown, and this report should always be empty with simple testing. + +- Windows: CPU memory leaks will be reported as `Detected memory leaks!` followed by the relevant memory locations. +- Direct3D 12: GPU memory leaks will be reported with `D3D12 Warning` or `DXGI Warning` followed by the relevant memory locations. + +## Rule One: Implementation Comments + +> The purpose of commenting is to help the reader know as much as the writer did. +> +> — "The Art of Readable Code" (2012) + +**All code must include programmer-level documentation as comments.** Comments are absolutely essential for proper understanding, debugging, and modification of existing code. Other developers looking at your code (and yourself after you have forgotten the code!) are depending on this. + +Comments should convey **design intent**, i.e. more about "why" and less about "how." This can be supplemented with design documentation, but comments should be the first source for developers working on the code. Recovering design intent without comments or documentation is very difficult, and often involves laborious "code archaeology." Specifically, It should not be necessary to consult the original author (who may no longer be available) or comb through old commits to understand how code works. + +Some specific guidelines include: + +* Implementation comments (usually inside functions) are extremely important. + * Each non-trivial "block" of code should have a comment. + * A block is therefore: empty line, comment, lines of code, followed by an empty line. + * Avoid end-of-line comments and comments that are not at the top of blocks. +* On average, there will be a comment for roughly every ten lines of code. In some cases, the number of lines of comments will exceed the number of lines of code! +* Add a `// TODO`: comment starting on its own line to highlight and describe missing, incomplete, or potentially error-prone code. You may refer to the ID of a corresponding GitHub issue, e.g. `// TODO: See issue #42 for details.` +* Add a `// NOTE`: comment starting on its own line for background information, e.g. additional details on why something is being done, or links to additional information. +* You may include links to public references (e.g. technical papers) that don't require authentication. However, never include links to _internal_ issue tracking systems, wikis, etc. +* Remove all wizard-generated comments unless they are actually useful. +* Avoid comments that explain the obvious. +* All comments should be kept up-to-date. Make sure to add new comments or edit existing comments when editing existing code. +* All comments follow these guidelines for a professional appearance and readability: + * Use complete sentences, i.e. start with a capital letter and end with a period. + * Sentences should be separated by single spaces (not double). + * Spelling should be correct. + * Avoid the use of abbreviations like "dtor." + * Acronyms can be used depending on the context of the code, e.g. "UAV" (unordered access view) in the context of low-level rendering code, but not in high-level API where the acronym may not be understood. +* Do not comment out obsolete code; simply delete it. It can be retrieved with source control history if needed. +* If you want to retain a small amount of commented out reference code, include a comment explaining why it is commented out. For example, "Uncomment this code to log additional information." + +## Doc Comments + +- Include doc comments for classes and their public members in their corresponding header files. + +- Doc comments are specially formatted comments used to generate documentation for types and functions. We use [Doxygen](http://www.doxygen.nl) to generate this documentation, which is used by clients. + +- This is different from the implementation comments described in "Rule One" above. Both are important: implementation comments for internal developers, and doc comments for external (and internal) developers. + +- At a minimum, **every public type, enumerator, and function** (including public members) must have a brief, single sentence doc comment. Beyond this, it is desirable to include comments for function parameters and return values, especially when they are not trivial. + +- We use the following doc comment style: + + ```c++ + /// A brief description, and _only_ a single sentence, ending in a period. + /// + /// An empty line above, followed by more information. Doxygen will automatically separate the + /// single sentence (above) and this longer description. This can be multiple sentences and + /// wrap multiple lines like this. Follow this with another empty line. + /// + /// \note Optional note which appears separate in the generated documentation. This can be used + /// for special guidance or disclaimers to developers. + /// \param myParameterName Describe the parameter here. The first word after "\param" must be + /// the parameter name. This can be multiple sentences and wrap multiple lines like this. It is + /// not necessary to mention default arguments; they appears in the generated documentation. + /// \return Describe the return value here. + bool doSomething(float myParameterName = 1.0f); + ``` + +- Use the present tense (usually ending in "s") and articles ("the" "a" "an") for function descriptions, e.g. "Compute**s** the length of the vector." + +## Linting and Formatting Tools + +If you encounter unexpected issues with automatic linting and formatting tools, please log an issue in GitHub or propose an appropriate override. As with disabling warnings, any overrides must be commented. + +### clang-format + +We use [clang-format](https://clang.llvm.org/docs/ClangFormat.html) to automatically format code to be compliant with the standards. +- Formatting rules are defined by the `.clang-format` file in the root of each repository. +- To manually run clang-format on a file in Visual Studio, use _Edit | Advanced | Format Document_ (Ctrl-K+D). +- You can also use the [Format All Files extension](https://marketplace.visualstudio.com/items?itemName=munyabe.FormatAllFiles) to format all the files in a project. Make sure to undo changes to files that should not be formatted with clang-format, e.g. HLSL files. + +## C++ Features + +### Overview + +- **We generally follow the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines).** +- Any features from **C++17 or earlier** are assumed to be available. +- If you find it necessary to use features in a new version of C++, please log a request as a GitHub issue. +- Avoid use of "clever" techniques that use new features, but that don't improve readability, performance, or stability. + - For more perspective on possible pitfalls of "clever" use of C++, see [this blog post](https://aras-p.info/blog/2018/12/28/Modern-C-Lamentations). + - In other words: you should not need a "PhD in C++" to develop for Aurora. +- The STL types (or near-identical implementations) are now part of the [C++ Standard Libary](https://en.wikipedia.org/wiki/C%2B%2B_Standard_Library), so they can appear in public header files. + +### Arrays + +- For fixed-size arrays, prefer the use of `std::array<>`. +- For variable-size arrays, prefer the use of `std::vector<>`. +- As a convenience, unique fixed-size arrays can be created with `std::make_unique(yourSize)`. + - Support for shared fixed-size arrays with `std::make_shared` is only available with C++20. + - An alternative is to use `std::make_shared>(yourSize)`. +- If possible, use `reserve` to pre-allocate vectors with their known sizes. + +### Auto + +- [The `auto` keyword](https://en.cppreference.com/w/cpp/language/auto) should be used to automatically deduce types when the type is otherwise obvious or would be cumbersome to include. +- Conversely, do not use `auto` when it prevents a developer from knowing the type by simply reading the code without tools, e.g. in GitHub. An exception is iterators: `auto` can and should be used for iterator types. +- Use an asterisk for pointer types: `auto* pMyPointer...` The keyword works the same with and without the asterisk; we are using it for clarity. +- Specifically, follow the guidelines of the clang-tidy [modernize-use-auto](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-auto.html) check. + +### Callback Functions + +- Use `std::function<>` as the type for function parameters that accept callback functions. +- This allows the caller to specify the callback function in one of several ways. +- [See this code sample for more details](https://github.com/pixnblox/Tutorials/tree/master/Callback%20Functions). + +### Casting + +- Use C++ casts instead of C-style casts, e.g. `static_cast(myFloat)` instead of `(int)myFloat`. +- These include: `static_cast<> const_cast<> dynamic_cast<> reinterpret_cast<>`. +- For more information, see this [Stack Overflow question and answer](https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used). + +### Conditional Statements + +- It is _not_ necessary to compare against `nullptr` when testing pointers for values, e.g. `if (pFoo != nullptr)`. This is safe with raw pointers in modern C++, and directly supported with a boolean operator for smart pointers. Also, the guideline of using a "p" prefix makes it clear to the reader what is happening. +- For an an `if / else` statement, prefer using the positive state in the `if` statement, so it appears first. For example, use `if (myFlag) / else ...` instead of `if (!myFlag) / else ...`. +- A boolean **false and true** automatically and safely converts to an integer **zero and one** (respectively). Similarly, an integer zero converts to false, and any other integer converts to true. Casting is not necessary. For example, it is valid to use a bitwise operator to get a test result like `bool result = testBits & value` . Similarly, it is not necessary to cast `int value = functionThatReturnsBool()`. + +### Enumerations + +- Enumerations should be declared with `enum class` instead of simply `enum`. +- Enumerators of such enumerations have their own scope, and are not implicitly converted to integers. +- Enumerators should be prefixed with "k" like constants: `kMyEnumerator`. +- [See the C++ Core Guidelines for details](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#enum3-prefer-class-enums-over-plain-enums). + +### Files and Paths + +- Do not parse or create file names or paths manually with string manipulation, as this can be error-prone across operating systems. +- Instead use (and preferably encapsulate) operating system functions. For example, on Windows the `Path*` functions from [shlwapi.h](https://docs.microsoft.com/en-us/windows/win32/api/shlwapi) can be used safely. +- Note that we can't use the [STL filesystem library](https://en.cppreference.com/w/cpp/filesystem) yet due to lack of support on macOS before 10.15 (Catalina). +- For identifiers referring to file paths, prefer the use of the word "path" unless a file _name_ is specifically being referenced, i.e. not including possible directories. For example, `setFilePath` instead of `setFileName`. Also note that "file name" or "file path" is always two words. + +### Integer Types + +- Use `int` in most cases, where numbers are not particularly large (or very negative). +- Use the [fixed width integer types](https://en.cppreference.com/w/cpp/types/integer) from ``, such as `int64_t` when you need a guaranteed size. +- Use `size_t` for variables referring to sizes in memory, including offsets and strides. +- Avoid the used of unsigned integer types. + - In particular, don't use them to indicate than a value can't be negative; use an assert instead. + - There are cases where they are needed, e.g. bit manipulation or byte arrays. + - [See here for more information](https://www.learncpp.com/cpp-tutorial/unsigned-integers-and-why-to-avoid-them/). +- Use `uint8_t` for byte arrays, at least until we can use `std::byte` from C++17. + +### Loops + +- Use [range-based loops](https://en.cppreference.com/w/cpp/language/range-for) when possible, instead of traditional `for` loops. +- This can be done with statically allocated arrays, containers (using iterators), and array-like containers (with the `[]` operator). +- Specifically, follow the guidelines of the clang-tidy [modernize-loop-convert](https://clang.llvm.org/extra/clang-tidy/checks/modernize/loop-convert.html) check. + +### Macros + +- Macros are function-like preprocessor definitions, defined with `#define`. These have a number of properties that make them [dangerous to use](https://isocpp.org/wiki/faq/inline-functions#inline-vs-macros), so avoid creating them. +- Inline functions and template function can often be used to accomplish the same tasks more reliably. +- If you must define macros, use UPPER_CASE for names: `MY_MACRO` + +### Member Initialization + +- Use [default member initializers](https://en.cppreference.com/w/cpp/language/data_members) instead of a default constructor's member initializers. + +- This capability was expanded in C++11; it was previously limited to static const integral types. + +- For example: + + ```c++ + public: + MyClass::MyClass() : _myMember(1.0f) {} // don't do this... + float _myMember; + + public: + MyClass::MyClass() {} + float _myMember = 1.0f; // ... instead do this + ``` + +- [See this blog post for examples](https://abseil.io/tips/61). + +### Namespaces + +- When declaring a namespace, don't indent the contained code; there is no real advantage to indenting and it just loses some (useful) space. +- At the end of a namespace declaration, use a comment at the closing brace to make it clear what is ending, e.g. `} // namespace Aurora`. +- When using global identifiers, such as from the Win32 API, explicitly refer to the global namespace with the `::` operator to make this clear, e.g. `::SetWindowText(...)`. +- Be careful not to pollute the client's namespace with `using` directives. +- You may apply `using` to use common namespaces like `std` in internal code that is not accessed by clients. This is typically done in the precompiled header file (`pch.h`). In this case, the relevant namespace should be omitted from identifiers, e.g. use simply `shared_ptr` instead of `std::shared_ptr`. + +### Numbers + +- Avoid the use of "magic" numbers that don't have an explicit meaning. +- Use named constants instead. For example, instead of using the constant "4" to refer to the number of samples for a function call (`myFunction(4);`), define a constant and use it: `int kSampleCount = 4; myFunction(kSampleCount);`. +- Constants with a complex or non-intuitive meaning should have a corresponding comment. + +### Override Keyword + +- Use the `override` keyword on functions overridden in derived classes... +- ... and do not use `virtual` on overridden functions (only in the base declaration) . +- Specifically, follow the guidelines of the clang-tidy [modernize-use-override](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-override.html) check. + +### Smart Pointers + +- Use smart pointers to control object lifetime: + + - Use the `std::shared_ptr<>` type for sharing objects with a reference count. + - Use the `std::unique_ptr<>` type for retaining (or transferring, with [std::move](https://en.cppreference.com/w/cpp/utility/move)) exclusive control of the object. + - Use the `std::weak_ptr<>` type to break potential circular dependencies between smart pointers, if needed. + +- Create objects with `std::make_shared<>` and `std::make_unique<>`, instead of `new` / `delete`. + +- Exception: Use `ComPtr` for lifetime management of DirectX objects, and any other COM objects. + + - Use `` to include this class. + - Since this type should not appear in public API, you can declare `using Microsoft::WRL::ComPtr;`. + - See [this DirectXTK wiki page](https://github.com/Microsoft/DirectXTK/wiki/ComPtr) for more information. + +- Pass / return objects and smart pointers in functions as follows: + + - Pass smart pointers by const reference (not by value) when the function needs to share (or transfer) ownership. + - Pass smart pointers by reference only when the the smart pointer itself must be manipulated; this is not common. + - In other cases (which are the most common), prefer passing objects by reference (when not nullable) or raw pointer (when nullable), not by smart pointer. + + - Return smart pointers by value (never by reference) when the function needs to share (or transfer ownership). Otherwise return the raw pointer. + - [Much more on this subject can be found here](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters). Note that it suggests passing by value instead of const reference, but the argument for this is not very strong, and it would be inconsistent with common practice for other types. +- Because smart pointers initialize to `nullptr` and it is not necessary to compare to `nullptr` (see [Conditional Statements](#conditional-statements)), the direct use of the `nullptr` keyword should be rare. Raw pointers, where used, should still be initialized to `nullptr`. +- It is common to alias smart pointers with a type ending in "Ptr" such as `IRendererPtr` for `shared_ptr`. This allows for brevity as well as hinting that a smart pointer is being used. Do not use this suffix to alias raw pointer types. + +### Strings and Unicode + +- Use `std::string` with UTF-8 encoding as much as possible. UTF-8 can represent all possible [Unicode code points](https://en.wikipedia.org/wiki/List_of_Unicode_characters). +- Do not assume that each byte is one character when using UTF-8 encoding, i.e. `string::length()` will not necessarily return the string length in characters. +- On Windows: + - You MUST use the "W" versions of Win32 functions and wide character overloads of Standard Library functions like the `ifstream` constructor. + - **This is the only way to support Unicode on Windows.** The "A" functions and Standard Library narrow character overloads don't support UTF-8 and should not be used. + - "Widen" the UTF-8 strings to UTF-16 as needed (and as close as possible to the calls) to provide inputs to / get results from the wide functions. Use the functions `Aurora::Foundation::s2w()` and `Aurora::Foundation::w2s()` or [the code in this gist](https://gist.github.com/pixnblox/b4980243334a1292bed957493941f071) to convert to and from UTF-16 strings. + - String literals with special characters (which are rare) must be declared with wide characters, and then narrowed for storage. + - Third-party libraries that accept narrow character inputs require special support for Windows, as described above. Verify such support is present before using them. +- For more on this: [http://utf8everywhere.org](http://utf8everywhere.org/). + +### Type Aliases + +Do not use `typedef` for type aliases. Instead use the `using` keyword to create an [alias declaration](https://en.cppreference.com/w/cpp/language/type_alias). +- `typedef int MyType;` ⬅ do not do this +- `using MyType = int;` ⬅ do this instead + +## Formatting + +NOTE: clang-format should enforce most of these standards, but still try to observe them as you write. + +### Accessors + +- For set accessors, use a `set` prefix, e.g. `void setViewMatrix(mat4 value)`. +- For trivial get accessors, or those that perform lazy evaluation, _do not_ use a prefix, e.g. `const mat4& viewMatrix() const`. This is an indication to the caller that it is OK to call the function many times in a row if needed. +- For trivial get accessors that use reference parameters for return values, use a `get` prefix, e.g. `void getDimensions(int& width, int& height) const`. +- For get accessors that perform non-trivial work, use the prefix `compute` instead, e.g. `float computeDistance() const`. This is an indication to the caller that the function should not be called repeatedly. +- For all get accessors, where possible declare functions as const and return values as const references, as in the `viewMatrix` example above. + +### Braces + +- Braces should appear on their own lines. +- Single-line statements inside conditional expressions should be surrounded by braces. +``` +if (foo == bar) +{ + return foo; +} +else +{ + bar++; +} +``` + +### Complexity + +- Avoid long functions (e.g. over 50 lines of code, not including comments), and refactor them into separate functions as needed. +- Similarly, avoid long indented blocks of code, e.g. the body of a conditional statement. +- When closing a `#if` preprocessor directive after several lines of code, use an end-of-line comment that matches the definition name, similar to what is expected for namespace declarations, e.g. +``` +#if defined(_WIN32) + // Several + // lines + // of + // code +#end // _WIN32 <- add this +``` +- On a related note, use the `#if` directive with `defined()` instead of `#ifdef`, as shown above. + +### File and Directory Names + +- All file and directory names should use UpperCamelCase, e.g. `MyDirectory/MyFile.cpp`. This keeps class names consistent with their file names. +- Exceptions: + - `pch.h` for precompiled header files. + - Files that require a different naming convention, e.g. `.gitignore` and `.clang-format`. + - Binary libraries (.lib, .dll, etc.) should use lowerCamelCase, e.g. `hdAurora.dll`. Executables should use UpperCamelCase. + +### Identifiers + +- Use UpperCamelCase for type names, including classes: `MyAwesomeThing`. +- Use lowerCamelCase for variables and function names: `doSomethingCool()`. +- Use UPPER_CASE for macros: `MY_MACRO`. Note that macros are discouraged. +- Prefix constants and enumerators with "k": `kMyConstant`. +- Prefix member variables with an underscore: `_myMemberVariable`. +- Prefix pointer variables (including smart pointers) with a "p": `pMyPointer`. +- Use a verb prefix like `is` for boolean variables, e.g. `isEnabled`. +- Prefix global variables with "g": `gMyGlobalVariable`. Global variables should be rare, however. +- See the section below on prefixes for accessors. +- Other prefixes are optional. +- In general avoid the use of acronyms or abbreviations for identifiers. Some acceptable exceptions: + - You may use `ID` (all caps) for "identifier" such as `objectID`. + - You may use `Desc` for "description" such as `vertexDesc`. + - You may use `it` for STL iterators, such as `auto it = cache.find(name)`. + +### Indenting + +Lines of code should be indented using four spaces, not tabs. This behavior is normally handled automatically by the IDE, and can be customized in Visual Studio: + +1. Select *Tools | Options* to show the *Options* dialog. +2. Select *Text Editor | C/C++ | Tabs* from the tree. +3. Set *Tab size* and *Indent size* to 4. +4. Select *Insert spaces*. +5. Click OK. + +Visual Studio displays the spacing type in the status bar, at the far right. Make sure it says *SPC*. If not, click it and select *Spaces*. You can also convert tabs to spaces with *Edit | Advanced | Untabify Selected Lines*. + +### Line Length + +- Each line of code, including comments, in a source file should not exceed 100 characters, except in rare cases. +- This allows two files to be viewed side-by-side (e.g. for comparing code) on a typical 16x9 monitor. +- [Visual Assist](https://www.wholetomato.com/) has an option to mark the right margin. In Visual Studio, use *Extensions | VAssistX | Visual Assist Options* (at bottom) and in the *Display* section, check the box *Display indicator after column* and put "100" in the box. + +### Spacing Inside Expressions + +- Should be done as follows: `if (AFunctionCall(x, y) == 0)` +- Note that there are no spaces just inside the parentheses, and there are spaces between keywords and operators. +- Avoided extended use of member access and pointer dereferencing, e.g. + `thisPointer->someFunction().anotherFunction()->_someMember->yetAnotherFunction()` + Instead store intermediate results, which will aid readability and debugging. + +## Whitespace + +Always remove all trailing whitespace, at the end of each line. Use [this Visual Studio extension](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer) to show trailing whitespace and automatically remove it on file save. + +Do not use multiple consecutive empty lines. + +See the rest of the standards for the proper use of empty lines, e.g. preceding comment lines with blank lines. + +## Header Files + +- Always include the license block (see below). Update the copyright year when updating the file. +- **In header files**, put `#pragma once` immediately after the license block (no empty line), followed by an empty line. +- **In CPP files**, if a precompiled header file is being used, put `#include "pch.h"` immediately after the license block (no empty line), followed by an empty line. +- Do not put `using` directives in public header files; that would pollute the client's namespace. + - For example, something like `using namespace std;` must not appear in public header files. + - A [type alias](#type-aliases) which also uses `using` is OK; this is different from a directive. +- Never use private header files outside their libraries, including `pch.h`. Such header files can only be used by code in the libraries to which they belong. +- Do not use deprecated header files from the C library. Instead, use the C++ equivalents. For example, use `` instead of ``. See [this clang-tidy check](https://clang.llvm.org/extra/clang-tidy/checks/modernize/deprecated-headers.html) for details. + +### Including Header Files + +- **Private headers:** Use quotes `"foo.h"` for header files that are not part of the public API for the current library, e.g. `#include "MyClass.h"`. +- **Public headers:** Use angle brackets `` for header files that are part of the public API for a library (including the current library), e.g. `#include `. +- **Aurora public headers:** Always treat Aurora public header files in the same way, with angle brackets and the fully qualified header file path starting with "Aurora", e.g. `#include `. +- The exception to this is for Aurora header files that contain the declarations for the current CPP file: those should use quotes to distinguish them, e.g. within `Foo.cpp` the header file `Foo.h` which contains the declarations for the class or functions being implemented should be included as `#include "Aurora/Foo.h"`. +- Put header file includes in the following order: + 1. **Precompiled header file** (`pch.h`) for CPP files, immediately following the license block, followed by an empty line, as specified above. + 2. **Declaration header files** for the current CPP file as a group (usually just one), in quotes, followed by an empty line. + 3. **Public header files**, in alphabetical order with angle brackets, followed by an empty line. In general, these includes are only used in *public* header files; private header and CPP files should use a precompiled header file, as described below. + 4. **All other header files**, in alphabetical order. +- Put includes for public header files _that are outside the current library_ (e.g. Standard Library and system header files) in a precompiled header file (`pch.h`). + - They will not change (add, remove, or modify) frequently in the course of developing that library, and so will benefit from being precompiled. + - Avoid using those includes in private header or CPP files, in order to get the compile performance benefit. + - Continue to put those includes in _public_ header files, as clients won't have access to `pch.h`. + +### Including Header Files - Example + +```c++ +// Copyright 20XX Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once // only for header files +// OR +#include "pch.h" // only for CPP files + +#include "MyClass.h" // declarations for this CPP file: always with quotes + +#include // public headers: always with angle brackets +#include + +#include "AClass.h" // private headers: always with quotes +#include "ZClass.h" +``` + +### Member Order + +Within a class, members should be declared in the following order: + +- Members with the `public` access specifier, followed by `protected` and `private`. +- For each access specifier, specify members in the following order: + 1. **Types**: including nested classes and enumerations. + 2. **Functions**: in this order: + - Static functions. + - Constructors and destructors (lifetime management). + - "get" accessors immediately preceding "set" accessors. + - All other functions. + 3. **Variables**: roughly in decreasing order of importance. If variables require special ordering for performance or correctness (e.g. cache alignment), comment accordingly to prevent breaking changes by other developers. + +Implementations of members in source files must be in the same order as their declarations. + +## Logging + +The Foundation library contains a Log.h header file with macros to support logging. These are not intended to be used for error handling. They accept a printf-style message that is written to the log. The messages are intended for other developers only, not for end users. The list below describes how and when to use each macro. + +- **AU_INFO**: Use this to provide diagnostic information to developers. For example, progress reports or the results of successful file operations. +- **AU_WARN**: Use this to indicate to developers that a requested operation succeeded, but that some undesirable result may have occurred. For example, a resource exceeding a recommended size in memory. +- **AU_ERROR**: Use this to indicate to developers that a requested operation could not be completed as requested. For example, a requested data entry or file was not found and therefore could not be processed. This is usually followed by some form of error handling. +- **AU_FAIL**: In addition to reporting a message, this also aborts the application. Use this to indicate a catastrophic failure, due to an unhandled programming error. For example, a switch statement reaches an unexpected case. User action should not intentionally result in a failure. +- **AU_ASSERT**: This accepts a test condition that is expected to be true. If the condition evaluates to false, the failure is reported and the application is immediately aborted as with AU_FAIL. By convention, the test condition must not produce side effects. Use this throughout the code to ensure expected conditions are met, with any failures being the result of unhandled programming errors. +- **AU_ASSERT_DEBUG**: This is identical to AU_ASSERT except that it only executes in debug builds. Use this in performance-critical sections of the code, where it would be undesirable to test conditions in release builds. This should be used rarely, preferring AU_ASSERT. + +Log.h also includes a singleton *Log* class for global configuration of logging, e.g. setting the level of logging that occurs at runtime. + +## Error Handling + +- Aurora currently does not have specific error handling standards. Until they are devised, use AU_ASSERT and AU_FAIL as described in the [Logging](#logging) section. These can be used to validate inputs and any other expected state in functions, which will help identify runtime errors that should be handled. +- **WIP:** Determine full standards for this, e.g. return values, exception handling, etc. + +## CMake + +Working with CMake scripts deserves its own set of complete standards. For now please consider [this small list of CMake "do's and don'ts"](https://cliutils.gitlab.io/modern-cmake/chapters/intro/dodonot.html) when you edit CMake scripts. In particular: + +> Treat CMake as code: It is code. It should be as clean and readable as all other code. + +In particular, use the same commenting standards for CMake scripts as for other code, as documented above. diff --git a/Doc/HdAurora.md b/Doc/HdAurora.md new file mode 100644 index 0000000..6e3709c --- /dev/null +++ b/Doc/HdAurora.md @@ -0,0 +1,35 @@ +# HdAurora + +**HdAurora** is a USD Hydra render delegate that allows Aurora to be used in applications that implement a USD Hydra scene delegate. + +Hydra is a framework used by many applications such as [Autodesk Maya](https://www.autodesk.com/products/maya) to support multiple renderers implemented as render delegates. Examples of other render delegates include [HdStorm](https://graphics.pixar.com/usd/dev/api/hd_storm_page_front.html), a rasterization renderer that uses OpenGL, Vulkan, and Metal, and [HdArnold](https://github.com/Autodesk/arnold-usd), which enables the use of [Autodesk Arnold](https://arnoldrenderer.com). + +To learn more about Hydra, see [this presentation](https://graphics.pixar.com/usd/files/Siggraph2019_Hydra.pdf) from SIGGRAPH 2019 as well as [the USD documentation](https://graphics.pixar.com/usd/release/intro.html). + +## usdview + +The easiest way to run HdAurora is using the **usdview** tool, part of the USD toolset that is used for viewing USD files. + + + +Once **HdAurora** has been deployed to your USD folder you can select hdAurora in **usdview** using the renderer menu with the **Aurora** option: + + +## Deploying hdAurora + +The provided Python script [deployHdAurora.py](../Scripts/deployHdAurora.py) can be used to deploy **HdAurora** to a USD installation (and create a new USD installation if one does not exist.) + +Run the following command to create a new USD installation located at *../USD* relative to the Aurora repository root and deploy **HdAurora** to it. Creating a new USD installation requires a command prompt with compiler tools, such as "x64 Native Tools Command Prompt for VS 2019", and should be run from the root of the Aurora repository (this will take up to an hour to complete): + +``` +python Scripts/deployHdAurora.py ../USD --externals_folder ../AuroraExternals --config=Release --build +``` + +You can then run **usdview** from the bin subfolder within that installed USD folder, replacing *AssetFolder* with the folder the [Autodesk Telescope USD model](https://drive.google.com/file/d/1RM09qDOGcRinLJTbXCsiRfQrHmKA-1aN/view?usp=share_link) was unzipped into. + +``` +cd ../USD/bin +python usdview AssetFolder/AutodeskTelescope/AutodeskTelescope.usda--renderer=hdAurora +``` + + diff --git a/Doc/Plasma.jpg b/Doc/Plasma.jpg new file mode 100644 index 0000000..e44a0f5 --- /dev/null +++ b/Doc/Plasma.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8661b066b6418110f9a491011ea2519c75a8adac23ffc9dff805eefb72d386c9 +size 375253 diff --git a/Doc/Plasma.md b/Doc/Plasma.md new file mode 100644 index 0000000..a49e6f0 --- /dev/null +++ b/Doc/Plasma.md @@ -0,0 +1,65 @@ +# Plasma + +**Plasma** is a small reference application used to exercise Aurora, without requiring the use of a full Autodesk application or USD Hydra scene delegate. Plasma can load OBJ files like the one shown below, along with certain material properties. See the usage instructions below. The [McGuire Computer Graphics Archive](https://casual-effects.com/data) is a good resource for OBJ files. + + + +## System Requirements + +Plasma has the same [system requirements](../README.md) as Aurora. In particular, a GPU with support for DirectX Raytracing or Vulkan Ray Tracing is required. + +At this time, interactive use of Plasma is only supported on Windows. For Linux, command-line single image rendering is available as described below, and interactive support is coming soon. + +## Using Plasma + +| Feature | Input | +| :-------------------------------------------------- | :----------------------------------------------------------- | +| Load an OBJ File | CTRL-O key *OR*
Drag and drop *OR*
File path as first command line argument | +| Load an environment image (HDR format) | CTRL-E key *OR*
Drag and drop | +| Load a MaterialX document (.mltx) | CTRL-M key: Applies to all loaded objects
M: Reload the previous document
R key: Reset materials to original | +| Save screenshot as PNG (to last accessed directory) | S key | +| Set to Full Screen | F11 key | +| Toggle v-sync | V key | +| Toggle animation | Space key | +| Toggle tone mapping | T key | +| Toggle directional light | L key | +| Toggle diffuse only rendering | D key | +| Toggle denoising | SHIFT-D key | +| Toggle transparent shadows * | O key | +| Toggle ground plane | G: matte shadow
SHIFT-G: matte reflection | +| Cycle importance sampling | I (letter) key, cycles in the following order:
1) Multiple importance sampling (MIS)
2) BSDF importance sampling
3) Environment light importance sampling | +| Adjust exposure (½ stop increments) | + key (increase) and - key (decrease) | +| Adjust max luminance for samples (firefly clamping) | CTRL+ key (increase) and CTRL- key (decrease)
(full stops starting at 1000 luminance) | +| Adjust trace depth (path length) ** | [ key (increase) and ] key (decrease)
(default 5, range is [1, 10]) | +| Display debug buffers (AOVs) | ~ = Output (beauty)
1 = Output with errors
2 = ViewZ (depth)
3 = Normals
4 = Base Color
5 = Roughness
6 = Metalness

The following are used for denoising:
7 = Diffuse radiance
8 = Diffuse hit distance
9 = Glossy radiance
0 = Glossy hit distance | +| Orbit View | Left click and drag | +| Pan View | Right click and drag | +| Dolly View | Middle click and drag *OR*
Mouse wheel | +| Fit View to Scene | F key | +| Quit | ESC key | + +\* This is a performance optimization. + +** It may be necessary to increase trace depth for overlapping transparent / transmissive surfaces, at the cost of performance. + +## Command Line Rendering +The following command renders an output image with the specified scene file. The optional `--renderer hgi` argument enables the HGI backend in the renderer, with DirectX used as the default backend. On Linux, you must use the HGI backend. +``` +Plasma --output {OUTPUT_FILE_NAME.png} --scene {INPUT_SCENE.obj} [--renderer hgi] +``` + +## OBJ Material Properties in Plasma + +The following properties from OBJ MTL files are supported. OBJ MTL properties not listed here are ignored. Materials are converted to use the [Autodesk Standard Surface](https://autodesk.github.io/standard-surface) material model. + +| Standard Surface | OBJ MTL File | Notes | +| :----------------- | :----------- | :----------------------------------------------------------- | +| base_color | Kd / map_Kd | Image supported. | +| metalness | Pm | | +| specular_color | Ks | | +| specular_roughness | Pr / map_Pr | Image supported.

"Ns" for non-physical shininess is not supported; use Pr instead. | +| specular_IOR | Ni | This is the index of refraction, e.g. 1.5 is a good value for glass. | +| transmission | Tr / d | The two values are inverses of each of other, i.e. *d* of 1 is the same as *Tr* of 0, i.e. opaque. It may be necessary to increase the trace depth (described above) for certain models to look correct.

*Tr / d* in OBJ are typically used for opacity (transparency) but here they are used for *transmission* instead, which is more interesting, e.g. for refraction. If values for both are present, the value for *d* will be used. | +| transmission_color | Tf | Black (the default) is assumed to be useless and will be interpreted as white (no tint). | +| opacity | map_d | Image only.

This is different from *transmission*. In Standard Surface, opacity refers to *transparency*, which is simple alpha blending. It was determined that it is more useful for *map_d* to be used for opacity, to support cutouts. | +| normal | bump / norm | Image only, and it must be a normal map (not a height map) | \ No newline at end of file diff --git a/Doc/RendererMenu.jpg b/Doc/RendererMenu.jpg new file mode 100644 index 0000000..ad180c8 --- /dev/null +++ b/Doc/RendererMenu.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cf49a8c0d13d689aaae87d6d079644f6378d51ec0e2b9ec0daa06298b70c4fe +size 33050 diff --git a/Doc/USDView.JPG b/Doc/USDView.JPG new file mode 100644 index 0000000..4f9c9b2 --- /dev/null +++ b/Doc/USDView.JPG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:700bea45eaf4b386da520e050f1af42cedfc96019a97487571d13b3c7df16634 +size 326156 diff --git a/Doc/sample.jpg b/Doc/sample.jpg new file mode 100644 index 0000000..0fd94d5 --- /dev/null +++ b/Doc/sample.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ba9dc2f2d7721aa0d839f3df30d08c9c2ba5e8a01c1c1c93a6f77ea16bc6099 +size 236652 diff --git a/LICENSE.md b/LICENSE.md index ccf536b..b4db171 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -2,7 +2,7 @@ Version 2.0, January 2004 http://www.apache.org/licenses/ - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. @@ -173,482 +173,4 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS - -============= -Luma Pictures -============= - ----------- -usd-arnold ----------- - - Modified Apache 2.0 License - - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor - and its affiliates, except as required to comply with Section 4(c) of - the License and to reproduce the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2019 Luma Pictures - - -============= -RODEOFX -============= - ----------- -OpenWalter ----------- - - WALTER software, © 2018, RodeoFx inc. - - - Modified Apache License for WALTER software - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this license. - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work. - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, - irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or - Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, - irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to - those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such - Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution - incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the - date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that - You meet the following conditions: - a.You must give any other recipients of the Work or Derivative Works a copy of this License; and - b.You must cause any modified files to carry prominent notices stating that You changed the files; and - c.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding - those notices that do not pertain to any part of the Derivative Works; and - d.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as - part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever - such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices - within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as - modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and - conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you - may have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary - use in describing the origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or - FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of - permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and - grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any - character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or - malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, - indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole - responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted - against, such Contributor by reason of your accepting any such warranty or additional liability. - - -==================== -The SCons Foundation -==================== - ------ -scons ------ - -Copyright (c) 2001 - 2019 The SCons Foundation - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY -KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -============= -Eli Bendersky -============= - --------- -elftools --------- - -Eli Bendersky (eliben@gmail.com) -This code is in the public domain - -================== -Leonard Richardson -================== - --------------- -beautiful_soup --------------- - -Copyright (c) 2004-2010, Leonard Richardson - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the the Beautiful Soup Consortium and All - Night Kosher Bakery nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. - -================ -Jonathan Hartley -================ - --------- -colorama --------- - -Copyright (c) 2010 Jonathan Hartley -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holders, nor those of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -=============== -Marcel Hellkamp -=============== - ------- -bottle ------- - -Copyright (c) 2012, Marcel Hellkamp. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -============= -Justus Calvin -============= - -------------- -findTBB.cmake -------------- - - -The MIT License (MIT) - -Copyright (c) 2015 Justus Calvin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -=========== -Google Inc. -=========== - ------------ -Google Test ------------ - - -Copyright 2008, Google Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -- Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -- Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +END OF TERMS AND CONDITIONS diff --git a/Libraries/Aurora/API/Aurora/Aurora.h b/Libraries/Aurora/API/Aurora/Aurora.h new file mode 100644 index 0000000..8b8024a --- /dev/null +++ b/Libraries/Aurora/API/Aurora/Aurora.h @@ -0,0 +1,1087 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// Aurora headers. +#include + +// STL headers. +#include +#include +#include +#include +#include +#include +#include +#include + +// GLM - OpenGL Mathematics. +// NOTE: This is a math library, and not specific to OpenGL. +#define GLM_FORCE_CTOR_INIT +#pragma warning(push) +#pragma warning(disable : 4201) // nameless struct/union +#include +#pragma warning(pop) + +// API import symbol. +#if !defined(AURORA_API) +#ifdef WIN32 +#define AURORA_API __declspec(dllimport) +#else +#define AURORA_API +#endif +#endif + +#include "AuroraNames.h" + +// A macro to define smart pointer aliases, e.g. shared_ptr gets a "IMaterialPtr" alias. +#define MAKE_AURORA_PTR(_x) using _x##Ptr = std::shared_ptr<_x> + +namespace Aurora +{ + +/// Load resource function, used by the renderer to load a buffer for a resource (e.g. textures +/// loaded from MaterialX file) +/// +/// \param uri Universal Resource Identifier (URI) for the resource to load. +/// \param pBufferOut The buffer containing the loaded resource. +/// \param pFileNameOut File name of the loaded resource containing the (used as hint for subsequent +/// processing) +/// \return True if loaded successfully. +using LoadResourceFunction = + function* pBufferOut, string* pFileNameOut)>; + +// Math objects. +using vec2 = glm::vec2; +using vec3 = glm::vec3; +using vec4 = glm::vec4; +using mat4 = glm::mat4; +using rgb = glm::vec3; +using rgba = glm::vec4; + +/// A unique identifier for Scene elements. +using Path = std::string; +using PathView = std::string_view; + +// Array of strings represented as vector. +using Strings = std::vector; + +/// Property value variant (of basic types). +struct PropertyValue +{ + enum class Type : uint8_t + { + Undefined, + Bool, + Int, + Float, + Float2, + Float3, + Float4, + Matrix4, + String, + Strings + }; + + PropertyValue() : type(Type::Undefined) {} + PropertyValue(const PropertyValue& in) { *this = in; } + const PropertyValue& operator=(const PropertyValue& in) + { + type = in.type; + switch (type) + { + default: + break; + case Type::Bool: + _bool = in._bool; + break; + case Type::Int: + _int = in._int; + break; + case Type::Float: + _float = in._float; + break; + case Type::Float2: + _float2 = in._float2; + break; + case Type::Float3: + _float3 = in._float3; + break; + case Type::Float4: + _float4 = in._float4; + break; + case Type::Matrix4: + _matrix4 = in._matrix4; + break; + case Type::String: + _string = in._string; + break; + case Type::Strings: + _strings = in._strings; + break; + } + return *this; + } + PropertyValue(bool value) : type(Type::Bool), _bool(value) {} + PropertyValue(int value) : type(Type::Int), _int(value) {} + PropertyValue(float value) : type(Type::Float), _float(value) {} + PropertyValue(const vec2& value) : type(Type::Float2), _float2(value) {} + PropertyValue(const vec3& value) : type(Type::Float3), _float3(value) {} + PropertyValue(const vec4& value) : type(Type::Float4), _float4(value) {} + PropertyValue(const mat4& value) : type(Type::Matrix4), _matrix4(value) {} + PropertyValue(const char* value) : type(Type::String), _string(value) {} + PropertyValue(const std::string& value) : type(Type::String), _string(value) {} + PropertyValue(const Strings& value) : type(Type::Strings) { _strings = value; } + + // Allow construction of an undefined value by passing in nullptr (e.g. properties = + // {{"clearMeProp", nullptr}}) + PropertyValue(nullptr_t) : type(Type::Undefined) {} + + /// The type of the value, or Type::Undefined if no value. + Type type = Type::Undefined; + + /// Get the value as a string (will fail if not correct type.) + const std::string& asString() const + { + AU_ASSERT(type == Type::String, "Not a string"); + return _string; + } + + /// Get the value as a boolean (will fail if not correct type.) + bool asBool() const + { + AU_ASSERT(type == Type::Bool, "Not a bool"); + return _bool; + } + + /// Get the value as an int (will fail if not correct type.) + int asInt() const + { + AU_ASSERT(type == Type::Int, "Not an int"); + return _int; + } + + /// Get the value as a float (will fail if not correct type.) + float asFloat() const + { + AU_ASSERT(type == Type::Float, "Not a float"); + return _float; + } + + /// Get the value as a vec2 (will fail if not correct type.) + vec2 asFloat2() const + { + AU_ASSERT(type == Type::Float2, "Not a float2"); + return _float2; + } + + /// Get the value as a vec3 (will fail if not correct type.) + vec3 asFloat3() const + { + AU_ASSERT(type == Type::Float3, "Not a float3"); + return _float3; + } + + /// Get the value as a vec4 (will fail if not correct type.) + vec4 asFloat4() const + { + AU_ASSERT(type == Type::Float4, "Not a float4"); + return _float4; + } + + /// Get the value as a mat4 (will fail if not correct type.) + mat4 asMatrix4() const + { + AU_ASSERT(type == Type::Matrix4, "Not a matrix4"); + return _matrix4; + } + + /// Get the value as a string array (will fail if not correct type.) + Strings& asStrings() + { + AU_ASSERT(type == Type::Strings, "Not a string array"); + return _strings; + } + + /// Get the value as a const string array (will fail if not correct type.) + const Strings& asStrings() const + { + AU_ASSERT(type == Type::Strings, "Not a string array"); + return _strings; + } + + // Does this property have a value or is it undefined ? + bool hasValue() { return type != Type::Undefined; } + + /// Clears the property value. + void clear() + { + type = Type::Undefined; + _string.clear(); + _strings.clear(); + } + + union + { + bool _bool; + int _int; + float _float; + vec2 _float2; + vec3 _float3; + vec4 _float4; + mat4 _matrix4; + }; + + // These properties are outside the union due to being an element of variable size. + // TODO: Should consider using std::variant. + + // String value (only valid if type is Type::String) + std::string _string; + + // String array value (only valid if type is Type::Strings) + Strings _strings; +}; + +// A collection of named properties, of varying types. +using Properties = std::map; + +/// Input vertex attribute formats. +enum class AttributeFormat : uint8_t +{ + // Signed 8-bit integer. + SInt8, + + // Unsigned 8-bit integer. + UInt8, + + // Signed 16-bit integer. + SInt16, + + // Unsigned 16-bit integer. + UInt16, + + // Signed 32-bit integer. + SInt32, + + // Unsigned 32-bit integer. + UInt32, + + // Single precision float. + Float, + + // Single precision 2D point. + Float2, + + // Single precision 3D point. + Float3, + + // Single precision 4D point. + Float4 +}; + +/// A collection of primitive types. +enum class PrimitiveType : uint8_t +{ + // An array of points. + Points, + + // An array of separate lines. + Lines, + + // An array of connected lines. + Linestrip, + + // An array of separate triangles. + Triangles, + + // An array of connected triangles. + Trianglestrip +}; + +/// Input image pixel formats. +enum class ImageFormat : uint8_t +{ + /// 8-bit per-channel, normalized byte. + Byte_R, + + /// 8-bit per-channel, 4-channel normalized integer. + Integer_RGBA, + + /// 32-bit per-channel, 2-channel normalized integer. + Integer_RG, + + /// 16-bit per-channel, 4-channel normalized short. + Short_RGBA, + + /// 16-bit per-channel, 4-channel half-float. + Half_RGBA, + + /// 32-bit per-channel, 4-channel float. + Float_RGBA, + + /// 32-bit per-channel, 3-channel float. + Float_RGB, + + /// 32-bit per-channel, single channel float. + Float_R +}; + +/// Input vertex description. Defines which vertex attributes each vertex has, and the number of +/// vertices. +struct VertexDescription +{ + /// Map of named vertex attributes. + std::unordered_map attributes; + + /// The total number of vertices. + size_t count = 0; + + bool hasAttribute(const std::string& name) const + { + return attributes.find(name) != attributes.end(); + } +}; + +/// \struct Vertex attribute data for single attribute, filled in by client in +/// GetAttributeDataFunction. +struct AttributeData +{ + /// Pointer to actual vertex or index atttribute data. + const void* address = nullptr; + + /// Offset in bytes to start of attribute data. + size_t offset = 0; + + /// The total size in bytes of the provided buffer, used for validation. + size_t size = 0; + + /// The stride in bytes between attributes. If left as zero assumed to be exactly the size of + /// attribute. + size_t stride = 0; +}; + +/// Map of attribute data for all the vertex and index attributes, filed in by getVertexData +/// callback when geometry is activated. +using AttributeDataMap = std::map; + +/// Callback function to get the vertex and index attribute data for a geometry object from the +/// client. +/// +/// \param dataOut A map of AttributeData containing an uninitialized entry for each vertex and +/// index attribute. Should be filled in by client providing the vertex and index data required. +/// \param firstVertex The first vertex data is required for. Currently always zero. +/// \param vertexCount The number of vertices data is required for. Currently always vertex count +/// provided in descriptor. +/// \param firstIndex The first index data is required for. Currently +/// always zero. +/// \param lastIndex The last index data is required for. Currently always vertex +/// count provided in descriptor. +/// \return false if client was not able to create attribute data. +using GetAttributeDataFunction = std::function; + +/// Callback function to signal the vertex and index attribute data for the geometry object has been +/// updated, and the pointers provided by the GetAttributeDataFunction can be freed by the client. +/// Provides the same arguments that were provided by getAttributeData. +using AttributeUpdateCompleteFunction = std::function; + +/// Input geometry description. +struct GeometryDescriptor +{ + /// The primitive and topology. + PrimitiveType type = PrimitiveType::Triangles; + + /// Complete description of vertex attributes. + VertexDescription vertexDesc; + + /// Number of indices. + size_t indexCount = 0; + + /// Callback for getting the vertex attribute data from the client, called when geometry + /// resource is activated. + GetAttributeDataFunction getAttributeData = nullptr; + + /// Optional completion callback, called after geometry resource is activated and vertex + /// attribute data is no longer in use by the renderer and can be freed by the client. + AttributeUpdateCompleteFunction attributeUpdateComplete = nullptr; +}; + +/// \struct Image pixel data desription, filed in by getPixelData callback when IImage is activated. +struct PixelData +{ + /// Buffer address. + const void* address = nullptr; + + /// Size of one row of pixels. + size_t bytesPerRow = 0; + + /// The total size in bytes of the buffer. + size_t size = 0; +}; + +/// Callback function to get the pixel data for an image object from the +/// client. +/// +/// \param dataOut The pixel data, including pointer to actual pixels, to be filled in by client. +/// \param bottomLeft The bottom left of the image region to be filled in. Currently always the +/// bottom left corner of entire image. \param topRight The top right of the image region to be +/// filled in. Currently always the top right corner of entire image. \return false if client was +/// not able to create image data. +using GetPixelDataFunction = + std::function; + +/// Callback function to signal the vertex and index attribute data for the geometry object has been +/// updated, and the pointers provided by the GetAttributeDataFunction can be freed by the client. +/// Provides the same arguments that were provided by getAttributeData. +using PixelUpdateCompleteFunction = + std::function; + +/// Input image description. +struct ImageDescriptor +{ + /// The format of the image. + ImageFormat format = ImageFormat::Integer_RGBA; + + /// Whether the image should be linearized from sRGB to linear color space. + bool linearize; + + /// Whether the image is to be used to represent an environment. + bool isEnvironment = false; + + /// The width of image in pixels. + uint32_t width = 0; + + /// The height of image in pixels. + uint32_t height = 0; + + /// Callback for getting the pixel data, called when geometry resource is activated. + GetPixelDataFunction getPixelData = nullptr; + + /// Optional completion callback, called after image resource is activated and pixel data is no + /// longer in use by the renderer. + PixelUpdateCompleteFunction pixelUpdateComplete = nullptr; +}; + +/// Instance definition, all the data required to create an instance resource. +struct InstanceDefinition +{ + Path path; + Properties properties; +}; + +// Vector of instance definitions. +using InstanceDefinitions = std::vector; + +// Vector of paths. +using Paths = std::vector; + +// A class representing an image for use in the rendering pipeline. +class AURORA_API IImage +{ +public: + /// Structure containing the information required to initialize an image. + /// TODO: This should use ImageDescriptor directly. + struct InitData + { + /// The image pixels. + /// \note This data is not retained by the renderer after the image is created. + const void* pImageData = nullptr; + + /// The format of the image. + ImageFormat format = ImageFormat::Integer_RGBA; + + /// Whether the image should be linearized from sRGB to linear color space. + bool linearize; + + /// Whether the image is to be used to represent an environment. + bool isEnvironment = false; + + /// The width of image in pixels. + uint32_t width = 0; + + /// The height of image in pixels. + uint32_t height = 0; + + /// The name of the image. + /// \note This is for client reference only, and does not need to be unique. + std::string name; + }; + +protected: + virtual ~IImage() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IImage); + +// A class representing an sampler for use in the rendering pipeline, defining texture sampling +// parameters. +class AURORA_API ISampler +{ +public: + virtual ~ISampler() = default; // hidden destructor +}; +MAKE_AURORA_PTR(ISampler); + +// A class representing a set of values for fixed properties of various types. The derived class +// defines the properties and default values. +class AURORA_API IValues +{ +public: + // An enumeration of types for properties and values. + enum class Type + { + Undefined, + Boolean, + Int, + Float, + Float2, + Float3, + Matrix, + Image, + Sampler, + String + }; + + virtual void setBoolean(const std::string& name, bool value) = 0; + virtual void setInt(const std::string& name, int value) = 0; + virtual void setFloat(const std::string& name, float value) = 0; + virtual void setFloat2(const std::string& name, const float* value) = 0; + virtual void setFloat3(const std::string& name, const float* value) = 0; + virtual void setMatrix(const std::string& name, const float* value) = 0; + virtual void setImage(const std::string& name, const IImagePtr& value) = 0; + virtual void setSampler(const std::string& name, const ISamplerPtr& value) = 0; + virtual void setString(const std::string& name, const std::string& value) = 0; + virtual void clearValue(const std::string& name) = 0; + + virtual Type type(const std::string& name) = 0; + +protected: + virtual ~IValues() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IValues); + +// A buffer that can receive a rendered image. +class AURORA_API ITarget +{ +public: + virtual void resize(uint32_t width, uint32_t height) = 0; + +protected: + virtual ~ITarget() = default; // hidden destructor +}; +MAKE_AURORA_PTR(ITarget); + +// A window handle, used when creating window targets. +using WindowHandle = void*; + +// A class representing a target that is associated with an operating system window. +class AURORA_API IWindow : public ITarget +{ +public: + virtual void setVSyncEnabled(bool enabled) = 0; + +protected: + virtual ~IWindow() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IWindow); + +// A class representing a target that can have its data accessed by the client. For example, this is +// useful for saving screenshots to disk. +class AURORA_API IRenderBuffer : public ITarget +{ +public: + /// Class used to hold a readable buffer. + /// If the this class is holding a readback buffer it handles mapping and unmapping data for + /// readback based on the lifetime of the object. + /// It may also hold a handle to a GPU buffer + class IBuffer + { + public: + /// The raw data pointer. + virtual const void* data() = 0; + + // Handle to a GPU buffer + virtual const void* handle() { return nullptr; } + + protected: + IBuffer() = default; + virtual ~IBuffer() = default; + }; + using IBufferPtr = std::shared_ptr; + + /// Get the contents of the render buffer on the CPU. + /// If removePadding is true the returned data will match image dimensions without any extra + /// padding at the end of each row. stride will return the stride in bytes of each row (if + /// removePadding is true this will always equal row length of image). + virtual const void* data(size_t& stride, bool removePadding = false) = 0; + + /// Returns a class holding GPU buffer data that is ready to read. The IBuffer class + /// handles mapping and unmapping data for readback based on the lifetime of the object. + virtual IBufferPtr asReadable(size_t& stride) = 0; + + /// Returns a class holding a handle to shareable GPU buffer memory. + virtual IBufferPtr asShared() = 0; + +protected: + virtual ~IRenderBuffer() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IRenderBuffer); + +// A class representing triangle geometry, as vertex data buffers and an index buffer. +class AURORA_API IGeometry +{ +public: +protected: + virtual ~IGeometry() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IGeometry); + +// A class representing the environment that surrounds the scene, including lighting and background. +// Properties include the following: +// - light_top (float3): The top color of the gradient used for lighting. +// - light_bottom (float3): The bottom color of the gradient used for lighting. +// - light_image (IImage): The image used for lighting, which takes precedence over the gradient. +// - light_transform (float4x4): A transformation to apply to the lighting. +// - background_top (float3): The top color of the gradient used for the background. +// - background_bottom (float3): The top color of the gradient used for the background. +// - background_image (IImage): The image used for the background, which takes precedence over the +// gradient. +// - background_transform (float4x4): A transformation to apply to the background. +// - background_use_screen (bool): Whether to use a simple screen mapping for the background; +// otherwise a wraparound spherical mapping is used (default false). +class AURORA_API IEnvironment +{ +public: + virtual IValues& values() = 0; + +protected: + virtual ~IEnvironment() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IEnvironment); + +/// A class representing a infinite ground plane in the scene, that acts as a "catcher" for shadows +/// and reflections of the scene, but is otherwise invisible. +/// +/// Properties include the following: +/// - position: An arbitrary point on the plane. +/// - normal: The normal of the plane, which points away from the rendered side of the plane. The +/// other side is not rendered, i.e. is completely invisible. +/// - shadow_opacity: The visibility of the matte shadow effect in [0.0, 1.0]. +/// - shadow_color: The tint color of the matte shadow effect. This is normally black. +/// - reflection_opacity: The visibility of the matte reflection effect in [0.0, 1.0]. +/// - reflection_color: The tint color of the matte reflection effect. This is normally white. +/// - reflection_roughness: The GGX roughness of the matte reflection effect in [0.0, 1.0]. +class AURORA_API IGroundPlane +{ +public: + virtual IValues& values() = 0; + +protected: + virtual ~IGroundPlane() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IGroundPlane); + +/// A class representing a material that can be assigned to an instance, that determines surface +/// appearance (shading) of the instance. +class AURORA_API IMaterial +{ +public: + /// Gets the values of the material's properties, which can be read and modified as needed. + /// + /// For some materials the properties are those defined by Autodesk Standard Surface, e.g. + /// "base_color" and "specular_roughness." + virtual IValues& values() = 0; + +protected: + virtual ~IMaterial() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IMaterial); + +// Definition of an instance layer (material+geometry) +using LayerDefinition = pair; + +// Array of layer definitions. +using LayerDefinitions = vector; + +/// A class representing an instance of geometry, including a per-instance material and transform. +class AURORA_API IInstance +{ +public: + /// Sets the material of the instance. + /// + /// The material to assign. Setting to null (the default) refers to a simple default material. + virtual void setMaterial(const IMaterialPtr& pMaterial) = 0; + + /// Sets the transform of the instance, as a 4x4 transformation matrix. + /// + /// \param pTransform The transformation matrix to assign. The array must be in column-major + /// layout, with column vectors. Setting to null (the default) uses the identity matrix. + virtual void setTransform(const mat4& pTransform) = 0; + + /// Sets the unique integer identifier for the instance, which is used for selection + /// + /// \param objectId The object id to assign. + virtual void setObjectIdentifier(int objectId) = 0; + + /// Makes the instance visible or invisible. + /// + /// \param visible The object id to assign. + virtual void setVisible(bool visible) = 0; + + virtual IGeometryPtr geometry() const = 0; + +protected: + virtual ~IInstance() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IInstance); + +/// The types of resources that can be associated with an Aurora scene (directly or indirectly). +enum ResourceType +{ + Material = 0, + Instance, + Environment, + Geometry, + Sampler, + GroundPlane, + Image, + Invalid +}; + +/// A class representing a scene for rendering, consisting of instances of geometry, an environment, +/// and a global directional light. +class AURORA_API IScene +{ +public: + /// Get the resource type for the given path. + /// \param atPath The path to be queried. + /// \return The type of resource, or ResourceType::Invalid if no resource for given path. + virtual ResourceType getResourceType(const Path& atPath) = 0; + + /// Set the image descriptor for image with given path. + /// This will create a new image (or force it to be recreated an image already exists at that + /// path). + /// + /// \param atPath The path at which the image will be created. + /// \param desc A description of the image. + virtual void setImageDescriptor(const Path& atPath, const ImageDescriptor& desc) = 0; + + /// Create an image with the given file path. The image descriptor will be created using the + /// default load callback provided by setLoadResourceFunction. + /// + /// \param atPath The Aurora path at which the image will be created. + /// \param filePath The path to the image file, if the string is empty, then atPath is used. + virtual void setImageFromFilePath(const Path& atPath, const string& filePath = "", + bool linearize = true, bool isEnvironment = false) = 0; + + /// Set the properties for sampler with given path. + /// This will create a new sampler (or force it to be recreated an sampler already exists at + /// that path). + /// + /// \param atPath The path at which the sampler will be created. + /// \param props Sampler properties + virtual void setSamplerProperties(const Path& atPath, const Properties& props) = 0; + + /// Set the material type and document for material with given path. + /// This will create a new material (or recreate it if one already exists at that path). + /// \param atPath The path at which the material will be added. + /// \param materialType Type of material (one of strings for Material properties). + /// \param document Document describing the material (based on value of materialType). + virtual void setMaterialType(const Path& atPath, + const std::string& materialType = Names::MaterialTypes::kBuiltIn, + const std::string& document = "Default") = 0; + + /// Set geometry descriptor, if geometry already exists it will be recreated. + /// \param atPath The path at which the geometry will be created. + /// \param desc A description of the geometry. + virtual void setGeometryDescriptor(const Path& atPath, const GeometryDescriptor& desc) = 0; + + /// Prevents the resource from being purged by the renderer if unused (can be nested). + virtual void addPermanent(const Path& resource) = 0; + + /// Restores the resource's purgeability. + virtual void removePermanent(const Path& resource) = 0; + + /// Adds an instance of specified geometry. + /// \param atPath The path at which to create the geometry. + /// \param geometry A path to the geometry description. + /// \param properties Instance property settings. + /// \returns True if successful. + virtual bool addInstance( + const Path& atPath, const Path& geometry, const Properties& properties = {}) = 0; + + /// Adds several instances with specified geometry. + /// \param atPath The path at which to create the geometry. + /// \param geometry A path to the geometry description. + /// \param properties Instance property settings. + /// \returns True if successful. + virtual Paths addInstances(const Path& geometry, const InstanceDefinitions& definitions) = 0; + + /// Sets the specified properties for the environment at the path. Environment will be created + /// if none exists at that path. + /// + /// \param environment The environment path. + /// \returns True if successful. + virtual bool setEnvironmentProperties( + const Path& environment, const Properties& environmentProperties) = 0; + + /// Sets the environment (singleton) to be used for the scene. + /// + /// This will cause the resources associated with the environment, including any images, to be + /// created. + /// + /// \param environment The environment path. Uses default environment if path is empty. + /// \returns True if successful. + virtual bool setEnvironment(const Path& environment) = 0; + + /// Removes the instance at the specified path from the scene, so that it is no longer + /// rendered. + /// The associated resources (including referenced geometry and materials) will be destroy once + /// they are no longer attached to scene. + /// \param path The path to remove from the scene. Nothing + /// is done if the path is + /// not in the scene. + virtual void removeInstance(const Path& path) = 0; + + /// Removes the instance at the specified set of paths from the scene, so that they are no + /// longer rendered. + /// The associated resources (including referenced geometry and materials) will be destroy once + /// they are no longer attached to scene. + /// + /// \param path The paths to remove from the scene. Nothing is done if a path is + /// not in the scene. + virtual void removeInstances(const Paths& paths) = 0; + + /// Sets the specified properties of the material. + /// This will create a new material if one does not exist for that path. + /// \param The path to the material. + /// \param materialProperties The set of properties to change. + virtual void setMaterialProperties(const Path& path, const Properties& materialProperties) = 0; + + /// Sets the specified properties of the instance. + /// \param path The path to instance to modify (will fail if path does not exist). + /// \param instanceProperties The set of properties to modify. + virtual void setInstanceProperties(const Path& path, const Properties& instanceProperties) = 0; + + /// Sets the specified properties of the instances. + /// \param paths The paths to the instances to modify (will fail if path does not exist). + /// \param instanceProperties The set of properties to modify. + virtual void setInstanceProperties( + const Paths& paths, const Properties& instanceProperties) = 0; + + /// Sets the bounding box for the scene. + /// + /// \note The client is expected to update the bounding box as it changes. Not doing so may + /// result in undefined behavior. This may be updated internally in the future. + virtual void setBounds(const vec3& min, const vec3& max) = 0; + + /// Sets the bounding box for the scene. + /// + /// TODO: Remove. Replaced by GLM types. + /// \note The client is expected to update the bounding box as it changes. Not doing so may + /// result in undefined behavior. This may be updated internally in the future. + virtual void setBounds(const float* min, const float* max) = 0; + + /// Sets the properties of the global directional light. + /// + /// \param intensity The light intensity. Set this to zero to disable the light. + /// \param color The light color, which is multiplied by the intensity. + /// \param direction The light direction, away from the light / toward the scene. + /// \param angularDiameter The light angular diameter in radians. For example, the angular + /// diameter of the sun is about 0.017 radians. + virtual void setLight( + float intensity, const rgb& color, const vec3& direction, float angularDiameter = 0.1f) = 0; + + /// Sets the properties of the global directional light. + /// + /// TODO: Remove. Replaced by GLM types. + /// \param intensity The light intensity. Set this to zero to disable the light. + /// \param color The light color, which is multiplied by the intensity. + /// \param direction The light direction, away from the light / toward the scene. + /// \param angularDiameter The light angular diameter in radians. For example, the angular + /// diameter of the sun is about 0.017 radians. + virtual void setLight(float intensity, const float* color, const float* direction, + float angularDiameter = 0.1f) = 0; + + /// Sets the ground plane for the scene. + virtual void setGroundPlanePointer(const IGroundPlanePtr& pGroundPlane) = 0; + + virtual IInstancePtr addInstancePointer(const Path& path, const IGeometryPtr& geom, + const IMaterialPtr& pMaterial, const mat4& pTransform, + const LayerDefinitions& materialLayers = {}) = 0; + +protected: + virtual ~IScene() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IScene); + +/// Arbitrary output variables (AOV): Types of data that can be produced from the renderer and +/// written to targets. +/// +/// \note The name "AOV" refers to a source of data. A target is assigned to and is filled with data +/// from an AOV, but the target itself is not technically an AOV. +enum class AOV +{ + /// The final, user-visible result of rendering, sometimes called "beauty". + kFinal, + + /// The depth of scene geometry in normalized device coordinates (NDC), as determined by the + /// provided projection matrix. + kDepthNDC +}; + +/// A map of AOVs to targets, to indicate which targets should receive which AOVs. +using TargetAssignments = std::unordered_map; + +// A class for rendering images and creating objects associated with the renderer. +class AURORA_API IRenderer +{ +public: + /// Type of rendering backend used by Aurora. + enum class Backend + { + // USD HGI rendering backend (uses for Vulkan). + HGI, + // DirectX DXR rendering backend. + DirectX, + // Choose default backend for current platform. + Default + }; + + /// Create rendering window from provided OS windows handle. + virtual IWindowPtr createWindow(WindowHandle handle, uint32_t width, uint32_t height) = 0; + + /// Create a new render buffer object, that can be used as a target for the renderer. + /// \param width Width of the render buffer in pixels. + /// \param height Height of the render buffer in pixels. + /// \desc The buffer image format. + /// \return A smart pointer to the new render buffer + virtual IRenderBufferPtr createRenderBuffer(int width, int height, ImageFormat format) = 0; + + /// Create a new image object, that can be used as a material texture or as part of the + /// environment. + /// + /// \note This uses the resource pointer directly, the preferred usage is to access via the path + /// API in IScene instead. + /// \param initData The data required to create image. + /// \return A smart pointer to the new image. + /// TODO: The pointer functions should be hidden and only accessibly from the implementation, + /// not the public interface. + virtual IImagePtr createImagePointer(const IImage::InitData& initData) = 0; + + /// Create a new sampler object. + /// + /// \note This uses the resource pointer directly, the preferred usage is to access via the path + /// API in IScene instead. + /// \param props The sampler's properties. + /// \return A smart pointer to the new sampler. + /// TODO: The pointer functions should be hidden and only accessibly from the implementation, + /// not the public interface. + virtual ISamplerPtr createSamplerPointer(const Properties& props) = 0; + + /// Create a new material, which describes the appearance of an instance. + /// + /// \note This uses the resource pointer directly, the preferred usage is to access via the path + /// API in IScene instead. + /// \param materialType Type of material (one of strings in + /// Aurora::Names::MaterialTypes). + /// \param document Document describing the material (interpreted based on value of + /// materialType.) + /// \param name Name of material. + /// \return A smart pointer to the new material. + /// TODO: The pointer functions should be hidden and only accessibly from the implementation, + /// not the public interface. + virtual IMaterialPtr createMaterialPointer( + const std::string& materialType = Names::MaterialTypes::kBuiltIn, + const std::string& document = "Default", const std::string& name = "") = 0; + + /// Creates a new environment object that describes the infinite area lighting and background of + /// a scene. + /// + /// \note This uses the resource pointer directly, the preferred usage is to access via the path + /// API in IScene instead. + /// \return A smart pointer to the new render buffer + /// TODO: The pointer functions should be hidden and only accessibly from the implementation, + /// not the public interface. + virtual IEnvironmentPtr createEnvironmentPointer() = 0; + + /// Creates a new ground plane object that describes a virtual plane that shows shadows and + /// reflections of the scene. + virtual IGeometryPtr createGeometryPointer( + const GeometryDescriptor& desc, const std::string& name = "") = 0; + + /// Creates a new ground plane object that describes a virtual plane that shows shadows and + /// reflections of the scene. + virtual IGroundPlanePtr createGroundPlanePointer() = 0; + + /// Creates a new scene for rendering. + virtual IScenePtr createScene() = 0; + + /// Sets renderer options. + virtual void setOptions(const Properties& options) = 0; + + /// TODO: Remove. This has been replaced by setOptions(). + virtual IValues& options() = 0; + + /// Gets the backend used by the renderer. + virtual Backend backend() const = 0; + + // Set the scene to be rendered. + virtual void setScene(const IScenePtr& pScene) = 0; + + /// Sets the AOVs that will be written to the associated targets when rendering is performed. + /// + /// Each AOV can be written to at most one target. + virtual void setTargets(const TargetAssignments& targetAssignments) = 0; + + /// Sets the camera properties. + /// \param view View matrix + /// \param projection Projection matrix + /// \param focalLength The distance between the camera pinhole and the image plane. + /// \param lensRadius Lens radius of curvature (for depth of field). + virtual void setCamera(const mat4& view, const mat4& projection, float focalDistance = 1.0f, + float lensRadius = 0.0f) = 0; + + // TODO: Remove. This has been replaced by GM types. + virtual void setCamera(const float* view, const float* proj, float focalDistance = 1.0f, + float lensRadius = 0.0f) = 0; + + // Render the current scene. + virtual void render(uint32_t sampleStart = 0, uint32_t sampleCount = 1) = 0; + + // Wait for all the currently executing render tasks on the GPU to complete. + virtual void waitForTask() = 0; + + /// \desc Get the supported set of built-in materials for this renderer, can be passed as a + /// document to setMaterialType. + /// \return Vector of built-in material names. + virtual const std::vector& builtInMaterials() = 0; + + /// \desc Set the callback function used to load resources, such as textures, from a URI. + /// \param func Callback function to be used for all subsquent loading. + virtual void setLoadResourceFunction(LoadResourceFunction func) = 0; + +protected: + virtual ~IRenderer() = default; // hidden destructor +}; +MAKE_AURORA_PTR(IRenderer); + +// Gets the logger for the Aurora library, used to report console output and errors. +AURORA_API Foundation::Log& logger(); + +// Creates a renderer with the specified baclend and number of simultaneously active tasks. +AURORA_API IRendererPtr createRenderer( + IRenderer::Backend type = IRenderer::Backend::Default, uint32_t taskCount = 3); + +} // namespace Aurora diff --git a/Libraries/Aurora/API/Aurora/AuroraNames.h b/Libraries/Aurora/API/Aurora/AuroraNames.h new file mode 100644 index 0000000..720d8ee --- /dev/null +++ b/Libraries/Aurora/API/Aurora/AuroraNames.h @@ -0,0 +1,123 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +namespace Aurora +{ + +/// Name constants. +/// TODO should use constexpr std::string_view when with support C++17. +namespace Names +{ +/// Instance properties +struct InstanceProperties +{ + /// Instance geometry path, modifiying will trigger recreation of instance resource (path) + static AURORA_API const std::string kGeometry; + /// Instance material path (path) + static AURORA_API const std::string kMaterial; + /// Instance transform matrix (float4x4) + static AURORA_API const std::string kTransform; + /// Instance object identifier (int) + static AURORA_API const std::string kObjectID; + /// Is instance visible ? (bool) + static AURORA_API const std::string kVisible; + /// Is instance selectable ? (bool) + static AURORA_API const std::string kSelectable; + /// Array of paths for the materials layers for this instance. (strings) + /// Array begins at the inner-most layer (closest to base layer) and moves outward. + static AURORA_API const std::string kMaterialLayers; + /// Array of paths for the geometry layers for this instance. Length must less than or equal to + /// length of layer materials array. (strings) + /// Array begins at the inner-most layer (closest to base layer) and moves outward. + static AURORA_API const std::string kGeometryLayers; +}; + +/// Environment properties. +struct EnvironmentProperties +{ + /// The top color of the gradient used for lighting (float3) + static AURORA_API const std::string kLightTop; + /// The bottom color of the gradient used for lighting (float3) + static AURORA_API const std::string kLightBottom; + /// The image used for lighting, which takes precedence over the gradient. (path) + static AURORA_API const std::string kLightImage; + /// A transformation to apply to the background. (float4x4) + static AURORA_API const std::string kLightTransform; + /// The top color of the gradient used for background (float3) + static AURORA_API const std::string kBackgroundTop; + /// The top color of the gradient used for background (float3) + static AURORA_API const std::string kBackgroundBottom; + /// The image used for background, which takes precedence over the gradient. (path) + static AURORA_API const std::string kBackgroundImage; + /// A transformation to apply to the background. (float4x4) + static AURORA_API const std::string kBackgroundTransform; + /// Whether to use a simple screen mapping for the background; otherwise a wraparound spherical + /// mapping is used, default is false (bool) + static AURORA_API const std::string kBackgroundUseScreen; +}; + +struct AddressModes +{ + static AURORA_API const std::string kWrap; + static AURORA_API const std::string kMirror; + static AURORA_API const std::string kClamp; + static AURORA_API const std::string kBorder; + static AURORA_API const std::string kMirrorOnce; +}; + +/// Sampler properties. +struct SamplerProperties +{ + /// U address mode + static AURORA_API const std::string kAddressModeU; + /// V address mode + static AURORA_API const std::string kAddressModeV; +}; + +// Vertex attributes. +struct VertexAttributes +{ + static AURORA_API const std::string kPosition; + static AURORA_API const std::string kNormal; + static AURORA_API const std::string kTexCoord0; + static AURORA_API const std::string kTexCoord1; + static AURORA_API const std::string kTexCoord2; + static AURORA_API const std::string kTexCoord3; + static AURORA_API const std::string kTexCoord4; + static AURORA_API const std::string kTangent; + static AURORA_API const std::string kIndices; +}; + +// Material types. +struct MaterialTypes +{ + /// Built-in material type. + /// Document argument must be one a fixed set of built-in materials for renderer + /// Use IRenderer::builtInMaterials() to retrieve set of built-in materials supported by + /// renderer. + static AURORA_API const std::string kBuiltIn; + + /// MaterialX material type. + /// Document argument must be MaterialX XML document string. + static AURORA_API const std::string kMaterialX; + + /// MaterialX path material type. + /// Document argument must be path to a MaterialX XML file. + static AURORA_API const std::string kMaterialXPath; +}; + +} // namespace Names + +} // namespace Aurora diff --git a/Libraries/Aurora/CMakeLists.txt b/Libraries/Aurora/CMakeLists.txt new file mode 100644 index 0000000..6329755 --- /dev/null +++ b/Libraries/Aurora/CMakeLists.txt @@ -0,0 +1,422 @@ +project(Aurora) + +if(ENABLE_DIRECTX_BACKEND) + # Add the preprocessor definition for directX backend flag. + add_compile_definitions(DIRECTX_SUPPORT=1) + + find_package(D3D12 REQUIRED) # DirectX 12 + + set(MINIMUM_WINDOWS_SDK_VERSION "10.0.22000.0") + if(WINDOWS_SDK_VERSION VERSION_LESS MINIMUM_WINDOWS_SDK_VERSION) + message(FATAL_ERROR + " Windows SDK version ${WINDOWS_SDK_VERSION} is found but version ${MINIMUM_WINDOWS_SDK_VERSION} or later is required.\n" + " Windows SDK version ${MINIMUM_WINDOWS_SDK_VERSION} or later can be installed from https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/" + ) + else() + message(STATUS "Found Windows SDK version ${WINDOWS_SDK_VERSION}") + endif() +else() + add_compile_definitions(DIRECTX_SUPPORT=0) +endif() + +if(ENABLE_HGI_BACKEND) + # Add the preprocessor definition for HGI backend flag. + add_compile_definitions(HGI_SUPPORT=1) + + find_package(pxr REQUIRED) # Pixar Universal Scene Description + # Alias the namespace to meet the cmake convention on imported targets + add_library(pxr::usd ALIAS usd) + add_library(pxr::hgi ALIAS hgi) + + if (NOT PXR_hgiVulkan_LIBRARY) + message(FATAL_ERROR "USD hgiVulkan library is required to build Aurora with HGI backend enabled.") + endif() + + find_package(Vulkan REQUIRED) # Vulkan SDK +else() + add_compile_definitions(HGI_SUPPORT=0) +endif() + +find_package(Slang REQUIRED) # The Slang library +find_package(glm REQUIRED) # The OpenGL Mathematics library. +find_package(stb REQUIRED) # The single file image/font librray. + +# Optionally enable MaterialX code generator. +option(ENABLE_MATERIALX "Build with support for MaterialX material types." ON) +if(ENABLE_MATERIALX) + # Add the preprocessor definition for MaterialX flag. + add_compile_definitions(ENABLE_MATERIALX=1) + + find_package(MaterialX REQUIRED) # MaterialX SDK + # Alias the namespace to meet the cmake convention on imported targets + add_library(MaterialX::GenGlsl ALIAS MaterialXGenGlsl) +endif() + +# Optionally enable NVIDIA denoiser. +option(ENABLE_DENOISER "Build with support for NVIDIA denoiser." OFF) +if(ENABLE_DENOISER) + add_compile_definitions(ENABLE_DENOISER=1) + + find_package(NRD REQUIRED) # NVIDIA Real-Time Denoisers + find_package(NRI REQUIRED) # NVIDIA Render Interface +endif() + + +# Public header files +set(API_HEADERS + "API/Aurora/Aurora.h" + "API/Aurora/AuroraNames.h" +) + +# Shared source used by all backends. +set(COMMON_SOURCE + "Source/AliasMap.cpp" + "Source/AliasMap.h" + "Source/AssetManager.cpp" + "Source/AssetManager.h" + "Source/Aurora.cpp" + "Source/AuroraNames.cpp" + "Source/DLL.cpp" + "Source/EnvironmentBase.cpp" + "Source/EnvironmentBase.h" + "Source/GeometryBase.cpp" + "Source/GeometryBase.h" + "Source/MaterialBase.cpp" + "Source/MaterialBase.h" + "Source/pch.h" + "Source/Properties.h" + "Source/RendererBase.cpp" + "Source/RendererBase.h" + "Source/Resources.cpp" + "Source/Resources.h" + "Source/ResourceTracker.h" + "Source/ResourceStub.cpp" + "Source/ResourceStub.h" + "Source/SceneBase.cpp" + "Source/SceneBase.h" + "Source/Transpiler.h" + "Source/Transpiler.cpp" + "Source/WindowsHeaders.h" +) + +# Create the CompiledShaders directory in advance while cmake is generating the project files +set(COMPILED_SHADERS_DIR "${PROJECT_BINARY_DIR}/CompiledShaders") +file(MAKE_DIRECTORY "${COMPILED_SHADERS_DIR}") + +# The slang shaders that are shared by DX and HGI backends. +set(COMMON_SHADERS + "Source/Shaders/BSDF.slang" + "Source/Shaders/BSDFCommon.slang" + "Source/Shaders/Colors.slang" + "Source/Shaders/Environment.slang" + "Source/Shaders/Frame.slang" + "Source/Shaders/Geometry.slang" + "Source/Shaders/Globals.slang" + "Source/Shaders/GLSLToHLSL.slang" + "Source/Shaders/GroundPlane.slang" + "Source/Shaders/ClosestHitEntryPointTemplate.slang" + "Source/Shaders/ShadowHitEntryPointTemplate.slang" + "Source/Shaders/LayerShaderEntryPointTemplate.slang" + "Source/Shaders/Material.slang" + "Source/Shaders/MaterialXCommon.slang" + "Source/Shaders/PathTracingCommon.slang" + "Source/Shaders/BackgroundMissShader.slang" + "Source/Shaders/ShadowMissShader.slang" + "Source/Shaders/RadianceMissShader.slang" + "Source/Shaders/RayGenShader.slang" + "Source/Shaders/ShadeFunctions.slang" + "Source/Shaders/Random.slang" + "Source/Shaders/RayTrace.slang" + "Source/Shaders/ReferenceBSDF.slang" + "Source/Shaders/StandardSurfaceBSDF.slang" + "Source/Shaders/InitializeDefaultMaterialType.slang" + "Source/Shaders/Sampling.slang" +) + +# Set MINIFIED_SHADERS_HEADER to filename of auto-generated header file containing minified Slang shader strings. +set(MINIFIED_COMMON_SHADERS_HEADER "${COMPILED_SHADERS_DIR}/CommonShaders.h") +# Add commands to minifiy common shaders. +minify_shaders("${MINIFIED_COMMON_SHADERS_HEADER}" "${PROJECT_SOURCE_DIR}/Source/Shaders" "${COMMON_SHADERS}") + + +if(ENABLE_DIRECTX_BACKEND) + if(ENABLE_MATERIALX) + set(DIRECTX_MATERIALX_SOURCE + "Source/MaterialX/BSDFCodeGenerator.cpp" + "Source/MaterialX/BSDFCodeGenerator.h" + "Source/MaterialX/MaterialGenerator.cpp" + "Source/MaterialX/MaterialGenerator.h" + ) + endif() + + if(ENABLE_DENOISER) + set(DIRECTX_DENOISER_SOURCE + "Source/DirectX/Denoiser.cpp" + "Source/DirectX/Denoiser.h" + ) + endif() + + # DirectX backend source. + set(DIRECTX_SOURCE + "Source/DirectX/MemoryPool.h" + "Source/DirectX/PTDevice.cpp" + "Source/DirectX/PTDevice.h" + "Source/DirectX/PTEnvironment.cpp" + "Source/DirectX/PTEnvironment.h" + "Source/DirectX/PTGeometry.cpp" + "Source/DirectX/PTGeometry.h" + "Source/DirectX/PTGroundPlane.cpp" + "Source/DirectX/PTGroundPlane.h" + "Source/DirectX/PTImage.cpp" + "Source/DirectX/PTImage.h" + "Source/DirectX/PTMaterial.cpp" + "Source/DirectX/PTMaterial.h" + "Source/DirectX/PTRenderer.cpp" + "Source/DirectX/PTRenderer.h" + "Source/DirectX/PTSampler.cpp" + "Source/DirectX/PTSampler.h" + "Source/DirectX/PTScene.cpp" + "Source/DirectX/PTScene.h" + "Source/DirectX/PTShaderLibrary.cpp" + "Source/DirectX/PTShaderLibrary.h" + "Source/DirectX/PTTarget.cpp" + "Source/DirectX/PTTarget.h" + "${DIRECTX_DENOISER_SOURCE}" + "${DIRECTX_MATERIALX_SOURCE}" + ) + + set(DIRECTX_PRECOMPILED_SHADERS + "Source/DirectX/Shaders/Accumulation.hlsl" + "Source/DirectX/Shaders/PostProcessing.hlsl" + ) +endif() + +if(ENABLE_HGI_BACKEND) + set(HGI_SOURCE + "Source/HGI/HGIEnvironment.cpp" + "Source/HGI/HGIEnvironment.h" + "Source/HGI/HGIGroundPlane.cpp" + "Source/HGI/HGIGroundPlane.h" + "Source/HGI/HGIImage.cpp" + "Source/HGI/HGIImage.h" + "Source/HGI/HGIMaterial.cpp" + "Source/HGI/HGIMaterial.h" + "Source/HGI/HGIGeometry.cpp" + "Source/HGI/HGIGeometry.h" + "Source/HGI/HGIHandleWrapper.cpp" + "Source/HGI/HGIHandleWrapper.h" + "Source/HGI/HGIRenderBuffer.cpp" + "Source/HGI/HGIRenderBuffer.h" + "Source/HGI/HGIRenderer.cpp" + "Source/HGI/HGIRenderer.h" + "Source/HGI/HGIScene.cpp" + "Source/HGI/HGIScene.h" + "Source/HGI/HGIWindow.cpp" + "Source/HGI/HGIWindow.h" + ) + + set(HGI_SHADERS + "Source/HGI/Shaders/Accumulation.glsl" + "Source/HGI/Shaders/PostProcessing.glsl" + "Source/HGI/Shaders/InstanceData.glsl" + ) + + # Set MINIFIED_SHADERS_HEADER to filename of auto-generated header file containing minified HLSL shader strings. + set(MINIFIED_HGI_SHADERS_HEADER "${COMPILED_SHADERS_DIR}/HGIShaders.h") + # Add commands to minifiy hgi shaders. + minify_shaders("${MINIFIED_HGI_SHADERS_HEADER}" "${CMAKE_CURRENT_SOURCE_DIR}/Source/HGI/Shaders" "${HGI_SHADERS}") +endif() + +# Create shared library for the project containing all the sources. +add_library(${PROJECT_NAME} SHARED + ${API_HEADERS} + ${COMMON_SOURCE} + ${DIRECTX_SOURCE} + ${COMMON_SHADERS} + ${MINIFIED_COMMON_SHADERS_HEADER} + ${DIRECTX_PRECOMPILED_SHADERS} + ${HGI_SOURCE} + ${HGI_SHADERS} + ${MINIFIED_HGI_SHADERS_HEADER} +) + +# Add to libraries folder +set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Libraries") + +# Specify a header file from which to generate precompiled headers. +# NOTE: This will be used by all source files. +target_precompile_headers(${PROJECT_NAME} PRIVATE "Source/pch.h") + +# Add the source to groups within IDE. +source_group("API" FILES ${API_HEADERS}) +source_group("Common" FILES ${COMMON_SOURCE}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/Source" FILES ${DIRECTX_SOURCE}) +source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/Source" FILES ${HGI_SOURCE}) +source_group("Shaders" FILES ${COMMON_SHADERS}) +source_group("DirectX/Shaders" FILES ${DIRECTX_PRECOMPILED_SHADERS}) +source_group("HGI/Shaders" FILES ${HGI_SHADERS}) + +if(WIN32) + # Create custom targets that will copy Windows-specific DLLs to the runtime folder. This + # includes the DirectX shader compiler and NVIDIA NRD and NRI. + + if(ENABLE_DIRECTX_BACKEND) + message(${DXCOMPILER_DLLS}) + add_custom_target(CopyDXCompilerDLLs ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${RUNTIME_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DXCOMPILER_DLLS} ${RUNTIME_OUTPUT_DIR} + ) + set_property(TARGET CopyDXCompilerDLLs PROPERTY FOLDER "Deployment") + add_dependencies(${PROJECT_NAME} CopyDXCompilerDLLs) + endif() + + # Copy all dlls of the externals to our runtime directory + # TODO: this is kinda a hack. This assumes all dlls are stored in the installation tree created by + # our build script. But what if users build with custom external libraries (for example, + # "-D ZLIB_ROOT=my_ZLIB_build")? Better way seems to be letting cmake to copy the dlls of + # the dependent targets as in the following custom command. Does not seems to work on the + # dependencies of the dependencies. Need to research on this. + # add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + # COMMAND ${CMAKE_COMMAND} -E copy $ $ + # COMMAND_EXPAND_LISTS + # ) + if(DEFINED EXTERNALS_DIR) + file(GLOB EXTERNAL_LIB_DLLS ${EXTERNALS_DIR}/lib/*.dll) + file(GLOB EXTERNAL_BIN_DLLS ${EXTERNALS_DIR}/bin/*.dll) + add_custom_target(CopyExternalDLLs ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${EXTERNAL_LIB_DLLS} ${RUNTIME_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${EXTERNAL_BIN_DLLS} ${RUNTIME_OUTPUT_DIR} + ) + set_property(TARGET CopyExternalDLLs PROPERTY FOLDER "Deployment") + add_dependencies(CopyExternalDLLs MakeRuntimeDir) + add_dependencies(${PROJECT_NAME} CopyExternalDLLs) + endif() + + # Create a custom target that will copy NVIDIA NRD and NRI DLLs to the runtime folder, if the + # denoiser is enabled. + if(ENABLE_DENOISER) + add_custom_target(CopyNvidiaDenoiserDLLs ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${NRD_LIBRARY_DLL} ${RUNTIME_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${NRI_LIBRARY_DLL} ${RUNTIME_OUTPUT_DIR} + ) + set_property(TARGET CopyNvidiaDenoiserDLLs PROPERTY FOLDER "Deployment") + add_dependencies(CopyNvidiaDenoiserDLLs MakeRuntimeDir) + add_dependencies(${PROJECT_NAME} CopyNvidiaDenoiserDLLs) + endif() + + # Create a custom target that will copy MaterialX DLLs to the runtime folder, if MaterialX + # is enabled. + if(ENABLE_MATERIALX) + add_custom_target(CopyMaterialXLibrary ALL + COMMAND ${CMAKE_COMMAND} -E make_directory ${RUNTIME_OUTPUT_DIR}/MaterialX/libraries + COMMAND ${CMAKE_COMMAND} -E copy_directory ${MATERIALX_STDLIB_DIR} ${RUNTIME_OUTPUT_DIR}/MaterialX/libraries + ) + set_property(TARGET CopyMaterialXLibrary PROPERTY FOLDER "Deployment") + add_dependencies(${PROJECT_NAME} CopyMaterialXLibrary) + endif() + + if(ENABLE_HGI_BACKEND) + # Copy additional HGI dlls of the Pixar USD to our runtime directory + file(GLOB USD_LIB_DLLs ${pxr_DIR}/lib/*.dll) + file(GLOB USD_BIN_DLLs ${pxr_DIR}/bin/*.dll) + add_custom_target(CopyUSDDLLs ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${USD_LIB_DLLs} ${RUNTIME_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${USD_BIN_DLLs} ${RUNTIME_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${RUNTIME_OUTPUT_DIR}/usd/hgiVulkan/resources + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${pxr_DIR}/lib/usd/plugInfo.json ${RUNTIME_OUTPUT_DIR}/usd + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${pxr_DIR}/lib/usd/hgiVulkan/resources/plugInfo.json ${RUNTIME_OUTPUT_DIR}/usd/hgiVulkan/resources + ) + set_property(TARGET CopyUSDDLLs PROPERTY FOLDER "Deployment") + add_dependencies(CopyUSDDLLs MakeRuntimeDir) + add_dependencies(${PROJECT_NAME} CopyUSDDLLs) + endif() + + # Create a custom target that will copy Slang DLLs to the runtime folder. + add_custom_target(CopySlangDLLs ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${Slang_LIBRARY_DLL} ${RUNTIME_OUTPUT_DIR} + ) + set_property(TARGET CopySlangDLLs PROPERTY FOLDER "Deployment") + add_dependencies(CopySlangDLLs MakeRuntimeDir) + add_dependencies(${PROJECT_NAME} CopySlangDLLs) + +else() # Linux + #TODO anything we need to set to support the Aurora execution? +endif() + +# Set custom ouput properties. +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER "Libraries" + RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" +) + +if(ENABLE_MATERIALX) + set(MATERIALX_TARGETS MaterialX::GenGlsl) +else() + set(MATERIALX_TARGETS "") +endif() + +if(ENABLE_DENOISER) + set(DENOISER_TARGETS NRD::NRD NRI::NRI) +else() + set(DENOISER_TARGETS "") +endif() + +if(ENABLE_HGI_BACKEND) + set(USD_TARGETS pxr::usd pxr::hgi) +endif() + +if(ENABLE_DIRECTX_BACKEND) + set(D2D12_TARGETS D3D12::D3D12 D3D12::compiler) +else() + set(D2D12_TARGETS "") +endif() + +target_link_libraries(${PROJECT_NAME} +PRIVATE + Foundation + ${MATERIALX_TARGETS} # MaterialX needs to be in front of USD due to the conflicts of MaterialX.h in USD + ${D2D12_TARGETS} + ${USD_TARGETS} + ${DENOISER_TARGETS} + stb::stb + glm::glm + Slang::Slang +) + +target_include_directories(${PROJECT_NAME} +PUBLIC + "API" # Public include path +PRIVATE + "Source" # Source folder for PCH + "${PROJECT_BINARY_DIR}" +) + +# Set the default compile definitions. +target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFAULT_COMPILE_DEFINITIONS}) + +if(ENABLE_DIRECTX_BACKEND) + # Compile the standalone compute shaders. + # NOTE: These are compiled with DXC at build time unlike all the other shaders. + # TODO: Runtime compile this too, see OGSMOD-1215. + set_source_files_properties(Source/DirectX/Shaders/Accumulation.hlsl PROPERTIES + VS_SHADER_TYPE Compute + VS_SHADER_MODEL 6.3 + VS_SHADER_ENTRYPOINT "Accumulation" + VS_SHADER_OUTPUT_HEADER_FILE "${COMPILED_SHADERS_DIR}/Accumulation.hlsl.h" + VS_SHADER_VARIABLE_NAME "g_pAccumulationShader" + VS_SHADER_OBJECT_FILE_NAME " " + ) + set_source_files_properties(Source/DirectX/Shaders/PostProcessing.hlsl PROPERTIES + VS_SHADER_TYPE Compute + VS_SHADER_MODEL 6.3 + VS_SHADER_ENTRYPOINT "PostProcessing" + VS_SHADER_OUTPUT_HEADER_FILE "${COMPILED_SHADERS_DIR}/PostProcessing.hlsl.h" + VS_SHADER_VARIABLE_NAME "g_pPostProcessingShader" + VS_SHADER_OBJECT_FILE_NAME " " + ) + + # TODO: do we have Vulkan computer shaders amd need to do similiar things for them? +endif() diff --git a/Libraries/Aurora/Source/AliasMap.cpp b/Libraries/Aurora/Source/AliasMap.cpp new file mode 100644 index 0000000..8c83190 --- /dev/null +++ b/Libraries/Aurora/Source/AliasMap.cpp @@ -0,0 +1,170 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "AliasMap.h" + +BEGIN_AURORA + +namespace AliasMap +{ + +// Computes the perceived luminance of a color. +static float computeLuminance(const vec3& value) +{ + static constexpr vec3 kLuminanceFactors = vec3(0.2125f, 0.7154f, 0.0721f); + + return dot(value, kLuminanceFactors); +} + +void build(const float* pPixels, uvec2 dimensions, Entry* pOutputBuffer, size_t outputBufferSize, + float& luminanceIntegralOut) +{ + // Calculate total number of pixels. + unsigned int pixelCount = dimensions.x * dimensions.y; + size_t bufferSize = pixelCount * sizeof(Entry); + + // Ensure output buffer size is correct (is being put in GPU texture so must match exactly). + AU_ASSERT(outputBufferSize == bufferSize, + "Expected ouput buffer of size %d bytes, instead is %d bytes", pixelCount * sizeof(Entry), + outputBufferSize); + + // A pair of luminance-related values, for a single pixel. Pixel luminance is used to determine + // the relative importance of pixels, i.e. perceptually brighter pixels will be more likely to + // be selected during sampling. + struct LuminanceEntry + { + float luminance; // the luminance of the pixel + float luminanceAndArea; // the luminance of the pixel multiplied by its solid angle + }; + + // Prepare an array of temporary luminance data, used to build the alias map. + vector luminanceData(pixelCount); + + // The luminance integral is computed below, and used to compute probabilities and PDF. + luminanceIntegralOut = 0.0f; + + // Iterate the image pixels, storing the luminance and area-scaled luminance of each one. + const float* pPixel = pPixels; + size_t luminanceIndex = 0; + auto lonIncrement = static_cast(2.0f * M_PI / dimensions.x); // vertical (lat) + auto latIncrement = static_cast(M_PI / dimensions.y); // horizontal (lon) + auto latAngle = static_cast(M_PI_2); + for (unsigned int y = 0; y < dimensions.y; y++) + { + // Compute the solid angle covered by each pixel for current row (latitude) of the + // environment image: (sin(upperAngle) - sin(lowerAngle)) * longitudeIncrement. The pixels + // at the poles have a smaller solid angle than those at the equator. + float solidAngle = (sin(latAngle) - sin(latAngle - latIncrement)) * lonIncrement; + latAngle -= latIncrement; + + // Iterate the pixels of the current row, computing and accumulating luminance for each. + for (unsigned int x = 0; x < dimensions.x; x++) + { + // Compute the luminance and area-scaled luminance for the current pixel. The area- + // scaled luminance is actually a term in the luminance integral. + float luminance = computeLuminance(make_vec3(pPixel)); + float luminanceAndArea = solidAngle * luminance; + + // Store the luminance entry, and add the area-scaled luminance as another term in the + // accumulated integral. + luminanceData[luminanceIndex] = { luminance, luminanceAndArea }; + luminanceIntegralOut += luminanceAndArea; + + // Advance to the next pixel and luminance entry. + pPixel += 3; + luminanceIndex++; + } + } + + // Declare the generated alias map and a temporary index map. + vector aliasMap(pixelCount); + vector indexMap(pixelCount); + + // Iterate the pixels, preparing initial data for both the alias map and the index map. + float average = luminanceIntegralOut / pixelCount; + unsigned int sml = 0, lrg = pixelCount; + for (unsigned int i = 0; i < pixelCount; i++) + { + LuminanceEntry& luminanceEntry = luminanceData[i]; + + // Initialize the probability for the alias map entry: + // - Probability: This is a *normalized* probability, where the probability is the pixel's + // area-scaled luminance divided by the average of the luminance integral. In this way, + // there are "small" alias map entries with probabilities under 1.0 and "large" entries + // above 1.0 (below and above the average, respectively). These will be updated in the + // following loop. + // - PDF: The PDF for an entry in a discrete distribution like this one (as there are a + // specific set of directions represented by the image pixels) is the pixel luminance + // divided by the luminance integral. In this way the integral of the PDF over the domain + // a sphere of directions) is 1.0, as required for Monte Carlo integration. + Entry& aliasEntry = aliasMap[i]; + aliasEntry.prob = luminanceEntry.luminanceAndArea / average; + aliasEntry.pdf = luminanceEntry.luminance / luminanceIntegralOut; + + // Compute an index based on the entry's probability, putting references to small + // entries at the beginning of the index map and large entries at the end of the index + // map. This also initializes each entry's alias index to point to itself. + unsigned index = aliasEntry.prob < 1.0f ? sml++ : --lrg; + indexMap[index] = i; + aliasEntry.alias = i; + } + + // At this point lrg points somewhere in the middle of the index map. Iterate the index map, + // establishing alias indices for alias map entries, and distributing probability evenly. This + // stops when the sml index reaches the lrg index, or when the lrg index reaches the end. + // NOTE: The lrg index is updated in this loop, which is why the second check is needed. + for (sml = 0; sml < lrg && lrg < pixelCount; sml++) + { + // Get the indices of the small and large alias map entries. + unsigned int indexSmall = indexMap[sml]; + unsigned int indexLarge = indexMap[lrg]; + + // Assign the small alias map entry to point to the large one. This pairing of alias map + // entries is arbitrary (i.e. it depends on the original image data), but gives the desired + // behavior. + aliasMap[indexSmall].alias = indexLarge; + + // "Remove" probability from the large alias map entry, by the amount remaining in the small + // entry, i.e. the amount below 1.0. This is effectively moved to the small entry, since the + // small entry has an alias to the large entry. + aliasMap[indexLarge].prob -= 1.0f - aliasMap[indexSmall].prob; + + // If the large entry now has a probability below 1.0, increment the lrg index. This means + // the alias map entry is now treated as small, and will (later) receive an alias. + lrg += aliasMap[indexLarge].prob < 1.0f ? 1 : 0; + } + + // NOTE: At this point the alias map now has the total relative probability "distributed" evenly + // across all entries as follows: + // - Every small entry has an alias index for another entry from which it reduced probability. + // That other entry corresponds to a pixel with above average luminance. + // - Even entries corresponding to pixels with above average luminance may have an alias to + // another pixel with above average luminance, because they had their probability reduced. + // - Theoretically the large index should be close to the pixel count, i.e. every entry + // represents 1.0 relative probability between the corresponding pixel and the aliased pixel. + // - However, due to numerical precision, the large index will likely be less than, but still + // relatively close to, the pixel count. The corresponding entries will have probabilities + // slightly above 1.0 and alias to themselves, which is fine when sampling in practice. + // + // See this extensive explanation of this technique, in the context of general discrete + // probability distributions: https://www.keithschwarz.com/darts-dice-coins. + + // Copy to output buffer. + memcpy(pOutputBuffer, aliasMap.data(), bufferSize); +} + +} // namespace AliasMap + +END_AURORA diff --git a/Libraries/Aurora/Source/AliasMap.h b/Libraries/Aurora/Source/AliasMap.h new file mode 100644 index 0000000..e69ea49 --- /dev/null +++ b/Libraries/Aurora/Source/AliasMap.h @@ -0,0 +1,40 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +namespace AliasMap +{ + +// An entry in the alias map. These are eventually used for sampling during rendering. +// NOTE: Padding to 16 byte (float4) alignment is added for best performance. +struct Entry +{ + unsigned int alias; // index of another entry accounting for the rest of the probability + float prob; // normalized probability of the entry + float pdf; // PDF value of the entry, for Monte Carlo integration + vec1 _padding1; +}; + +// Creates an alias map from the pixel data for an environment image with lat-long layout. This is +// used for importance sampling from the environment image, by treating it as a discrete probability +// distribution. +// The luminance integral is also computed as part of alias map calculation. +void build(const float* pPixels, uvec2 dimensions, Entry* pOutputBuffer, size_t outputBufferSize, + float& luminanceIntegralOut); + +} // namespace AliasMap + +END_AURORA diff --git a/Libraries/Aurora/Source/AssetManager.cpp b/Libraries/Aurora/Source/AssetManager.cpp new file mode 100644 index 0000000..e6bcb92 --- /dev/null +++ b/Libraries/Aurora/Source/AssetManager.cpp @@ -0,0 +1,289 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "AssetManager.h" + +// Include STB for image loading. +// TODO: Image loading will eventually be handled by clients. +#define STB_IMAGE_IMPLEMENTATION +#include + +BEGIN_AURORA + +// Returns true if the image uses the sRGB color space, and false if it is linear. +// NOTE: This will only return false if the image is a PNG, with no sRGB tag, and gamma set to 1.0. +// TODO: We should use ImageManager for this in a client callback, rather than using STBI internal +// functions, but that is not easily done using OIIO or the current ImageManager API interface. +bool isImageSRGB(stbi_uc const* buffer, int len) +{ + // Set up STBI context. + stbi__context s; + stbi__start_mem(&s, buffer, len); + + // Return true if not a PNG file. Non-PNG images assumed to be sRGB. + if (!stbi__png_test(&s)) + { + return true; + } + + // Setup PNG reading structure. + stbi__png p; + p.s = &s; + p.expanded = nullptr; + p.idata = nullptr; + p.out = nullptr; + + // Return and print error if header not parsed. + if (!stbi__check_png_header(&s)) + { + AU_ERROR("Failed to parse PNG header"); + return true; + } + + // Default to gamma of -1 (invalid) + int gamma = -1; + + // Maximum number of chunk in header (avoid infinite loop) + int kHeaderMaxChunks = 0xFFFF; + + // Iterate through chunks until image data is found. + for (int i = 0; i < kHeaderMaxChunks; i++) + { + // Read next chunk. + stbi__pngchunk c = stbi__get_chunk_header(&s); + + // Process based on types. + switch (c.type) + { + case STBI__PNG_TYPE('s', 'R', 'G', 'B'): + { + // Return true if "sRGB" chunk found (overrides gamma chunk regardless of value.) + return true; + } + case STBI__PNG_TYPE('g', 'A', 'M', 'A'): + { + // Set the gamma from "gAMA" chunk. + gamma = stbi__get32be(&s); + break; + } + case STBI__PNG_TYPE('I', 'D', 'A', 'T'): + { + // This is the "IDAT" chunk, so we've reached the image data at the end of the header, + // and we can return the result. PNG is considered linear if the gamma is exactly 100000 + // (meaning gamma of 1.0). + return gamma != 100000; + } + default: + { + // Skip other chunks. + stbi__skip(&s, c.length); + break; + } + } + + // Read the checksum at the end of the chunk. + stbi__get32be(&s); + } + + // This should never be reached, because reading should stop at the IDAT chunk (above). + AU_ERROR("Error parsing PNG"); + + return true; +} + +// Default load resource function. +bool defaultLoadResourceFunction( + const string& uri, vector* pBufferOut, string* pFileNameOut) +{ + // Default load resource function just loads URI as file path directly. + ifstream is(uri, ifstream::binary); + if (!is) + return false; + is.seekg(0, is.end); + size_t length = is.tellg(); + is.seekg(0, is.beg); + pBufferOut->resize(length); + is.read((char*)&(*pBufferOut)[0], length); + + // Copy the URI to file name with no manipulation. + *pFileNameOut = uri; + + return true; +} + +// Default process image function. +// Has extra flipImageY argument that must be filled in. +bool defaultProcessImageFunction(const vector& buffer, const string& filename, + ImageAsset* pImageOut, bool flipImageY) +{ + // Use STB to decode the image buffer. + // TODO: Image loading will eventually be handled by clients. + int width, height, components; + + // Is this image HDR? + bool isHDR = stbi_is_hdr_from_memory(&buffer[0], (int)buffer.size()); + + if (isHDR) + { + // Don't flip HDR images. + stbi_set_flip_vertically_on_load(false); + + // Load HDR image as floats. + float* pPixels = + stbi_loadf_from_memory(&buffer[0], (int)buffer.size(), &width, &height, &components, 0); + + AU_ASSERT( + components == 3 || components == 4, "Only RGB and RGBA HDR images currently supported"); + + // Fill in Aurora image data struct. + pImageOut->data.width = width; + pImageOut->data.height = height; + pImageOut->data.name = filename; + pImageOut->data.linearize = false; + pImageOut->data.format = components == 3 ? ImageFormat::Float_RGB : ImageFormat::Float_RGBA; + size_t sizeBytes = static_cast(width * height * components * sizeof(float)); + pImageOut->pixels = make_unique(sizeBytes); + pImageOut->data.pImageData = pImageOut->pixels.get(); + pImageOut->sizeBytes = sizeBytes; + + // Copy pixels. + memcpy(pImageOut->pixels.get(), pPixels, sizeBytes); + + // Free the pixels allocated by STB. + stbi_image_free(pPixels); + } + else + { + + // Set the flipped flag based on static variable in AssetManager. + stbi_set_flip_vertically_on_load(flipImageY); + + // Load LDR image as bytes. + unsigned char* pPixels = + stbi_load_from_memory(&buffer[0], (int)buffer.size(), &width, &height, &components, 0); + + // Check if image is sRGB (if so set linearize flag.) + bool isSRGB = isImageSRGB(&buffer[0], (int)buffer.size()); + + // Fill in Aurora image data struct. + pImageOut->data.width = width; + pImageOut->data.height = height; + pImageOut->data.name = filename; + pImageOut->data.linearize = isSRGB; + pImageOut->data.format = ImageFormat::Integer_RGBA; + size_t sizeBytes = static_cast(width * height * 4); + pImageOut->pixels = make_unique(sizeBytes); + pImageOut->data.pImageData = pImageOut->pixels.get(); + pImageOut->sizeBytes = sizeBytes; + + // Output images must have four components (RGBA) so fill out missing components as needed. + if (components == 1) + { + // Single-component (R) image: duplicate the R component to G and B, and set alpha to + // the maximum value. + unsigned char* pIn = pPixels; + unsigned char* pOut = pImageOut->pixels.get(); + for (int i = 0; i < width * height; i++) + { + unsigned char chIn = *(pIn++); + *(pOut++) = chIn; + *(pOut++) = chIn; + *(pOut++) = chIn; + *(pOut++) = 255; + } + } + else if (components == 3) + { + // RGB image: set alpha to the maximum value. + unsigned char* pIn = pPixels; + unsigned char* pOut = pImageOut->pixels.get(); + for (int i = 0; i < width * height; i++) + { + *(pOut++) = *(pIn++); + *(pOut++) = *(pIn++); + *(pOut++) = *(pIn++); + *(pOut++) = 255; + } + } + else if (components == 4) + { + // Just copy directly if the image already has RGBA components. + memcpy(pImageOut->pixels.get(), pPixels, sizeBytes); + } + else + { + // Fail if unsupported component count. + AU_FAIL("%s invalid number of components %d", filename.c_str(), components); + } + + // Free the pixels allocated by STB. + stbi_image_free(pPixels); + } + + return true; +}; + +AssetManager::AssetManager( + LoadResourceFunction loadResourceFunction, ProcessImageFunction processImageFunction) +{ + // Set the load resource callback, use the default if argument is null. + _loadResourceFunction = + loadResourceFunction ? loadResourceFunction : defaultLoadResourceFunction; + + // Set the process image callback, use the default (getting the value of flipImageY from + // member variable) if argument is null. + if (processImageFunction) + _processImageFunction = processImageFunction; + else + _processImageFunction = [this](const vector& buffer, const string& filename, + ImageAsset* pImageOut) { + return defaultProcessImageFunction(buffer, filename, pImageOut, _flipImageY); + }; +} + +shared_ptr AssetManager::acquireTextFile(const string& uri) +{ + // Use callback function to load buffer. + vector buffer; + string filename; + if (!_loadResourceFunction(uri, &buffer, &filename)) + return nullptr; + + // Return shared pointer to buffer as string. + // TODO: Should cache based on URI. + return make_shared((char*)&buffer[0], buffer.size()); +} + +shared_ptr AssetManager::acquireImage(const string& uri) +{ + // Use callback function to load buffer. + vector buffer; + string filename; + if (!_loadResourceFunction(uri, &buffer, &filename)) + return nullptr; + + // Create shared pointer to image asset. + // TODO: Should cache based on URI. + shared_ptr pImageData = make_shared(); + + // Process the raw image using callback function to produce Aurora image data. + if (!_processImageFunction(buffer, filename, pImageData.get())) + return nullptr; + + // Return shared pointer. + return pImageData; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/AssetManager.h b/Libraries/Aurora/Source/AssetManager.h new file mode 100644 index 0000000..eec67db --- /dev/null +++ b/Libraries/Aurora/Source/AssetManager.h @@ -0,0 +1,79 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +/// Image asset loaded by AssetManager::acquireImage. +struct ImageAsset +{ + // The Aurora image data structure. + IImage::InitData data; + + // The pixels referenced image data. + unique_ptr pixels; + + // Size in bytes of the pixel data. + size_t sizeBytes; +}; + +/// Process image function, used by asset manager to process image pixels from a raw buffer. +/// +/// \param buffer The raw buffer containing the unprocessed image data. +/// \param filename The filename of the image resource. +/// \param pImageOut The processed image data. +/// \param forceLinear If true the image will be loaded with a linear color space, otherwise +/// inferred from image metadata. +/// \return True if loaded successfully. +using ProcessImageFunction = function& buffer, const string& filename, ImageAsset* pImageOut)>; + +/// Asset manager class, used to load and cache external asset files. +class AssetManager +{ +public: + /// Constructor. + /// + /// \param loadResourceFunction Function used to load resource buffers from a URI. + /// \param processImageFunction Function used to process a image data after loading. + AssetManager(LoadResourceFunction loadResourceFunction = nullptr, + ProcessImageFunction processImageFunction = nullptr); + + /// Load a new text file from a Universal Resource Identifier(URI) string, or return existing + /// one if already loaded. + shared_ptr acquireTextFile(const string& uri); + + /// Load a new image from a Universal Resource Identifier(URI) string, or return existing + /// one if already loaded. + shared_ptr acquireImage(const string& uri); + + /// Set the global flag to enable flipping images vertically in the default image decoding + /// function + /// \param enabled If true image rows loaded bottom-to-top. + void enableVerticalFlipOnImageLoad(bool enabled) { _flipImageY = enabled; } + + /// Set the callback function used to load all resources from a provided URI. + /// + /// \param The callback function to used for all resource loading. + void setLoadResourceFunction(LoadResourceFunction func) { _loadResourceFunction = func; } + +protected: + // Flipped vertically defaults to true, this matches traditional Aurora. + bool _flipImageY = true; + + LoadResourceFunction _loadResourceFunction; + ProcessImageFunction _processImageFunction; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/Aurora.cpp b/Libraries/Aurora/Source/Aurora.cpp new file mode 100644 index 0000000..2b6204a --- /dev/null +++ b/Libraries/Aurora/Source/Aurora.cpp @@ -0,0 +1,71 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#if DIRECTX_SUPPORT +#include "DirectX/PTRenderer.h" +#endif +#if HGI_SUPPORT +#include "HGI/HGIRenderer.h" +#endif +#include "RendererBase.h" + +BEGIN_AURORA + +Foundation::Log& logger() +{ + return Foundation::Log::logger(); +} + +IRendererPtr createRenderer(IRenderer::Backend type, [[maybe_unused]] uint32_t taskCount) +{ + RendererBasePtr pRenderer; + + // Create an instance of the appropriate renderer class, with the specified active task count. + switch (type) + { + case IRenderer::Backend::DirectX: +#if DIRECTX_SUPPORT + pRenderer = make_shared(taskCount); +#else + AU_FAIL( + "ENABLE_DIRECTX_BACKEND option must be enabled in CMake to support DirectX back end.", + type); +#endif + break; + case IRenderer::Backend::HGI: +#if HGI_SUPPORT + pRenderer = make_shared(taskCount); +#else + AU_FAIL( + "ENABLE_HGI_BACKEND option must be enabled in CMake to support HGI back end.", type); +#endif + break; + default: +#if DIRECTX_SUPPORT + pRenderer = make_shared(taskCount); +#elif HGI_SUPPORT + pRenderer = make_shared(taskCount); +#else + AU_FAIL("No backend available."); +#endif + break; + } + AU_ASSERT(pRenderer, "Invalid renderer type %x", type); + + // Return nullptr if the renderer did not initialize; otherwise return it. + return pRenderer->isValid() ? pRenderer : nullptr; +} + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/AuroraNames.cpp b/Libraries/Aurora/Source/AuroraNames.cpp new file mode 100644 index 0000000..af94ba0 --- /dev/null +++ b/Libraries/Aurora/Source/AuroraNames.cpp @@ -0,0 +1,61 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "SceneBase.h" + +BEGIN_AURORA +const string Names::InstanceProperties::kMaterial("material"); +const string Names::InstanceProperties::kGeometry("geometry"); +const string Names::InstanceProperties::kTransform("transform"); +const string Names::InstanceProperties::kObjectID("objectID"); +const string Names::InstanceProperties::kVisible("visible"); +const string Names::InstanceProperties::kSelectable("selectable"); +const string Names::InstanceProperties::kMaterialLayers("layer_materials"); +const string Names::InstanceProperties::kGeometryLayers("layer_geometry"); + +const string Names::EnvironmentProperties::kLightTop("light_top"); +const string Names::EnvironmentProperties::kLightBottom("light_bottom"); +const string Names::EnvironmentProperties::kLightImage("light_image"); +const string Names::EnvironmentProperties::kLightTransform("light_transform"); +const string Names::EnvironmentProperties::kBackgroundTop("background_top"); +const string Names::EnvironmentProperties::kBackgroundBottom("background_bottom"); +const string Names::EnvironmentProperties::kBackgroundImage("background_image"); +const string Names::EnvironmentProperties::kBackgroundTransform("background_transform"); +const string Names::EnvironmentProperties::kBackgroundUseScreen("background_use_screen"); + +const string Names::VertexAttributes::kPosition("position"); +const string Names::VertexAttributes::kNormal("normal"); +const string Names::VertexAttributes::kTexCoord0("tcoord0"); +const string Names::VertexAttributes::kTexCoord1("tcoord1"); +const string Names::VertexAttributes::kTexCoord2("tcoord2"); +const string Names::VertexAttributes::kTexCoord3("tcoord3"); +const string Names::VertexAttributes::kTexCoord4("tcoord4"); +const string Names::VertexAttributes::kTangent("tangent"); +const string Names::VertexAttributes::kIndices("indices"); + +const string Names::MaterialTypes::kBuiltIn("BuiltIn"); +const string Names::MaterialTypes::kMaterialX("MaterialX"); +const string Names::MaterialTypes::kMaterialXPath("MaterialXPath"); + +const string Names::AddressModes::kWrap("Wrap"); +const string Names::AddressModes::kMirror("Mirror"); +const string Names::AddressModes::kClamp("Clamp"); +const string Names::AddressModes::kBorder("Border"); +const string Names::AddressModes::kMirrorOnce("MirrorOnce"); + +const string Names::SamplerProperties::kAddressModeU("AddressModeU"); +const string Names::SamplerProperties::kAddressModeV("AddressModeV"); + +END_AURORA diff --git a/Libraries/Aurora/Source/DLL.cpp b/Libraries/Aurora/Source/DLL.cpp new file mode 100644 index 0000000..7cdd832 --- /dev/null +++ b/Libraries/Aurora/Source/DLL.cpp @@ -0,0 +1,55 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#ifdef WIN32 + +// Reports currently live DirectX objects. +static void dxReportLiveObjects() +{ +#if defined AU_DIRECTX + IDXGIDebug1Ptr pDebug; + checkHR(::DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDebug))); + pDebug->ReportLiveObjects(DXGI_DEBUG_ALL, + DXGI_DEBUG_RLO_FLAGS(DXGI_DEBUG_RLO_SUMMARY | DXGI_DEBUG_RLO_IGNORE_INTERNAL)); +#endif +} + +// The module entry point. +BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + // Report memory leaks. + dxReportLiveObjects(); + break; + } + return TRUE; +} +#else +extern "C" +{ + __attribute__((constructor)) static void Initializer( + int /*argc*/, char** /*argv*/, char** /*envp*/) + { + } + + __attribute__((destructor)) static void Finalizer() {} +} +#endif \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/DirectXHeaders.h b/Libraries/Aurora/Source/DirectX/DirectXHeaders.h new file mode 100644 index 0000000..f175759 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/DirectXHeaders.h @@ -0,0 +1,80 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// DirectX 12 headers. +#include +#include +#include +#include +#include +#include + +// d3dx12.h is a local include. It was acquired from https://github.com/microsoft/DirectX-Headers +#include "DirectXHeaders/d3dx12.h" + +// Microsoft Active Template library +#include + +// DirectX 12 smart pointers. +// NOTE: ComPtr is recommended. See https://github.com/Microsoft/DirectXTK/wiki/ComPtr. +#include +using Microsoft::WRL::ComPtr; +#define MAKE_DX_PTR(_x) using _x##Ptr = ComPtr<_x> +MAKE_DX_PTR(ID3D12CommandAllocator); +MAKE_DX_PTR(ID3D12CommandQueue); +MAKE_DX_PTR(ID3D12Debug); +MAKE_DX_PTR(ID3D12DescriptorHeap); +MAKE_DX_PTR(ID3D12Device5); +MAKE_DX_PTR(ID3D12Fence); +MAKE_DX_PTR(ID3D12GraphicsCommandList4); +MAKE_DX_PTR(ID3D12InfoQueue); +MAKE_DX_PTR(ID3D12PipelineState); +MAKE_DX_PTR(ID3D12Resource); +MAKE_DX_PTR(ID3D12RootSignature); +MAKE_DX_PTR(ID3D12StateObject); +MAKE_DX_PTR(ID3D12StateObjectProperties); +MAKE_DX_PTR(ID3DBlob); +MAKE_DX_PTR(IDXGIAdapter1); +MAKE_DX_PTR(IDXGIDebug1); +MAKE_DX_PTR(IDXGIFactory4); +MAKE_DX_PTR(IDXGIInfoQueue); +MAKE_DX_PTR(IDXGISwapChain1); +MAKE_DX_PTR(IDXGISwapChain3); + +// Short names for relevant DirectX 12 constants. +// NOTE: A shader record must be aligned on the byte boundary of the size specified here. Within the +// shader record, the arguments must likewise be aligned by their respective size, e.g. a root +// descriptor (or descriptor table handle) must be aligned on an 8-byte boundary. +static const size_t SHADER_ID_SIZE = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; +static const size_t SHADER_RECORD_ALIGNMENT = D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT; +static const size_t SHADER_RECORD_CONSTANT_SIZE = 4; // defined by DirectX 12 +static const size_t SHADER_RECORD_DESCRIPTOR_SIZE = 8; // defined by DirectX 12 + +#if defined(ENABLE_DENOISER) +// NVIDIA Real-time Denoisers (NRD) and NRI libraries. +#include +#include +#include + +// NVIDIA NRI helper interface. +// NOTE: Must be included after NRI, but before NRD integration. +#include +#include +#include + +// NVIDIA NRD integration utility. +// NOTE: NRI must be included before this. +#include +#endif diff --git a/Libraries/Aurora/Source/DirectX/DirectXHeaders/d3dx12.h b/Libraries/Aurora/Source/DirectX/DirectXHeaders/d3dx12.h new file mode 100644 index 0000000..4fe49e0 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/DirectXHeaders/d3dx12.h @@ -0,0 +1,4044 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#ifndef __D3DX12_H__ +#define __D3DX12_H__ + +#include "d3d12.h" + +#if defined(__cplusplus) + +struct CD3DX12_DEFAULT +{ +}; +extern const DECLSPEC_SELECTANY CD3DX12_DEFAULT D3D12_DEFAULT; + +//------------------------------------------------------------------------------------------------ +inline bool operator==(const D3D12_VIEWPORT& l, const D3D12_VIEWPORT& r) noexcept +{ + return l.TopLeftX == r.TopLeftX && l.TopLeftY == r.TopLeftY && l.Width == r.Width && + l.Height == r.Height && l.MinDepth == r.MinDepth && l.MaxDepth == r.MaxDepth; +} + +//------------------------------------------------------------------------------------------------ +inline bool operator!=(const D3D12_VIEWPORT& l, const D3D12_VIEWPORT& r) noexcept +{ + return !(l == r); +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RECT : public D3D12_RECT +{ + CD3DX12_RECT() = default; + explicit CD3DX12_RECT(const D3D12_RECT& o) noexcept : D3D12_RECT(o) {} + explicit CD3DX12_RECT(LONG Left, LONG Top, LONG Right, LONG Bottom) noexcept + { + left = Left; + top = Top; + right = Right; + bottom = Bottom; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_VIEWPORT : public D3D12_VIEWPORT +{ + CD3DX12_VIEWPORT() = default; + explicit CD3DX12_VIEWPORT(const D3D12_VIEWPORT& o) noexcept : D3D12_VIEWPORT(o) {} + explicit CD3DX12_VIEWPORT(FLOAT topLeftX, FLOAT topLeftY, FLOAT width, FLOAT height, + FLOAT minDepth = D3D12_MIN_DEPTH, FLOAT maxDepth = D3D12_MAX_DEPTH) noexcept + { + TopLeftX = topLeftX; + TopLeftY = topLeftY; + Width = width; + Height = height; + MinDepth = minDepth; + MaxDepth = maxDepth; + } + explicit CD3DX12_VIEWPORT(_In_ ID3D12Resource* pResource, UINT mipSlice = 0, + FLOAT topLeftX = 0.0f, FLOAT topLeftY = 0.0f, FLOAT minDepth = D3D12_MIN_DEPTH, + FLOAT maxDepth = D3D12_MAX_DEPTH) noexcept + { + auto Desc = pResource->GetDesc(); + const UINT64 SubresourceWidth = Desc.Width >> mipSlice; + const UINT64 SubresourceHeight = Desc.Height >> mipSlice; + switch (Desc.Dimension) + { + case D3D12_RESOURCE_DIMENSION_BUFFER: + TopLeftX = topLeftX; + TopLeftY = 0.0f; + Width = float(Desc.Width) - topLeftX; + Height = 1.0f; + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE1D: + TopLeftX = topLeftX; + TopLeftY = 0.0f; + Width = (SubresourceWidth ? float(SubresourceWidth) : 1.0f) - topLeftX; + Height = 1.0f; + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE2D: + case D3D12_RESOURCE_DIMENSION_TEXTURE3D: + TopLeftX = topLeftX; + TopLeftY = topLeftY; + Width = (SubresourceWidth ? float(SubresourceWidth) : 1.0f) - topLeftX; + Height = (SubresourceHeight ? float(SubresourceHeight) : 1.0f) - topLeftY; + break; + default: + break; + } + + MinDepth = minDepth; + MaxDepth = maxDepth; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_BOX : public D3D12_BOX +{ + CD3DX12_BOX() = default; + explicit CD3DX12_BOX(const D3D12_BOX& o) noexcept : D3D12_BOX(o) {} + explicit CD3DX12_BOX(LONG Left, LONG Right) noexcept + { + left = static_cast(Left); + top = 0; + front = 0; + right = static_cast(Right); + bottom = 1; + back = 1; + } + explicit CD3DX12_BOX(LONG Left, LONG Top, LONG Right, LONG Bottom) noexcept + { + left = static_cast(Left); + top = static_cast(Top); + front = 0; + right = static_cast(Right); + bottom = static_cast(Bottom); + back = 1; + } + explicit CD3DX12_BOX( + LONG Left, LONG Top, LONG Front, LONG Right, LONG Bottom, LONG Back) noexcept + { + left = static_cast(Left); + top = static_cast(Top); + front = static_cast(Front); + right = static_cast(Right); + bottom = static_cast(Bottom); + back = static_cast(Back); + } +}; +inline bool operator==(const D3D12_BOX& l, const D3D12_BOX& r) noexcept +{ + return l.left == r.left && l.top == r.top && l.front == r.front && l.right == r.right && + l.bottom == r.bottom && l.back == r.back; +} +inline bool operator!=(const D3D12_BOX& l, const D3D12_BOX& r) noexcept +{ + return !(l == r); +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_DEPTH_STENCIL_DESC : public D3D12_DEPTH_STENCIL_DESC +{ + CD3DX12_DEPTH_STENCIL_DESC() = default; + explicit CD3DX12_DEPTH_STENCIL_DESC(const D3D12_DEPTH_STENCIL_DESC& o) noexcept : + D3D12_DEPTH_STENCIL_DESC(o) + { + } + explicit CD3DX12_DEPTH_STENCIL_DESC(CD3DX12_DEFAULT) noexcept + { + DepthEnable = TRUE; + DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + DepthFunc = D3D12_COMPARISON_FUNC_LESS; + StencilEnable = FALSE; + StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; + StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; + const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = { D3D12_STENCIL_OP_KEEP, + D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS }; + FrontFace = defaultStencilOp; + BackFace = defaultStencilOp; + } + explicit CD3DX12_DEPTH_STENCIL_DESC(BOOL depthEnable, D3D12_DEPTH_WRITE_MASK depthWriteMask, + D3D12_COMPARISON_FUNC depthFunc, BOOL stencilEnable, UINT8 stencilReadMask, + UINT8 stencilWriteMask, D3D12_STENCIL_OP frontStencilFailOp, + D3D12_STENCIL_OP frontStencilDepthFailOp, D3D12_STENCIL_OP frontStencilPassOp, + D3D12_COMPARISON_FUNC frontStencilFunc, D3D12_STENCIL_OP backStencilFailOp, + D3D12_STENCIL_OP backStencilDepthFailOp, D3D12_STENCIL_OP backStencilPassOp, + D3D12_COMPARISON_FUNC backStencilFunc) noexcept + { + DepthEnable = depthEnable; + DepthWriteMask = depthWriteMask; + DepthFunc = depthFunc; + StencilEnable = stencilEnable; + StencilReadMask = stencilReadMask; + StencilWriteMask = stencilWriteMask; + FrontFace.StencilFailOp = frontStencilFailOp; + FrontFace.StencilDepthFailOp = frontStencilDepthFailOp; + FrontFace.StencilPassOp = frontStencilPassOp; + FrontFace.StencilFunc = frontStencilFunc; + BackFace.StencilFailOp = backStencilFailOp; + BackFace.StencilDepthFailOp = backStencilDepthFailOp; + BackFace.StencilPassOp = backStencilPassOp; + BackFace.StencilFunc = backStencilFunc; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_DEPTH_STENCIL_DESC1 : public D3D12_DEPTH_STENCIL_DESC1 +{ + CD3DX12_DEPTH_STENCIL_DESC1() = default; + explicit CD3DX12_DEPTH_STENCIL_DESC1(const D3D12_DEPTH_STENCIL_DESC1& o) noexcept : + D3D12_DEPTH_STENCIL_DESC1(o) + { + } + explicit CD3DX12_DEPTH_STENCIL_DESC1(const D3D12_DEPTH_STENCIL_DESC& o) noexcept + { + DepthEnable = o.DepthEnable; + DepthWriteMask = o.DepthWriteMask; + DepthFunc = o.DepthFunc; + StencilEnable = o.StencilEnable; + StencilReadMask = o.StencilReadMask; + StencilWriteMask = o.StencilWriteMask; + FrontFace.StencilFailOp = o.FrontFace.StencilFailOp; + FrontFace.StencilDepthFailOp = o.FrontFace.StencilDepthFailOp; + FrontFace.StencilPassOp = o.FrontFace.StencilPassOp; + FrontFace.StencilFunc = o.FrontFace.StencilFunc; + BackFace.StencilFailOp = o.BackFace.StencilFailOp; + BackFace.StencilDepthFailOp = o.BackFace.StencilDepthFailOp; + BackFace.StencilPassOp = o.BackFace.StencilPassOp; + BackFace.StencilFunc = o.BackFace.StencilFunc; + DepthBoundsTestEnable = FALSE; + } + explicit CD3DX12_DEPTH_STENCIL_DESC1(CD3DX12_DEFAULT) noexcept + { + DepthEnable = TRUE; + DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + DepthFunc = D3D12_COMPARISON_FUNC_LESS; + StencilEnable = FALSE; + StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; + StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; + const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = { D3D12_STENCIL_OP_KEEP, + D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS }; + FrontFace = defaultStencilOp; + BackFace = defaultStencilOp; + DepthBoundsTestEnable = FALSE; + } + explicit CD3DX12_DEPTH_STENCIL_DESC1(BOOL depthEnable, D3D12_DEPTH_WRITE_MASK depthWriteMask, + D3D12_COMPARISON_FUNC depthFunc, BOOL stencilEnable, UINT8 stencilReadMask, + UINT8 stencilWriteMask, D3D12_STENCIL_OP frontStencilFailOp, + D3D12_STENCIL_OP frontStencilDepthFailOp, D3D12_STENCIL_OP frontStencilPassOp, + D3D12_COMPARISON_FUNC frontStencilFunc, D3D12_STENCIL_OP backStencilFailOp, + D3D12_STENCIL_OP backStencilDepthFailOp, D3D12_STENCIL_OP backStencilPassOp, + D3D12_COMPARISON_FUNC backStencilFunc, BOOL depthBoundsTestEnable) noexcept + { + DepthEnable = depthEnable; + DepthWriteMask = depthWriteMask; + DepthFunc = depthFunc; + StencilEnable = stencilEnable; + StencilReadMask = stencilReadMask; + StencilWriteMask = stencilWriteMask; + FrontFace.StencilFailOp = frontStencilFailOp; + FrontFace.StencilDepthFailOp = frontStencilDepthFailOp; + FrontFace.StencilPassOp = frontStencilPassOp; + FrontFace.StencilFunc = frontStencilFunc; + BackFace.StencilFailOp = backStencilFailOp; + BackFace.StencilDepthFailOp = backStencilDepthFailOp; + BackFace.StencilPassOp = backStencilPassOp; + BackFace.StencilFunc = backStencilFunc; + DepthBoundsTestEnable = depthBoundsTestEnable; + } + operator D3D12_DEPTH_STENCIL_DESC() const noexcept + { + D3D12_DEPTH_STENCIL_DESC D; + D.DepthEnable = DepthEnable; + D.DepthWriteMask = DepthWriteMask; + D.DepthFunc = DepthFunc; + D.StencilEnable = StencilEnable; + D.StencilReadMask = StencilReadMask; + D.StencilWriteMask = StencilWriteMask; + D.FrontFace.StencilFailOp = FrontFace.StencilFailOp; + D.FrontFace.StencilDepthFailOp = FrontFace.StencilDepthFailOp; + D.FrontFace.StencilPassOp = FrontFace.StencilPassOp; + D.FrontFace.StencilFunc = FrontFace.StencilFunc; + D.BackFace.StencilFailOp = BackFace.StencilFailOp; + D.BackFace.StencilDepthFailOp = BackFace.StencilDepthFailOp; + D.BackFace.StencilPassOp = BackFace.StencilPassOp; + D.BackFace.StencilFunc = BackFace.StencilFunc; + return D; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_BLEND_DESC : public D3D12_BLEND_DESC +{ + CD3DX12_BLEND_DESC() = default; + explicit CD3DX12_BLEND_DESC(const D3D12_BLEND_DESC& o) noexcept : D3D12_BLEND_DESC(o) {} + explicit CD3DX12_BLEND_DESC(CD3DX12_DEFAULT) noexcept + { + AlphaToCoverageEnable = FALSE; + IndependentBlendEnable = FALSE; + const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = { + 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, + }; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) + RenderTarget[i] = defaultRenderTargetBlendDesc; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RASTERIZER_DESC : public D3D12_RASTERIZER_DESC +{ + CD3DX12_RASTERIZER_DESC() = default; + explicit CD3DX12_RASTERIZER_DESC(const D3D12_RASTERIZER_DESC& o) noexcept : + D3D12_RASTERIZER_DESC(o) + { + } + explicit CD3DX12_RASTERIZER_DESC(CD3DX12_DEFAULT) noexcept + { + FillMode = D3D12_FILL_MODE_SOLID; + CullMode = D3D12_CULL_MODE_BACK; + FrontCounterClockwise = FALSE; + DepthBias = D3D12_DEFAULT_DEPTH_BIAS; + DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; + SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; + DepthClipEnable = TRUE; + MultisampleEnable = FALSE; + AntialiasedLineEnable = FALSE; + ForcedSampleCount = 0; + ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; + } + explicit CD3DX12_RASTERIZER_DESC(D3D12_FILL_MODE fillMode, D3D12_CULL_MODE cullMode, + BOOL frontCounterClockwise, INT depthBias, FLOAT depthBiasClamp, FLOAT slopeScaledDepthBias, + BOOL depthClipEnable, BOOL multisampleEnable, BOOL antialiasedLineEnable, + UINT forcedSampleCount, D3D12_CONSERVATIVE_RASTERIZATION_MODE conservativeRaster) noexcept + { + FillMode = fillMode; + CullMode = cullMode; + FrontCounterClockwise = frontCounterClockwise; + DepthBias = depthBias; + DepthBiasClamp = depthBiasClamp; + SlopeScaledDepthBias = slopeScaledDepthBias; + DepthClipEnable = depthClipEnable; + MultisampleEnable = multisampleEnable; + AntialiasedLineEnable = antialiasedLineEnable; + ForcedSampleCount = forcedSampleCount; + ConservativeRaster = conservativeRaster; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RESOURCE_ALLOCATION_INFO : public D3D12_RESOURCE_ALLOCATION_INFO +{ + CD3DX12_RESOURCE_ALLOCATION_INFO() = default; + explicit CD3DX12_RESOURCE_ALLOCATION_INFO(const D3D12_RESOURCE_ALLOCATION_INFO& o) noexcept : + D3D12_RESOURCE_ALLOCATION_INFO(o) + { + } + CD3DX12_RESOURCE_ALLOCATION_INFO(UINT64 size, UINT64 alignment) noexcept + { + SizeInBytes = size; + Alignment = alignment; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_HEAP_PROPERTIES : public D3D12_HEAP_PROPERTIES +{ + CD3DX12_HEAP_PROPERTIES() = default; + explicit CD3DX12_HEAP_PROPERTIES(const D3D12_HEAP_PROPERTIES& o) noexcept : + D3D12_HEAP_PROPERTIES(o) + { + } + CD3DX12_HEAP_PROPERTIES(D3D12_CPU_PAGE_PROPERTY cpuPageProperty, + D3D12_MEMORY_POOL memoryPoolPreference, UINT creationNodeMask = 1, UINT nodeMask = 1) + noexcept + { + Type = D3D12_HEAP_TYPE_CUSTOM; + CPUPageProperty = cpuPageProperty; + MemoryPoolPreference = memoryPoolPreference; + CreationNodeMask = creationNodeMask; + VisibleNodeMask = nodeMask; + } + explicit CD3DX12_HEAP_PROPERTIES( + D3D12_HEAP_TYPE type, UINT creationNodeMask = 1, UINT nodeMask = 1) noexcept + { + Type = type; + CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + CreationNodeMask = creationNodeMask; + VisibleNodeMask = nodeMask; + } + bool IsCPUAccessible() const noexcept + { + return Type == D3D12_HEAP_TYPE_UPLOAD || Type == D3D12_HEAP_TYPE_READBACK || + (Type == D3D12_HEAP_TYPE_CUSTOM && + (CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE || + CPUPageProperty == D3D12_CPU_PAGE_PROPERTY_WRITE_BACK)); + } +}; +inline bool operator==(const D3D12_HEAP_PROPERTIES& l, const D3D12_HEAP_PROPERTIES& r) noexcept +{ + return l.Type == r.Type && l.CPUPageProperty == r.CPUPageProperty && + l.MemoryPoolPreference == r.MemoryPoolPreference && + l.CreationNodeMask == r.CreationNodeMask && l.VisibleNodeMask == r.VisibleNodeMask; +} +inline bool operator!=(const D3D12_HEAP_PROPERTIES& l, const D3D12_HEAP_PROPERTIES& r) noexcept +{ + return !(l == r); +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_HEAP_DESC : public D3D12_HEAP_DESC +{ + CD3DX12_HEAP_DESC() = default; + explicit CD3DX12_HEAP_DESC(const D3D12_HEAP_DESC& o) noexcept : D3D12_HEAP_DESC(o) {} + CD3DX12_HEAP_DESC(UINT64 size, D3D12_HEAP_PROPERTIES properties, UINT64 alignment = 0, + D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_NONE) + noexcept + { + SizeInBytes = size; + Properties = properties; + Alignment = alignment; + Flags = flags; + } + CD3DX12_HEAP_DESC(UINT64 size, D3D12_HEAP_TYPE type, UINT64 alignment = 0, + D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_NONE) + noexcept + { + SizeInBytes = size; + Properties = CD3DX12_HEAP_PROPERTIES(type); + Alignment = alignment; + Flags = flags; + } + CD3DX12_HEAP_DESC(UINT64 size, D3D12_CPU_PAGE_PROPERTY cpuPageProperty, + D3D12_MEMORY_POOL memoryPoolPreference, UINT64 alignment = 0, + D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_NONE) + noexcept + { + SizeInBytes = size; + Properties = CD3DX12_HEAP_PROPERTIES(cpuPageProperty, memoryPoolPreference); + Alignment = alignment; + Flags = flags; + } + CD3DX12_HEAP_DESC(const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_HEAP_PROPERTIES properties, D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_NONE) + noexcept + { + SizeInBytes = resAllocInfo.SizeInBytes; + Properties = properties; + Alignment = resAllocInfo.Alignment; + Flags = flags; + } + CD3DX12_HEAP_DESC(const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, D3D12_HEAP_TYPE type, + D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_NONE) + noexcept + { + SizeInBytes = resAllocInfo.SizeInBytes; + Properties = CD3DX12_HEAP_PROPERTIES(type); + Alignment = resAllocInfo.Alignment; + Flags = flags; + } + CD3DX12_HEAP_DESC(const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_CPU_PAGE_PROPERTY cpuPageProperty, D3D12_MEMORY_POOL memoryPoolPreference, + D3D12_HEAP_FLAGS flags = D3D12_HEAP_FLAG_NONE) + noexcept + { + SizeInBytes = resAllocInfo.SizeInBytes; + Properties = CD3DX12_HEAP_PROPERTIES(cpuPageProperty, memoryPoolPreference); + Alignment = resAllocInfo.Alignment; + Flags = flags; + } + bool IsCPUAccessible() const noexcept + { + return static_cast(&Properties)->IsCPUAccessible(); + } +}; +inline bool operator==(const D3D12_HEAP_DESC& l, const D3D12_HEAP_DESC& r) noexcept +{ + return l.SizeInBytes == r.SizeInBytes && l.Properties == r.Properties && + l.Alignment == r.Alignment && l.Flags == r.Flags; +} +inline bool operator!=(const D3D12_HEAP_DESC& l, const D3D12_HEAP_DESC& r) noexcept +{ + return !(l == r); +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_CLEAR_VALUE : public D3D12_CLEAR_VALUE +{ + CD3DX12_CLEAR_VALUE() = default; + explicit CD3DX12_CLEAR_VALUE(const D3D12_CLEAR_VALUE& o) noexcept : D3D12_CLEAR_VALUE(o) {} + CD3DX12_CLEAR_VALUE(DXGI_FORMAT format, const FLOAT color[4]) noexcept + { + Format = format; + memcpy(Color, color, sizeof(Color)); + } + CD3DX12_CLEAR_VALUE(DXGI_FORMAT format, FLOAT depth, UINT8 stencil) noexcept + { + Format = format; + memset(&Color, 0, sizeof(Color)); + /* Use memcpy to preserve NAN values */ + memcpy(&DepthStencil.Depth, &depth, sizeof(depth)); + DepthStencil.Stencil = stencil; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RANGE : public D3D12_RANGE +{ + CD3DX12_RANGE() = default; + explicit CD3DX12_RANGE(const D3D12_RANGE& o) noexcept : D3D12_RANGE(o) {} + CD3DX12_RANGE(SIZE_T begin, SIZE_T end) noexcept + { + Begin = begin; + End = end; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RANGE_UINT64 : public D3D12_RANGE_UINT64 +{ + CD3DX12_RANGE_UINT64() = default; + explicit CD3DX12_RANGE_UINT64(const D3D12_RANGE_UINT64& o) noexcept : D3D12_RANGE_UINT64(o) {} + CD3DX12_RANGE_UINT64(UINT64 begin, UINT64 end) noexcept + { + Begin = begin; + End = end; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_SUBRESOURCE_RANGE_UINT64 : public D3D12_SUBRESOURCE_RANGE_UINT64 +{ + CD3DX12_SUBRESOURCE_RANGE_UINT64() = default; + explicit CD3DX12_SUBRESOURCE_RANGE_UINT64(const D3D12_SUBRESOURCE_RANGE_UINT64& o) noexcept : + D3D12_SUBRESOURCE_RANGE_UINT64(o) + { + } + CD3DX12_SUBRESOURCE_RANGE_UINT64(UINT subresource, const D3D12_RANGE_UINT64& range) noexcept + { + Subresource = subresource; + Range = range; + } + CD3DX12_SUBRESOURCE_RANGE_UINT64(UINT subresource, UINT64 begin, UINT64 end) noexcept + { + Subresource = subresource; + Range.Begin = begin; + Range.End = end; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_SHADER_BYTECODE : public D3D12_SHADER_BYTECODE +{ + CD3DX12_SHADER_BYTECODE() = default; + explicit CD3DX12_SHADER_BYTECODE(const D3D12_SHADER_BYTECODE& o) noexcept : + D3D12_SHADER_BYTECODE(o) + { + } + CD3DX12_SHADER_BYTECODE(_In_ ID3DBlob* pShaderBlob) noexcept + { + pShaderBytecode = pShaderBlob->GetBufferPointer(); + BytecodeLength = pShaderBlob->GetBufferSize(); + } + CD3DX12_SHADER_BYTECODE(const void* _pShaderBytecode, SIZE_T bytecodeLength) noexcept + { + pShaderBytecode = _pShaderBytecode; + BytecodeLength = bytecodeLength; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_TILED_RESOURCE_COORDINATE : public D3D12_TILED_RESOURCE_COORDINATE +{ + CD3DX12_TILED_RESOURCE_COORDINATE() = default; + explicit CD3DX12_TILED_RESOURCE_COORDINATE(const D3D12_TILED_RESOURCE_COORDINATE& o) noexcept : + D3D12_TILED_RESOURCE_COORDINATE(o) + { + } + CD3DX12_TILED_RESOURCE_COORDINATE(UINT x, UINT y, UINT z, UINT subresource) noexcept + { + X = x; + Y = y; + Z = z; + Subresource = subresource; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_TILE_REGION_SIZE : public D3D12_TILE_REGION_SIZE +{ + CD3DX12_TILE_REGION_SIZE() = default; + explicit CD3DX12_TILE_REGION_SIZE(const D3D12_TILE_REGION_SIZE& o) noexcept : + D3D12_TILE_REGION_SIZE(o) + { + } + CD3DX12_TILE_REGION_SIZE(UINT numTiles, BOOL useBox, UINT width, UINT16 height, UINT16 depth) + noexcept + { + NumTiles = numTiles; + UseBox = useBox; + Width = width; + Height = height; + Depth = depth; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_SUBRESOURCE_TILING : public D3D12_SUBRESOURCE_TILING +{ + CD3DX12_SUBRESOURCE_TILING() = default; + explicit CD3DX12_SUBRESOURCE_TILING(const D3D12_SUBRESOURCE_TILING& o) noexcept : + D3D12_SUBRESOURCE_TILING(o) + { + } + CD3DX12_SUBRESOURCE_TILING(UINT widthInTiles, UINT16 heightInTiles, UINT16 depthInTiles, + UINT startTileIndexInOverallResource) + noexcept + { + WidthInTiles = widthInTiles; + HeightInTiles = heightInTiles; + DepthInTiles = depthInTiles; + StartTileIndexInOverallResource = startTileIndexInOverallResource; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_TILE_SHAPE : public D3D12_TILE_SHAPE +{ + CD3DX12_TILE_SHAPE() = default; + explicit CD3DX12_TILE_SHAPE(const D3D12_TILE_SHAPE& o) noexcept : D3D12_TILE_SHAPE(o) {} + CD3DX12_TILE_SHAPE(UINT widthInTexels, UINT heightInTexels, UINT depthInTexels) noexcept + { + WidthInTexels = widthInTexels; + HeightInTexels = heightInTexels; + DepthInTexels = depthInTexels; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RESOURCE_BARRIER : public D3D12_RESOURCE_BARRIER +{ + CD3DX12_RESOURCE_BARRIER() = default; + explicit CD3DX12_RESOURCE_BARRIER(const D3D12_RESOURCE_BARRIER& o) noexcept : + D3D12_RESOURCE_BARRIER(o) + { + } + static inline CD3DX12_RESOURCE_BARRIER Transition(_In_ ID3D12Resource* pResource, + D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter, + UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + D3D12_RESOURCE_BARRIER_FLAGS flags = D3D12_RESOURCE_BARRIER_FLAG_NONE) noexcept + { + CD3DX12_RESOURCE_BARRIER result = {}; + D3D12_RESOURCE_BARRIER& barrier = result; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + result.Flags = flags; + barrier.Transition.pResource = pResource; + barrier.Transition.StateBefore = stateBefore; + barrier.Transition.StateAfter = stateAfter; + barrier.Transition.Subresource = subresource; + return result; + } + static inline CD3DX12_RESOURCE_BARRIER Aliasing( + _In_ ID3D12Resource* pResourceBefore, _In_ ID3D12Resource* pResourceAfter) noexcept + { + CD3DX12_RESOURCE_BARRIER result = {}; + D3D12_RESOURCE_BARRIER& barrier = result; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_ALIASING; + barrier.Aliasing.pResourceBefore = pResourceBefore; + barrier.Aliasing.pResourceAfter = pResourceAfter; + return result; + } + static inline CD3DX12_RESOURCE_BARRIER UAV(_In_ ID3D12Resource* pResource) noexcept + { + CD3DX12_RESOURCE_BARRIER result = {}; + D3D12_RESOURCE_BARRIER& barrier = result; + result.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.UAV.pResource = pResource; + return result; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_PACKED_MIP_INFO : public D3D12_PACKED_MIP_INFO +{ + CD3DX12_PACKED_MIP_INFO() = default; + explicit CD3DX12_PACKED_MIP_INFO(const D3D12_PACKED_MIP_INFO& o) noexcept : + D3D12_PACKED_MIP_INFO(o) + { + } + CD3DX12_PACKED_MIP_INFO(UINT8 numStandardMips, UINT8 numPackedMips, UINT numTilesForPackedMips, + UINT startTileIndexInOverallResource) + noexcept + { + NumStandardMips = numStandardMips; + NumPackedMips = numPackedMips; + NumTilesForPackedMips = numTilesForPackedMips; + StartTileIndexInOverallResource = startTileIndexInOverallResource; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_SUBRESOURCE_FOOTPRINT : public D3D12_SUBRESOURCE_FOOTPRINT +{ + CD3DX12_SUBRESOURCE_FOOTPRINT() = default; + explicit CD3DX12_SUBRESOURCE_FOOTPRINT(const D3D12_SUBRESOURCE_FOOTPRINT& o) noexcept : + D3D12_SUBRESOURCE_FOOTPRINT(o) + { + } + CD3DX12_SUBRESOURCE_FOOTPRINT( + DXGI_FORMAT format, UINT width, UINT height, UINT depth, UINT rowPitch) + noexcept + { + Format = format; + Width = width; + Height = height; + Depth = depth; + RowPitch = rowPitch; + } + explicit CD3DX12_SUBRESOURCE_FOOTPRINT( + const D3D12_RESOURCE_DESC& resDesc, UINT rowPitch) noexcept + { + Format = resDesc.Format; + Width = UINT(resDesc.Width); + Height = resDesc.Height; + Depth = (resDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? resDesc.DepthOrArraySize + : 1); + RowPitch = rowPitch; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_TEXTURE_COPY_LOCATION : public D3D12_TEXTURE_COPY_LOCATION +{ + CD3DX12_TEXTURE_COPY_LOCATION() = default; + explicit CD3DX12_TEXTURE_COPY_LOCATION(const D3D12_TEXTURE_COPY_LOCATION& o) noexcept : + D3D12_TEXTURE_COPY_LOCATION(o) + { + } + CD3DX12_TEXTURE_COPY_LOCATION(_In_ ID3D12Resource* pRes) noexcept + { + pResource = pRes; + Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + PlacedFootprint = {}; + } + CD3DX12_TEXTURE_COPY_LOCATION( + _In_ ID3D12Resource* pRes, D3D12_PLACED_SUBRESOURCE_FOOTPRINT const& Footprint) + noexcept + { + pResource = pRes; + Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + PlacedFootprint = Footprint; + } + CD3DX12_TEXTURE_COPY_LOCATION(_In_ ID3D12Resource* pRes, UINT Sub) noexcept + { + pResource = pRes; + Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + PlacedFootprint = {}; + SubresourceIndex = Sub; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_DESCRIPTOR_RANGE : public D3D12_DESCRIPTOR_RANGE +{ + CD3DX12_DESCRIPTOR_RANGE() = default; + explicit CD3DX12_DESCRIPTOR_RANGE(const D3D12_DESCRIPTOR_RANGE& o) noexcept : + D3D12_DESCRIPTOR_RANGE(o) + { + } + CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, + UINT baseShaderRegister, UINT registerSpace = 0, + UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) + noexcept + { + Init(rangeType, numDescriptors, baseShaderRegister, registerSpace, + offsetInDescriptorsFromTableStart); + } + + inline void Init(D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, + UINT baseShaderRegister, UINT registerSpace = 0, + UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) noexcept + { + Init(*this, rangeType, numDescriptors, baseShaderRegister, registerSpace, + offsetInDescriptorsFromTableStart); + } + + static inline void Init(_Out_ D3D12_DESCRIPTOR_RANGE& range, + D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, UINT baseShaderRegister, + UINT registerSpace = 0, + UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) noexcept + { + range.RangeType = rangeType; + range.NumDescriptors = numDescriptors; + range.BaseShaderRegister = baseShaderRegister; + range.RegisterSpace = registerSpace; + range.OffsetInDescriptorsFromTableStart = offsetInDescriptorsFromTableStart; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_DESCRIPTOR_TABLE : public D3D12_ROOT_DESCRIPTOR_TABLE +{ + CD3DX12_ROOT_DESCRIPTOR_TABLE() = default; + explicit CD3DX12_ROOT_DESCRIPTOR_TABLE(const D3D12_ROOT_DESCRIPTOR_TABLE& o) noexcept : + D3D12_ROOT_DESCRIPTOR_TABLE(o) + { + } + CD3DX12_ROOT_DESCRIPTOR_TABLE(UINT numDescriptorRanges, + _In_reads_opt_(numDescriptorRanges) const D3D12_DESCRIPTOR_RANGE* _pDescriptorRanges) + noexcept + { + Init(numDescriptorRanges, _pDescriptorRanges); + } + + inline void Init(UINT numDescriptorRanges, + _In_reads_opt_(numDescriptorRanges) + const D3D12_DESCRIPTOR_RANGE* _pDescriptorRanges) noexcept + { + Init(*this, numDescriptorRanges, _pDescriptorRanges); + } + + static inline void Init(_Out_ D3D12_ROOT_DESCRIPTOR_TABLE& rootDescriptorTable, + UINT numDescriptorRanges, + _In_reads_opt_(numDescriptorRanges) + const D3D12_DESCRIPTOR_RANGE* _pDescriptorRanges) noexcept + { + rootDescriptorTable.NumDescriptorRanges = numDescriptorRanges; + rootDescriptorTable.pDescriptorRanges = _pDescriptorRanges; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_CONSTANTS : public D3D12_ROOT_CONSTANTS +{ + CD3DX12_ROOT_CONSTANTS() = default; + explicit CD3DX12_ROOT_CONSTANTS(const D3D12_ROOT_CONSTANTS& o) noexcept : + D3D12_ROOT_CONSTANTS(o) + { + } + CD3DX12_ROOT_CONSTANTS(UINT num32BitValues, UINT shaderRegister, UINT registerSpace = 0) + noexcept + { + Init(num32BitValues, shaderRegister, registerSpace); + } + + inline void Init(UINT num32BitValues, UINT shaderRegister, UINT registerSpace = 0) noexcept + { + Init(*this, num32BitValues, shaderRegister, registerSpace); + } + + static inline void Init(_Out_ D3D12_ROOT_CONSTANTS& rootConstants, UINT num32BitValues, + UINT shaderRegister, UINT registerSpace = 0) noexcept + { + rootConstants.Num32BitValues = num32BitValues; + rootConstants.ShaderRegister = shaderRegister; + rootConstants.RegisterSpace = registerSpace; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_DESCRIPTOR : public D3D12_ROOT_DESCRIPTOR +{ + CD3DX12_ROOT_DESCRIPTOR() = default; + explicit CD3DX12_ROOT_DESCRIPTOR(const D3D12_ROOT_DESCRIPTOR& o) noexcept : + D3D12_ROOT_DESCRIPTOR(o) + { + } + CD3DX12_ROOT_DESCRIPTOR(UINT shaderRegister, UINT registerSpace = 0) noexcept + { + Init(shaderRegister, registerSpace); + } + + inline void Init(UINT shaderRegister, UINT registerSpace = 0) noexcept + { + Init(*this, shaderRegister, registerSpace); + } + + static inline void Init( + _Out_ D3D12_ROOT_DESCRIPTOR& table, UINT shaderRegister, UINT registerSpace = 0) noexcept + { + table.ShaderRegister = shaderRegister; + table.RegisterSpace = registerSpace; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_PARAMETER : public D3D12_ROOT_PARAMETER +{ + CD3DX12_ROOT_PARAMETER() = default; + explicit CD3DX12_ROOT_PARAMETER(const D3D12_ROOT_PARAMETER& o) noexcept : + D3D12_ROOT_PARAMETER(o) + { + } + + static inline void InitAsDescriptorTable(_Out_ D3D12_ROOT_PARAMETER& rootParam, + UINT numDescriptorRanges, + _In_reads_(numDescriptorRanges) const D3D12_DESCRIPTOR_RANGE* pDescriptorRanges, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR_TABLE::Init( + rootParam.DescriptorTable, numDescriptorRanges, pDescriptorRanges); + } + + static inline void InitAsConstants(_Out_ D3D12_ROOT_PARAMETER& rootParam, UINT num32BitValues, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_CONSTANTS::Init( + rootParam.Constants, num32BitValues, shaderRegister, registerSpace); + } + + static inline void InitAsConstantBufferView(_Out_ D3D12_ROOT_PARAMETER& rootParam, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR::Init(rootParam.Descriptor, shaderRegister, registerSpace); + } + + static inline void InitAsShaderResourceView(_Out_ D3D12_ROOT_PARAMETER& rootParam, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR::Init(rootParam.Descriptor, shaderRegister, registerSpace); + } + + static inline void InitAsUnorderedAccessView(_Out_ D3D12_ROOT_PARAMETER& rootParam, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR::Init(rootParam.Descriptor, shaderRegister, registerSpace); + } + + inline void InitAsDescriptorTable(UINT numDescriptorRanges, + _In_reads_(numDescriptorRanges) const D3D12_DESCRIPTOR_RANGE* pDescriptorRanges, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsDescriptorTable(*this, numDescriptorRanges, pDescriptorRanges, visibility); + } + + inline void InitAsConstants(UINT num32BitValues, UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsConstants(*this, num32BitValues, shaderRegister, registerSpace, visibility); + } + + inline void InitAsConstantBufferView(UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsConstantBufferView(*this, shaderRegister, registerSpace, visibility); + } + + inline void InitAsShaderResourceView(UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsShaderResourceView(*this, shaderRegister, registerSpace, visibility); + } + + inline void InitAsUnorderedAccessView(UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsUnorderedAccessView(*this, shaderRegister, registerSpace, visibility); + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_STATIC_SAMPLER_DESC : public D3D12_STATIC_SAMPLER_DESC +{ + CD3DX12_STATIC_SAMPLER_DESC() = default; + explicit CD3DX12_STATIC_SAMPLER_DESC(const D3D12_STATIC_SAMPLER_DESC& o) noexcept : + D3D12_STATIC_SAMPLER_DESC(o) + { + } + CD3DX12_STATIC_SAMPLER_DESC(UINT shaderRegister, D3D12_FILTER filter = D3D12_FILTER_ANISOTROPIC, + D3D12_TEXTURE_ADDRESS_MODE addressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE addressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE addressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP, FLOAT mipLODBias = 0, + UINT maxAnisotropy = 16, + D3D12_COMPARISON_FUNC comparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL, + D3D12_STATIC_BORDER_COLOR borderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE, + FLOAT minLOD = 0.f, FLOAT maxLOD = D3D12_FLOAT32_MAX, + D3D12_SHADER_VISIBILITY shaderVisibility = D3D12_SHADER_VISIBILITY_ALL, + UINT registerSpace = 0) + noexcept + { + Init(shaderRegister, filter, addressU, addressV, addressW, mipLODBias, maxAnisotropy, + comparisonFunc, borderColor, minLOD, maxLOD, shaderVisibility, registerSpace); + } + + static inline void Init(_Out_ D3D12_STATIC_SAMPLER_DESC& samplerDesc, UINT shaderRegister, + D3D12_FILTER filter = D3D12_FILTER_ANISOTROPIC, + D3D12_TEXTURE_ADDRESS_MODE addressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE addressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE addressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP, FLOAT mipLODBias = 0, + UINT maxAnisotropy = 16, + D3D12_COMPARISON_FUNC comparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL, + D3D12_STATIC_BORDER_COLOR borderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE, + FLOAT minLOD = 0.f, FLOAT maxLOD = D3D12_FLOAT32_MAX, + D3D12_SHADER_VISIBILITY shaderVisibility = D3D12_SHADER_VISIBILITY_ALL, + UINT registerSpace = 0) noexcept + { + samplerDesc.ShaderRegister = shaderRegister; + samplerDesc.Filter = filter; + samplerDesc.AddressU = addressU; + samplerDesc.AddressV = addressV; + samplerDesc.AddressW = addressW; + samplerDesc.MipLODBias = mipLODBias; + samplerDesc.MaxAnisotropy = maxAnisotropy; + samplerDesc.ComparisonFunc = comparisonFunc; + samplerDesc.BorderColor = borderColor; + samplerDesc.MinLOD = minLOD; + samplerDesc.MaxLOD = maxLOD; + samplerDesc.ShaderVisibility = shaderVisibility; + samplerDesc.RegisterSpace = registerSpace; + } + inline void Init(UINT shaderRegister, D3D12_FILTER filter = D3D12_FILTER_ANISOTROPIC, + D3D12_TEXTURE_ADDRESS_MODE addressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE addressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP, + D3D12_TEXTURE_ADDRESS_MODE addressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP, FLOAT mipLODBias = 0, + UINT maxAnisotropy = 16, + D3D12_COMPARISON_FUNC comparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL, + D3D12_STATIC_BORDER_COLOR borderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_WHITE, + FLOAT minLOD = 0.f, FLOAT maxLOD = D3D12_FLOAT32_MAX, + D3D12_SHADER_VISIBILITY shaderVisibility = D3D12_SHADER_VISIBILITY_ALL, + UINT registerSpace = 0) noexcept + { + Init(*this, shaderRegister, filter, addressU, addressV, addressW, mipLODBias, maxAnisotropy, + comparisonFunc, borderColor, minLOD, maxLOD, shaderVisibility, registerSpace); + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_SIGNATURE_DESC : public D3D12_ROOT_SIGNATURE_DESC +{ + CD3DX12_ROOT_SIGNATURE_DESC() = default; + explicit CD3DX12_ROOT_SIGNATURE_DESC(const D3D12_ROOT_SIGNATURE_DESC& o) noexcept : + D3D12_ROOT_SIGNATURE_DESC(o) + { + } + CD3DX12_ROOT_SIGNATURE_DESC(UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) + noexcept + { + Init(numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags); + } + CD3DX12_ROOT_SIGNATURE_DESC(CD3DX12_DEFAULT) noexcept + { + Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_NONE); + } + + inline void Init(UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) noexcept + { + Init(*this, numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags); + } + + static inline void Init(_Out_ D3D12_ROOT_SIGNATURE_DESC& desc, UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) noexcept + { + desc.NumParameters = numParameters; + desc.pParameters = _pParameters; + desc.NumStaticSamplers = numStaticSamplers; + desc.pStaticSamplers = _pStaticSamplers; + desc.Flags = flags; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_DESCRIPTOR_RANGE1 : public D3D12_DESCRIPTOR_RANGE1 +{ + CD3DX12_DESCRIPTOR_RANGE1() = default; + explicit CD3DX12_DESCRIPTOR_RANGE1(const D3D12_DESCRIPTOR_RANGE1& o) noexcept : + D3D12_DESCRIPTOR_RANGE1(o) + { + } + CD3DX12_DESCRIPTOR_RANGE1(D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, + UINT baseShaderRegister, UINT registerSpace = 0, + D3D12_DESCRIPTOR_RANGE_FLAGS flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE, + UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) + noexcept + { + Init(rangeType, numDescriptors, baseShaderRegister, registerSpace, flags, + offsetInDescriptorsFromTableStart); + } + + inline void Init(D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, + UINT baseShaderRegister, UINT registerSpace = 0, + D3D12_DESCRIPTOR_RANGE_FLAGS flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE, + UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) noexcept + { + Init(*this, rangeType, numDescriptors, baseShaderRegister, registerSpace, flags, + offsetInDescriptorsFromTableStart); + } + + static inline void Init(_Out_ D3D12_DESCRIPTOR_RANGE1& range, + D3D12_DESCRIPTOR_RANGE_TYPE rangeType, UINT numDescriptors, UINT baseShaderRegister, + UINT registerSpace = 0, + D3D12_DESCRIPTOR_RANGE_FLAGS flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE, + UINT offsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) noexcept + { + range.RangeType = rangeType; + range.NumDescriptors = numDescriptors; + range.BaseShaderRegister = baseShaderRegister; + range.RegisterSpace = registerSpace; + range.Flags = flags; + range.OffsetInDescriptorsFromTableStart = offsetInDescriptorsFromTableStart; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_DESCRIPTOR_TABLE1 : public D3D12_ROOT_DESCRIPTOR_TABLE1 +{ + CD3DX12_ROOT_DESCRIPTOR_TABLE1() = default; + explicit CD3DX12_ROOT_DESCRIPTOR_TABLE1(const D3D12_ROOT_DESCRIPTOR_TABLE1& o) noexcept : + D3D12_ROOT_DESCRIPTOR_TABLE1(o) + { + } + CD3DX12_ROOT_DESCRIPTOR_TABLE1(UINT numDescriptorRanges, + _In_reads_opt_(numDescriptorRanges) const D3D12_DESCRIPTOR_RANGE1* _pDescriptorRanges) + noexcept + { + Init(numDescriptorRanges, _pDescriptorRanges); + } + + inline void Init(UINT numDescriptorRanges, + _In_reads_opt_(numDescriptorRanges) + const D3D12_DESCRIPTOR_RANGE1* _pDescriptorRanges) noexcept + { + Init(*this, numDescriptorRanges, _pDescriptorRanges); + } + + static inline void Init(_Out_ D3D12_ROOT_DESCRIPTOR_TABLE1& rootDescriptorTable, + UINT numDescriptorRanges, + _In_reads_opt_(numDescriptorRanges) + const D3D12_DESCRIPTOR_RANGE1* _pDescriptorRanges) noexcept + { + rootDescriptorTable.NumDescriptorRanges = numDescriptorRanges; + rootDescriptorTable.pDescriptorRanges = _pDescriptorRanges; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_DESCRIPTOR1 : public D3D12_ROOT_DESCRIPTOR1 +{ + CD3DX12_ROOT_DESCRIPTOR1() = default; + explicit CD3DX12_ROOT_DESCRIPTOR1(const D3D12_ROOT_DESCRIPTOR1& o) noexcept : + D3D12_ROOT_DESCRIPTOR1(o) + { + } + CD3DX12_ROOT_DESCRIPTOR1(UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE) + noexcept + { + Init(shaderRegister, registerSpace, flags); + } + + inline void Init(UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE) noexcept + { + Init(*this, shaderRegister, registerSpace, flags); + } + + static inline void Init(_Out_ D3D12_ROOT_DESCRIPTOR1& table, UINT shaderRegister, + UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE) noexcept + { + table.ShaderRegister = shaderRegister; + table.RegisterSpace = registerSpace; + table.Flags = flags; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_ROOT_PARAMETER1 : public D3D12_ROOT_PARAMETER1 +{ + CD3DX12_ROOT_PARAMETER1() = default; + explicit CD3DX12_ROOT_PARAMETER1(const D3D12_ROOT_PARAMETER1& o) noexcept : + D3D12_ROOT_PARAMETER1(o) + { + } + + static inline void InitAsDescriptorTable(_Out_ D3D12_ROOT_PARAMETER1& rootParam, + UINT numDescriptorRanges, + _In_reads_(numDescriptorRanges) const D3D12_DESCRIPTOR_RANGE1* pDescriptorRanges, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR_TABLE1::Init( + rootParam.DescriptorTable, numDescriptorRanges, pDescriptorRanges); + } + + static inline void InitAsConstants(_Out_ D3D12_ROOT_PARAMETER1& rootParam, UINT num32BitValues, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_CONSTANTS::Init( + rootParam.Constants, num32BitValues, shaderRegister, registerSpace); + } + + static inline void InitAsConstantBufferView(_Out_ D3D12_ROOT_PARAMETER1& rootParam, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR1::Init(rootParam.Descriptor, shaderRegister, registerSpace, flags); + } + + static inline void InitAsShaderResourceView(_Out_ D3D12_ROOT_PARAMETER1& rootParam, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR1::Init(rootParam.Descriptor, shaderRegister, registerSpace, flags); + } + + static inline void InitAsUnorderedAccessView(_Out_ D3D12_ROOT_PARAMETER1& rootParam, + UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV; + rootParam.ShaderVisibility = visibility; + CD3DX12_ROOT_DESCRIPTOR1::Init(rootParam.Descriptor, shaderRegister, registerSpace, flags); + } + + inline void InitAsDescriptorTable(UINT numDescriptorRanges, + _In_reads_(numDescriptorRanges) const D3D12_DESCRIPTOR_RANGE1* pDescriptorRanges, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsDescriptorTable(*this, numDescriptorRanges, pDescriptorRanges, visibility); + } + + inline void InitAsConstants(UINT num32BitValues, UINT shaderRegister, UINT registerSpace = 0, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsConstants(*this, num32BitValues, shaderRegister, registerSpace, visibility); + } + + inline void InitAsConstantBufferView(UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsConstantBufferView(*this, shaderRegister, registerSpace, flags, visibility); + } + + inline void InitAsShaderResourceView(UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsShaderResourceView(*this, shaderRegister, registerSpace, flags, visibility); + } + + inline void InitAsUnorderedAccessView(UINT shaderRegister, UINT registerSpace = 0, + D3D12_ROOT_DESCRIPTOR_FLAGS flags = D3D12_ROOT_DESCRIPTOR_FLAG_NONE, + D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept + { + InitAsUnorderedAccessView(*this, shaderRegister, registerSpace, flags, visibility); + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC : public D3D12_VERSIONED_ROOT_SIGNATURE_DESC +{ + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC() = default; + explicit CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC( + const D3D12_VERSIONED_ROOT_SIGNATURE_DESC& o) noexcept : + D3D12_VERSIONED_ROOT_SIGNATURE_DESC(o) + { + } + explicit CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC(const D3D12_ROOT_SIGNATURE_DESC& o) noexcept + { + Version = D3D_ROOT_SIGNATURE_VERSION_1_0; + Desc_1_0 = o; + } + explicit CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC(const D3D12_ROOT_SIGNATURE_DESC1& o) noexcept + { + Version = D3D_ROOT_SIGNATURE_VERSION_1_1; + Desc_1_1 = o; + } + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC(UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) + noexcept + { + Init_1_0(numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags); + } + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC(UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER1* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) + noexcept + { + Init_1_1(numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags); + } + CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC(CD3DX12_DEFAULT) noexcept + { + Init_1_1(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_NONE); + } + + inline void Init_1_0(UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) noexcept + { + Init_1_0(*this, numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags); + } + + static inline void Init_1_0(_Out_ D3D12_VERSIONED_ROOT_SIGNATURE_DESC& desc, UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) noexcept + { + desc.Version = D3D_ROOT_SIGNATURE_VERSION_1_0; + desc.Desc_1_0.NumParameters = numParameters; + desc.Desc_1_0.pParameters = _pParameters; + desc.Desc_1_0.NumStaticSamplers = numStaticSamplers; + desc.Desc_1_0.pStaticSamplers = _pStaticSamplers; + desc.Desc_1_0.Flags = flags; + } + + inline void Init_1_1(UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER1* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) noexcept + { + Init_1_1(*this, numParameters, _pParameters, numStaticSamplers, _pStaticSamplers, flags); + } + + static inline void Init_1_1(_Out_ D3D12_VERSIONED_ROOT_SIGNATURE_DESC& desc, UINT numParameters, + _In_reads_opt_(numParameters) const D3D12_ROOT_PARAMETER1* _pParameters, + UINT numStaticSamplers = 0, + _In_reads_opt_(numStaticSamplers) + const D3D12_STATIC_SAMPLER_DESC* _pStaticSamplers = nullptr, + D3D12_ROOT_SIGNATURE_FLAGS flags = D3D12_ROOT_SIGNATURE_FLAG_NONE) noexcept + { + desc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; + desc.Desc_1_1.NumParameters = numParameters; + desc.Desc_1_1.pParameters = _pParameters; + desc.Desc_1_1.NumStaticSamplers = numStaticSamplers; + desc.Desc_1_1.pStaticSamplers = _pStaticSamplers; + desc.Desc_1_1.Flags = flags; + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_CPU_DESCRIPTOR_HANDLE : public D3D12_CPU_DESCRIPTOR_HANDLE +{ + CD3DX12_CPU_DESCRIPTOR_HANDLE() = default; + explicit CD3DX12_CPU_DESCRIPTOR_HANDLE(const D3D12_CPU_DESCRIPTOR_HANDLE& o) noexcept : + D3D12_CPU_DESCRIPTOR_HANDLE(o) + { + } + CD3DX12_CPU_DESCRIPTOR_HANDLE(CD3DX12_DEFAULT) noexcept { ptr = 0; } + CD3DX12_CPU_DESCRIPTOR_HANDLE( + _In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other, INT offsetScaledByIncrementSize) + noexcept + { + InitOffsetted(other, offsetScaledByIncrementSize); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE(_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other, + INT offsetInDescriptors, UINT descriptorIncrementSize) + noexcept + { + InitOffsetted(other, offsetInDescriptors, descriptorIncrementSize); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& Offset( + INT offsetInDescriptors, UINT descriptorIncrementSize) noexcept + { + ptr = SIZE_T(INT64(ptr) + INT64(offsetInDescriptors) * INT64(descriptorIncrementSize)); + return *this; + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& Offset(INT offsetScaledByIncrementSize) noexcept + { + ptr = SIZE_T(INT64(ptr) + INT64(offsetScaledByIncrementSize)); + return *this; + } + bool operator==(_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other) const noexcept + { + return (ptr == other.ptr); + } + bool operator!=(_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& other) const noexcept + { + return (ptr != other.ptr); + } + CD3DX12_CPU_DESCRIPTOR_HANDLE& operator=(const D3D12_CPU_DESCRIPTOR_HANDLE& other) noexcept + { + ptr = other.ptr; + return *this; + } + + inline void InitOffsetted( + _In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, INT offsetScaledByIncrementSize) noexcept + { + InitOffsetted(*this, base, offsetScaledByIncrementSize); + } + + inline void InitOffsetted(_In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, INT offsetInDescriptors, + UINT descriptorIncrementSize) noexcept + { + InitOffsetted(*this, base, offsetInDescriptors, descriptorIncrementSize); + } + + static inline void InitOffsetted(_Out_ D3D12_CPU_DESCRIPTOR_HANDLE& handle, + _In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, INT offsetScaledByIncrementSize) noexcept + { + handle.ptr = SIZE_T(INT64(base.ptr) + INT64(offsetScaledByIncrementSize)); + } + + static inline void InitOffsetted(_Out_ D3D12_CPU_DESCRIPTOR_HANDLE& handle, + _In_ const D3D12_CPU_DESCRIPTOR_HANDLE& base, INT offsetInDescriptors, + UINT descriptorIncrementSize) noexcept + { + handle.ptr = + SIZE_T(INT64(base.ptr) + INT64(offsetInDescriptors) * INT64(descriptorIncrementSize)); + } +}; + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_GPU_DESCRIPTOR_HANDLE : public D3D12_GPU_DESCRIPTOR_HANDLE +{ + CD3DX12_GPU_DESCRIPTOR_HANDLE() = default; + explicit CD3DX12_GPU_DESCRIPTOR_HANDLE(const D3D12_GPU_DESCRIPTOR_HANDLE& o) noexcept : + D3D12_GPU_DESCRIPTOR_HANDLE(o) + { + } + CD3DX12_GPU_DESCRIPTOR_HANDLE(CD3DX12_DEFAULT) noexcept { ptr = 0; } + CD3DX12_GPU_DESCRIPTOR_HANDLE( + _In_ const D3D12_GPU_DESCRIPTOR_HANDLE& other, INT offsetScaledByIncrementSize) + noexcept + { + InitOffsetted(other, offsetScaledByIncrementSize); + } + CD3DX12_GPU_DESCRIPTOR_HANDLE(_In_ const D3D12_GPU_DESCRIPTOR_HANDLE& other, + INT offsetInDescriptors, UINT descriptorIncrementSize) + noexcept + { + InitOffsetted(other, offsetInDescriptors, descriptorIncrementSize); + } + CD3DX12_GPU_DESCRIPTOR_HANDLE& Offset( + INT offsetInDescriptors, UINT descriptorIncrementSize) noexcept + { + ptr = UINT64(INT64(ptr) + INT64(offsetInDescriptors) * INT64(descriptorIncrementSize)); + return *this; + } + CD3DX12_GPU_DESCRIPTOR_HANDLE& Offset(INT offsetScaledByIncrementSize) noexcept + { + ptr = UINT64(INT64(ptr) + INT64(offsetScaledByIncrementSize)); + return *this; + } + inline bool operator==(_In_ const D3D12_GPU_DESCRIPTOR_HANDLE& other) const noexcept + { + return (ptr == other.ptr); + } + inline bool operator!=(_In_ const D3D12_GPU_DESCRIPTOR_HANDLE& other) const noexcept + { + return (ptr != other.ptr); + } + CD3DX12_GPU_DESCRIPTOR_HANDLE& operator=(const D3D12_GPU_DESCRIPTOR_HANDLE& other) noexcept + { + ptr = other.ptr; + return *this; + } + + inline void InitOffsetted( + _In_ const D3D12_GPU_DESCRIPTOR_HANDLE& base, INT offsetScaledByIncrementSize) noexcept + { + InitOffsetted(*this, base, offsetScaledByIncrementSize); + } + + inline void InitOffsetted(_In_ const D3D12_GPU_DESCRIPTOR_HANDLE& base, INT offsetInDescriptors, + UINT descriptorIncrementSize) noexcept + { + InitOffsetted(*this, base, offsetInDescriptors, descriptorIncrementSize); + } + + static inline void InitOffsetted(_Out_ D3D12_GPU_DESCRIPTOR_HANDLE& handle, + _In_ const D3D12_GPU_DESCRIPTOR_HANDLE& base, INT offsetScaledByIncrementSize) noexcept + { + handle.ptr = UINT64(INT64(base.ptr) + INT64(offsetScaledByIncrementSize)); + } + + static inline void InitOffsetted(_Out_ D3D12_GPU_DESCRIPTOR_HANDLE& handle, + _In_ const D3D12_GPU_DESCRIPTOR_HANDLE& base, INT offsetInDescriptors, + UINT descriptorIncrementSize) noexcept + { + handle.ptr = + UINT64(INT64(base.ptr) + INT64(offsetInDescriptors) * INT64(descriptorIncrementSize)); + } +}; + +//------------------------------------------------------------------------------------------------ +inline constexpr UINT D3D12CalcSubresource( + UINT MipSlice, UINT ArraySlice, UINT PlaneSlice, UINT MipLevels, UINT ArraySize) noexcept +{ + return MipSlice + ArraySlice * MipLevels + PlaneSlice * MipLevels * ArraySize; +} + +//------------------------------------------------------------------------------------------------ +template +inline void D3D12DecomposeSubresource(UINT Subresource, UINT MipLevels, UINT ArraySize, + _Out_ T& MipSlice, _Out_ U& ArraySlice, _Out_ V& PlaneSlice) noexcept +{ + MipSlice = static_cast(Subresource % MipLevels); + ArraySlice = static_cast((Subresource / MipLevels) % ArraySize); + PlaneSlice = static_cast(Subresource / (MipLevels * ArraySize)); +} + +//------------------------------------------------------------------------------------------------ +inline UINT8 D3D12GetFormatPlaneCount(_In_ ID3D12Device* pDevice, DXGI_FORMAT Format) noexcept +{ + D3D12_FEATURE_DATA_FORMAT_INFO formatInfo = { Format, 0 }; + if (FAILED(pDevice->CheckFeatureSupport( + D3D12_FEATURE_FORMAT_INFO, &formatInfo, sizeof(formatInfo)))) + { + return 0; + } + return formatInfo.PlaneCount; +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RESOURCE_DESC : public D3D12_RESOURCE_DESC +{ + CD3DX12_RESOURCE_DESC() = default; + explicit CD3DX12_RESOURCE_DESC(const D3D12_RESOURCE_DESC& o) noexcept : D3D12_RESOURCE_DESC(o) + { + } + CD3DX12_RESOURCE_DESC(D3D12_RESOURCE_DIMENSION dimension, UINT64 alignment, UINT64 width, + UINT height, UINT16 depthOrArraySize, UINT16 mipLevels, DXGI_FORMAT format, + UINT sampleCount, UINT sampleQuality, D3D12_TEXTURE_LAYOUT layout, + D3D12_RESOURCE_FLAGS flags) + noexcept + { + Dimension = dimension; + Alignment = alignment; + Width = width; + Height = height; + DepthOrArraySize = depthOrArraySize; + MipLevels = mipLevels; + Format = format; + SampleDesc.Count = sampleCount; + SampleDesc.Quality = sampleQuality; + Layout = layout; + Flags = flags; + } + static inline CD3DX12_RESOURCE_DESC Buffer(const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE) noexcept + { + return CD3DX12_RESOURCE_DESC(D3D12_RESOURCE_DIMENSION_BUFFER, resAllocInfo.Alignment, + resAllocInfo.SizeInBytes, 1, 1, 1, DXGI_FORMAT_UNKNOWN, 1, 0, + D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags); + } + static inline CD3DX12_RESOURCE_DESC Buffer(UINT64 width, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC(D3D12_RESOURCE_DIMENSION_BUFFER, alignment, width, 1, 1, 1, + DXGI_FORMAT_UNKNOWN, 1, 0, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags); + } + static inline CD3DX12_RESOURCE_DESC Tex1D(DXGI_FORMAT format, UINT64 width, + UINT16 arraySize = 1, UINT16 mipLevels = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC(D3D12_RESOURCE_DIMENSION_TEXTURE1D, alignment, width, 1, + arraySize, mipLevels, format, 1, 0, layout, flags); + } + static inline CD3DX12_RESOURCE_DESC Tex2D(DXGI_FORMAT format, UINT64 width, UINT height, + UINT16 arraySize = 1, UINT16 mipLevels = 0, UINT sampleCount = 1, UINT sampleQuality = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC(D3D12_RESOURCE_DIMENSION_TEXTURE2D, alignment, width, height, + arraySize, mipLevels, format, sampleCount, sampleQuality, layout, flags); + } + static inline CD3DX12_RESOURCE_DESC Tex3D(DXGI_FORMAT format, UINT64 width, UINT height, + UINT16 depth, UINT16 mipLevels = 0, D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC(D3D12_RESOURCE_DIMENSION_TEXTURE3D, alignment, width, height, + depth, mipLevels, format, 1, 0, layout, flags); + } + inline UINT16 Depth() const noexcept + { + return (Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? DepthOrArraySize : 1); + } + inline UINT16 ArraySize() const noexcept + { + return (Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE3D ? DepthOrArraySize : 1); + } + inline UINT8 PlaneCount(_In_ ID3D12Device* pDevice) const noexcept + { + return D3D12GetFormatPlaneCount(pDevice, Format); + } + inline UINT Subresources(_In_ ID3D12Device* pDevice) const noexcept + { + return MipLevels * ArraySize() * PlaneCount(pDevice); + } + inline UINT CalcSubresource(UINT MipSlice, UINT ArraySlice, UINT PlaneSlice) noexcept + { + return D3D12CalcSubresource(MipSlice, ArraySlice, PlaneSlice, MipLevels, ArraySize()); + } +}; +inline bool operator==(const D3D12_RESOURCE_DESC& l, const D3D12_RESOURCE_DESC& r) noexcept +{ + return l.Dimension == r.Dimension && l.Alignment == r.Alignment && l.Width == r.Width && + l.Height == r.Height && l.DepthOrArraySize == r.DepthOrArraySize && + l.MipLevels == r.MipLevels && l.Format == r.Format && + l.SampleDesc.Count == r.SampleDesc.Count && l.SampleDesc.Quality == r.SampleDesc.Quality && + l.Layout == r.Layout && l.Flags == r.Flags; +} +inline bool operator!=(const D3D12_RESOURCE_DESC& l, const D3D12_RESOURCE_DESC& r) noexcept +{ + return !(l == r); +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RESOURCE_DESC1 : public D3D12_RESOURCE_DESC1 +{ + CD3DX12_RESOURCE_DESC1() = default; + explicit CD3DX12_RESOURCE_DESC1(const D3D12_RESOURCE_DESC1& o) noexcept : + D3D12_RESOURCE_DESC1(o) + { + } + CD3DX12_RESOURCE_DESC1(D3D12_RESOURCE_DIMENSION dimension, UINT64 alignment, UINT64 width, + UINT height, UINT16 depthOrArraySize, UINT16 mipLevels, DXGI_FORMAT format, + UINT sampleCount, UINT sampleQuality, D3D12_TEXTURE_LAYOUT layout, + D3D12_RESOURCE_FLAGS flags, UINT samplerFeedbackMipRegionWidth = 0, + UINT samplerFeedbackMipRegionHeight = 0, UINT samplerFeedbackMipRegionDepth = 0) + noexcept + { + Dimension = dimension; + Alignment = alignment; + Width = width; + Height = height; + DepthOrArraySize = depthOrArraySize; + MipLevels = mipLevels; + Format = format; + SampleDesc.Count = sampleCount; + SampleDesc.Quality = sampleQuality; + Layout = layout; + Flags = flags; + SamplerFeedbackMipRegion.Width = samplerFeedbackMipRegionWidth; + SamplerFeedbackMipRegion.Height = samplerFeedbackMipRegionHeight; + SamplerFeedbackMipRegion.Depth = samplerFeedbackMipRegionDepth; + } + static inline CD3DX12_RESOURCE_DESC1 Buffer(const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE) noexcept + { + return CD3DX12_RESOURCE_DESC1(D3D12_RESOURCE_DIMENSION_BUFFER, resAllocInfo.Alignment, + resAllocInfo.SizeInBytes, 1, 1, 1, DXGI_FORMAT_UNKNOWN, 1, 0, + D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags, 0, 0, 0); + } + static inline CD3DX12_RESOURCE_DESC1 Buffer(UINT64 width, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC1(D3D12_RESOURCE_DIMENSION_BUFFER, alignment, width, 1, 1, 1, + DXGI_FORMAT_UNKNOWN, 1, 0, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags, 0, 0, 0); + } + static inline CD3DX12_RESOURCE_DESC1 Tex1D(DXGI_FORMAT format, UINT64 width, + UINT16 arraySize = 1, UINT16 mipLevels = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC1(D3D12_RESOURCE_DIMENSION_TEXTURE1D, alignment, width, 1, + arraySize, mipLevels, format, 1, 0, layout, flags, 0, 0, 0); + } + static inline CD3DX12_RESOURCE_DESC1 Tex2D(DXGI_FORMAT format, UINT64 width, UINT height, + UINT16 arraySize = 1, UINT16 mipLevels = 0, UINT sampleCount = 1, UINT sampleQuality = 0, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0, + UINT samplerFeedbackMipRegionWidth = 0, UINT samplerFeedbackMipRegionHeight = 0, + UINT samplerFeedbackMipRegionDepth = 0) noexcept + { + return CD3DX12_RESOURCE_DESC1(D3D12_RESOURCE_DIMENSION_TEXTURE2D, alignment, width, height, + arraySize, mipLevels, format, sampleCount, sampleQuality, layout, flags, + samplerFeedbackMipRegionWidth, samplerFeedbackMipRegionHeight, + samplerFeedbackMipRegionDepth); + } + static inline CD3DX12_RESOURCE_DESC1 Tex3D(DXGI_FORMAT format, UINT64 width, UINT height, + UINT16 depth, UINT16 mipLevels = 0, D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_TEXTURE_LAYOUT layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, UINT64 alignment = 0) noexcept + { + return CD3DX12_RESOURCE_DESC1(D3D12_RESOURCE_DIMENSION_TEXTURE3D, alignment, width, height, + depth, mipLevels, format, 1, 0, layout, flags, 0, 0, 0); + } + inline UINT16 Depth() const noexcept + { + return (Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? DepthOrArraySize : 1); + } + inline UINT16 ArraySize() const noexcept + { + return (Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE3D ? DepthOrArraySize : 1); + } + inline UINT8 PlaneCount(_In_ ID3D12Device* pDevice) const noexcept + { + return D3D12GetFormatPlaneCount(pDevice, Format); + } + inline UINT Subresources(_In_ ID3D12Device* pDevice) const noexcept + { + return MipLevels * ArraySize() * PlaneCount(pDevice); + } + inline UINT CalcSubresource(UINT MipSlice, UINT ArraySlice, UINT PlaneSlice) noexcept + { + return D3D12CalcSubresource(MipSlice, ArraySlice, PlaneSlice, MipLevels, ArraySize()); + } +}; +inline bool operator==(const D3D12_RESOURCE_DESC1& l, const D3D12_RESOURCE_DESC1& r) noexcept +{ + return l.Dimension == r.Dimension && l.Alignment == r.Alignment && l.Width == r.Width && + l.Height == r.Height && l.DepthOrArraySize == r.DepthOrArraySize && + l.MipLevels == r.MipLevels && l.Format == r.Format && + l.SampleDesc.Count == r.SampleDesc.Count && l.SampleDesc.Quality == r.SampleDesc.Quality && + l.Layout == r.Layout && l.Flags == r.Flags && + l.SamplerFeedbackMipRegion.Width == r.SamplerFeedbackMipRegion.Width && + l.SamplerFeedbackMipRegion.Height == r.SamplerFeedbackMipRegion.Height && + l.SamplerFeedbackMipRegion.Depth == r.SamplerFeedbackMipRegion.Depth; +} +inline bool operator!=(const D3D12_RESOURCE_DESC1& l, const D3D12_RESOURCE_DESC1& r) noexcept +{ + return !(l == r); +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_VIEW_INSTANCING_DESC : public D3D12_VIEW_INSTANCING_DESC +{ + CD3DX12_VIEW_INSTANCING_DESC() = default; + explicit CD3DX12_VIEW_INSTANCING_DESC(const D3D12_VIEW_INSTANCING_DESC& o) noexcept : + D3D12_VIEW_INSTANCING_DESC(o) + { + } + explicit CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT) noexcept + { + ViewInstanceCount = 0; + pViewInstanceLocations = nullptr; + Flags = D3D12_VIEW_INSTANCING_FLAG_NONE; + } + explicit CD3DX12_VIEW_INSTANCING_DESC(UINT InViewInstanceCount, + const D3D12_VIEW_INSTANCE_LOCATION* InViewInstanceLocations, + D3D12_VIEW_INSTANCING_FLAGS InFlags) noexcept + { + ViewInstanceCount = InViewInstanceCount; + pViewInstanceLocations = InViewInstanceLocations; + Flags = InFlags; + } +}; + +//------------------------------------------------------------------------------------------------ +// Row-by-row memcpy +inline void MemcpySubresource(_In_ const D3D12_MEMCPY_DEST* pDest, + _In_ const D3D12_SUBRESOURCE_DATA* pSrc, SIZE_T RowSizeInBytes, UINT NumRows, + UINT NumSlices) noexcept +{ + for (UINT z = 0; z < NumSlices; ++z) + { + auto pDestSlice = static_cast(pDest->pData) + pDest->SlicePitch * z; + auto pSrcSlice = static_cast(pSrc->pData) + pSrc->SlicePitch * LONG_PTR(z); + for (UINT y = 0; y < NumRows; ++y) + { + memcpy(pDestSlice + pDest->RowPitch * y, pSrcSlice + pSrc->RowPitch * LONG_PTR(y), + RowSizeInBytes); + } + } +} + +//------------------------------------------------------------------------------------------------ +// Row-by-row memcpy +inline void MemcpySubresource(_In_ const D3D12_MEMCPY_DEST* pDest, _In_ const void* pResourceData, + _In_ const D3D12_SUBRESOURCE_INFO* pSrc, SIZE_T RowSizeInBytes, UINT NumRows, + UINT NumSlices) noexcept +{ + for (UINT z = 0; z < NumSlices; ++z) + { + auto pDestSlice = static_cast(pDest->pData) + pDest->SlicePitch * z; + auto pSrcSlice = (static_cast(pResourceData) + pSrc->Offset) + + pSrc->DepthPitch * LONG_PTR(z); + for (UINT y = 0; y < NumRows; ++y) + { + memcpy(pDestSlice + pDest->RowPitch * y, pSrcSlice + pSrc->RowPitch * LONG_PTR(y), + RowSizeInBytes); + } + } +} + +//------------------------------------------------------------------------------------------------ +// Returns required size of a buffer to be used for data upload +inline UINT64 GetRequiredIntermediateSize(_In_ ID3D12Resource* pDestinationResource, + _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource, + _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources) noexcept +{ + auto Desc = pDestinationResource->GetDesc(); + UINT64 RequiredSize = 0; + + ID3D12Device* pDevice = nullptr; + pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast(&pDevice)); + pDevice->GetCopyableFootprints( + &Desc, FirstSubresource, NumSubresources, 0, nullptr, nullptr, nullptr, &RequiredSize); + pDevice->Release(); + + return RequiredSize; +} + +//------------------------------------------------------------------------------------------------ +// All arrays must be populated (e.g. by calling GetCopyableFootprints) +inline UINT64 UpdateSubresources(_In_ ID3D12GraphicsCommandList* pCmdList, + _In_ ID3D12Resource* pDestinationResource, _In_ ID3D12Resource* pIntermediate, + _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource, + _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources, + UINT64 RequiredSize, + _In_reads_(NumSubresources) const D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts, + _In_reads_(NumSubresources) const UINT* pNumRows, + _In_reads_(NumSubresources) const UINT64* pRowSizesInBytes, + _In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData) noexcept +{ + // Minor validation + auto IntermediateDesc = pIntermediate->GetDesc(); + auto DestinationDesc = pDestinationResource->GetDesc(); + if (IntermediateDesc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER || + IntermediateDesc.Width < RequiredSize + pLayouts[0].Offset || RequiredSize > SIZE_T(-1) || + (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER && + (FirstSubresource != 0 || NumSubresources != 1))) + { + return 0; + } + + BYTE* pData; + HRESULT hr = pIntermediate->Map(0, nullptr, reinterpret_cast(&pData)); + if (FAILED(hr)) + { + return 0; + } + + for (UINT i = 0; i < NumSubresources; ++i) + { + if (pRowSizesInBytes[i] > SIZE_T(-1)) + return 0; + D3D12_MEMCPY_DEST DestData = { pData + pLayouts[i].Offset, pLayouts[i].Footprint.RowPitch, + SIZE_T(pLayouts[i].Footprint.RowPitch) * SIZE_T(pNumRows[i]) }; + MemcpySubresource(&DestData, &pSrcData[i], static_cast(pRowSizesInBytes[i]), + pNumRows[i], pLayouts[i].Footprint.Depth); + } + pIntermediate->Unmap(0, nullptr); + + if (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + pCmdList->CopyBufferRegion(pDestinationResource, 0, pIntermediate, pLayouts[0].Offset, + pLayouts[0].Footprint.Width); + } + else + { + for (UINT i = 0; i < NumSubresources; ++i) + { + CD3DX12_TEXTURE_COPY_LOCATION Dst(pDestinationResource, i + FirstSubresource); + CD3DX12_TEXTURE_COPY_LOCATION Src(pIntermediate, pLayouts[i]); + pCmdList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr); + } + } + return RequiredSize; +} + +//------------------------------------------------------------------------------------------------ +// All arrays must be populated (e.g. by calling GetCopyableFootprints) +inline UINT64 UpdateSubresources(_In_ ID3D12GraphicsCommandList* pCmdList, + _In_ ID3D12Resource* pDestinationResource, _In_ ID3D12Resource* pIntermediate, + _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource, + _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources, + UINT64 RequiredSize, + _In_reads_(NumSubresources) const D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts, + _In_reads_(NumSubresources) const UINT* pNumRows, + _In_reads_(NumSubresources) const UINT64* pRowSizesInBytes, _In_ const void* pResourceData, + _In_reads_(NumSubresources) const D3D12_SUBRESOURCE_INFO* pSrcData) noexcept +{ + // Minor validation + auto IntermediateDesc = pIntermediate->GetDesc(); + auto DestinationDesc = pDestinationResource->GetDesc(); + if (IntermediateDesc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER || + IntermediateDesc.Width < RequiredSize + pLayouts[0].Offset || RequiredSize > SIZE_T(-1) || + (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER && + (FirstSubresource != 0 || NumSubresources != 1))) + { + return 0; + } + + BYTE* pData; + HRESULT hr = pIntermediate->Map(0, nullptr, reinterpret_cast(&pData)); + if (FAILED(hr)) + { + return 0; + } + + for (UINT i = 0; i < NumSubresources; ++i) + { + if (pRowSizesInBytes[i] > SIZE_T(-1)) + return 0; + D3D12_MEMCPY_DEST DestData = { pData + pLayouts[i].Offset, pLayouts[i].Footprint.RowPitch, + SIZE_T(pLayouts[i].Footprint.RowPitch) * SIZE_T(pNumRows[i]) }; + MemcpySubresource(&DestData, pResourceData, &pSrcData[i], + static_cast(pRowSizesInBytes[i]), pNumRows[i], pLayouts[i].Footprint.Depth); + } + pIntermediate->Unmap(0, nullptr); + + if (DestinationDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + pCmdList->CopyBufferRegion(pDestinationResource, 0, pIntermediate, pLayouts[0].Offset, + pLayouts[0].Footprint.Width); + } + else + { + for (UINT i = 0; i < NumSubresources; ++i) + { + CD3DX12_TEXTURE_COPY_LOCATION Dst(pDestinationResource, i + FirstSubresource); + CD3DX12_TEXTURE_COPY_LOCATION Src(pIntermediate, pLayouts[i]); + pCmdList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr); + } + } + return RequiredSize; +} + +//------------------------------------------------------------------------------------------------ +// Heap-allocating UpdateSubresources implementation +inline UINT64 UpdateSubresources(_In_ ID3D12GraphicsCommandList* pCmdList, + _In_ ID3D12Resource* pDestinationResource, _In_ ID3D12Resource* pIntermediate, + UINT64 IntermediateOffset, _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource, + _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources, + _In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData) noexcept +{ + UINT64 RequiredSize = 0; + auto MemToAlloc = static_cast(sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + + sizeof(UINT) + sizeof(UINT64)) * + NumSubresources; + if (MemToAlloc > SIZE_MAX) + { + return 0; + } + void* pMem = HeapAlloc(GetProcessHeap(), 0, static_cast(MemToAlloc)); + if (pMem == nullptr) + { + return 0; + } + auto pLayouts = static_cast(pMem); + auto pRowSizesInBytes = reinterpret_cast(pLayouts + NumSubresources); + auto pNumRows = reinterpret_cast(pRowSizesInBytes + NumSubresources); + + auto Desc = pDestinationResource->GetDesc(); + ID3D12Device* pDevice = nullptr; + pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast(&pDevice)); + pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, + pLayouts, pNumRows, pRowSizesInBytes, &RequiredSize); + pDevice->Release(); + + UINT64 Result = + UpdateSubresources(pCmdList, pDestinationResource, pIntermediate, FirstSubresource, + NumSubresources, RequiredSize, pLayouts, pNumRows, pRowSizesInBytes, pSrcData); + HeapFree(GetProcessHeap(), 0, pMem); + return Result; +} + +//------------------------------------------------------------------------------------------------ +// Heap-allocating UpdateSubresources implementation +inline UINT64 UpdateSubresources(_In_ ID3D12GraphicsCommandList* pCmdList, + _In_ ID3D12Resource* pDestinationResource, _In_ ID3D12Resource* pIntermediate, + UINT64 IntermediateOffset, _In_range_(0, D3D12_REQ_SUBRESOURCES) UINT FirstSubresource, + _In_range_(0, D3D12_REQ_SUBRESOURCES - FirstSubresource) UINT NumSubresources, + _In_ const void* pResourceData, + _In_reads_(NumSubresources) D3D12_SUBRESOURCE_INFO* pSrcData) noexcept +{ + UINT64 RequiredSize = 0; + auto MemToAlloc = static_cast(sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) + + sizeof(UINT) + sizeof(UINT64)) * + NumSubresources; + if (MemToAlloc > SIZE_MAX) + { + return 0; + } + void* pMem = HeapAlloc(GetProcessHeap(), 0, static_cast(MemToAlloc)); + if (pMem == nullptr) + { + return 0; + } + auto pLayouts = reinterpret_cast(pMem); + auto pRowSizesInBytes = reinterpret_cast(pLayouts + NumSubresources); + auto pNumRows = reinterpret_cast(pRowSizesInBytes + NumSubresources); + + auto Desc = pDestinationResource->GetDesc(); + ID3D12Device* pDevice = nullptr; + pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast(&pDevice)); + pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, + pLayouts, pNumRows, pRowSizesInBytes, &RequiredSize); + pDevice->Release(); + + UINT64 Result = UpdateSubresources(pCmdList, pDestinationResource, pIntermediate, + FirstSubresource, NumSubresources, RequiredSize, pLayouts, pNumRows, pRowSizesInBytes, + pResourceData, pSrcData); + HeapFree(GetProcessHeap(), 0, pMem); + return Result; +} + +//------------------------------------------------------------------------------------------------ +// Stack-allocating UpdateSubresources implementation +template +inline UINT64 UpdateSubresources(_In_ ID3D12GraphicsCommandList* pCmdList, + _In_ ID3D12Resource* pDestinationResource, _In_ ID3D12Resource* pIntermediate, + UINT64 IntermediateOffset, _In_range_(0, MaxSubresources) UINT FirstSubresource, + _In_range_(1, MaxSubresources - FirstSubresource) UINT NumSubresources, + _In_reads_(NumSubresources) const D3D12_SUBRESOURCE_DATA* pSrcData) noexcept +{ + UINT64 RequiredSize = 0; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT Layouts[MaxSubresources]; + UINT NumRows[MaxSubresources]; + UINT64 RowSizesInBytes[MaxSubresources]; + + auto Desc = pDestinationResource->GetDesc(); + ID3D12Device* pDevice = nullptr; + pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast(&pDevice)); + pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, + Layouts, NumRows, RowSizesInBytes, &RequiredSize); + pDevice->Release(); + + return UpdateSubresources(pCmdList, pDestinationResource, pIntermediate, FirstSubresource, + NumSubresources, RequiredSize, Layouts, NumRows, RowSizesInBytes, pSrcData); +} + +//------------------------------------------------------------------------------------------------ +// Stack-allocating UpdateSubresources implementation +template +inline UINT64 UpdateSubresources(_In_ ID3D12GraphicsCommandList* pCmdList, + _In_ ID3D12Resource* pDestinationResource, _In_ ID3D12Resource* pIntermediate, + UINT64 IntermediateOffset, _In_range_(0, MaxSubresources) UINT FirstSubresource, + _In_range_(1, MaxSubresources - FirstSubresource) UINT NumSubresources, + _In_ const void* pResourceData, + _In_reads_(NumSubresources) D3D12_SUBRESOURCE_INFO* pSrcData) noexcept +{ + UINT64 RequiredSize = 0; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT Layouts[MaxSubresources]; + UINT NumRows[MaxSubresources]; + UINT64 RowSizesInBytes[MaxSubresources]; + + auto Desc = pDestinationResource->GetDesc(); + ID3D12Device* pDevice = nullptr; + pDestinationResource->GetDevice(IID_ID3D12Device, reinterpret_cast(&pDevice)); + pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, + Layouts, NumRows, RowSizesInBytes, &RequiredSize); + pDevice->Release(); + + return UpdateSubresources(pCmdList, pDestinationResource, pIntermediate, FirstSubresource, + NumSubresources, RequiredSize, Layouts, NumRows, RowSizesInBytes, pResourceData, pSrcData); +} + +//------------------------------------------------------------------------------------------------ +inline constexpr bool D3D12IsLayoutOpaque(D3D12_TEXTURE_LAYOUT Layout) noexcept +{ + return Layout == D3D12_TEXTURE_LAYOUT_UNKNOWN || + Layout == D3D12_TEXTURE_LAYOUT_64KB_UNDEFINED_SWIZZLE; +} + +//------------------------------------------------------------------------------------------------ +template +inline ID3D12CommandList* const* CommandListCast(t_CommandListType* const* pp) noexcept +{ + // This cast is useful for passing strongly typed command list pointers into + // ExecuteCommandLists. + // This cast is valid as long as the const-ness is respected. D3D12 APIs do + // respect the const-ness of their arguments. + return reinterpret_cast(pp); +} + +//------------------------------------------------------------------------------------------------ +// D3D12 exports a new method for serializing root signatures in the Windows 10 Anniversary Update. +// To help enable root signature 1.1 features when they are available and not require maintaining +// two code paths for building root signatures, this helper method reconstructs a 1.0 signature when +// 1.1 is not supported. +inline HRESULT D3DX12SerializeVersionedRootSignature( + _In_ const D3D12_VERSIONED_ROOT_SIGNATURE_DESC* pRootSignatureDesc, + D3D_ROOT_SIGNATURE_VERSION MaxVersion, _Outptr_ ID3DBlob** ppBlob, + _Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorBlob) noexcept +{ + if (ppErrorBlob != nullptr) + { + *ppErrorBlob = nullptr; + } + + switch (MaxVersion) + { + case D3D_ROOT_SIGNATURE_VERSION_1_0: + switch (pRootSignatureDesc->Version) + { + case D3D_ROOT_SIGNATURE_VERSION_1_0: + return D3D12SerializeRootSignature( + &pRootSignatureDesc->Desc_1_0, D3D_ROOT_SIGNATURE_VERSION_1, ppBlob, ppErrorBlob); + + case D3D_ROOT_SIGNATURE_VERSION_1_1: + { + HRESULT hr = S_OK; + const D3D12_ROOT_SIGNATURE_DESC1& desc_1_1 = pRootSignatureDesc->Desc_1_1; + + const SIZE_T ParametersSize = sizeof(D3D12_ROOT_PARAMETER) * desc_1_1.NumParameters; + void* pParameters = + (ParametersSize > 0) ? HeapAlloc(GetProcessHeap(), 0, ParametersSize) : nullptr; + if (ParametersSize > 0 && pParameters == nullptr) + { + hr = E_OUTOFMEMORY; + } + auto pParameters_1_0 = static_cast(pParameters); + + if (SUCCEEDED(hr)) + { + for (UINT n = 0; n < desc_1_1.NumParameters; n++) + { + __analysis_assume( + ParametersSize == sizeof(D3D12_ROOT_PARAMETER) * desc_1_1.NumParameters); + pParameters_1_0[n].ParameterType = desc_1_1.pParameters[n].ParameterType; + pParameters_1_0[n].ShaderVisibility = desc_1_1.pParameters[n].ShaderVisibility; + + switch (desc_1_1.pParameters[n].ParameterType) + { + case D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS: + pParameters_1_0[n].Constants.Num32BitValues = + desc_1_1.pParameters[n].Constants.Num32BitValues; + pParameters_1_0[n].Constants.RegisterSpace = + desc_1_1.pParameters[n].Constants.RegisterSpace; + pParameters_1_0[n].Constants.ShaderRegister = + desc_1_1.pParameters[n].Constants.ShaderRegister; + break; + + case D3D12_ROOT_PARAMETER_TYPE_CBV: + case D3D12_ROOT_PARAMETER_TYPE_SRV: + case D3D12_ROOT_PARAMETER_TYPE_UAV: + pParameters_1_0[n].Descriptor.RegisterSpace = + desc_1_1.pParameters[n].Descriptor.RegisterSpace; + pParameters_1_0[n].Descriptor.ShaderRegister = + desc_1_1.pParameters[n].Descriptor.ShaderRegister; + break; + + case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE: + const D3D12_ROOT_DESCRIPTOR_TABLE1& table_1_1 = + desc_1_1.pParameters[n].DescriptorTable; + + const SIZE_T DescriptorRangesSize = + sizeof(D3D12_DESCRIPTOR_RANGE) * table_1_1.NumDescriptorRanges; + void* pDescriptorRanges = (DescriptorRangesSize > 0 && SUCCEEDED(hr)) + ? HeapAlloc(GetProcessHeap(), 0, DescriptorRangesSize) + : nullptr; + if (DescriptorRangesSize > 0 && pDescriptorRanges == nullptr) + { + hr = E_OUTOFMEMORY; + } + auto pDescriptorRanges_1_0 = + static_cast(pDescriptorRanges); + + if (SUCCEEDED(hr)) + { + for (UINT x = 0; x < table_1_1.NumDescriptorRanges; x++) + { + __analysis_assume(DescriptorRangesSize == + sizeof(D3D12_DESCRIPTOR_RANGE) * table_1_1.NumDescriptorRanges); + pDescriptorRanges_1_0[x].BaseShaderRegister = + table_1_1.pDescriptorRanges[x].BaseShaderRegister; + pDescriptorRanges_1_0[x].NumDescriptors = + table_1_1.pDescriptorRanges[x].NumDescriptors; + pDescriptorRanges_1_0[x].OffsetInDescriptorsFromTableStart = + table_1_1.pDescriptorRanges[x] + .OffsetInDescriptorsFromTableStart; + pDescriptorRanges_1_0[x].RangeType = + table_1_1.pDescriptorRanges[x].RangeType; + pDescriptorRanges_1_0[x].RegisterSpace = + table_1_1.pDescriptorRanges[x].RegisterSpace; + } + } + + D3D12_ROOT_DESCRIPTOR_TABLE& table_1_0 = pParameters_1_0[n].DescriptorTable; + table_1_0.NumDescriptorRanges = table_1_1.NumDescriptorRanges; + table_1_0.pDescriptorRanges = pDescriptorRanges_1_0; + } + } + } + + if (SUCCEEDED(hr)) + { + CD3DX12_ROOT_SIGNATURE_DESC desc_1_0(desc_1_1.NumParameters, pParameters_1_0, + desc_1_1.NumStaticSamplers, desc_1_1.pStaticSamplers, desc_1_1.Flags); + hr = D3D12SerializeRootSignature( + &desc_1_0, D3D_ROOT_SIGNATURE_VERSION_1, ppBlob, ppErrorBlob); + } + + if (pParameters) + { + for (UINT n = 0; n < desc_1_1.NumParameters; n++) + { + if (desc_1_1.pParameters[n].ParameterType == + D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE) + { + HeapFree(GetProcessHeap(), 0, + reinterpret_cast(const_cast( + pParameters_1_0[n].DescriptorTable.pDescriptorRanges))); + } + } + HeapFree(GetProcessHeap(), 0, pParameters); + } + return hr; + } + } + break; + + case D3D_ROOT_SIGNATURE_VERSION_1_1: + return D3D12SerializeVersionedRootSignature(pRootSignatureDesc, ppBlob, ppErrorBlob); + } + + return E_INVALIDARG; +} + +//------------------------------------------------------------------------------------------------ +struct CD3DX12_RT_FORMAT_ARRAY : public D3D12_RT_FORMAT_ARRAY +{ + CD3DX12_RT_FORMAT_ARRAY() = default; + explicit CD3DX12_RT_FORMAT_ARRAY(const D3D12_RT_FORMAT_ARRAY& o) noexcept : + D3D12_RT_FORMAT_ARRAY(o) + { + } + explicit CD3DX12_RT_FORMAT_ARRAY( + _In_reads_(NumFormats) const DXGI_FORMAT* pFormats, UINT NumFormats) noexcept + { + NumRenderTargets = NumFormats; + memcpy(RTFormats, pFormats, sizeof(RTFormats)); + // assumes ARRAY_SIZE(pFormats) == ARRAY_SIZE(RTFormats) + } +}; + +//------------------------------------------------------------------------------------------------ +// Pipeline State Stream Helpers +//------------------------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------------------------ +// Stream Subobjects, i.e. elements of a stream + +struct DefaultSampleMask +{ + operator UINT() noexcept { return UINT_MAX; } +}; +struct DefaultSampleDesc +{ + operator DXGI_SAMPLE_DESC() noexcept { return DXGI_SAMPLE_DESC { 1, 0 }; } +}; + +#pragma warning(push) +#pragma warning(disable : 4324) +template +class alignas(void*) CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT +{ +private: + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE _Type; + InnerStructType _Inner; + +public: + CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT() noexcept : _Type(Type), _Inner(DefaultArg()) {} + CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT(InnerStructType const& i) + noexcept : _Type(Type), _Inner(i) + { + } + CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT& operator=(InnerStructType const& i) noexcept + { + _Type = Type; + _Inner = i; + return *this; + } + operator InnerStructType const &() const noexcept { return _Inner; } + operator InnerStructType&() noexcept { return _Inner; } + InnerStructType* operator&() noexcept { return &_Inner; } + InnerStructType const* operator&() const noexcept { return &_Inner; } +}; +#pragma warning(pop) +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_FLAGS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_NODE_MASK; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_INPUT_LAYOUT; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_IB_STRIP_CUT_VALUE; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_PRIMITIVE_TOPOLOGY; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_VS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_GS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_STREAM_OUTPUT; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_HS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_DS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_PS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_AS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_MS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_CS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_BLEND_DESC; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL1; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_RASTERIZER; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_DESC; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_MASK; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_CACHED_PSO; +typedef CD3DX12_PIPELINE_STATE_STREAM_SUBOBJECT + CD3DX12_PIPELINE_STATE_STREAM_VIEW_INSTANCING; + +//------------------------------------------------------------------------------------------------ +// Stream Parser Helpers + +struct ID3DX12PipelineParserCallbacks +{ + // Subobject Callbacks + virtual void FlagsCb(D3D12_PIPELINE_STATE_FLAGS) {} + virtual void NodeMaskCb(UINT) {} + virtual void RootSignatureCb(ID3D12RootSignature*) {} + virtual void InputLayoutCb(const D3D12_INPUT_LAYOUT_DESC&) {} + virtual void IBStripCutValueCb(D3D12_INDEX_BUFFER_STRIP_CUT_VALUE) {} + virtual void PrimitiveTopologyTypeCb(D3D12_PRIMITIVE_TOPOLOGY_TYPE) {} + virtual void VSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void GSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void StreamOutputCb(const D3D12_STREAM_OUTPUT_DESC&) {} + virtual void HSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void DSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void PSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void CSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void ASCb(const D3D12_SHADER_BYTECODE&) {} + virtual void MSCb(const D3D12_SHADER_BYTECODE&) {} + virtual void BlendStateCb(const D3D12_BLEND_DESC&) {} + virtual void DepthStencilStateCb(const D3D12_DEPTH_STENCIL_DESC&) {} + virtual void DepthStencilState1Cb(const D3D12_DEPTH_STENCIL_DESC1&) {} + virtual void DSVFormatCb(DXGI_FORMAT) {} + virtual void RasterizerStateCb(const D3D12_RASTERIZER_DESC&) {} + virtual void RTVFormatsCb(const D3D12_RT_FORMAT_ARRAY&) {} + virtual void SampleDescCb(const DXGI_SAMPLE_DESC&) {} + virtual void SampleMaskCb(UINT) {} + virtual void ViewInstancingCb(const D3D12_VIEW_INSTANCING_DESC&) {} + virtual void CachedPSOCb(const D3D12_CACHED_PIPELINE_STATE&) {} + + // Error Callbacks + virtual void ErrorBadInputParameter(UINT /*ParameterIndex*/) {} + virtual void ErrorDuplicateSubobject(D3D12_PIPELINE_STATE_SUBOBJECT_TYPE /*DuplicateType*/) {} + virtual void ErrorUnknownSubobject(UINT /*UnknownTypeValue*/) {} + + virtual ~ID3DX12PipelineParserCallbacks() = default; +}; + +struct D3DX12_MESH_SHADER_PIPELINE_STATE_DESC +{ + ID3D12RootSignature* pRootSignature; + D3D12_SHADER_BYTECODE AS; + D3D12_SHADER_BYTECODE MS; + D3D12_SHADER_BYTECODE PS; + D3D12_BLEND_DESC BlendState; + UINT SampleMask; + D3D12_RASTERIZER_DESC RasterizerState; + D3D12_DEPTH_STENCIL_DESC DepthStencilState; + D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType; + UINT NumRenderTargets; + DXGI_FORMAT RTVFormats[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT]; + DXGI_FORMAT DSVFormat; + DXGI_SAMPLE_DESC SampleDesc; + UINT NodeMask; + D3D12_CACHED_PIPELINE_STATE CachedPSO; + D3D12_PIPELINE_STATE_FLAGS Flags; +}; + +// CD3DX12_PIPELINE_STATE_STREAM2 Works on OS Build 19041+ (where there is a new mesh shader +// pipeline). Use CD3DX12_PIPELINE_STATE_STREAM1 for OS Build 16299+ (where there is a new view +// instancing subobject). Use CD3DX12_PIPELINE_STATE_STREAM for OS Build 15063+ support. +struct CD3DX12_PIPELINE_STATE_STREAM2 +{ + CD3DX12_PIPELINE_STATE_STREAM2() = default; + // Mesh and amplification shaders must be set manually, since they do not have representation in + // D3D12_GRAPHICS_PIPELINE_STATE_DESC + CD3DX12_PIPELINE_STATE_STREAM2(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + InputLayout(Desc.InputLayout), + IBStripCutValue(Desc.IBStripCutValue), + PrimitiveTopologyType(Desc.PrimitiveTopologyType), + VS(Desc.VS), + GS(Desc.GS), + StreamOutput(Desc.StreamOutput), + HS(Desc.HS), + DS(Desc.DS), + PS(Desc.PS), + BlendState(CD3DX12_BLEND_DESC(Desc.BlendState)), + DepthStencilState(CD3DX12_DEPTH_STENCIL_DESC1(Desc.DepthStencilState)), + DSVFormat(Desc.DSVFormat), + RasterizerState(CD3DX12_RASTERIZER_DESC(Desc.RasterizerState)), + RTVFormats(CD3DX12_RT_FORMAT_ARRAY(Desc.RTVFormats, Desc.NumRenderTargets)), + SampleDesc(Desc.SampleDesc), + SampleMask(Desc.SampleMask), + CachedPSO(Desc.CachedPSO), + ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) + { + } + CD3DX12_PIPELINE_STATE_STREAM2(const D3DX12_MESH_SHADER_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + PrimitiveTopologyType(Desc.PrimitiveTopologyType), + PS(Desc.PS), + AS(Desc.AS), + MS(Desc.MS), + BlendState(CD3DX12_BLEND_DESC(Desc.BlendState)), + DepthStencilState(CD3DX12_DEPTH_STENCIL_DESC1(Desc.DepthStencilState)), + DSVFormat(Desc.DSVFormat), + RasterizerState(CD3DX12_RASTERIZER_DESC(Desc.RasterizerState)), + RTVFormats(CD3DX12_RT_FORMAT_ARRAY(Desc.RTVFormats, Desc.NumRenderTargets)), + SampleDesc(Desc.SampleDesc), + SampleMask(Desc.SampleMask), + CachedPSO(Desc.CachedPSO), + ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) + { + } + CD3DX12_PIPELINE_STATE_STREAM2(const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + CS(CD3DX12_SHADER_BYTECODE(Desc.CS)), + CachedPSO(Desc.CachedPSO) + { + static_cast(DepthStencilState).DepthEnable = false; + } + CD3DX12_PIPELINE_STATE_STREAM_FLAGS Flags; + CD3DX12_PIPELINE_STATE_STREAM_NODE_MASK NodeMask; + CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature; + CD3DX12_PIPELINE_STATE_STREAM_INPUT_LAYOUT InputLayout; + CD3DX12_PIPELINE_STATE_STREAM_IB_STRIP_CUT_VALUE IBStripCutValue; + CD3DX12_PIPELINE_STATE_STREAM_PRIMITIVE_TOPOLOGY PrimitiveTopologyType; + CD3DX12_PIPELINE_STATE_STREAM_VS VS; + CD3DX12_PIPELINE_STATE_STREAM_GS GS; + CD3DX12_PIPELINE_STATE_STREAM_STREAM_OUTPUT StreamOutput; + CD3DX12_PIPELINE_STATE_STREAM_HS HS; + CD3DX12_PIPELINE_STATE_STREAM_DS DS; + CD3DX12_PIPELINE_STATE_STREAM_PS PS; + CD3DX12_PIPELINE_STATE_STREAM_AS AS; + CD3DX12_PIPELINE_STATE_STREAM_MS MS; + CD3DX12_PIPELINE_STATE_STREAM_CS CS; + CD3DX12_PIPELINE_STATE_STREAM_BLEND_DESC BlendState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL1 DepthStencilState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT DSVFormat; + CD3DX12_PIPELINE_STATE_STREAM_RASTERIZER RasterizerState; + CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS RTVFormats; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_DESC SampleDesc; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_MASK SampleMask; + CD3DX12_PIPELINE_STATE_STREAM_CACHED_PSO CachedPSO; + CD3DX12_PIPELINE_STATE_STREAM_VIEW_INSTANCING ViewInstancingDesc; + D3D12_GRAPHICS_PIPELINE_STATE_DESC GraphicsDescV0() const noexcept + { + D3D12_GRAPHICS_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.InputLayout = this->InputLayout; + D.IBStripCutValue = this->IBStripCutValue; + D.PrimitiveTopologyType = this->PrimitiveTopologyType; + D.VS = this->VS; + D.GS = this->GS; + D.StreamOutput = this->StreamOutput; + D.HS = this->HS; + D.DS = this->DS; + D.PS = this->PS; + D.BlendState = this->BlendState; + D.DepthStencilState = + CD3DX12_DEPTH_STENCIL_DESC1(D3D12_DEPTH_STENCIL_DESC1(this->DepthStencilState)); + D.DSVFormat = this->DSVFormat; + D.RasterizerState = this->RasterizerState; + D.NumRenderTargets = D3D12_RT_FORMAT_ARRAY(this->RTVFormats).NumRenderTargets; + memcpy( + D.RTVFormats, D3D12_RT_FORMAT_ARRAY(this->RTVFormats).RTFormats, sizeof(D.RTVFormats)); + D.SampleDesc = this->SampleDesc; + D.SampleMask = this->SampleMask; + D.CachedPSO = this->CachedPSO; + return D; + } + D3D12_COMPUTE_PIPELINE_STATE_DESC ComputeDescV0() const noexcept + { + D3D12_COMPUTE_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.CS = this->CS; + D.CachedPSO = this->CachedPSO; + return D; + } +}; + +// CD3DX12_PIPELINE_STATE_STREAM1 Works on OS Build 16299+ (where there is a new view instancing +// subobject). Use CD3DX12_PIPELINE_STATE_STREAM for OS Build 15063+ support. +struct CD3DX12_PIPELINE_STATE_STREAM1 +{ + CD3DX12_PIPELINE_STATE_STREAM1() = default; + // Mesh and amplification shaders must be set manually, since they do not have representation in + // D3D12_GRAPHICS_PIPELINE_STATE_DESC + CD3DX12_PIPELINE_STATE_STREAM1(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + InputLayout(Desc.InputLayout), + IBStripCutValue(Desc.IBStripCutValue), + PrimitiveTopologyType(Desc.PrimitiveTopologyType), + VS(Desc.VS), + GS(Desc.GS), + StreamOutput(Desc.StreamOutput), + HS(Desc.HS), + DS(Desc.DS), + PS(Desc.PS), + BlendState(CD3DX12_BLEND_DESC(Desc.BlendState)), + DepthStencilState(CD3DX12_DEPTH_STENCIL_DESC1(Desc.DepthStencilState)), + DSVFormat(Desc.DSVFormat), + RasterizerState(CD3DX12_RASTERIZER_DESC(Desc.RasterizerState)), + RTVFormats(CD3DX12_RT_FORMAT_ARRAY(Desc.RTVFormats, Desc.NumRenderTargets)), + SampleDesc(Desc.SampleDesc), + SampleMask(Desc.SampleMask), + CachedPSO(Desc.CachedPSO), + ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) + { + } + CD3DX12_PIPELINE_STATE_STREAM1(const D3DX12_MESH_SHADER_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + PrimitiveTopologyType(Desc.PrimitiveTopologyType), + PS(Desc.PS), + BlendState(CD3DX12_BLEND_DESC(Desc.BlendState)), + DepthStencilState(CD3DX12_DEPTH_STENCIL_DESC1(Desc.DepthStencilState)), + DSVFormat(Desc.DSVFormat), + RasterizerState(CD3DX12_RASTERIZER_DESC(Desc.RasterizerState)), + RTVFormats(CD3DX12_RT_FORMAT_ARRAY(Desc.RTVFormats, Desc.NumRenderTargets)), + SampleDesc(Desc.SampleDesc), + SampleMask(Desc.SampleMask), + CachedPSO(Desc.CachedPSO), + ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) + { + } + CD3DX12_PIPELINE_STATE_STREAM1(const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + CS(CD3DX12_SHADER_BYTECODE(Desc.CS)), + CachedPSO(Desc.CachedPSO) + { + static_cast(DepthStencilState).DepthEnable = false; + } + CD3DX12_PIPELINE_STATE_STREAM_FLAGS Flags; + CD3DX12_PIPELINE_STATE_STREAM_NODE_MASK NodeMask; + CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature; + CD3DX12_PIPELINE_STATE_STREAM_INPUT_LAYOUT InputLayout; + CD3DX12_PIPELINE_STATE_STREAM_IB_STRIP_CUT_VALUE IBStripCutValue; + CD3DX12_PIPELINE_STATE_STREAM_PRIMITIVE_TOPOLOGY PrimitiveTopologyType; + CD3DX12_PIPELINE_STATE_STREAM_VS VS; + CD3DX12_PIPELINE_STATE_STREAM_GS GS; + CD3DX12_PIPELINE_STATE_STREAM_STREAM_OUTPUT StreamOutput; + CD3DX12_PIPELINE_STATE_STREAM_HS HS; + CD3DX12_PIPELINE_STATE_STREAM_DS DS; + CD3DX12_PIPELINE_STATE_STREAM_PS PS; + CD3DX12_PIPELINE_STATE_STREAM_CS CS; + CD3DX12_PIPELINE_STATE_STREAM_BLEND_DESC BlendState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL1 DepthStencilState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT DSVFormat; + CD3DX12_PIPELINE_STATE_STREAM_RASTERIZER RasterizerState; + CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS RTVFormats; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_DESC SampleDesc; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_MASK SampleMask; + CD3DX12_PIPELINE_STATE_STREAM_CACHED_PSO CachedPSO; + CD3DX12_PIPELINE_STATE_STREAM_VIEW_INSTANCING ViewInstancingDesc; + D3D12_GRAPHICS_PIPELINE_STATE_DESC GraphicsDescV0() const noexcept + { + D3D12_GRAPHICS_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.InputLayout = this->InputLayout; + D.IBStripCutValue = this->IBStripCutValue; + D.PrimitiveTopologyType = this->PrimitiveTopologyType; + D.VS = this->VS; + D.GS = this->GS; + D.StreamOutput = this->StreamOutput; + D.HS = this->HS; + D.DS = this->DS; + D.PS = this->PS; + D.BlendState = this->BlendState; + D.DepthStencilState = + CD3DX12_DEPTH_STENCIL_DESC1(D3D12_DEPTH_STENCIL_DESC1(this->DepthStencilState)); + D.DSVFormat = this->DSVFormat; + D.RasterizerState = this->RasterizerState; + D.NumRenderTargets = D3D12_RT_FORMAT_ARRAY(this->RTVFormats).NumRenderTargets; + memcpy( + D.RTVFormats, D3D12_RT_FORMAT_ARRAY(this->RTVFormats).RTFormats, sizeof(D.RTVFormats)); + D.SampleDesc = this->SampleDesc; + D.SampleMask = this->SampleMask; + D.CachedPSO = this->CachedPSO; + return D; + } + D3D12_COMPUTE_PIPELINE_STATE_DESC ComputeDescV0() const noexcept + { + D3D12_COMPUTE_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.CS = this->CS; + D.CachedPSO = this->CachedPSO; + return D; + } +}; + +struct CD3DX12_PIPELINE_MESH_STATE_STREAM +{ + CD3DX12_PIPELINE_MESH_STATE_STREAM() = default; + CD3DX12_PIPELINE_MESH_STATE_STREAM(const D3DX12_MESH_SHADER_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + PS(Desc.PS), + AS(Desc.AS), + MS(Desc.MS), + BlendState(CD3DX12_BLEND_DESC(Desc.BlendState)), + DepthStencilState(CD3DX12_DEPTH_STENCIL_DESC1(Desc.DepthStencilState)), + DSVFormat(Desc.DSVFormat), + RasterizerState(CD3DX12_RASTERIZER_DESC(Desc.RasterizerState)), + RTVFormats(CD3DX12_RT_FORMAT_ARRAY(Desc.RTVFormats, Desc.NumRenderTargets)), + SampleDesc(Desc.SampleDesc), + SampleMask(Desc.SampleMask), + CachedPSO(Desc.CachedPSO), + ViewInstancingDesc(CD3DX12_VIEW_INSTANCING_DESC(CD3DX12_DEFAULT())) + { + } + CD3DX12_PIPELINE_STATE_STREAM_FLAGS Flags; + CD3DX12_PIPELINE_STATE_STREAM_NODE_MASK NodeMask; + CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature; + CD3DX12_PIPELINE_STATE_STREAM_PS PS; + CD3DX12_PIPELINE_STATE_STREAM_AS AS; + CD3DX12_PIPELINE_STATE_STREAM_MS MS; + CD3DX12_PIPELINE_STATE_STREAM_BLEND_DESC BlendState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL1 DepthStencilState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT DSVFormat; + CD3DX12_PIPELINE_STATE_STREAM_RASTERIZER RasterizerState; + CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS RTVFormats; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_DESC SampleDesc; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_MASK SampleMask; + CD3DX12_PIPELINE_STATE_STREAM_CACHED_PSO CachedPSO; + CD3DX12_PIPELINE_STATE_STREAM_VIEW_INSTANCING ViewInstancingDesc; + D3DX12_MESH_SHADER_PIPELINE_STATE_DESC MeshShaderDescV0() const noexcept + { + D3DX12_MESH_SHADER_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.PS = this->PS; + D.AS = this->AS; + D.MS = this->MS; + D.BlendState = this->BlendState; + D.DepthStencilState = + CD3DX12_DEPTH_STENCIL_DESC1(D3D12_DEPTH_STENCIL_DESC1(this->DepthStencilState)); + D.DSVFormat = this->DSVFormat; + D.RasterizerState = this->RasterizerState; + D.NumRenderTargets = D3D12_RT_FORMAT_ARRAY(this->RTVFormats).NumRenderTargets; + memcpy( + D.RTVFormats, D3D12_RT_FORMAT_ARRAY(this->RTVFormats).RTFormats, sizeof(D.RTVFormats)); + D.SampleDesc = this->SampleDesc; + D.SampleMask = this->SampleMask; + D.CachedPSO = this->CachedPSO; + return D; + } +}; + +// CD3DX12_PIPELINE_STATE_STREAM works on OS Build 15063+ but does not support new subobject(s) +// added in OS Build 16299+. See CD3DX12_PIPELINE_STATE_STREAM1 for instance. +struct CD3DX12_PIPELINE_STATE_STREAM +{ + CD3DX12_PIPELINE_STATE_STREAM() = default; + CD3DX12_PIPELINE_STATE_STREAM(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + InputLayout(Desc.InputLayout), + IBStripCutValue(Desc.IBStripCutValue), + PrimitiveTopologyType(Desc.PrimitiveTopologyType), + VS(Desc.VS), + GS(Desc.GS), + StreamOutput(Desc.StreamOutput), + HS(Desc.HS), + DS(Desc.DS), + PS(Desc.PS), + BlendState(CD3DX12_BLEND_DESC(Desc.BlendState)), + DepthStencilState(CD3DX12_DEPTH_STENCIL_DESC1(Desc.DepthStencilState)), + DSVFormat(Desc.DSVFormat), + RasterizerState(CD3DX12_RASTERIZER_DESC(Desc.RasterizerState)), + RTVFormats(CD3DX12_RT_FORMAT_ARRAY(Desc.RTVFormats, Desc.NumRenderTargets)), + SampleDesc(Desc.SampleDesc), + SampleMask(Desc.SampleMask), + CachedPSO(Desc.CachedPSO) + { + } + CD3DX12_PIPELINE_STATE_STREAM(const D3D12_COMPUTE_PIPELINE_STATE_DESC& Desc) + noexcept : + Flags(Desc.Flags), + NodeMask(Desc.NodeMask), + pRootSignature(Desc.pRootSignature), + CS(CD3DX12_SHADER_BYTECODE(Desc.CS)), + CachedPSO(Desc.CachedPSO) + { + } + CD3DX12_PIPELINE_STATE_STREAM_FLAGS Flags; + CD3DX12_PIPELINE_STATE_STREAM_NODE_MASK NodeMask; + CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature; + CD3DX12_PIPELINE_STATE_STREAM_INPUT_LAYOUT InputLayout; + CD3DX12_PIPELINE_STATE_STREAM_IB_STRIP_CUT_VALUE IBStripCutValue; + CD3DX12_PIPELINE_STATE_STREAM_PRIMITIVE_TOPOLOGY PrimitiveTopologyType; + CD3DX12_PIPELINE_STATE_STREAM_VS VS; + CD3DX12_PIPELINE_STATE_STREAM_GS GS; + CD3DX12_PIPELINE_STATE_STREAM_STREAM_OUTPUT StreamOutput; + CD3DX12_PIPELINE_STATE_STREAM_HS HS; + CD3DX12_PIPELINE_STATE_STREAM_DS DS; + CD3DX12_PIPELINE_STATE_STREAM_PS PS; + CD3DX12_PIPELINE_STATE_STREAM_CS CS; + CD3DX12_PIPELINE_STATE_STREAM_BLEND_DESC BlendState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL1 DepthStencilState; + CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT DSVFormat; + CD3DX12_PIPELINE_STATE_STREAM_RASTERIZER RasterizerState; + CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS RTVFormats; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_DESC SampleDesc; + CD3DX12_PIPELINE_STATE_STREAM_SAMPLE_MASK SampleMask; + CD3DX12_PIPELINE_STATE_STREAM_CACHED_PSO CachedPSO; + D3D12_GRAPHICS_PIPELINE_STATE_DESC GraphicsDescV0() const noexcept + { + D3D12_GRAPHICS_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.InputLayout = this->InputLayout; + D.IBStripCutValue = this->IBStripCutValue; + D.PrimitiveTopologyType = this->PrimitiveTopologyType; + D.VS = this->VS; + D.GS = this->GS; + D.StreamOutput = this->StreamOutput; + D.HS = this->HS; + D.DS = this->DS; + D.PS = this->PS; + D.BlendState = this->BlendState; + D.DepthStencilState = + CD3DX12_DEPTH_STENCIL_DESC1(D3D12_DEPTH_STENCIL_DESC1(this->DepthStencilState)); + D.DSVFormat = this->DSVFormat; + D.RasterizerState = this->RasterizerState; + D.NumRenderTargets = D3D12_RT_FORMAT_ARRAY(this->RTVFormats).NumRenderTargets; + memcpy( + D.RTVFormats, D3D12_RT_FORMAT_ARRAY(this->RTVFormats).RTFormats, sizeof(D.RTVFormats)); + D.SampleDesc = this->SampleDesc; + D.SampleMask = this->SampleMask; + D.CachedPSO = this->CachedPSO; + return D; + } + D3D12_COMPUTE_PIPELINE_STATE_DESC ComputeDescV0() const noexcept + { + D3D12_COMPUTE_PIPELINE_STATE_DESC D; + D.Flags = this->Flags; + D.NodeMask = this->NodeMask; + D.pRootSignature = this->pRootSignature; + D.CS = this->CS; + D.CachedPSO = this->CachedPSO; + return D; + } +}; + +struct CD3DX12_PIPELINE_STATE_STREAM2_PARSE_HELPER : public ID3DX12PipelineParserCallbacks +{ + CD3DX12_PIPELINE_STATE_STREAM2 PipelineStream; + CD3DX12_PIPELINE_STATE_STREAM2_PARSE_HELPER() noexcept : SeenDSS(false) + { + // Adjust defaults to account for absent members. + PipelineStream.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + + // Depth disabled if no DSV format specified. + static_cast(PipelineStream.DepthStencilState).DepthEnable = + false; + } + + // ID3DX12PipelineParserCallbacks + void FlagsCb(D3D12_PIPELINE_STATE_FLAGS Flags) override { PipelineStream.Flags = Flags; } + void NodeMaskCb(UINT NodeMask) override { PipelineStream.NodeMask = NodeMask; } + void RootSignatureCb(ID3D12RootSignature* pRootSignature) override + { + PipelineStream.pRootSignature = pRootSignature; + } + void InputLayoutCb(const D3D12_INPUT_LAYOUT_DESC& InputLayout) override + { + PipelineStream.InputLayout = InputLayout; + } + void IBStripCutValueCb(D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue) override + { + PipelineStream.IBStripCutValue = IBStripCutValue; + } + void PrimitiveTopologyTypeCb(D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType) override + { + PipelineStream.PrimitiveTopologyType = PrimitiveTopologyType; + } + void VSCb(const D3D12_SHADER_BYTECODE& VS) override { PipelineStream.VS = VS; } + void GSCb(const D3D12_SHADER_BYTECODE& GS) override { PipelineStream.GS = GS; } + void StreamOutputCb(const D3D12_STREAM_OUTPUT_DESC& StreamOutput) override + { + PipelineStream.StreamOutput = StreamOutput; + } + void HSCb(const D3D12_SHADER_BYTECODE& HS) override { PipelineStream.HS = HS; } + void DSCb(const D3D12_SHADER_BYTECODE& DS) override { PipelineStream.DS = DS; } + void PSCb(const D3D12_SHADER_BYTECODE& PS) override { PipelineStream.PS = PS; } + void CSCb(const D3D12_SHADER_BYTECODE& CS) override { PipelineStream.CS = CS; } + void ASCb(const D3D12_SHADER_BYTECODE& AS) override { PipelineStream.AS = AS; } + void MSCb(const D3D12_SHADER_BYTECODE& MS) override { PipelineStream.MS = MS; } + void BlendStateCb(const D3D12_BLEND_DESC& BlendState) override + { + PipelineStream.BlendState = CD3DX12_BLEND_DESC(BlendState); + } + void DepthStencilStateCb(const D3D12_DEPTH_STENCIL_DESC& DepthStencilState) override + { + PipelineStream.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC1(DepthStencilState); + SeenDSS = true; + } + void DepthStencilState1Cb(const D3D12_DEPTH_STENCIL_DESC1& DepthStencilState) override + { + PipelineStream.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC1(DepthStencilState); + SeenDSS = true; + } + void DSVFormatCb(DXGI_FORMAT DSVFormat) override + { + PipelineStream.DSVFormat = DSVFormat; + if (!SeenDSS && DSVFormat != DXGI_FORMAT_UNKNOWN) + { + // Re-enable depth for the default state. + static_cast(PipelineStream.DepthStencilState).DepthEnable = + true; + } + } + void RasterizerStateCb(const D3D12_RASTERIZER_DESC& RasterizerState) override + { + PipelineStream.RasterizerState = CD3DX12_RASTERIZER_DESC(RasterizerState); + } + void RTVFormatsCb(const D3D12_RT_FORMAT_ARRAY& RTVFormats) override + { + PipelineStream.RTVFormats = RTVFormats; + } + void SampleDescCb(const DXGI_SAMPLE_DESC& SampleDesc) override + { + PipelineStream.SampleDesc = SampleDesc; + } + void SampleMaskCb(UINT SampleMask) override { PipelineStream.SampleMask = SampleMask; } + void ViewInstancingCb(const D3D12_VIEW_INSTANCING_DESC& ViewInstancingDesc) override + { + PipelineStream.ViewInstancingDesc = CD3DX12_VIEW_INSTANCING_DESC(ViewInstancingDesc); + } + void CachedPSOCb(const D3D12_CACHED_PIPELINE_STATE& CachedPSO) override + { + PipelineStream.CachedPSO = CachedPSO; + } + +private: + bool SeenDSS; +}; + +struct CD3DX12_PIPELINE_STATE_STREAM_PARSE_HELPER : public ID3DX12PipelineParserCallbacks +{ + CD3DX12_PIPELINE_STATE_STREAM1 PipelineStream; + CD3DX12_PIPELINE_STATE_STREAM_PARSE_HELPER() noexcept : SeenDSS(false) + { + // Adjust defaults to account for absent members. + PipelineStream.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + + // Depth disabled if no DSV format specified. + static_cast(PipelineStream.DepthStencilState).DepthEnable = + false; + } + + // ID3DX12PipelineParserCallbacks + void FlagsCb(D3D12_PIPELINE_STATE_FLAGS Flags) override { PipelineStream.Flags = Flags; } + void NodeMaskCb(UINT NodeMask) override { PipelineStream.NodeMask = NodeMask; } + void RootSignatureCb(ID3D12RootSignature* pRootSignature) override + { + PipelineStream.pRootSignature = pRootSignature; + } + void InputLayoutCb(const D3D12_INPUT_LAYOUT_DESC& InputLayout) override + { + PipelineStream.InputLayout = InputLayout; + } + void IBStripCutValueCb(D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue) override + { + PipelineStream.IBStripCutValue = IBStripCutValue; + } + void PrimitiveTopologyTypeCb(D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType) override + { + PipelineStream.PrimitiveTopologyType = PrimitiveTopologyType; + } + void VSCb(const D3D12_SHADER_BYTECODE& VS) override { PipelineStream.VS = VS; } + void GSCb(const D3D12_SHADER_BYTECODE& GS) override { PipelineStream.GS = GS; } + void StreamOutputCb(const D3D12_STREAM_OUTPUT_DESC& StreamOutput) override + { + PipelineStream.StreamOutput = StreamOutput; + } + void HSCb(const D3D12_SHADER_BYTECODE& HS) override { PipelineStream.HS = HS; } + void DSCb(const D3D12_SHADER_BYTECODE& DS) override { PipelineStream.DS = DS; } + void PSCb(const D3D12_SHADER_BYTECODE& PS) override { PipelineStream.PS = PS; } + void CSCb(const D3D12_SHADER_BYTECODE& CS) override { PipelineStream.CS = CS; } + void BlendStateCb(const D3D12_BLEND_DESC& BlendState) override + { + PipelineStream.BlendState = CD3DX12_BLEND_DESC(BlendState); + } + void DepthStencilStateCb(const D3D12_DEPTH_STENCIL_DESC& DepthStencilState) override + { + PipelineStream.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC1(DepthStencilState); + SeenDSS = true; + } + void DepthStencilState1Cb(const D3D12_DEPTH_STENCIL_DESC1& DepthStencilState) override + { + PipelineStream.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC1(DepthStencilState); + SeenDSS = true; + } + void DSVFormatCb(DXGI_FORMAT DSVFormat) override + { + PipelineStream.DSVFormat = DSVFormat; + if (!SeenDSS && DSVFormat != DXGI_FORMAT_UNKNOWN) + { + // Re-enable depth for the default state. + static_cast(PipelineStream.DepthStencilState).DepthEnable = + true; + } + } + void RasterizerStateCb(const D3D12_RASTERIZER_DESC& RasterizerState) override + { + PipelineStream.RasterizerState = CD3DX12_RASTERIZER_DESC(RasterizerState); + } + void RTVFormatsCb(const D3D12_RT_FORMAT_ARRAY& RTVFormats) override + { + PipelineStream.RTVFormats = RTVFormats; + } + void SampleDescCb(const DXGI_SAMPLE_DESC& SampleDesc) override + { + PipelineStream.SampleDesc = SampleDesc; + } + void SampleMaskCb(UINT SampleMask) override { PipelineStream.SampleMask = SampleMask; } + void ViewInstancingCb(const D3D12_VIEW_INSTANCING_DESC& ViewInstancingDesc) override + { + PipelineStream.ViewInstancingDesc = CD3DX12_VIEW_INSTANCING_DESC(ViewInstancingDesc); + } + void CachedPSOCb(const D3D12_CACHED_PIPELINE_STATE& CachedPSO) override + { + PipelineStream.CachedPSO = CachedPSO; + } + +private: + bool SeenDSS; +}; + +inline D3D12_PIPELINE_STATE_SUBOBJECT_TYPE D3DX12GetBaseSubobjectType( + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE SubobjectType) noexcept +{ + switch (SubobjectType) + { + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL1: + return D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL; + default: + return SubobjectType; + } +} + +inline HRESULT D3DX12ParsePipelineStream( + const D3D12_PIPELINE_STATE_STREAM_DESC& Desc, ID3DX12PipelineParserCallbacks* pCallbacks) +{ + if (pCallbacks == nullptr) + { + return E_INVALIDARG; + } + + if (Desc.SizeInBytes == 0 || Desc.pPipelineStateSubobjectStream == nullptr) + { + pCallbacks->ErrorBadInputParameter(1); // first parameter issue + return E_INVALIDARG; + } + + bool SubobjectSeen[D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MAX_VALID] = {}; + for (SIZE_T CurOffset = 0, SizeOfSubobject = 0; CurOffset < Desc.SizeInBytes; + CurOffset += SizeOfSubobject) + { + BYTE* pStream = static_cast(Desc.pPipelineStateSubobjectStream) + CurOffset; + auto SubobjectType = *reinterpret_cast(pStream); + if (SubobjectType < 0 || SubobjectType >= D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MAX_VALID) + { + pCallbacks->ErrorUnknownSubobject(SubobjectType); + return E_INVALIDARG; + } + if (SubobjectSeen[D3DX12GetBaseSubobjectType(SubobjectType)]) + { + pCallbacks->ErrorDuplicateSubobject(SubobjectType); + return E_INVALIDARG; // disallow subobject duplicates in a stream + } + SubobjectSeen[SubobjectType] = true; + switch (SubobjectType) + { + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE: + pCallbacks->RootSignatureCb( + *reinterpret_cast( + pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::pRootSignature); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS: + pCallbacks->VSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::VS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS: + pCallbacks->PSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::PS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS: + pCallbacks->DSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::DS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS: + pCallbacks->HSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::HS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS: + pCallbacks->GSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::GS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS: + pCallbacks->CSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::CS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS: + pCallbacks->ASCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM2::AS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MS: + pCallbacks->MSCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM2::MS); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_STREAM_OUTPUT: + pCallbacks->StreamOutputCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::StreamOutput); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND: + pCallbacks->BlendStateCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::BlendState); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK: + pCallbacks->SampleMaskCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::SampleMask); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER: + pCallbacks->RasterizerStateCb( + *reinterpret_cast( + pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::RasterizerState); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL: + pCallbacks->DepthStencilStateCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL1: + pCallbacks->DepthStencilState1Cb( + *reinterpret_cast( + pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::DepthStencilState); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_INPUT_LAYOUT: + pCallbacks->InputLayoutCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::InputLayout); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_IB_STRIP_CUT_VALUE: + pCallbacks->IBStripCutValueCb( + *reinterpret_cast( + pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::IBStripCutValue); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PRIMITIVE_TOPOLOGY: + pCallbacks->PrimitiveTopologyTypeCb( + *reinterpret_cast( + pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::PrimitiveTopologyType); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS: + pCallbacks->RTVFormatsCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::RTVFormats); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT: + pCallbacks->DSVFormatCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::DSVFormat); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC: + pCallbacks->SampleDescCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::SampleDesc); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_NODE_MASK: + pCallbacks->NodeMaskCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::NodeMask); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CACHED_PSO: + pCallbacks->CachedPSOCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::CachedPSO); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_FLAGS: + pCallbacks->FlagsCb( + *reinterpret_cast(pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM::Flags); + break; + case D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING: + pCallbacks->ViewInstancingCb( + *reinterpret_cast( + pStream)); + SizeOfSubobject = sizeof(CD3DX12_PIPELINE_STATE_STREAM1::ViewInstancingDesc); + break; + default: + pCallbacks->ErrorUnknownSubobject(SubobjectType); + return E_INVALIDARG; + } + } + + return S_OK; +} + +//------------------------------------------------------------------------------------------------ +inline bool operator==(const D3D12_CLEAR_VALUE& a, const D3D12_CLEAR_VALUE& b) noexcept +{ + if (a.Format != b.Format) + return false; + if (a.Format == DXGI_FORMAT_D24_UNORM_S8_UINT || a.Format == DXGI_FORMAT_D16_UNORM || + a.Format == DXGI_FORMAT_D32_FLOAT || a.Format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT) + { + return (a.DepthStencil.Depth == b.DepthStencil.Depth) && + (a.DepthStencil.Stencil == b.DepthStencil.Stencil); + } + else + { + return (a.Color[0] == b.Color[0]) && (a.Color[1] == b.Color[1]) && + (a.Color[2] == b.Color[2]) && (a.Color[3] == b.Color[3]); + } +} +inline bool operator==(const D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS& a, + const D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS& b) noexcept +{ + return a.ClearValue == b.ClearValue; +} +inline bool operator==(const D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS& a, + const D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_PARAMETERS& b) noexcept +{ + if (a.pSrcResource != b.pSrcResource) + return false; + if (a.pDstResource != b.pDstResource) + return false; + if (a.SubresourceCount != b.SubresourceCount) + return false; + if (a.Format != b.Format) + return false; + if (a.ResolveMode != b.ResolveMode) + return false; + if (a.PreserveResolveSource != b.PreserveResolveSource) + return false; + return true; +} +inline bool operator==(const D3D12_RENDER_PASS_BEGINNING_ACCESS& a, + const D3D12_RENDER_PASS_BEGINNING_ACCESS& b) noexcept +{ + if (a.Type != b.Type) + return false; + if (a.Type == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR && !(a.Clear == b.Clear)) + return false; + return true; +} +inline bool operator==( + const D3D12_RENDER_PASS_ENDING_ACCESS& a, const D3D12_RENDER_PASS_ENDING_ACCESS& b) noexcept +{ + if (a.Type != b.Type) + return false; + if (a.Type == D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE && !(a.Resolve == b.Resolve)) + return false; + return true; +} +inline bool operator==(const D3D12_RENDER_PASS_RENDER_TARGET_DESC& a, + const D3D12_RENDER_PASS_RENDER_TARGET_DESC& b) noexcept +{ + if (a.cpuDescriptor.ptr != b.cpuDescriptor.ptr) + return false; + if (!(a.BeginningAccess == b.BeginningAccess)) + return false; + if (!(a.EndingAccess == b.EndingAccess)) + return false; + return true; +} +inline bool operator==(const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC& a, + const D3D12_RENDER_PASS_DEPTH_STENCIL_DESC& b) noexcept +{ + if (a.cpuDescriptor.ptr != b.cpuDescriptor.ptr) + return false; + if (!(a.DepthBeginningAccess == b.DepthBeginningAccess)) + return false; + if (!(a.StencilBeginningAccess == b.StencilBeginningAccess)) + return false; + if (!(a.DepthEndingAccess == b.DepthEndingAccess)) + return false; + if (!(a.StencilEndingAccess == b.StencilEndingAccess)) + return false; + return true; +} + +#ifndef D3DX12_NO_STATE_OBJECT_HELPERS + +//================================================================================================ +// D3DX12 State Object Creation Helpers +// +// Helper classes for creating new style state objects out of an arbitrary set of subobjects. +// Uses STL +// +// Start by instantiating CD3DX12_STATE_OBJECT_DESC (see it's public methods). +// One of its methods is CreateSubobject(), which has a comment showing a couple of options for +// defining subobjects using the helper classes for each subobject (CD3DX12_DXIL_LIBRARY_SUBOBJECT +// etc.). The subobject helpers each have methods specific to the subobject for configuring it's +// contents. +// +//================================================================================================ +#include +#include +#include +#include +#ifndef D3DX12_USE_ATL +#include +#define D3DX12_COM_PTR Microsoft::WRL::ComPtr +#define D3DX12_COM_PTR_GET(x) x.Get() +#define D3DX12_COM_PTR_ADDRESSOF(x) x.GetAddressOf() +#else +#include +#define D3DX12_COM_PTR ATL::CComPtr +#define D3DX12_COM_PTR_GET(x) x.p +#define D3DX12_COM_PTR_ADDRESSOF(x) &x.p +#endif + +//------------------------------------------------------------------------------------------------ +class CD3DX12_STATE_OBJECT_DESC +{ +public: + CD3DX12_STATE_OBJECT_DESC() noexcept { Init(D3D12_STATE_OBJECT_TYPE_COLLECTION); } + CD3DX12_STATE_OBJECT_DESC(D3D12_STATE_OBJECT_TYPE Type) noexcept { Init(Type); } + void SetStateObjectType(D3D12_STATE_OBJECT_TYPE Type) noexcept { m_Desc.Type = Type; } + operator const D3D12_STATE_OBJECT_DESC&() + { + // Do final preparation work + m_RepointedAssociations.clear(); + m_SubobjectArray.clear(); + m_SubobjectArray.reserve(m_Desc.NumSubobjects); + // Flatten subobjects into an array (each flattened subobject still has a + // member that's a pointer to it's desc that's not flattened) + for (auto Iter = m_SubobjectList.begin(); Iter != m_SubobjectList.end(); Iter++) + { + m_SubobjectArray.push_back(*Iter); + // Store new location in array so we can redirect pointers contained in subobjects + Iter->pSubobjectArrayLocation = &m_SubobjectArray.back(); + } + // For subobjects with pointer fields, create a new copy of those subobject definitions + // with fixed pointers + for (UINT i = 0; i < m_Desc.NumSubobjects; i++) + { + if (m_SubobjectArray[i].Type == + D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION) + { + auto pOriginalSubobjectAssociation = + static_cast( + m_SubobjectArray[i].pDesc); + D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION Repointed = *pOriginalSubobjectAssociation; + auto pWrapper = static_cast( + pOriginalSubobjectAssociation->pSubobjectToAssociate); + Repointed.pSubobjectToAssociate = pWrapper->pSubobjectArrayLocation; + m_RepointedAssociations.push_back(Repointed); + m_SubobjectArray[i].pDesc = &m_RepointedAssociations.back(); + } + } + // Below: using ugly way to get pointer in case .data() is not defined + m_Desc.pSubobjects = m_Desc.NumSubobjects ? &m_SubobjectArray[0] : nullptr; + return m_Desc; + } + operator const D3D12_STATE_OBJECT_DESC*() + { + // Cast calls the above final preparation work + return &static_cast(*this); + } + + // CreateSubobject creates a sububject helper (e.g. CD3DX12_HIT_GROUP_SUBOBJECT) + // whose lifetime is owned by this class. + // e.g. + // + // CD3DX12_STATE_OBJECT_DESC Collection1(D3D12_STATE_OBJECT_TYPE_COLLECTION); + // auto Lib0 = Collection1.CreateSubobject(); + // Lib0->SetDXILLibrary(&pMyAppDxilLibs[0]); + // Lib0->DefineExport(L"rayGenShader0"); // in practice these export listings might be + // // data/engine driven + // etc. + // + // Alternatively, users can instantiate sububject helpers explicitly, such as via local + // variables instead, passing the state object desc that should point to it into the helper + // constructor (or call mySubobjectHelper.AddToStateObject(Collection1)). + // In this alternative scenario, the user must keep the subobject alive as long as the state + // object it is associated with is alive, else it's pointer references will be stale. + // e.g. + // + // CD3DX12_STATE_OBJECT_DESC RaytracingState2(D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE); + // CD3DX12_DXIL_LIBRARY_SUBOBJECT LibA(RaytracingState2); + // LibA.SetDXILLibrary(&pMyAppDxilLibs[4]); // not manually specifying exports + // // - meaning all exports in the libraries + // // are exported + // etc. + + template + T* CreateSubobject() + { + T* pSubobject = new T(*this); + m_OwnedSubobjectHelpers.emplace_back(pSubobject); + return pSubobject; + } + +private: + D3D12_STATE_SUBOBJECT* TrackSubobject(D3D12_STATE_SUBOBJECT_TYPE Type, void* pDesc) + { + SUBOBJECT_WRAPPER Subobject; + Subobject.pSubobjectArrayLocation = nullptr; + Subobject.Type = Type; + Subobject.pDesc = pDesc; + m_SubobjectList.push_back(Subobject); + m_Desc.NumSubobjects++; + return &m_SubobjectList.back(); + } + void Init(D3D12_STATE_OBJECT_TYPE Type) noexcept + { + SetStateObjectType(Type); + m_Desc.pSubobjects = nullptr; + m_Desc.NumSubobjects = 0; + m_SubobjectList.clear(); + m_SubobjectArray.clear(); + m_RepointedAssociations.clear(); + } + typedef struct SUBOBJECT_WRAPPER : public D3D12_STATE_SUBOBJECT + { + D3D12_STATE_SUBOBJECT* pSubobjectArrayLocation; // new location when flattened into array + // for repointing pointers in subobjects + } SUBOBJECT_WRAPPER; + D3D12_STATE_OBJECT_DESC m_Desc; + std::list m_SubobjectList; // Pointers to list nodes handed out so + // these can be edited live + std::vector m_SubobjectArray; // Built at the end, copying list contents + + std::list + m_RepointedAssociations; // subobject type that contains pointers to other subobjects, + // repointed to flattened array + + class StringContainer + { + public: + LPCWSTR LocalCopy(LPCWSTR string, bool bSingleString = false) + { + if (string) + { + if (bSingleString) + { + m_Strings.clear(); + m_Strings.push_back(string); + } + else + { + m_Strings.push_back(string); + } + return m_Strings.back().c_str(); + } + else + { + return nullptr; + } + } + void clear() noexcept { m_Strings.clear(); } + + private: + std::list m_Strings; + }; + + class SUBOBJECT_HELPER_BASE + { + public: + SUBOBJECT_HELPER_BASE() noexcept { Init(); } + virtual ~SUBOBJECT_HELPER_BASE() = default; + virtual D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept = 0; + void AddToStateObject(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + m_pSubobject = ContainingStateObject.TrackSubobject(Type(), Data()); + } + + protected: + virtual void* Data() noexcept = 0; + void Init() noexcept { m_pSubobject = nullptr; } + D3D12_STATE_SUBOBJECT* m_pSubobject; + }; + +#if (__cplusplus >= 201103L) + std::list> m_OwnedSubobjectHelpers; +#else + class OWNED_HELPER + { + public: + OWNED_HELPER(const SUBOBJECT_HELPER_BASE* pHelper) noexcept { m_pHelper = pHelper; } + ~OWNED_HELPER() { delete m_pHelper; } + const SUBOBJECT_HELPER_BASE* m_pHelper; + }; + + std::list m_OwnedSubobjectHelpers; +#endif + + friend class CD3DX12_DXIL_LIBRARY_SUBOBJECT; + friend class CD3DX12_EXISTING_COLLECTION_SUBOBJECT; + friend class CD3DX12_SUBOBJECT_TO_EXPORTS_ASSOCIATION_SUBOBJECT; + friend class CD3DX12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION; + friend class CD3DX12_HIT_GROUP_SUBOBJECT; + friend class CD3DX12_RAYTRACING_SHADER_CONFIG_SUBOBJECT; + friend class CD3DX12_RAYTRACING_PIPELINE_CONFIG_SUBOBJECT; + friend class CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT; + friend class CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT; + friend class CD3DX12_LOCAL_ROOT_SIGNATURE_SUBOBJECT; + friend class CD3DX12_STATE_OBJECT_CONFIG_SUBOBJECT; + friend class CD3DX12_NODE_MASK_SUBOBJECT; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_DXIL_LIBRARY_SUBOBJECT : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_DXIL_LIBRARY_SUBOBJECT() noexcept { Init(); } + CD3DX12_DXIL_LIBRARY_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetDXILLibrary(const D3D12_SHADER_BYTECODE* pCode) noexcept + { + static const D3D12_SHADER_BYTECODE Default = {}; + m_Desc.DXILLibrary = pCode ? *pCode : Default; + } + void DefineExport(LPCWSTR Name, LPCWSTR ExportToRename = nullptr, + D3D12_EXPORT_FLAGS Flags = D3D12_EXPORT_FLAG_NONE) + { + D3D12_EXPORT_DESC Export; + Export.Name = m_Strings.LocalCopy(Name); + Export.ExportToRename = m_Strings.LocalCopy(ExportToRename); + Export.Flags = Flags; + m_Exports.push_back(Export); + m_Desc.pExports = + &m_Exports[0]; // using ugly way to get pointer in case .data() is not defined + m_Desc.NumExports = static_cast(m_Exports.size()); + } + template + void DefineExports(LPCWSTR (&Exports)[N]) + { + for (UINT i = 0; i < N; i++) + { + DefineExport(Exports[i]); + } + } + void DefineExports(const LPCWSTR* Exports, UINT N) + { + for (UINT i = 0; i < N; i++) + { + DefineExport(Exports[i]); + } + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_DXIL_LIBRARY_DESC&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + m_Strings.clear(); + m_Exports.clear(); + } + void* Data() noexcept override { return &m_Desc; } + D3D12_DXIL_LIBRARY_DESC m_Desc; + CD3DX12_STATE_OBJECT_DESC::StringContainer m_Strings; + std::vector m_Exports; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_EXISTING_COLLECTION_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_EXISTING_COLLECTION_SUBOBJECT() noexcept { Init(); } + CD3DX12_EXISTING_COLLECTION_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetExistingCollection(ID3D12StateObject* pExistingCollection) noexcept + { + m_Desc.pExistingCollection = pExistingCollection; + m_CollectionRef = pExistingCollection; + } + void DefineExport(LPCWSTR Name, LPCWSTR ExportToRename = nullptr, + D3D12_EXPORT_FLAGS Flags = D3D12_EXPORT_FLAG_NONE) + { + D3D12_EXPORT_DESC Export; + Export.Name = m_Strings.LocalCopy(Name); + Export.ExportToRename = m_Strings.LocalCopy(ExportToRename); + Export.Flags = Flags; + m_Exports.push_back(Export); + m_Desc.pExports = + &m_Exports[0]; // using ugly way to get pointer in case .data() is not defined + m_Desc.NumExports = static_cast(m_Exports.size()); + } + template + void DefineExports(LPCWSTR (&Exports)[N]) + { + for (UINT i = 0; i < N; i++) + { + DefineExport(Exports[i]); + } + } + void DefineExports(const LPCWSTR* Exports, UINT N) + { + for (UINT i = 0; i < N; i++) + { + DefineExport(Exports[i]); + } + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_EXISTING_COLLECTION; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_EXISTING_COLLECTION_DESC&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + m_CollectionRef = nullptr; + m_Strings.clear(); + m_Exports.clear(); + } + void* Data() noexcept override { return &m_Desc; } + D3D12_EXISTING_COLLECTION_DESC m_Desc; + D3DX12_COM_PTR m_CollectionRef; + CD3DX12_STATE_OBJECT_DESC::StringContainer m_Strings; + std::vector m_Exports; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_SUBOBJECT_TO_EXPORTS_ASSOCIATION_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_SUBOBJECT_TO_EXPORTS_ASSOCIATION_SUBOBJECT() noexcept { Init(); } + CD3DX12_SUBOBJECT_TO_EXPORTS_ASSOCIATION_SUBOBJECT( + CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetSubobjectToAssociate(const D3D12_STATE_SUBOBJECT& SubobjectToAssociate) noexcept + { + m_Desc.pSubobjectToAssociate = &SubobjectToAssociate; + } + void AddExport(LPCWSTR Export) + { + m_Desc.NumExports++; + m_Exports.push_back(m_Strings.LocalCopy(Export)); + m_Desc.pExports = + &m_Exports[0]; // using ugly way to get pointer in case .data() is not defined + } + template + void AddExports(LPCWSTR (&Exports)[N]) + { + for (UINT i = 0; i < N; i++) + { + AddExport(Exports[i]); + } + } + void AddExports(const LPCWSTR* Exports, UINT N) + { + for (UINT i = 0; i < N; i++) + { + AddExport(Exports[i]); + } + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + m_Strings.clear(); + m_Exports.clear(); + } + void* Data() noexcept override { return &m_Desc; } + D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION m_Desc; + CD3DX12_STATE_OBJECT_DESC::StringContainer m_Strings; + std::vector m_Exports; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION() noexcept { Init(); } + CD3DX12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetSubobjectNameToAssociate(LPCWSTR SubobjectToAssociate) + { + m_Desc.SubobjectToAssociate = m_SubobjectName.LocalCopy(SubobjectToAssociate, true); + } + void AddExport(LPCWSTR Export) + { + m_Desc.NumExports++; + m_Exports.push_back(m_Strings.LocalCopy(Export)); + m_Desc.pExports = + &m_Exports[0]; // using ugly way to get pointer in case .data() is not defined + } + template + void AddExports(LPCWSTR (&Exports)[N]) + { + for (UINT i = 0; i < N; i++) + { + AddExport(Exports[i]); + } + } + void AddExports(const LPCWSTR* Exports, UINT N) + { + for (UINT i = 0; i < N; i++) + { + AddExport(Exports[i]); + } + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + m_Strings.clear(); + m_SubobjectName.clear(); + m_Exports.clear(); + } + void* Data() noexcept override { return &m_Desc; } + D3D12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION m_Desc; + CD3DX12_STATE_OBJECT_DESC::StringContainer m_Strings; + CD3DX12_STATE_OBJECT_DESC::StringContainer m_SubobjectName; + std::vector m_Exports; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_HIT_GROUP_SUBOBJECT : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_HIT_GROUP_SUBOBJECT() noexcept { Init(); } + CD3DX12_HIT_GROUP_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetHitGroupExport(LPCWSTR exportName) + { + m_Desc.HitGroupExport = m_Strings[0].LocalCopy(exportName, true); + } + void SetHitGroupType(D3D12_HIT_GROUP_TYPE Type) noexcept { m_Desc.Type = Type; } + void SetAnyHitShaderImport(LPCWSTR importName) + { + m_Desc.AnyHitShaderImport = m_Strings[1].LocalCopy(importName, true); + } + void SetClosestHitShaderImport(LPCWSTR importName) + { + m_Desc.ClosestHitShaderImport = m_Strings[2].LocalCopy(importName, true); + } + void SetIntersectionShaderImport(LPCWSTR importName) + { + m_Desc.IntersectionShaderImport = m_Strings[3].LocalCopy(importName, true); + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_HIT_GROUP_DESC&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + for (UINT i = 0; i < m_NumStrings; i++) + { + m_Strings[i].clear(); + } + } + void* Data() noexcept override { return &m_Desc; } + D3D12_HIT_GROUP_DESC m_Desc; + static const UINT m_NumStrings = 4; + CD3DX12_STATE_OBJECT_DESC::StringContainer + m_Strings[m_NumStrings]; // one string for every entrypoint name +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_RAYTRACING_SHADER_CONFIG_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_RAYTRACING_SHADER_CONFIG_SUBOBJECT() noexcept { Init(); } + CD3DX12_RAYTRACING_SHADER_CONFIG_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void Config(UINT MaxPayloadSizeInBytes, UINT MaxAttributeSizeInBytes) noexcept + { + m_Desc.MaxPayloadSizeInBytes = MaxPayloadSizeInBytes; + m_Desc.MaxAttributeSizeInBytes = MaxAttributeSizeInBytes; + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_RAYTRACING_SHADER_CONFIG&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + } + void* Data() noexcept override { return &m_Desc; } + D3D12_RAYTRACING_SHADER_CONFIG m_Desc; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_RAYTRACING_PIPELINE_CONFIG_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_RAYTRACING_PIPELINE_CONFIG_SUBOBJECT() noexcept { Init(); } + CD3DX12_RAYTRACING_PIPELINE_CONFIG_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void Config(UINT MaxTraceRecursionDepth) noexcept + { + m_Desc.MaxTraceRecursionDepth = MaxTraceRecursionDepth; + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_RAYTRACING_PIPELINE_CONFIG&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + } + void* Data() noexcept override { return &m_Desc; } + D3D12_RAYTRACING_PIPELINE_CONFIG m_Desc; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT() noexcept { Init(); } + CD3DX12_RAYTRACING_PIPELINE_CONFIG1_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void Config(UINT MaxTraceRecursionDepth, D3D12_RAYTRACING_PIPELINE_FLAGS Flags) noexcept + { + m_Desc.MaxTraceRecursionDepth = MaxTraceRecursionDepth; + m_Desc.Flags = Flags; + } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG1; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_RAYTRACING_PIPELINE_CONFIG1&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + } + void* Data() noexcept override { return &m_Desc; } + D3D12_RAYTRACING_PIPELINE_CONFIG1 m_Desc; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT() noexcept { Init(); } + CD3DX12_GLOBAL_ROOT_SIGNATURE_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetRootSignature(ID3D12RootSignature* pRootSig) noexcept { m_pRootSig = pRootSig; } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator ID3D12RootSignature*() const noexcept { return D3DX12_COM_PTR_GET(m_pRootSig); } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_pRootSig = nullptr; + } + void* Data() noexcept override { return D3DX12_COM_PTR_ADDRESSOF(m_pRootSig); } + D3DX12_COM_PTR m_pRootSig; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_LOCAL_ROOT_SIGNATURE_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_LOCAL_ROOT_SIGNATURE_SUBOBJECT() noexcept { Init(); } + CD3DX12_LOCAL_ROOT_SIGNATURE_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetRootSignature(ID3D12RootSignature* pRootSig) noexcept { m_pRootSig = pRootSig; } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator ID3D12RootSignature*() const noexcept { return D3DX12_COM_PTR_GET(m_pRootSig); } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_pRootSig = nullptr; + } + void* Data() noexcept override { return D3DX12_COM_PTR_ADDRESSOF(m_pRootSig); } + D3DX12_COM_PTR m_pRootSig; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_STATE_OBJECT_CONFIG_SUBOBJECT + : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_STATE_OBJECT_CONFIG_SUBOBJECT() noexcept { Init(); } + CD3DX12_STATE_OBJECT_CONFIG_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetFlags(D3D12_STATE_OBJECT_FLAGS Flags) noexcept { m_Desc.Flags = Flags; } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_STATE_OBJECT_CONFIG; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_STATE_OBJECT_CONFIG&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + } + void* Data() noexcept override { return &m_Desc; } + D3D12_STATE_OBJECT_CONFIG m_Desc; +}; + +//------------------------------------------------------------------------------------------------ +class CD3DX12_NODE_MASK_SUBOBJECT : public CD3DX12_STATE_OBJECT_DESC::SUBOBJECT_HELPER_BASE +{ +public: + CD3DX12_NODE_MASK_SUBOBJECT() noexcept { Init(); } + CD3DX12_NODE_MASK_SUBOBJECT(CD3DX12_STATE_OBJECT_DESC& ContainingStateObject) + { + Init(); + AddToStateObject(ContainingStateObject); + } + void SetNodeMask(UINT NodeMask) noexcept { m_Desc.NodeMask = NodeMask; } + D3D12_STATE_SUBOBJECT_TYPE Type() const noexcept override + { + return D3D12_STATE_SUBOBJECT_TYPE_NODE_MASK; + } + operator const D3D12_STATE_SUBOBJECT&() const noexcept { return *m_pSubobject; } + operator const D3D12_NODE_MASK&() const noexcept { return m_Desc; } + +private: + void Init() noexcept + { + SUBOBJECT_HELPER_BASE::Init(); + m_Desc = {}; + } + void* Data() noexcept override { return &m_Desc; } + D3D12_NODE_MASK m_Desc; +}; + +#undef D3DX12_COM_PTR +#undef D3DX12_COM_PTR_GET +#undef D3DX12_COM_PTR_ADDRESSOF +#endif // #ifndef D3DX12_NO_STATE_OBJECT_HELPERS + +#endif // defined( __cplusplus ) + +#endif //__D3DX12_H__ diff --git a/Libraries/Aurora/Source/DirectX/MemoryPool.h b/Libraries/Aurora/Source/DirectX/MemoryPool.h new file mode 100644 index 0000000..ba7886b --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/MemoryPool.h @@ -0,0 +1,210 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "PTGeometry.h" + +BEGIN_AURORA + +// A pool used to supply scratch buffers for the creation of BLAS and TLAS. +// NOTE: This improves performance in two ways: +// - By retaining scratch buffers for the current task, so that the build function doesn't need to +// manually wait for the GPU to finish before releasing the buffers. +// - By allocating small scratch buffers from a single larger buffer, to reduce the number of +// actual allocations required. +class ScratchBufferPool +{ +public: + using FuncCreateScratchBuffer = function; + + // Constructor. This accepts the number of simultaneous tasks and a function to create a single + // GPU scratch buffer. + ScratchBufferPool(uint32_t taskCount, FuncCreateScratchBuffer funcCreateScratchBuffer) + { + assert(funcCreateScratchBuffer); + + // Initialize the array of task buffers, i.e. one list of buffers for each simultaneously + // active task. + _taskBuffers.resize(taskCount); + + // Create the initial buffer. + _funcCreateScratchBuffer = funcCreateScratchBuffer; + _pBuffer = _funcCreateScratchBuffer(kBufferSize); + } + + // Gets the GPU address of a scratch buffer with the specified size. This scratch buffer is only + // guaranteed to be valid until the next task. + D3D12_GPU_VIRTUAL_ADDRESS get(size_t size) + { + // If the requested size exceeds the total buffer size, just create a dedicated buffer and + // add it the list of buffers for the current task. + if (size > kBufferSize) + { + ID3D12ResourcePtr pLargeBuffer = _funcCreateScratchBuffer(size); + _taskBuffers[_taskIndex].push_back(pLargeBuffer); + + return pLargeBuffer->GetGPUVirtualAddress(); + } + + // If the current buffer doesn't have enough space for the requested scratch buffer, add it + // to the list of buffers for the current task (i.e. "retire" it) and create a new buffer. + if (_bufferOffset + size > kBufferSize) + { + _taskBuffers[_taskIndex].push_back(_pBuffer); + _pBuffer = _funcCreateScratchBuffer(kBufferSize); + _bufferOffset = 0; + } + + // Get the GPU address for the current buffer offset. + D3D12_GPU_VIRTUAL_ADDRESS address = _bufferOffset + _pBuffer->GetGPUVirtualAddress(); + + // Increment the buffer offset, while taking the acceleration structure byte alignment (256) + // into account. + const size_t kAlignment = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT; + _bufferOffset += size; + _bufferOffset += ALIGNED_OFFSET(_bufferOffset, kAlignment); + + return address; + } + + // Notifies the scratch buffer pool that the current task has advanced to the specified index. + // This means the pool can purge scratch buffers for prior tasks. + // NOTE: This index should loop through the task count, e.g. 0, 1, 2, 0, 1, 2, etc. + void update(uint32_t taskIndex) + { + assert(taskIndex < _taskBuffers.size()); + + // Clear the list of buffers for the current task, which will also release the buffers. + // NOTE: The default destructor for this class will similarly release everything. + _taskBuffers[taskIndex].clear(); + _taskIndex = taskIndex; + } + +private: + // The buffer size is selected to support dozens of average BLAS scratch buffers, or at least + // one very large TLAS scratch buffer. + const size_t kBufferSize = 4 * 1024 * 1024; + + FuncCreateScratchBuffer _funcCreateScratchBuffer; + uint32_t _taskIndex = 0; + vector> _taskBuffers; + ID3D12ResourcePtr _pBuffer; + size_t _bufferOffset = 0; +}; + +// A pool used to supply vertex buffers, e.g. index, position, normal, and tex coords. This improves +// performance by collecting many small virtual allocations into fewer physical allocations of GPU +// memory. +class VertexBufferPool +{ +public: + using FuncCreateVertexBuffer = function; + + // Constructor. This accepts to create a single GPU vertex buffer. + VertexBufferPool(FuncCreateVertexBuffer funcCreateVertexBuffer) + { + assert(funcCreateVertexBuffer); + + // Create the initial buffer, mapped for writing. + // NOTE: We deliberately do not specify a read range here, i.e. second parameter for Map(). + // Testing has shown that specifying the read range (zero start and end, to indicate that no + // reading will be performed), actually results in *slower* upload in practice. + _funcCreateVertexBuffer = funcCreateVertexBuffer; + _pBuffer = _funcCreateVertexBuffer(kBufferSize); + checkHR(_pBuffer->Map(0, nullptr, reinterpret_cast(&_pMappedData))); + } + + // Gets a vertex buffer with the size specified in the provided vertex buffer description, and + // fills it with the provided data. The resource pointer and buffer offset of the vertex buffer + // description will be populated. + void get(VertexBuffer& vertexBuffer, void* pData) + { + AU_ASSERT(vertexBuffer.size > 0 && pData, "Invalid vertex buffer"); + + // If the requested size exceeds the total buffer size, just create a dedicated buffer and + // and use it directly. + size_t size = vertexBuffer.size; + if (size > kBufferSize) + { + // Create the (large) buffer. + ID3D12ResourcePtr pLargeBuffer = _funcCreateVertexBuffer(size); + + // Map it for reading and copy the provided data. + CD3DX12_RANGE writeRange(0, size); + uint8_t* pMappedData = nullptr; + checkHR(pLargeBuffer->Map(0, nullptr, reinterpret_cast(&pMappedData))); + ::memcpy_s(pMappedData, size, pData, size); + pLargeBuffer->Unmap(0, &writeRange); // no HRESULT + + // Update the provided vertex buffer structure. + vertexBuffer.pBuffer = pLargeBuffer; + vertexBuffer.offset = 0; + + return; + } + + // If the current buffer doesn't have enough space for the requested size, flush the pool, + // which creates a new buffer. + if (_bufferOffset + size > kBufferSize) + { + flush(); + } + + // Copy the provided data to the mapped buffer. + ::memcpy_s(_pMappedData + _bufferOffset, size, pData, size); + + // Update the provided vertex buffer structure. By assigning the buffer resource pointer to + // the vertex buffer, the owner of the vertex buffer takes on shared ownership of the + // resource, so that it can safely be released by the pool later. + vertexBuffer.pBuffer = _pBuffer; + vertexBuffer.offset = _bufferOffset; + + // Increment the buffer offset, while taking the acceleration structure byte alignment (256) + // into account. + _bufferOffset += size; + } + + // Flushes the vertex buffer pool, so that all created vertex buffers are available to the GPU. + // This must be done before using the vertex buffers on the GPU. + void flush() + { + // Return if the buffer offset is zero, because there is no pending data to flush. + if (_bufferOffset == 0) + { + return; + } + + // Unmap the (open) buffer, and release the buffer. + CD3DX12_RANGE writeRange(0, _bufferOffset - 1); + _pBuffer->Unmap(0, &writeRange); // no HRESULT + _pBuffer.Reset(); + _pMappedData = nullptr; + + // Create a new buffer, mapped for writing. + _pBuffer = _funcCreateVertexBuffer(kBufferSize); + _bufferOffset = 0; + checkHR(_pBuffer->Map(0, nullptr, reinterpret_cast(&_pMappedData))); + } + +private: + // The buffer size is selected to support dozens of average vertex buffers. + const size_t kBufferSize = 4 * 1024 * 1024; + + FuncCreateVertexBuffer _funcCreateVertexBuffer; + ID3D12ResourcePtr _pBuffer; + uint8_t* _pMappedData = nullptr; + size_t _bufferOffset = 0; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTDevice.cpp b/Libraries/Aurora/Source/DirectX/PTDevice.cpp new file mode 100644 index 0000000..10bf90e --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTDevice.cpp @@ -0,0 +1,186 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTDevice.h" + +BEGIN_AURORA + +static bool checkDeviceFeatures(ID3D12Device5* pDevice, PTDevice::Features features) +{ + // Check for low-power support. + // NOTE: We check for unified memory architecture (UMA) as a proxy for low-power devices. UMA is + // usually only supported by integrated GPUs, which are usually low-power devices. + if (features & PTDevice::Features::kLowPower) + { + D3D12_FEATURE_DATA_ARCHITECTURE1 architecture = {}; + checkHR(pDevice->CheckFeatureSupport( + D3D12_FEATURE_ARCHITECTURE1, &architecture, sizeof(architecture))); + if (architecture.UMA == FALSE) + { + return false; + } + } + + // Check for ray tracing support. + if (features & PTDevice::Features::kRayTracing) + { + D3D12_FEATURE_DATA_D3D12_OPTIONS5 options = { 0, D3D12_RENDER_PASS_TIER_0, + D3D12_RAYTRACING_TIER_NOT_SUPPORTED }; + checkHR( + pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options, sizeof(options))); + if (options.RaytracingTier == D3D12_RAYTRACING_TIER_NOT_SUPPORTED) + { + return false; + } + } + + return true; +} + +unique_ptr PTDevice::create(PTDevice::Features features, int sampleCount) +{ + // Create a device object. If it is not valid, return null. + unique_ptr pDevice = make_unique(features, sampleCount); + if (!pDevice->_isValid) + { + return nullptr; + } + + return pDevice; +} + +PTDevice::PTDevice(PTDevice::Features features, int sampleCount) +{ + // Initialize the device. If there is no system device that supports all of the desired + // features, it is considered invalid. + _isValid = initialize(features, sampleCount); +} + +bool PTDevice::initialize(PTDevice::Features features, int sampleCount) +{ + // Temporary values while the factory and device are being created. + ComPtr pFactory; + ComPtr pDevice; + UINT dxgiFactoryFlags = 0; + + // If a software device is request, no other features are allowed. + bool requireSoftware = features & PTDevice::Features::kSoftware; + if (requireSoftware && features != PTDevice::Features::kSoftware) + { + return false; + } + + // Enable the use of the debug layer on debug builds, and when NODXDEBUG is not defined. + // NOTE: The debug layer requires installation of Graphics Tools for Windows 10, which may not + // be desired by some clients. +#if defined(_DEBUG) && !defined(NODXDEBUG) + // Enable the D3D12 debug layer. + ComPtr pDebugInterface; + checkHR(::D3D12GetDebugInterface(IID_PPV_ARGS(&pDebugInterface))); + pDebugInterface->EnableDebugLayer(); + + // Break on DXGI errors. + ComPtr pDXGIInfoQueue; + checkHR(::DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDXGIInfoQueue))); + checkHR(pDXGIInfoQueue->SetBreakOnSeverity( + DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true)); + checkHR(pDXGIInfoQueue->SetBreakOnSeverity( + DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true)); + + // Create the factory with the debug flag. + dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; +#endif + + // Create the DXGI factory. + checkHR(::CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&pFactory))); + + // Enumerate the available adapters and create a device from the first valid adapter. + ComPtr pAdapter; + for (UINT i = 0; pFactory->EnumAdapters1(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; i++) + { + // Get the adapter information. + DXGI_ADAPTER_DESC1 adapterDesc; + pAdapter->GetDesc1(&adapterDesc); + bool isSoftwareAdapter = adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE; + + // If this is a software adapter, skip it if a software device *is not* required. Otherwise + // skip it if a software device *is* required. + if (isSoftwareAdapter) + { + if (!requireSoftware) + continue; + } + else + { + if (requireSoftware) + continue; + } + + // Create a device from the adapter. + checkHR( + ::D3D12CreateDevice(pAdapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&pDevice))); + + // Stop searching if a valid device is found. Otherwise clear the device. + if (!checkDeviceFeatures(pDevice.Get(), features)) + { + pDevice.Reset(); + continue; + } + + // Enable breaking on DirectX errors on debug builds, and when NODXDEBUG is not defined. +#if defined(_DEBUG) && !defined(NODXDEBUG) + ComPtr pD3D12InfoQueue; + pDevice.As(&pD3D12InfoQueue); + checkHR(pD3D12InfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true)); + checkHR(pD3D12InfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true)); +#endif + + // Record the factory, device, and name for the selected adapter. + _pFactory = pFactory; + _pDevice = pDevice; + _name = Foundation::w2s(adapterDesc.Description); + + // Cast the DXGI vendor ID to a vendor enumerator. + _vendor = static_cast(adapterDesc.VendorId); + + // Record the feature flags. + _features = features; + + // Check for MSAA support, determining the highest number of samples per pixel (at or below) + // the desired count), and the associated highest sample quality level. + for (_sampleCount = sampleCount; _sampleCount > 1; _sampleCount--) + { + D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS levels; + levels.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + levels.SampleCount = _sampleCount; + checkHR(_pDevice->CheckFeatureSupport( + D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &levels, sizeof(levels))); + + // If there is at least one quality level for the sample count, record it and stop. + if (levels.NumQualityLevels > 0) + { + _sampleQuality = levels.NumQualityLevels - 1; + break; + } + } + + return true; + } + + // If this point is reached, then no suitable device could be created. + return false; +} + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTDevice.h b/Libraries/Aurora/Source/DirectX/PTDevice.h new file mode 100644 index 0000000..b3a0a82 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTDevice.h @@ -0,0 +1,95 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +/// Wrapper around the DirectX 12 device. +class PTDevice +{ +public: + /// Enumeration of device features. + /// + /// The features are used as flags, so "enum class" is not applied here. + enum Features + { + /// Default feature set (hardware-based). + kDefault = 0x00, + + /// Software-based rendering (such as DirectX WARP). + /// + /// Not for normal use, for testing and other special scenarios. + kSoftware = 0x01, + + /// Ray tracing support. + kRayTracing = 0x02, + + /// Low power (energy saving) device. + kLowPower = 0x04, + + /// Feature combinations. + LowPower_RayTracing = kLowPower | kRayTracing, + Software_RayTracing = kSoftware | kRayTracing + }; + + /// Enumeration of device vendors. + /// + /// These hex values are "magic numbers" used to identify GPU vendors. + enum class Vendor + { + kAMD = 0x1002, + kNVIDIA = 0x10DE, + kIntel = 0x8086, + kARM = 0x13B5, + kImaginationTechnologies = 0x1010, + kQualcomm = 0x5143, + kUnknown = -1 + }; + + /// Creates a GPU device with required features and MSAA sample count. + static unique_ptr create(PTDevice::Features features, int sampleCount = 1); + + /// Do not use the constructor, use PTDevice::create() instead. + PTDevice(PTDevice::Features features, int sampleCount); + + /// Gets the DirectX device object. + ID3D12Device5* device() const { return _pDevice.Get(); }; + + /// Gets the DirectX factory object used to create device. + IDXGIFactory4* factory() const { return _pFactory.Get(); } + + // Gets the vendor for the DirectX device. + Vendor vendor() { return _vendor; } + +private: + bool initialize(Features features, int sampleCount); + + // DirectX12 device object. + ComPtr _pDevice; + // DirectX 12 factor object used to create device. + ComPtr _pFactory; + bool _isValid = false; + // Device name. + string _name = ""; + // Device features supported by this device. + Features _features = Features::kDefault; + // Vendor (int value of enum is DXGI adapter VendorId code) + Vendor _vendor = Vendor::kUnknown; + // MSAA sampler count for device. + uint32_t _sampleCount = 0; + // MSAA sample quality for device. + uint32_t _sampleQuality = 0; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp b/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp new file mode 100644 index 0000000..b4f129f --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp @@ -0,0 +1,78 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTEnvironment.h" + +#include "PTImage.h" +#include "PTRenderer.h" + +BEGIN_AURORA + +PTEnvironment::PTEnvironment(PTRenderer* pRenderer) : _pRenderer(pRenderer) {} + +ID3D12Resource* PTEnvironment::aliasMap() const +{ + // Get the light image and corresponding alias map. + // NOTE: If the light image exists (or not), the alias map must also exist (or not). + PTImagePtr pImage = dynamic_pointer_cast(_values.asImage("light_image")); + ID3D12Resource* pAliasMap = pImage ? pImage->aliasMap() : nullptr; + assert((bool)pImage == (bool)pAliasMap); + + return pAliasMap; +} + +bool PTEnvironment::update() +{ + // Do nothing if the environment is not dirty. + if (!_bIsDirty) + { + return false; + } + + // NOTE: Updating the constant buffer while it is being used can lead to ghosting artifacts + // with progressive path tracing, i.e. some samples are using old data while others are using + // new data, in the same task. This can be resolved by either flushing the renderer when the + // environment is changed, or using a copy of the environment data for each task that is being + // processed at once, as is done with per-frame data. With the latter option, that would have to + // include the environment images. + + // Update the constant buffer with environment data, creating the buffer if needed. The lambda + // here translates values to the data object for the constant buffer (GPU). + _pRenderer->updateBuffer( + _pConstantBuffer, [this](EnvironmentData& data) { updateGPUStruct(data); }); + + _bIsDirty = false; + + return true; +} + +void PTEnvironment::createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const +{ + // Create a SRV (descriptor) on the descriptor heap for the light image, if any. + PTImagePtr pImage = dynamic_pointer_cast(_values.asImage("light_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); + + // Increment the heap location pointed to by the handle past the light_image SRV. + handle.Offset(increment); + + // Create a SRV (descriptor) on the descriptor heap for the background image, if any. + pImage = dynamic_pointer_cast(_values.asImage("background_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); + + // Increment the heap location pointed to by the handle past the background_image SRV. + handle.Offset(increment); +} + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTEnvironment.h b/Libraries/Aurora/Source/DirectX/PTEnvironment.h new file mode 100644 index 0000000..c86180a --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTEnvironment.h @@ -0,0 +1,54 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "EnvironmentBase.h" + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; + +// An internal implementation for IEnvironment. +class PTEnvironment : public EnvironmentBase +{ +public: + /*** Lifetime Management ***/ + + PTEnvironment(PTRenderer* pRenderer); + ~PTEnvironment() {} + + /*** Functions ***/ + + uint32_t descriptorCount() const + { + // Light texture and background texture. + return 2; + } + ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } + ID3D12Resource* aliasMap() const; + bool update(); + void createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const; + +private: + /*** Private Types ***/ + + /*** Private Variables ***/ + + PTRenderer* _pRenderer = nullptr; + ID3D12ResourcePtr _pConstantBuffer; +}; +MAKE_AURORA_PTR(PTEnvironment); + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTGeometry.cpp b/Libraries/Aurora/Source/DirectX/PTGeometry.cpp new file mode 100644 index 0000000..3e25f99 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTGeometry.cpp @@ -0,0 +1,220 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTGeometry.h" + +#include "PTRenderer.h" + +BEGIN_AURORA + +// Copies data from the specified array to the specified vector, with each element having a +// specified number of components, e.g. XYZ for a vertex position. +template +static void copyVertexChannelData(vector& dst, const AttributeData& src, + uint32_t vertexCount, uint32_t componentCount = 1) +{ + // If no source data is provided, clear the destination vector and return. + if (!src.address) + { + dst.clear(); + return; + } + + // Get start of source buffer. + const uint8_t* pSrc = reinterpret_cast(src.address) + src.offset; + + // Set the size of the vector with enough elements to include the vertex data, based on the + // specified data type (e.g. float) and number of components per vertex (e.g. three for a + // position, XYZ). + size_t bufferSize = vertexCount * componentCount * sizeof(ComponentType); + dst.resize(bufferSize); + + // If the stride of the source data is just the size of the element, just do a memcpy. + if (src.stride == componentCount * sizeof(ComponentType)) + { + ::memcpy_s(dst.data(), bufferSize, pSrc, bufferSize); + } + else + { + // If it doesn't match the input must be interleaved in some way. So iterate through each + // vertex. + ComponentType* pDstComp = dst.data(); + for (size_t i = 0; i < vertexCount; i++) + { + // Copy the individual element from the soure buffer to destination. + const ComponentType* pSrcComp = reinterpret_cast(pSrc); + for (uint32_t j = 0; j < componentCount; j++) + { + pDstComp[j] = pSrcComp[j]; + } + + // Advance the destination pointer by size of element. + pDstComp += componentCount; + + // Advance the source pointer by the stride provided by client. + pSrc += src.stride; + } + } +} + +PTGeometry::PTGeometry( + PTRenderer* pRenderer, const string& name, const GeometryDescriptor& descriptor) : + GeometryBase(name, descriptor), _pRenderer(pRenderer) +{ +} + +bool PTGeometry::update() +{ + // Do nothing if the geometry is not dirty. + if (!_bIsDirty) + { + return false; + } + + // Create an index buffer from the index data. If there is no index data, create simple index + // data with sequential values: 0, 1, 2, ... + // NOTE: Index data is optional for the geometry, but required for shaders, which is why a + // buffer exist at this point. + if (_indices.empty()) + { + // Create sequential index data. + vector indices; + indices.reserve(_vertexCount); + for (uint32_t i = 0; i < _vertexCount; i++) + { + indices.push_back(i); + } + + // Create the index buffer from the sequential index data. + createVertexBuffer(_indexBuffer, indices.data(), sizeof(uint32_t) * _vertexCount); + } + else + { + // Create the index buffer from the existing index data. + createVertexBuffer(_indexBuffer, _indices.data(), sizeof(uint32_t) * _indexCount); + } + + // Create vertex buffers, one for each channel of data: positions (required), normals + // (optional), and texture coordinates (optional). + if (!_positions.empty()) + { + createVertexBuffer(_positionBuffer, _positions.data(), sizeof(float) * _vertexCount * 3); + } + if (!_normals.empty()) + { + createVertexBuffer(_normalBuffer, _normals.data(), sizeof(float) * _vertexCount * 3); + } + if (!_texCoords.empty()) + { + createVertexBuffer(_texCoordBuffer, _texCoords.data(), sizeof(float) * _vertexCount * 2); + } + + // Reset the BLAS pointer as it will have to be rebuilt. Also clear the dirty flag. + _pBLAS.Reset(); + _bIsDirty = false; + + return true; +} + +bool PTGeometry::updateBLAS() +{ + // Build the bottom-level acceleration structure (BLAS) if it doesn't exist. + // NOTE: The BLAS used to later build a TLAS must be retained somewhere (not released), even + // after the TLAS is created, and even if the BLAS are not used directly by the application. Not + // doing this results in the device being lost (crash), without feedback. + if (!_pBLAS) + { + _pBLAS = buildBLAS(); + + return true; + } + + return false; +} + +void PTGeometry::createVertexBuffer(VertexBuffer& vertexBuffer, void* pData, size_t dataSize) const +{ + vertexBuffer.size = dataSize; + _pRenderer->getVertexBuffer(vertexBuffer, pData); +} + +ID3D12ResourcePtr PTGeometry::buildBLAS() +{ + // Describe the geometry for the BLAS. + // NOTE: The geometry is not set as opaque here on the geometry flags, so that the any hit + // shader can be called if needed. If the any hit shader is not needed, the shader will use the + // opaque ray flag when calling TraceRay(). + D3D12_RAYTRACING_GEOMETRY_DESC geometryDesc = {}; + auto& triangles = geometryDesc.Triangles; + geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; + geometryDesc.Flags = D3D12_RAYTRACING_GEOMETRY_FLAG_NONE; + + // Specify the vertex data. + triangles.VertexCount = _vertexCount; + triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; + triangles.VertexBuffer.StartAddress = _positionBuffer.address(); + triangles.VertexBuffer.StrideInBytes = sizeof(float) * 3; + + // Specify the index data, if any. + if (!_indices.empty()) + { + triangles.IndexCount = _indexCount; + triangles.IndexFormat = DXGI_FORMAT_R32_UINT; + triangles.IndexBuffer = _indexBuffer.address(); + } + + // Describe the BLAS. + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS blasInputs = {}; + blasInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + blasInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + blasInputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_NONE; + blasInputs.NumDescs = 1; + blasInputs.pGeometryDescs = &geometryDesc; + + // Get the sizes required for the BLAS scratch and result buffers, and create them. + // NOTE: The scratch buffer is obtained from the renderer and will be retained by the renderer + // for the duration of the build task started below, and then it will be released. These are + // typically around 50 KB in size, depending on the represented geometry. + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO blasInfo = {}; + _pRenderer->dxDevice()->GetRaytracingAccelerationStructurePrebuildInfo(&blasInputs, &blasInfo); + D3D12_GPU_VIRTUAL_ADDRESS blasScratchAddress = + _pRenderer->getScratchBuffer(blasInfo.ScratchDataSizeInBytes); + ID3D12ResourcePtr pBLAS = _pRenderer->createBuffer(blasInfo.ResultDataMaxSizeInBytes, + "BLAS Buffer", D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE); + + // Describe the build for the BLAS. + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC blasDesc = {}; + blasDesc.Inputs = blasInputs; + blasDesc.ScratchAccelerationStructureData = blasScratchAddress; + blasDesc.DestAccelerationStructureData = pBLAS->GetGPUVirtualAddress(); + + // Build the BLAS using a command list. Insert a UAV barrier so that it can't be used until it + // is generated. + ID3D12GraphicsCommandList4Ptr pCommandList = _pRenderer->beginCommandList(); + pCommandList->BuildRaytracingAccelerationStructure(&blasDesc, 0, nullptr); + _pRenderer->addUAVBarrier(pBLAS.Get()); + + // Complete the command list and task. + // NOTE: It is not necessary to wait for the task to complete because of the UAV barrier added + // above. Also, the scratch buffer will be retained by the renderer for the duration of the + // task, because it was obtained from the renderer with getScratchBuffer(). + _pRenderer->submitCommandList(); + _pRenderer->completeTask(); + + return pBLAS; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTGeometry.h b/Libraries/Aurora/Source/DirectX/PTGeometry.h new file mode 100644 index 0000000..aecec60 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTGeometry.h @@ -0,0 +1,90 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "GeometryBase.h" + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; + +// A structure describing a vertex buffer obtained from a vertex buffer pool. +struct VertexBuffer +{ + ID3D12ResourcePtr pBuffer; + size_t size = 0; + size_t offset = 0; + + D3D12_GPU_VIRTUAL_ADDRESS address() const + { + return pBuffer ? pBuffer->GetGPUVirtualAddress() + offset : 0; + } +}; + +// An internal implementation for IGeometry. +class PTGeometry : public GeometryBase +{ +public: + /*** Types ***/ + + struct GeometryBuffers + { + D3D12_GPU_VIRTUAL_ADDRESS IndexBuffer = 0; + D3D12_GPU_VIRTUAL_ADDRESS PositionBuffer = 0; + D3D12_GPU_VIRTUAL_ADDRESS NormalBuffer = 0; + D3D12_GPU_VIRTUAL_ADDRESS TexCoordBuffer = 0; + }; + + /*** Lifetime Management ***/ + + PTGeometry(PTRenderer* pRenderer, const string& name, const GeometryDescriptor& descriptor); + ~PTGeometry() {}; + + /*** Functions ***/ + + GeometryBuffers buffers() const + { + GeometryBuffers buffers; + buffers.IndexBuffer = _indexBuffer.address(); + buffers.PositionBuffer = _positionBuffer.address(); + buffers.NormalBuffer = _normalBuffer.address(); + buffers.TexCoordBuffer = _texCoordBuffer.address(); + return buffers; + } + ID3D12Resource* blas() { return _pBLAS.Get(); } + bool update(); + bool updateBLAS(); + +private: + /*** Private Functions ***/ + + void createVertexBuffer(VertexBuffer& vertexBuffer, void* pData, size_t dataSize) const; + ID3D12ResourcePtr buildBLAS(); + + /*** Private Variables ***/ + + PTRenderer* _pRenderer = nullptr; + + /*** DirectX 12 Objects ***/ + + ID3D12ResourcePtr _pBLAS; + VertexBuffer _indexBuffer; + VertexBuffer _positionBuffer; + VertexBuffer _normalBuffer; + VertexBuffer _texCoordBuffer; +}; +MAKE_AURORA_PTR(PTGeometry); + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp b/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp new file mode 100644 index 0000000..7cff8ee --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp @@ -0,0 +1,90 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "PTGroundPlane.h" + +#include "PTRenderer.h" +#include "Properties.h" + +BEGIN_AURORA + +// Builds basis vectors (tangent and bitangent) from a normal vector. +static void buildBasis(const vec3& normal, vec3& tangent, vec3& bitangent) +{ + bitangent = abs(normal.y) < 0.999f ? vec3(0.0f, 1.0f, 0.0f) : vec3(1.0f, 0.0f, 0.0f); + tangent = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); +} + +// The global property set for ground plane data. +static PropertySetPtr gpPropertySet; + +// Creates (if needed) and get the property set for ground plane data. +static PropertySetPtr propertySet() +{ + // Return the property set, or create it if it doesn't exist. + if (gpPropertySet) + { + return gpPropertySet; + } + gpPropertySet = make_shared(); + + // Add ground plane properties and default values to the property set. + gpPropertySet->add("enabled", true); + gpPropertySet->add("position", vec3(0.0f, 0.0f, 0.0f)); + gpPropertySet->add("normal", vec3(0.0f, 1.0f, 0.0f)); + gpPropertySet->add("shadow_opacity", 1.0f); + gpPropertySet->add("shadow_color", vec3(0.0f, 0.0f, 0.0f)); + gpPropertySet->add("reflection_opacity", 0.5f); + gpPropertySet->add("reflection_color", vec3(0.5f, 0.5f, 0.5f)); + gpPropertySet->add("reflection_roughness", 0.1f); + + return gpPropertySet; +} + +PTGroundPlane::PTGroundPlane(PTRenderer* pRenderer) : + FixedValues(propertySet()), _pRenderer(pRenderer) +{ +} + +void PTGroundPlane::update() +{ + // Do nothing if the ground plane is not dirty. + if (!_bIsDirty) + { + return; + } + + // Update the constant buffer with ground plane data, creating the buffer if needed. The lambda + // here translates values to the data object for the constant buffer (GPU). + _pRenderer->updateBuffer(_pConstantBuffer, [this](GroundPlaneData& data) { + data.enabled = _values.asBoolean("enabled") ? 1 : 0; + data.position = _values.asFloat3("position"); + data.normal = _values.asFloat3("normal"); + data.shadowOpacity = _values.asFloat("shadow_opacity"); + data.shadowColor = _values.asFloat3("shadow_color"); + data.reflectionOpacity = _values.asFloat("reflection_opacity"); + data.reflectionColor = _values.asFloat3("reflection_color"); + data.reflectionRoughness = _values.asFloat("reflection_roughness"); + + // Build basis vectors from the plane normal. + // NOTE: In the future, these may be supplied by the client for specific effects. + buildBasis(data.normal, data.tangent, data.bitangent); + }); + + _bIsDirty = false; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTGroundPlane.h b/Libraries/Aurora/Source/DirectX/PTGroundPlane.h new file mode 100644 index 0000000..0a9b9ca --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTGroundPlane.h @@ -0,0 +1,66 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +class PTRenderer; + +// An internal implementation for IGroundPlane. +class PTGroundPlane : public IGroundPlane, public FixedValues +{ +public: + /*** Lifetime Management ***/ + + PTGroundPlane(PTRenderer* pRenderer); + ~PTGroundPlane() {} + + /*** IGroundPlane Functions ***/ + + IValues& values() override { return *this; } + + /*** Functions ***/ + + ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } + void update(); + +private: + /*** Private Types ***/ + + struct GroundPlaneData + { + int enabled; + vec3 position; + vec3 normal; + float _padding1; + vec3 tangent; + float _padding2; + vec3 bitangent; + float shadowOpacity; + vec3 shadowColor; + float reflectionOpacity; + vec3 reflectionColor; + float reflectionRoughness; + }; + + /*** Private Variables ***/ + + PTRenderer* _pRenderer; + ID3D12ResourcePtr _pConstantBuffer; +}; +MAKE_AURORA_PTR(PTGroundPlane); + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTImage.cpp b/Libraries/Aurora/Source/DirectX/PTImage.cpp new file mode 100644 index 0000000..5b00d0b --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTImage.cpp @@ -0,0 +1,160 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "AliasMap.h" +#include "PTImage.h" + +#include "PTRenderer.h" + +BEGIN_AURORA + +PTImage::PTImage(PTRenderer* pRenderer, const IImage::InitData& initData) +{ + // Image data must be provided, along with valid dimensions. + assert(initData.pImageData && initData.width > 0 && initData.height > 0); + + // Copy the basic properties. + _pRenderer = pRenderer; + _format = initData.format; + _linearize = initData.linearize; + _dimensions = uvec2(initData.width, initData.height); + _name = initData.name; + + // Prepare the D3D12 texture resource. + initResource(initData.pImageData); + + // If the image is to be used as an environment image, prepare an alias map for importance + // sampling of that environment image. + if (initData.isEnvironment) + { + // Create a GPU buffer containing the alias map data. + size_t bufferSize = _dimensions.x * _dimensions.y * sizeof(AliasMap::Entry); + _pAliasMapBuffer = _pRenderer->createBuffer(bufferSize); + AliasMap::Entry* pMappedData = nullptr; + checkHR(_pAliasMapBuffer->Map(0, nullptr, reinterpret_cast(&pMappedData))); + AliasMap::build(static_cast(initData.pImageData), _dimensions, pMappedData, + bufferSize, _luminanceIntegral); + _pAliasMapBuffer->Unmap(0, nullptr); // no HRESULT + } +} + +// Gets the number of bytes per pixel for the specified IImage format. +size_t PTImage::getBytesPerPixel(ImageFormat format) +{ + switch (format) + { + case ImageFormat::Integer_RGBA: + return 4; + case ImageFormat::Float_R: + return 4; + case ImageFormat::Float_RGB: + return 12; + case ImageFormat::Float_RGBA: + return 16; + case ImageFormat::Half_RGBA: + return 8; + case ImageFormat::Short_RGBA: + return 8; + case ImageFormat::Integer_RG: + return 8; + case ImageFormat::Byte_R: + return 1; + } + + return 0; +} + +// Gets the corresponding DXGI format for the specified IImage format, optionally with sRGB +// linearization. +// TODO: Move this function and the one above to a central location. +DXGI_FORMAT PTImage::getDXFormat(ImageFormat format, bool linearize) +{ + switch (format) + { + case ImageFormat::Integer_RGBA: + return linearize ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM; + case ImageFormat::Float_R: + return DXGI_FORMAT_R32_FLOAT; + case ImageFormat::Float_RGB: + return DXGI_FORMAT_R32G32B32_FLOAT; + case ImageFormat::Float_RGBA: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + case ImageFormat::Half_RGBA: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case ImageFormat::Short_RGBA: + return DXGI_FORMAT_R16G16B16A16_UNORM; + case ImageFormat::Integer_RG: + return DXGI_FORMAT_R32G32_UINT; + case ImageFormat::Byte_R: + return DXGI_FORMAT_R8_UNORM; + } + + return DXGI_FORMAT_UNKNOWN; +} + +void PTImage::createSRV( + const PTRenderer& renderer, PTImage* pImage, const D3D12_CPU_DESCRIPTOR_HANDLE& handle) +{ + // Populate the SRV description. + // NOTE: Even if this is going to be a null descriptor (see below), the description is needed + // for unbound resources to work correctly when accessed, e.g. to return black. + DXGI_FORMAT dxFormat = getDXFormat( + pImage ? pImage->_format : ImageFormat::Integer_RGBA, pImage ? pImage->_linearize : false); + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Format = dxFormat; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = UINT_MAX; + + // Create the SRV with the specified descriptor handle. + // NOTE: This will create a null descriptor if the image pointer is null. + ID3D12Resource* pResource = pImage ? pImage->_pTexture.Get() : nullptr; + renderer.dxDevice()->CreateShaderResourceView(pResource, &srvDesc, handle); +} + +void PTImage::initResource(const void* pImageData) +{ + // Create the texture resource in GPU memory. This creates it in the default heap), with the + // copy destination (write) state as the initial resource state. + // NOTE: It is not possible to create a texture on an upload heap, for writing directly from the + // CPU, so a separate copy must be performed later. It is possible write directly from the CPU + // with a *custom* heap and Map+WriteToSubresource(), which may be optimal for integrated (UMA) + // devices that use system memory. + _pTexture = _pRenderer->createTexture(_dimensions, getDXFormat(_format), _name); + + // Create a temporary buffer with the image data, for upload to the GPU. + size_t tempBufferSize = ::GetRequiredIntermediateSize(_pTexture.Get(), 0, 1); + ID3D12ResourcePtr pTempBuffer = _pRenderer->createBuffer(tempBufferSize); + D3D12_SUBRESOURCE_DATA textureData = {}; + textureData.pData = pImageData; + textureData.RowPitch = getBytesPerPixel(_format) * _dimensions.x; + textureData.SlicePitch = textureData.RowPitch * _dimensions.y; + + // Copy the temporary buffer data to the texture using a command list. + ID3D12GraphicsCommandList4Ptr pCommandList = _pRenderer->beginCommandList(); + ::UpdateSubresources( + pCommandList.Get(), _pTexture.Get(), pTempBuffer.Get(), 0, 0, 1, &textureData); + + // Transition the texture's resource state from writing to (shader) reading, and complete the + // task. We must wait for the task to be completed to ensure the temporary buffer is no longer + // being used, and can therefore be released at the end of the function. + _pRenderer->addTransitionBarrier(_pTexture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + _pRenderer->submitCommandList(); + _pRenderer->completeTask(); + _pRenderer->waitForTask(); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTImage.h b/Libraries/Aurora/Source/DirectX/PTImage.h new file mode 100644 index 0000000..1b5e477 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTImage.h @@ -0,0 +1,76 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "ImageBase.h" + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; + +// An internal implementation for IImage. +class PTImage : public ImageBase +{ +public: + /*** Lifetime Management ***/ + + PTImage(PTRenderer* pRenderer, const IImage::InitData& initData); + ~PTImage() {}; + + /*** Static Functions ***/ + + // Gets the number of bytes per pixel for the specified IImage format. + static size_t getBytesPerPixel(ImageFormat format); + + // Gets the corresponding DXGI format for the specified IImage format, optionally with sRGB + // linearization. + static DXGI_FORMAT getDXFormat(ImageFormat format, bool linearize = false); + + // Creates an SRV for the specified image (which may be null) at the specified handle in a + // descriptor heap. + static void createSRV( + const PTRenderer& renderer, PTImage* pImage, const D3D12_CPU_DESCRIPTOR_HANDLE& handle); + + /*** Functions ***/ + + // Gets the image texture. + ID3D12Resource* texture() { return _pTexture.Get(); } + + // Gets the image alias map, if this image represents an environment. + ID3D12Resource* aliasMap() { return _pAliasMapBuffer.Get(); } + + // Gets the integral of the luminance over the entire environment, if this image represents an + // environment. + float luminanceIntegral() { return _luminanceIntegral; } + +private: + /*** Private Functions ***/ + + void initResource(const void* pImageData); + void initAliasMap(const void* pImageData); + + /*** Private Variables ***/ + + PTRenderer* _pRenderer = nullptr; + ID3D12ResourcePtr _pTexture; + ID3D12ResourcePtr _pAliasMapBuffer; + ImageFormat _format = ImageFormat::Integer_RGBA; + bool _linearize = true; + uvec2 _dimensions; + string _name = "UNINITIALIZED"; +}; +MAKE_AURORA_PTR(PTImage); + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/PTMaterial.cpp b/Libraries/Aurora/Source/DirectX/PTMaterial.cpp new file mode 100644 index 0000000..1055d2f --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTMaterial.cpp @@ -0,0 +1,240 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTMaterial.h" + +#include "PTImage.h" +#include "PTRenderer.h" +#include "PTShaderLibrary.h" + +BEGIN_AURORA + +// Convenience macro to compare reflection data from shader to CPU offset +// NOTE: Can only be used inside PTMaterial::validateOffsets(). +#define VALIDATE_OFFSET(_member) \ + if (structDescriptions[#_member].Offset != offsetof(MaterialData, _member)) \ + { \ + AU_ERROR("%s offset mismatch: %d!=%d\n", #_member, structDescriptions[#_member].Offset, \ + offsetof(MaterialData, _member)); \ + isValid = false; \ + } + +bool PTMaterial::validateOffsets(const PTShaderLibrary& pShaderLibrary) +{ + ComPtr pShaderLibraryReflection = pShaderLibrary.reflection(); + + // Get the first function in library. + auto pFuncRefl = pShaderLibraryReflection->GetFunctionByIndex(0); + + // Get the reflection data for constant buffer + auto pCBRefl = pFuncRefl->GetConstantBufferByName("gMaterialConstants"); + + // If we fail to get reflection data just abandon validation (should not be failure case.) + if (!pCBRefl) + return true; + + // Get the description of the constant buffer (which includes size). + D3D12_SHADER_BUFFER_DESC cbDesc; + pCBRefl->GetDesc(&cbDesc); + + // Get the first variable in the constant buffer. + // NOTE:this is actually the struct representing the members. + ID3D12ShaderReflectionVariable* pStructRefl = pCBRefl->GetVariableByIndex(0); + + // If we fail to get reflection data just abandon validation (should not be failure case.) + if (!pStructRefl) + return true; + + // Get the type for the struct, and its description. + ID3D12ShaderReflectionType* pStructType = pStructRefl->GetType(); + + // If we fail to get reflection struct data just abandon validation (should not be failure + // case.) + if (!pStructType) + return true; + + // Get description of struct. + // If the GetDesc call fails, which will happen if the struct is not referenced, just abandon + // validation (should not be failure case.) + D3D12_SHADER_TYPE_DESC structDesc; + if (pStructType->GetDesc(&structDesc) != S_OK) + return true; + + // Create a map of member name to description. + map structDescriptions; + + // Iterate through the members in the struct. + for (unsigned int i = 0; i < structDesc.Members; i++) + { + // Get the name and type of member. + const char* pVarName = pStructType->GetMemberTypeName(i); + ID3D12ShaderReflectionType* pVarType = pStructType->GetMemberTypeByIndex(i); + + // Get the description from the type + D3D12_SHADER_TYPE_DESC varDesc; + pVarType->GetDesc(&varDesc); + + // Add it to the map. + structDescriptions[pVarName] = varDesc; + } + + // Validate each offset; isValid flag will get set to false if invalid. + bool isValid = true; + VALIDATE_OFFSET(base); + VALIDATE_OFFSET(baseColor); + VALIDATE_OFFSET(diffuseRoughness); + VALIDATE_OFFSET(metalness); + VALIDATE_OFFSET(specular); + VALIDATE_OFFSET(specularColor); + VALIDATE_OFFSET(specularRoughness); + VALIDATE_OFFSET(specularIOR); + VALIDATE_OFFSET(specularAnisotropy); + VALIDATE_OFFSET(specularRotation); + VALIDATE_OFFSET(transmission); + VALIDATE_OFFSET(transmissionColor); + VALIDATE_OFFSET(subsurface); + VALIDATE_OFFSET(subsurfaceColor); + VALIDATE_OFFSET(subsurfaceRadius); + VALIDATE_OFFSET(subsurfaceScale); + VALIDATE_OFFSET(subsurfaceAnisotropy); + VALIDATE_OFFSET(sheen); + VALIDATE_OFFSET(sheenColor); + VALIDATE_OFFSET(sheenRoughness); + VALIDATE_OFFSET(coat); + VALIDATE_OFFSET(coatColor); + VALIDATE_OFFSET(coatRoughness); + VALIDATE_OFFSET(coatAnisotropy); + VALIDATE_OFFSET(coatRotation); + VALIDATE_OFFSET(coatIOR); + VALIDATE_OFFSET(coatAffectColor); + VALIDATE_OFFSET(coatAffectRoughness); + VALIDATE_OFFSET(opacity); + VALIDATE_OFFSET(thinWalled); + VALIDATE_OFFSET(hasBaseColorTex); + VALIDATE_OFFSET(baseColorTexTransform); + VALIDATE_OFFSET(hasSpecularRoughnessTex); + VALIDATE_OFFSET(specularRoughnessTexTransform); + VALIDATE_OFFSET(hasOpacityTex); + VALIDATE_OFFSET(opacityTexTransform); + VALIDATE_OFFSET(hasNormalTex); + VALIDATE_OFFSET(normalTexTransform); + VALIDATE_OFFSET(isOpaque); + + // Validate structure size + if (sizeof(MaterialData) != cbDesc.Size) + { + AU_ERROR("%s struct sizes differ: %d!=%d", cbDesc.Name, sizeof(MaterialData), cbDesc.Size); + isValid = false; + } + + return isValid; +} + +PTMaterial::PTMaterial(PTRenderer* pRenderer, shared_ptr pType) : + _pRenderer(pRenderer), _pType(pType) +{ +} + +bool PTMaterial::update() +{ + // Do nothing if the material is not dirty. + if (!_bIsDirty) + { + return false; + } + + // Update the constant buffer with material data, creating the buffer if needed. The lambda here + // translates values to the data object for the constant buffer (GPU). + // NOTE: The order here matches the order of the MaterialData structure. + _pRenderer->updateBuffer( + _pConstantBuffer, [this](MaterialData& data) { updateGPUStruct(data); }); + + _bIsDirty = false; + + return true; +} + +size_t PTMaterial::computeSamplerHash() const +{ + PTSamplerPtr pSampler; + + // Compute hash for base color image sampler. + pSampler = dynamic_pointer_cast(_values.asSampler("base_color_image_sampler")); + size_t res = pSampler ? pSampler->hash() : _pRenderer->defaultSampler()->hash(); + + // Combine with hash for opacity sampler. + pSampler = dynamic_pointer_cast(_values.asSampler("opacity_image_sampler")); + Foundation::hashCombine( + res, pSampler ? pSampler->hash() : _pRenderer->defaultSampler()->hash()); + + return res; +} + +void PTMaterial::createSamplerDescriptors( + CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const +{ + PTSamplerPtr pSampler; + + // Create a sampler descriptor on the descriptor heap for the base color sampler, if any. + pSampler = dynamic_pointer_cast(_values.asSampler("base_color_image_sampler")); + PTSampler::createDescriptor( + *_pRenderer, pSampler ? pSampler.get() : _pRenderer->defaultSampler().get(), handle); + + // Increment the heap location pointed to by the handle past the base color image sampler + // descriptor. + handle.Offset(increment); + + // Create a sampler descriptor on the descriptor heap for the opacity sampler, if any. + pSampler = dynamic_pointer_cast(_values.asSampler("opacity_image_sampler")); + PTSampler::createDescriptor( + *_pRenderer, pSampler ? pSampler.get() : _pRenderer->defaultSampler().get(), handle); + + // Increment the heap location pointed to by the handle past the opacity image sampler + // descriptor. + handle.Offset(increment); +} + +void PTMaterial::createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const +{ + // Create a SRV (descriptor) on the descriptor heap for the base color image, if any. + PTImagePtr pImage = dynamic_pointer_cast(_values.asImage("base_color_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); + + // Increment the heap location pointed to by the handle past the base color image SRV. + handle.Offset(increment); + + // Create a SRV (descriptor) on the descriptor heap for the specular roughness image, if any. + pImage = dynamic_pointer_cast(_values.asImage("specular_roughness_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); + + // Increment the heap location pointed to by the handle past the specular roughness image SRV. + handle.Offset(increment); + + // Create a SRV (descriptor) on the descriptor heap for the normal image, if any. + pImage = dynamic_pointer_cast(_values.asImage("normal_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); + + // Increment the heap location pointed to by the handle past the normal image SRV. + handle.Offset(increment); + + // Create a SRV (descriptor) on the descriptor heap for the normal image, if any. + pImage = dynamic_pointer_cast(_values.asImage("opacity_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); + + // Increment the heap location pointed to by the handle past the opacity image SRV. + handle.Offset(increment); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTMaterial.h b/Libraries/Aurora/Source/DirectX/PTMaterial.h new file mode 100644 index 0000000..2c12380 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTMaterial.h @@ -0,0 +1,81 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "MaterialBase.h" + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; +class PTMaterialType; +class PTShaderLibrary; + +// An internal implementation for IMaterial. +class PTMaterial : public MaterialBase +{ +public: + /*** Static Functions ***/ + + /// Validates that the offsets with CPU material structure match the GPU version in shader. + /// + /// \return True if valid. + static bool validateOffsets(const PTShaderLibrary& pShaderLibrary); + + /*** Lifetime Management ***/ + + PTMaterial(PTRenderer* pRenderer, shared_ptr pType); + ~PTMaterial() {}; + + /*** Functions ***/ + + // The total number of texture descriptors for each material instance. + static uint32_t descriptorCount() + { + // Base color, specular roughness, normal and opacity textures. + return 4; + } + + // The total number of sampler descriptors for each material instance. + static uint32_t samplerDescriptorCount() + { + // Base color + opacity only for now. + return 2; + } + + // Gets the constant buffer for this material. + ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } + + // Gets the material type for this material. + shared_ptr materialType() const { return _pType; } + + bool update(); + void createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const; + void createSamplerDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const; + size_t computeSamplerHash() const; + +private: + /*** Private Types ***/ + + /*** Private Functions ***/ + + /*** Private Variables ***/ + + PTRenderer* _pRenderer = nullptr; + ID3D12ResourcePtr _pConstantBuffer; + shared_ptr _pType; +}; +MAKE_AURORA_PTR(PTMaterial); + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTRenderer.cpp b/Libraries/Aurora/Source/DirectX/PTRenderer.cpp new file mode 100644 index 0000000..237d158 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTRenderer.cpp @@ -0,0 +1,1457 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTRenderer.h" + +#include "AssetManager.h" +#include "CompiledShaders/Accumulation.hlsl.h" +#include "CompiledShaders/PostProcessing.hlsl.h" +#include "MemoryPool.h" +#include "PTDevice.h" +#include "PTEnvironment.h" +#include "PTGeometry.h" +#include "PTGroundPlane.h" +#include "PTImage.h" +#include "PTMaterial.h" +#include "PTScene.h" +#include "PTShaderLibrary.h" +#include "PTTarget.h" + +#if ENABLE_MATERIALX +#include "MaterialX/MaterialGenerator.h" +#endif + +// Include the denoiser if it is enabled. +#if defined(ENABLE_DENOISER) +#include "Denoiser.h" +#endif + +// Development flag to dump materialX documents to disk. +// NOTE: This should never be enabled in committed code; it is only for local development. +#define AU_DEV_DUMP_MATERIALX_DOCUMENTS 0 +#define AU_DEV_DUMP_PROCESSED_MATERIALX_DOCUMENTS 0 + +// Development flag to turn of exception catching during the render loop. +// NOTE: This should never be 0 in committed code; it is only for local development. +#define AU_DEV_CATCH_EXCEPTIONS_DURING_RENDERING 1 + +BEGIN_AURORA + +// The number of descriptors in the descriptor heap used by the renderer. +// NOTE: This includes the output-related and denoising-related descriptors. +static const uint32_t kOutputDescriptorCount = 4; +static const uint32_t kDenoisingDescriptorCount = 7; +static const uint32_t kDescriptorCount = kOutputDescriptorCount + kDenoisingDescriptorCount; + +// The offsets of output-related descriptors. +static const int kFinalDescriptorOffset = 0; +static const int kAccumulationDescriptorOffset = 1; +static const int kDirectDescriptorOffset = 2; +static const int kDepthNDCDescriptorsOffset = 3; + +PTRenderer::PTRenderer(uint32_t taskCount) : RendererBase(taskCount) +{ + // Initialize a new device. If no suitable device can be created, return immediately. + _isValid = initDevice(); + if (!_isValid) + { + return; + } + + // Initialize the command list, which includes creating the command queue and a command + // allocator for each simultaneously active task. + initCommandList(); + + // Initialize the shader library. + // TODO: Should be per-scene not per-renderer. + _pShaderLibrary = make_unique(_pDXDevice); + + // Enable layer shaders on all platforms except AMD GPUs. + // TODO: There is a driver bug that causes a failure on AMD, when attempting to trace a ray with + // a null acceleration structure. This capability is needed for material layers. Once this + // driver bug is fixed, this can either be removed, or check for a specific driver version. + _pShaderLibrary->setOption("ENABLE_LAYERS", _pDevice->vendor() != PTDevice::Vendor::kAMD); + +#if ENABLE_MATERIALX + // Get the materialX folder relative to the module path. + string mtlxFolder = Foundation::getModulePath() + "MaterialX"; + // Initialize the MaterialX code generator. + _pMaterialXGenerator = make_unique(this, mtlxFolder); + + // Default to MaterialX distance unit to centimeters. + _pShaderLibrary->setOption( + "DISTANCE_UNIT", _pMaterialXGenerator->codeGenerator().units().indices.at("centimeter")); + + // Set the importance sampling mode. + _pShaderLibrary->setOption("IMPORTANCE_SAMPLING_MODE", kImportanceSamplingModeMIS); +#endif + + // Initialize a per-frame data buffer. + initFrameData(); + + // Initialize the shader table. + initRayGenShaderTable(); + + // Initialize the accumulation and post-processing compute shaders. + initAccumulation(); + initPostProcessing(); + + // Initialize the scratch buffer and vertex buffer pools. The function to create scratch buffers + // uses unordered access, as required for BLAS/TLAS scratch buffers. + _pScratchBufferCache = make_unique(_taskCount, [&](size_t size) { + return createBuffer(size, "Scratch Buffer Pool", D3D12_HEAP_TYPE_DEFAULT, + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + }); + _pVertexBufferPool = make_unique( + [&](size_t size) { return createBuffer(size, "Vertex Buffer Pool"); }); +} + +PTRenderer::~PTRenderer() +{ + // Do nothing if the renderer is not valid, i.e. construction had failed. + if (!_isValid) + { + return; + } + + // Wait for the GPU to complete any pending work, so that resources are not released while they + // are still being used. + waitForTask(); +} + +// NOTE: In the createXXX() functions below, the new object has a raw pointer to the renderer, and +// must not exist beyond the lifetime of the renderer. This could be enforced with weak_ptr<>, but +// that is cumbersome. + +IWindowPtr PTRenderer::createWindow(WindowHandle handle, uint32_t width, uint32_t height) +{ + // Create and return a new window object. + return make_shared(this, handle, width, height); +} + +IRenderBufferPtr PTRenderer::createRenderBuffer(int width, int height, ImageFormat imageFormat) +{ + // Create and return a new render buffer object. + return make_shared(this, width, height, imageFormat); +} + +IImagePtr PTRenderer::createImagePointer(const IImage::InitData& initData) +{ + // Create a return a new image object. + return make_shared(this, initData); +} + +ISamplerPtr PTRenderer::createSamplerPointer(const Properties& props) +{ + // Create a return a new sampler object. + return make_shared(this, props); +} + +IMaterialPtr PTRenderer::createMaterialPointer( + const string& materialType, const string& document, const string& name) +{ + // Validate material type. + AU_ASSERT(materialType.compare(Names::MaterialTypes::kBuiltIn) == 0 || + materialType.compare(Names::MaterialTypes::kMaterialX) == 0 || + materialType.compare(Names::MaterialTypes::kMaterialXPath) == 0, + "Invalid material type:", materialType.c_str()); + + // Set the global "flipY" flag on the asset manager, to match option. + // This has no overhead, so just do it each time. + _pAssetMgr->enableVerticalFlipOnImageLoad(_values.asBoolean(kLabelIsFlipImageYEnabled)); + + // The material type and default values for this material. + PTMaterialTypePtr pMtlType; + map defaultValues; + + // Create a material type based on the material type name provided. + if (materialType.compare(Names::MaterialTypes::kBuiltIn) == 0) + { + // Work out built-in type. + string builtInType = document; + + // Get the built-in material type from the shader library. + pMtlType = _pShaderLibrary->getBuiltInMaterialType(builtInType); + + // Print error and provide null material type if built-in not found. + // TODO: Proper error handling for this case. + if (!pMtlType) + { + AU_ERROR("Unknown built-in material type %s for material %s", document.c_str(), + name.c_str()); + pMtlType = nullptr; + } + } + else if (materialType.compare(Names::MaterialTypes::kMaterialX) == 0) + { + // Generate a material type from the materialX document. + pMtlType = generateMaterialX(document, &defaultValues); + + // If flag is set dump the document to disk for development purposes. + if (AU_DEV_DUMP_MATERIALX_DOCUMENTS) + { + string mltxPath = Foundation::getModulePath() + name + "Dumped.mtlx"; + AU_INFO("Dumping MTLX document to:%s", mltxPath.c_str()); + ofstream outputFile; + outputFile.open(mltxPath); + outputFile << document; + outputFile.close(); + } + } + else if (materialType.compare(Names::MaterialTypes::kMaterialXPath) == 0) + { + // Load the MaterialX file using asset manager. + auto pMtlxDocument = _pAssetMgr->acquireTextFile(document); + + // Print error and provide default material type if built-in not found. + // TODO: Proper error handling for this case. + if (!pMtlxDocument) + { + AU_ERROR("Failed to load MaterialX document %s for material %s", document.c_str(), + name.c_str()); + pMtlType = nullptr; + } + else + { + // If Material XML document loaded, use it to generate material type. + pMtlType = generateMaterialX(*pMtlxDocument, &defaultValues); + } + } + else + { + // Print error and return null material type if material type not found. + // TODO: Proper error handling for this case. + AU_ERROR("Unrecognized material type %s for material %s, using Default built-in instead.", + materialType.c_str(), name.c_str()); + pMtlType = nullptr; + } + + // Error case, just return null material. + if (!pMtlType) + return nullptr; + + // Create the material object with the material type. + auto pNewMtl = make_shared(this, pMtlType); + + // Set the default values on the new material. + for (auto valIter : defaultValues) + { + auto defaultVal = valIter.second; + switch (defaultVal.type()) + { + case Aurora::IValues::Type::Float: + // Set float default value. + pNewMtl->values().setFloat(valIter.first, defaultVal.asFloat()); + break; + case Aurora::IValues::Type::Int: + // Set int default value. + pNewMtl->values().setInt(valIter.first, defaultVal.asInt()); + break; + case Aurora::IValues::Type::Boolean: + // Set bool default value. + pNewMtl->values().setBoolean(valIter.first, defaultVal.asBoolean()); + break; + case Aurora::IValues::Type::Float2: + // Set 2d vector default value. + pNewMtl->values().setFloat2(valIter.first, &defaultVal.asFloat2().x); + break; + case Aurora::IValues::Type::Float3: + // Set 3d vector default value. + pNewMtl->values().setFloat3(valIter.first, &defaultVal.asFloat3().x); + break; + case Aurora::IValues::Type::Sampler: + { + pNewMtl->values().setSampler(valIter.first, defaultVal.asSampler()); + } + break; + case Aurora::IValues::Type::String: + { + // Image default values are provided as strings and must be loaded. + auto textureFilename = defaultVal.asString(); + + // Load the pixels for the image using asset manager. + auto pImageData = _pAssetMgr->acquireImage(textureFilename); + if (!pImageData) + { + // Print error if image fails to load, and then ignore default. + // TODO: Proper error handling here. + AU_ERROR("Failed to load image data %s for material %s", textureFilename.c_str(), + name.c_str()); + } + else + { + // Create Aurora image from the loaded pixels. + // TODO: This should be cached by filename. + auto pImage = createImagePointer(pImageData->data); + if (!pImage) + { + // Print error if image creation fails, and then ignore default. + // TODO: Proper error handling here. + AU_ERROR("Failed to create image %s for material %s", textureFilename.c_str(), + name.c_str()); + } + else + { + // Set the default image. + pNewMtl->setImage(valIter.first, pImage); + } + } + } + break; + default: + break; + } + } + + // Return new material. + return pNewMtl; +} + +IScenePtr PTRenderer::createScene() +{ + // Create and return a new scene object. + return make_shared(this, _pShaderLibrary.get(), kDescriptorCount); +} + +IEnvironmentPtr PTRenderer::createEnvironmentPointer() +{ + // Create and return a new environment object. + return make_shared(this); +} + +IGeometryPtr PTRenderer::createGeometryPointer(const GeometryDescriptor& desc, const string& name) +{ + // Create and return a new environment object. + return make_shared(this, name, desc); +} + +IGroundPlanePtr PTRenderer::createGroundPlanePointer() +{ + // Create and return a new ground plane object. + return make_shared(this); +} + +void PTRenderer::setScene(const IScenePtr& pScene) +{ + // Wait for the GPU to complete any pending work, so that scene resources are not released + // while they are still being used. + if (_pScene) + { + waitForTask(); + } + + // Assign the new scene. + _pScene = dynamic_pointer_cast(pScene); + ; +} + +void PTRenderer::setTargets(const TargetAssignments& targetAssignments) +{ + // TODO: Validate that all targets have the same dimensions. Will involve adding a dimensions + // accessor to ITarget. + AU_ASSERT(targetAssignments.size() > 0, "There must be at least one target assignment."); + + // Get the target assigned to the final AOV (required). + auto targetIt = targetAssignments.find(AOV::kFinal); + AU_ASSERT(targetIt != targetAssignments.end(), "A target must be assigned to the Final AOV."); + _pTargetFinal = dynamic_pointer_cast(targetIt->second); + + // Get the target assigned to the NDC depth AOV (optional). + targetIt = targetAssignments.find(AOV::kDepthNDC); + _pTargetDepthNDC = targetIt != targetAssignments.end() + ? dynamic_pointer_cast(targetIt->second) + : nullptr; +} + +void PTRenderer::render(uint32_t sampleStart, uint32_t sampleCount) +{ + // This function is a major entry point, so issue a fatal error if the renderer is not valid. It + // will not be valid if initialization failed, or if there was a fatal error during rendering. + // NOTE: The error handling further below is not sufficient to handle this because attempting to + // use an invalid renderer could result in a memory access violation, which is not covered by + // the try / catch block. Handling that requires platform-specific support, which is not worth + // doing here. + if (!_isValid) + { + AU_FAIL("Attempting to render with an invalid renderer."); + } + + // Perform rendering. This includes exception handling to catch any fatal errors that may occur + // in rendering. + // + // Specifically, this is meant to catch timeout detection and recovery (TDR) events, when a + // single operation takes longer than two seconds (by default). This can happen with path + // tracing on a slow GPU, or with large output dimensions, or with a complex scene. When this + // happens, the device is removed ("lost") and resources must be recreated. However, Aurora does + // not retain enough information for such a recovery, so this is treated as a fatal + // (catastrophic) error and AU_FAIL is used to indicate this. + // + // By default, AU_FAIL will abort the application, so if the application wants to attempt + // recovery, it must set a callback on the Log interface. Note that the Aurora renderer cannot + // be reused after a failure, so it should be released and recreated. +#if AU_DEV_CATCH_EXCEPTIONS_DURING_RENDERING + try + { + +#endif + renderInternal(sampleStart, sampleCount); + +#if AU_DEV_CATCH_EXCEPTIONS_DURING_RENDERING + } + catch (...) + { + _isValid = false; + AU_FAIL("Rendering has failed, likely due to an operation taking too long to complete."); + } +#endif +} + +void PTRenderer::waitForTask() +{ + // NOTE: This causes the CPU to wait for all pending GPU work to finish. This is generally used + // to avoid resource contention. However, this can affect performance should be avoided if + // possible. + + // Set an event to be triggered when the fence reaches the *previous* task number, and wait + // until that event is triggered. This means the the work for the previous task is done. + // NOTE: The very first task is skipped, as there is no prior task to wait for. Also the fence + // value must start at one, as zero is not valid, so one (1) is added here. + if (_taskNumber > 0) + { + uint64_t fenceValue = _taskNumber - 1; + checkHR(_pTaskFence->SetEventOnCompletion(fenceValue + 1, _hTaskEvent)); + ::WaitForSingleObject(_hTaskEvent, INFINITE); + } +} + +const vector& PTRenderer::builtInMaterials() +{ + return _pShaderLibrary->builtInMaterials(); +} + +void PTRenderer::setLoadResourceFunction(LoadResourceFunction func) +{ + _pAssetMgr->setLoadResourceFunction(func); +} + +ID3D12ResourcePtr PTRenderer::createBuffer(size_t size, const string& name, + D3D12_HEAP_TYPE heapType, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES state) +{ + // Specify the heap properties and buffer description. + CD3DX12_HEAP_PROPERTIES heapProps(heapType); + CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(size, flags); + + // Create a committed resource of the specified size. + ID3D12ResourcePtr pResource; + checkHR(_pDXDevice->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, &bufferDesc, state, nullptr, IID_PPV_ARGS(&pResource))); + + // Set the resource name. + checkHR(pResource->SetName(Foundation::s2w(name).c_str())); + + return pResource; +} + +ID3D12ResourcePtr PTRenderer::createTexture(uvec2 dimensions, DXGI_FORMAT format, + const string& name, bool isUnorderedAccess, bool shareable) +{ + // Prepare a texture description. + D3D12_RESOURCE_FLAGS resourceFlag = + isUnorderedAccess ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE; + CD3DX12_RESOURCE_DESC texDesc = + CD3DX12_RESOURCE_DESC::Tex2D(format, dimensions.x, dimensions.y, 1, 1, 1, 0, resourceFlag); + + // Set the initial resource state to "copy" or unordered access, because that is what the render + // process expects as the state. + D3D12_RESOURCE_STATES resourceState = + isUnorderedAccess ? D3D12_RESOURCE_STATE_UNORDERED_ACCESS : D3D12_RESOURCE_STATE_COPY_DEST; + + // Set the appropriate flags for sharing the texture across devices, if requested. + D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE; + if (shareable) + { + heapFlags = D3D12_HEAP_FLAG_SHARED; + texDesc.Flags = + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS; + } + + // Create a resource using the description and flags. + ID3D12ResourcePtr pResource; + CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT); + checkHR(_pDXDevice->CreateCommittedResource( + &heapProps, heapFlags, &texDesc, resourceState, nullptr, IID_PPV_ARGS(&pResource))); + + // Set the resource name. + checkHR(pResource->SetName(Foundation::s2w(name).c_str())); + + return pResource; +} + +D3D12_GPU_VIRTUAL_ADDRESS PTRenderer::getScratchBuffer(size_t size) +{ + return _pScratchBufferCache->get(size); +} + +void PTRenderer::getVertexBuffer(VertexBuffer& vertexBuffer, void* pData) +{ + _pVertexBufferPool->get(vertexBuffer, pData); +} + +void PTRenderer::flushVertexBufferPool() +{ + _pVertexBufferPool->flush(); +} + +ID3D12CommandAllocator* PTRenderer::getCommandAllocator() +{ + return _commandAllocators[_taskIndex].Get(); +} + +ID3D12GraphicsCommandList4* PTRenderer::beginCommandList() +{ + assert(!_isCommandListOpen); + _isCommandListOpen = true; + + // Reset the command list using the current command allocator. + // NOTE: It is safe to do this even if commands that were created with the command list are + // still being executed. It is the command *allocator* that can't be reset that way. + checkHR(_pCommandList->Reset(_commandAllocators[_taskIndex].Get(), nullptr)); + + return _pCommandList.Get(); +} + +void PTRenderer::submitCommandList() +{ + assert(_isCommandListOpen); + _isCommandListOpen = false; + + // Close the command list and execute it on the command queue. + checkHR(_pCommandList->Close()); + ID3D12CommandList* pCommandList = _pCommandList.Get(); + _pCommandQueue->ExecuteCommandLists(1, &pCommandList); // no HRESULT +} + +void PTRenderer::addTransitionBarrier( + ID3D12Resource* pResource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter) +{ + assert(_isCommandListOpen); + + // Insert a resource barrier into the command list to transition the resource from its previous + // state to a new one. + CD3DX12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition(pResource, stateBefore, stateAfter); + _pCommandList->ResourceBarrier(1, &barrier); // no HRESULT +} + +void PTRenderer::addUAVBarrier(ID3D12Resource* pResource) +{ + assert(_isCommandListOpen); + + // Insert a resource barrier to ensure all UAV reads / writes are completed. + CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::UAV(pResource); + _pCommandList->ResourceBarrier(1, &barrier); // no HRESULT +} + +void PTRenderer::completeTask() +{ + assert(!_isCommandListOpen); + + // NOTE: A "task" manages simultaneous access to limited resources. Specifically this applies + // to the following resources: + // - The command allocators: these must be reset regularly to avoid excessive memory + // consumption, so they are reset (arbitrarily) after each "task" of work. They can't be + // reset while they have commands that have been queued or are executing. + // - The per-frame constant buffer: this is a single buffer partitioned into copies of data for + // each possible task queued for rendering. These are updated by the CPU, but this must not + // be done while the GPU might be reading the data. + // + // There are three relevant members: + // - _taskCount: The number of simultaneously active tasks (default 3). Making this larger + // increases latency, but smaller may reduce performance. + // - _taskIndex: Which of the available task "slots" is currently being used for new work, as a + // value between zero and _taskCount. + // - _taskNumber: The current overall task number, a value that increments by one for each + // completed task. This does not necessarily correspond to "frames." + + // Have the command queue update the fence to the current task number plus one, when all pending + // command lists have been processed. + // NOTE: Fence value is always taskNumber+1 as zero is not a valid fence value. + checkHR(_pCommandQueue->Signal(_pTaskFence.Get(), (_taskNumber + 1))); + + // Increment the task number. + _taskNumber++; + + // Wait for the work for the task that last used the data for the current task index. For + // example, if there are three active tasks, and this is overall task #4, then we need to make + // sure that the work for task #1 (three tasks ago) is complete. This is "triple buffering." + // NOTE: The fence value must start at one, as zero is not valid, so one (1) is added here. + if (_taskNumber > _taskCount) + { + uint64_t fenceValue = _taskNumber - _taskCount; + checkHR(_pTaskFence->SetEventOnCompletion(fenceValue + 1, _hTaskEvent)); + ::WaitForSingleObject(_hTaskEvent, INFINITE); + } + + // Determine the current task index, which repeats: 0, 1, 2, 0, 1, etc. Reset the corresponding + // command allocator, and reset the command list using that command allocator. + _taskIndex = _taskNumber % _taskCount; + checkHR(_commandAllocators[_taskIndex]->Reset()); + + // Update the scratch buffer cache with the new task index. + _pScratchBufferCache->update(_taskIndex); +} + +PTSamplerPtr PTRenderer::defaultSampler() +{ + // Create a default sampler if it does not already exist. + // NOTE: The object has a raw pointer to the renderer, and must not exist beyond the lifetime of + // the renderer. This could be enforced with weak_ptr<>, but that is cumbersome. + if (!_pDefaultSampler) + { + _pDefaultSampler = dynamic_pointer_cast(createSamplerPointer({})); + } + + // Return the existing default sampler. + return _pDefaultSampler; +} + +PTGroundPlanePtr PTRenderer::defaultGroundPlane() +{ + // Create a default ground plane if it does not already exist. + // NOTE: The object has a raw pointer to the renderer, and must not exist beyond the lifetime of + // the renderer. This could be enforced with weak_ptr<>, but that is cumbersome. + if (!_pDefaultGroundPlane) + { + _pDefaultGroundPlane = dynamic_pointer_cast(createGroundPlanePointer()); + + // Set the default ground plane as disabled, so that setting a null ground plane on a scene + // will remove (i.e. not render) the ground plane. + _pDefaultGroundPlane->values().setBoolean("enabled", false); + } + + return _pDefaultGroundPlane; +} + +bool PTRenderer::initDevice() +{ + // Create a device with ray tracing support. + _pDevice = PTDevice::create(PTDevice::Features::kRayTracing); + if (!_pDevice) + { + return false; + } + + // Retain the DirectX device and factory objects. + _pDXDevice = _pDevice->device(); + _pDXFactory = _pDevice->factory(); + + // Store the descriptor heap handle increment size for future use. + _handleIncrementSize = + _pDXDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + return true; +} + +void PTRenderer::initCommandList() +{ + // Create a command queue. + D3D12_COMMAND_QUEUE_DESC queueDesc = {}; + queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + checkHR(_pDXDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_pCommandQueue))); + + // Create a command allocator for each active task, as a command allocator is needed for each + // possible command list that is queued at once. + _commandAllocators.resize(_taskCount); + for (uint32_t i = 0; i < _taskCount; i++) + { + checkHR(_pDXDevice->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&(_commandAllocators[i])))); + } + + // Create a command list. Only a single command list is required for each thread, just one here. + // NOTE: It is initialized with the first command allocator but will be reset with the command + // allocator for the current active task index. + checkHR(_pDXDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + _commandAllocators[0].Get(), nullptr, IID_PPV_ARGS(&_pCommandList))); + _pCommandList->Close(); + + // Create a fence and event for detecting when a command list is complete. + checkHR(_pDXDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_pTaskFence))); + _hTaskEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); +} + +void PTRenderer::initFrameData() +{ + // Create a buffer to store frame data for each buffer. A single buffer can be used for this, + // as long as each section of the buffer is not written to while it is being used by the GPU. + size_t frameDataBufferSize = _FRAME_DATA_SIZE * _taskCount; + _pFrameDataBuffer = createBuffer(frameDataBufferSize, "FrameData"); +} + +void PTRenderer::initRayGenShaderTable() +{ + // Compute the stride of each record in the shader table. The stride includes the shader + // identifier and its parameters. Also compute the total size of the table. + size_t rayGenShaderRecordStride = SHADER_ID_SIZE; + rayGenShaderRecordStride += SHADER_RECORD_DESCRIPTOR_SIZE; // for the descriptor table + rayGenShaderRecordStride = ALIGNED_SIZE(rayGenShaderRecordStride, SHADER_RECORD_ALIGNMENT); + _rayGenShaderTableSize = rayGenShaderRecordStride; // just one shader record + + // Create a buffer for the shader table and map it for writing. + _pRayGenShaderTable = createBuffer(_rayGenShaderTableSize); +} + +void PTRenderer::updateRayGenShaderTable() +{ + uint8_t* pShaderTableMappedData = nullptr; + checkHR( + _pRayGenShaderTable->Map(0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + + // Write the shader identifier for the ray gen shader. + ::memcpy_s(pShaderTableMappedData, _rayGenShaderTableSize, _pShaderLibrary->getRayGenShaderID(), + SHADER_ID_SIZE); + + // Close the shader table buffer. + _pRayGenShaderTable->Unmap(0, nullptr); // no HRESULT +} + +void PTRenderer::initAccumulation() +{ + // Get the byte code for the compute shader. + D3D12_SHADER_BYTECODE shaderByteCode = + CD3DX12_SHADER_BYTECODE(g_pAccumulationShader, _countof(g_pAccumulationShader)); + + // Create a root signature from the definition in the byte code. + checkHR(_pDXDevice->CreateRootSignature(0, g_pAccumulationShader, + _countof(g_pAccumulationShader), IID_PPV_ARGS(&_pAccumulationRootSignature))); + + // Prepare compute pipeline state with the compute shader. + D3D12_COMPUTE_PIPELINE_STATE_DESC desc = {}; + desc.CS = shaderByteCode; + checkHR( + _pDXDevice->CreateComputePipelineState(&desc, IID_PPV_ARGS(&_pAccumulationPipelineState))); +} + +void PTRenderer::initPostProcessing() +{ + // Get the byte code for the compute shader. + D3D12_SHADER_BYTECODE shaderByteCode = + CD3DX12_SHADER_BYTECODE(g_pPostProcessingShader, _countof(g_pPostProcessingShader)); + + // Create a root signature from the definition in the byte code. + checkHR(_pDXDevice->CreateRootSignature(0, g_pPostProcessingShader, + _countof(g_pPostProcessingShader), IID_PPV_ARGS(&_pPostProcessingRootSignature))); + + // Prepare compute pipeline state with the compute shader. + D3D12_COMPUTE_PIPELINE_STATE_DESC desc = {}; + desc.CS = shaderByteCode; + checkHR(_pDXDevice->CreateComputePipelineState( + &desc, IID_PPV_ARGS(&_pPostProcessingPipelineState))); +} + +void PTRenderer::renderInternal(uint32_t sampleStart, uint32_t sampleCount) +{ + assert(_pScene && _pTargetFinal); + assert(sampleCount > 0); + + // If non-zero instance count, then ensure we have valid bounds. + AU_ASSERT(!dxScene()->instanceCount() || dxScene()->bounds().isValid(), + "Scene bounds are not valid. Were valid bounds set with IScene::setBounds() ?"); + + // Retain certain option state for the frame. + bool isResetHistoryEnabled = _values.asBoolean(kLabelIsResetHistoryEnabled); + + // Have any options changed? + if (_bIsDirty) + { +#if ENABLE_MATERIALX + // Get the units option. + string unit = _values.asString(kLabelUnits); + // Lookup the unit in in the code generator, and ensure it is valid. + auto unitIter = _pMaterialXGenerator->codeGenerator().units().indices.find(unit); + if (unitIter == _pMaterialXGenerator->codeGenerator().units().indices.end()) + { + AU_ERROR("Invalid unit:" + unit); + } + else + { + // Set the option in the shader library. + _pShaderLibrary->setOption("DISTANCE_UNIT", unitIter->second); + } +#endif + + // Set the importance sampling mode. + _pShaderLibrary->setOption( + "IMPORTANCE_SAMPLING_MODE", _values.asInt(kLabelImportanceSamplingMode)); + + // Set the default BSDF (if true will use Reference, if false will use Standard Surface.) + _pShaderLibrary->setOption( + "USE_REFERENCE_BSDF", _values.asBoolean(kLabelIsReferenceBSDFEnabled)); + + // Clear the dirty flag. + _bIsDirty = false; + } + + // Rebuild shader library if needed. + if (_pShaderLibrary->rebuildRequired()) + { + // If shader rebuild required, wait for GPU to be idle, then rebuild. + waitForTask(); + _pShaderLibrary->rebuild(); + + // Update the ray gen shader table after rebuild. + updateRayGenShaderTable(); + + // Clear the scene's shader data, to ensure that is rebuilt too. + dxScene()->clearShaderData(); + } + + // Update the scene. + dxScene()->update(); + + // Update the resources associated with the scene, output (targets), and denoising. + // NOTE: These updates are very fast if nothing relevant has changed since the last render. + _isDimensionsChanged = _outputDimensions != _pTargetFinal->dimensions(); + _outputDimensions = _pTargetFinal->dimensions(); + _isDescriptorHeapChanged = _pDescriptorHeap.Get() != dxScene()->descriptorHeap(); + _pDescriptorHeap = dxScene()->descriptorHeap(); + _pSamplerDescriptorHeap = dxScene()->samplerDescriptorHeap(); + updateSceneResources(); + updateOutputResources(); + updateDenoisingResources(); + _isDimensionsChanged = false; + _isDescriptorHeapChanged = false; + + // Use an incrementing (but gradually repeating) random number seed offset when temporal + // accumulation is needed, for denoising. This offset ensures that the same set of random + // numbers is not reused frame-to-frame, which would prevent proper temporal accumulation. For + // temporal accumulation is not needed (not denoising), do not use an offset, to avoid obvious + // moving "static" between frames. + uint32_t seedOffset = 0; + if (_values.asBoolean(kLabelIsDenoisingEnabled)) + { + // Increment the stored seed offset when rendering has been restarted. + if (sampleStart == 0) + { + constexpr uint32_t kRepeat = 100; + _seedOffset = ++_seedOffset % kRepeat; + } + + // Use the stored seed offset, instead of the default zero. + seedOffset = _seedOffset; + } + + // Prepare a ray dispatch description, and perform a ray tracing dispatch for each requested + // sample, starting at the sample start index. + // NOTE: While it is possible to render multiple samples in a single dispatch instead of using a + // loop (as done here), that is actually much slower in practice. + D3D12_DISPATCH_RAYS_DESC dispatchRaysDesc = {}; + prepareRayDispatch(dispatchRaysDesc); + for (uint32_t i = 0; i < sampleCount; i++) + { + // Update the per-frame data. There is one copy of the data per task, and a task is + // completed at the end of this loop, so the data update must be performed here. + updateFrameData(); + + // Perform ray tracing for the current sample. + submitRayDispatch(dispatchRaysDesc, sampleStart + i, seedOffset); + + // Perform denoising of the diffuse / glossy radiance results. + // NOTE: This does nothing if denoising is not enabled. + submitDenoising(isResetHistoryEnabled); + + // Perform accumulation using the generated AOVs, some of which may have been denoised. + // NOTE: This should not use the sample offset, as it needs to know how far along the + // accumulation has proceeded. + submitAccumulation(sampleStart + i); + + // Complete the task here, because the denoiser itself only supports the same number of + // tasks and uses one for each sample counter increment. + completeTask(); + } + + // Perform post-processing and copy the results to the targets. + submitPostProcessing(); + + // Present the targets. + // NOTE: If the target has a swap chain, the following applies to avoid errors: + // - WRONGSWAPCHAINBUFFERREFERENCE: Present must be done *after* the previous command list is + // submitted to avoid this immediate error. + // - OBJECT_DELETED_WHILE_STILL_IN_USE: Present must be done *before* ending the task, to avoid + // this error on swap chain resize or application shutdown. Specifically, a command queue + // Signal() call must be made after presenting. The reason for this is unclear, but it has to + // do with the swap chain interacting with the command queue it was created with. + _pTargetFinal->present(); + if (_pTargetDepthNDC) + { + _pTargetDepthNDC->present(); + } + + // Complete the task, so that rendering is ready for the next task. + completeTask(); + + // Clear the history reset flag. + // NOTE: This is done as a convenience for the client, as resetting history is almost always + // intended only for one frame. + _values.setValue(kLabelIsResetHistoryEnabled, false); +} + +void PTRenderer::updateFrameData() +{ + // Call base-class update function to get GPU frame data. + updateFrameDataGPUStruct(); + + // Get other options. Limit the trace depth to the range [1, kMaxTraceDepth]. + // TODO: Should be in base class too. + _frameData.isDepthNDCEnabled = _pTargetDepthNDC ? 1 : 0; + _frameData.isDenoisingAOVsEnabled = isDenoisingAOVsEnabled() ? 1 : 0; + + // Copy frame data to the frame data buffer, at the buffer data location for the current task + // index. The updated section of the buffer must be set on the pipeline later with + // SetComputeRootConstantBufferView(). + uint8_t* pFrameDataBufferMappedData = nullptr; + size_t start = _FRAME_DATA_SIZE * _taskIndex; + size_t end = start + _FRAME_DATA_SIZE; + D3D12_RANGE range = { start, end }; + checkHR( + _pFrameDataBuffer->Map(0, &range, reinterpret_cast(&pFrameDataBufferMappedData))); + ::memcpy_s( + pFrameDataBufferMappedData + start, _FRAME_DATA_SIZE, &_frameData, sizeof(_frameData)); + _pFrameDataBuffer->Unmap(0, nullptr); // no HRESULT +} + +void PTRenderer::updateSceneResources() +{ + // Do nothing if the descriptor heap is unchanged. + // NOTE: A changing descriptor heap also corresponds with an entirely new scene. + if (!_isDescriptorHeapChanged) + { + return; + } + + // Set the descriptor heap on the ray gen shader table, for descriptors needed from the heap for + // the ray gen shader. The ray gen shader expects the first entry to be the direct texture. + uint8_t* pShaderTableMappedData = nullptr; + checkHR( + _pRayGenShaderTable->Map(0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + CD3DX12_GPU_DESCRIPTOR_HANDLE handle(_pDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), + kDirectDescriptorOffset, _handleIncrementSize); + ::memcpy_s(pShaderTableMappedData + SHADER_ID_SIZE, _rayGenShaderTableSize, &handle, + SHADER_RECORD_DESCRIPTOR_SIZE); + _pRayGenShaderTable->Unmap(0, nullptr); // no HRESULT +} + +void PTRenderer::updateOutputResources() +{ + // NOTE: If the client switches between several resident targets with different dimensions (e.g. + // several viewports), creating new output textures every time may become a performance + // bottleneck. This can be resolved by having a small cache of output textures with recently + // used dimensions. + // + // Only single textures are required (not one per active task), as the renderer does not employ + // multiple command queues that could operate simultaneously, and the CPU does not read or write + // these textures. + + // Get the final target image format, converted to a DXGI format. + DXGI_FORMAT newFormat = PTImage::getDXFormat(_pTargetFinal->format()); + + // Create new required textures if the output dimensions or image format have changed. + bool isTexturesUpdated = false; + if (_isDimensionsChanged || _finalFormat != newFormat) + { + // If there is an existing final texture, flush the renderer to make sure the related + // textures are no longer being used by the pipeline. NOTE: It would be redundant to also + // check the other textures, so that is not done. + if (_pTexFinal) + { + waitForTask(); + } + + // Create the final texture with the final target format (usually integer SDR), and the + // accumulation and direct textures with a floating-point (HDR) format, and all with + // unordered access. + // NOTE: A texture with unordered access can't have an "_SRGB" format, so gamma correction + // must be performed in the post-processing step. + const DXGI_FORMAT kHDRFormat = DXGI_FORMAT_R32G32B32A32_FLOAT; + _finalFormat = newFormat; + _pTexFinal = createTexture(_outputDimensions, _finalFormat, "Output", true); + _pTexAccumulation = createTexture(_outputDimensions, kHDRFormat, "HDR Accumulation", true); + _pTexDirect = createTexture(_outputDimensions, kHDRFormat, "HDR Direct", true); + isTexturesUpdated = true; + } + + // Create (or clear) the optional NDC depth texture if the output dimensions have changed or the + // target has been toggled (set or removed). + if (_isDimensionsChanged || + static_cast(_pTargetDepthNDC) != static_cast(_pTexDepthNDC)) + { + // If there is an existing NDC depth texture, flush the renderer to make sure the texture is + // no longer being used by the pipeline. + if (_pTexDepthNDC) + { + waitForTask(); + } + + // Create or clear the NDC depth texture. + if (_pTargetDepthNDC) + { + const DXGI_FORMAT kDepthNDCFormat = DXGI_FORMAT_R32_FLOAT; + _pTexDepthNDC = createTexture(_outputDimensions, kDepthNDCFormat, "NDC Depth", true); + } + else + { + _pTexDepthNDC.Reset(); + } + isTexturesUpdated = true; + } + + // If textures have been updated, or the descriptor heap has changed, create UAVs for the + // textures. + if (isTexturesUpdated || _isDescriptorHeapChanged) + { + // Flush the renderer to make sure the (unchanged) descriptor heap is not being used. + if (!_isDescriptorHeapChanged) + { + waitForTask(); + } + + // Create UAVs for the textures as the first entries in the descriptor heap. + CD3DX12_CPU_DESCRIPTOR_HANDLE handle( + _pDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); + createUAV(_pTexFinal.Get(), handle); + createUAV(_pTexAccumulation.Get(), handle); + createUAV(_pTexDirect.Get(), handle); + createUAV(_pTexDepthNDC.Get(), handle); + } +} + +void PTRenderer::updateDenoisingResources() +{ + // Create or destroy denoising resources as needed. + bool isTexturesUpdated = false; + if (isDenoisingAOVsEnabled()) + { + // If the dimensions have changed, or there are no existing denoising resources, + // recreate the denoising resources. + if (_isDimensionsChanged || !_pDenoisingTexDepthView) + { + isTexturesUpdated = true; + + // If there is any existing denoising resource (using one to represent all of them), + // flush the renderer to make sure the denoising resources are no longer being used + // by the pipeline. + if (_pDenoisingTexDepthView) + { + waitForTask(); + } + + // Define resource formats for denoising textures. + const DXGI_FORMAT kDepthViewFormat = DXGI_FORMAT_R32_FLOAT; + const DXGI_FORMAT kNormalRoughnessFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + const DXGI_FORMAT kBaseColorMetalnessFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + const DXGI_FORMAT kDiffuseFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + const DXGI_FORMAT kGlossyFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + + // Create the denoising textures, each with unordered access. + uvec2& dims = _outputDimensions; + _pDenoisingTexDepthView = createTexture(dims, kDepthViewFormat, "DepthView", true); + _pDenoisingTexNormalRoughness = + createTexture(dims, kNormalRoughnessFormat, "Normal / Roughness", true); + _pDenoisingTexBaseColorMetalness = + createTexture(dims, kBaseColorMetalnessFormat, "Base Color / Metalness", true); + _pDenoisingTexDiffuse = createTexture(dims, kDiffuseFormat, "Diffuse Input", true); + _pDenoisingTexGlossy = createTexture(dims, kGlossyFormat, "Glossy Input", true); + _pDenoisingTexDiffuseOut = createTexture(dims, kDiffuseFormat, "Diffuse Output", true); + _pDenoisingTexGlossyOut = createTexture(dims, kGlossyFormat, "Glossy Output", true); + } + } + else + { + // If there is any existing denoising resource (using one to represent all of them), + // flush the renderer to make sure the denoising resources are no longer being used by + // the pipeline, then clear the pointers. + if (_pDenoisingTexDepthView) + { + waitForTask(); + _pDenoisingTexDepthView.Reset(); + _pDenoisingTexNormalRoughness.Reset(); + _pDenoisingTexBaseColorMetalness.Reset(); + _pDenoisingTexDiffuse.Reset(); + _pDenoisingTexGlossy.Reset(); + isTexturesUpdated = true; + } + } + + // If textures have been updated, or the descriptor heap has changed, create UAVs for the + // textures. + if (isTexturesUpdated || _isDescriptorHeapChanged) + { + // Flush the renderer to make sure the (unchanged) descriptor heap is not being used. + if (!_isDescriptorHeapChanged) + { + waitForTask(); + } + + // Create UAVs for the denoising textures as the entries in the descriptor heap *after* the + // descriptors for the output-related textures. + CD3DX12_CPU_DESCRIPTOR_HANDLE handle(_pDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + kOutputDescriptorCount, _handleIncrementSize); + createUAV(_pDenoisingTexDepthView.Get(), handle); + createUAV(_pDenoisingTexNormalRoughness.Get(), handle); + createUAV(_pDenoisingTexBaseColorMetalness.Get(), handle); + createUAV(_pDenoisingTexDiffuse.Get(), handle); + createUAV(_pDenoisingTexGlossy.Get(), handle); + createUAV(_pDenoisingTexDiffuseOut.Get(), handle); + createUAV(_pDenoisingTexGlossyOut.Get(), handle); + } + +#if defined(ENABLE_DENOISER) + // Create or destroy the denoiser as needed. This can depend on the denoising resources + // created above. + if (_isDenoisingEnabled) + { + // Create a new denoiser if needed. + bool isNewDenoiser = false; + if (!_pDenoiser) + { + _pDenoiser = make_unique(_pDXDevice.Get(), _pCommandQueue.Get(), _taskCount); + isNewDenoiser = true; + } + + // If the denoiser is new, or the dimensions have changed (i.e. new resource), + // initialize the denoiser. + if (isNewDenoiser || _isDimensionsChanged) + { + // Collect the denoising textures. + // clang-format off + Denoiser::Textures textures = + { + _pDenoisingTexDepthView.Get(), + _pDenoisingTexNormalRoughness.Get(), + _pDenoisingTexDiffuse.Get(), + _pDenoisingTexGlossy.Get(), + _pDenoisingTexDiffuseOut.Get(), + _pDenoisingTexGlossyOut.Get() + }; + // clang-format on + + // Initialize the denoiser. + _pDenoiser->initialize(_outputDimensions, textures); + } + } + else if (_pDenoiser) + { + // Wait for tasks to complete before destroying the denoiser, as internal resources may + // still be in use. + waitForTask(); + _pDenoiser.reset(); + } +#endif // ENABLE_DENOISER +} + +void PTRenderer::prepareRayDispatch(D3D12_DISPATCH_RAYS_DESC& dispatchRaysDesc) +{ + // Prepare a ray dispatch description, including the dimensions... + dispatchRaysDesc.Width = _outputDimensions.x; + dispatchRaysDesc.Height = _outputDimensions.y; + dispatchRaysDesc.Depth = 1; + + // ... the shader table for the ray generation shader... + D3D12_GPU_VIRTUAL_ADDRESS address = _pRayGenShaderTable->GetGPUVirtualAddress(); + dispatchRaysDesc.RayGenerationShaderRecord.StartAddress = address; + dispatchRaysDesc.RayGenerationShaderRecord.SizeInBytes = _rayGenShaderTableSize; + + // ... the shader table for the miss shader(s)... + size_t missShaderRecordStride = 0; + uint32_t missShaderRecordCount = 0; + ID3D12ResourcePtr pMissShaderTable = + dxScene()->getMissShaderTable(missShaderRecordStride, missShaderRecordCount); + dispatchRaysDesc.MissShaderTable.StartAddress = pMissShaderTable->GetGPUVirtualAddress(); + dispatchRaysDesc.MissShaderTable.StrideInBytes = missShaderRecordStride; + dispatchRaysDesc.MissShaderTable.SizeInBytes = missShaderRecordStride * missShaderRecordCount; + + // ... the shader table for the hit group(s). + // NOTE: If there are no instances, the hit group shader table will be nullptr. + size_t hitGroupShaderRecordStride = 0; + uint32_t hitGroupShaderRecordCount = 0; + ID3D12ResourcePtr pHitGroupShaderTable = + dxScene()->getHitGroupShaderTable(hitGroupShaderRecordStride, hitGroupShaderRecordCount); + dispatchRaysDesc.HitGroupTable.StartAddress = + pHitGroupShaderTable ? pHitGroupShaderTable->GetGPUVirtualAddress() : 0; + dispatchRaysDesc.HitGroupTable.StrideInBytes = hitGroupShaderRecordStride; + dispatchRaysDesc.HitGroupTable.SizeInBytes = + hitGroupShaderRecordStride * hitGroupShaderRecordCount; +} + +void PTRenderer::submitRayDispatch( + const D3D12_DISPATCH_RAYS_DESC& dispatchRaysDesc, uint32_t sampleIndex, uint32_t seedOffset) +{ + // Begin a command list. + ID3D12GraphicsCommandList4Ptr pCommandList = beginCommandList(); + + // Prepare the descriptor heaps for CBV/SRV/UAV and for samplers. + ID3D12DescriptorHeap* pDescriptorHeaps[] = { _pDescriptorHeap.Get(), + _pSamplerDescriptorHeap.Get() }; + + // Prepare the root signature, and pipeline state. + pCommandList->SetDescriptorHeaps(2, pDescriptorHeaps); + pCommandList->SetComputeRootSignature(_pShaderLibrary->globalRootSignature().Get()); + pCommandList->SetPipelineState1(_pShaderLibrary->pipelineState().Get()); + + // Set the global root signature arguments. + { + // 0) The acceleration structure. + D3D12_GPU_VIRTUAL_ADDRESS accelStructureAddress = + dxScene()->accelerationStructure()->GetGPUVirtualAddress(); + pCommandList->SetComputeRootShaderResourceView(0, accelStructureAddress); + + // 1) The sample index, as a root constant. + SampleData sampleData = { sampleIndex, seedOffset }; + pCommandList->SetComputeRoot32BitConstants(1, 2, &sampleData, 0); + + // 2) The frame data constant buffer, for the current task index. + D3D12_GPU_VIRTUAL_ADDRESS frameDataBufferAddress = + _pFrameDataBuffer->GetGPUVirtualAddress() + _FRAME_DATA_SIZE * _taskIndex; + pCommandList->SetComputeRootConstantBufferView(2, frameDataBufferAddress); + + // 3) The environment data constant buffer. + ID3D12Resource* pEnvironmentDataBuffer = dxScene()->environment()->buffer(); + D3D12_GPU_VIRTUAL_ADDRESS environmentDataBufferAddress = + pEnvironmentDataBuffer->GetGPUVirtualAddress(); + pCommandList->SetComputeRootConstantBufferView(3, environmentDataBufferAddress); + + // 4) The environment alias map structured buffer. + // NOTE: Structured buffers must be specified with an SRV, not a CBV. + ID3D12Resource* pEnvironmentAliasMapBuffer = dxScene()->environment()->aliasMap(); + D3D12_GPU_VIRTUAL_ADDRESS environmentAliasMapBufferAddress = + pEnvironmentAliasMapBuffer ? pEnvironmentAliasMapBuffer->GetGPUVirtualAddress() : 0; + pCommandList->SetComputeRootShaderResourceView(4, environmentAliasMapBufferAddress); + + // 5) The environment texture descriptor table. + // NOTE: This starts right after the renderer's descriptors, as defined by the scene. + CD3DX12_GPU_DESCRIPTOR_HANDLE handle( + _pDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); + handle.Offset(kDescriptorCount, _handleIncrementSize); + pCommandList->SetComputeRootDescriptorTable(5, handle); + + // 6) The ground plane data constant buffer. + ID3D12Resource* pGroundPlaneDataBuffer = dxScene()->groundPlane()->buffer(); + D3D12_GPU_VIRTUAL_ADDRESS groundPlaneDataBufferAddress = + pGroundPlaneDataBuffer->GetGPUVirtualAddress(); + pCommandList->SetComputeRootConstantBufferView(6, groundPlaneDataBufferAddress); + + // 7) The null acceleration structure (used for layer material shading). + D3D12_GPU_VIRTUAL_ADDRESS nullAccelStructAddress = 0; + pCommandList->SetComputeRootShaderResourceView(7, nullAccelStructAddress); + } + + // Launch the ray generation shader with the dispatch, which performs path tracing. + // NOTE: Rendering performance is somewhat choppy if you try to perform multiple dispatches + // in a single command list. For that reason, there is a separate command list for each path + // tracing sample (dispatch). That means each command list has mostly redundant setup (above), + // but that is still a better runtime experience. + pCommandList->DispatchRays(&dispatchRaysDesc); + + // Submit the command list. + submitCommandList(); +} + +#if defined(ENABLE_DENOISER) +void PTRenderer::submitDenoising(bool isRestartRequested) +{ + // Do nothing if denoising is not enabled, i.e. there is no denoiser. + if (!_pDenoiser) + { + return; + } + + // Begin a command list. + ID3D12GraphicsCommandList4Ptr pCommandList = beginCommandList(); + + // Prepare denoiser state. + Denoiser::DenoiserState denoiserState; + denoiserState.isRestart = isRestartRequested; + denoiserState.taskIndex = _taskIndex; + denoiserState.cameraView = _cameraView; + denoiserState.cameraProj = _cameraProj; + denoiserState.bounds = dxScene()->bounds(); + denoiserState.pCommandList = pCommandList.Get(); + denoiserState.pCommandAllocator = getCommandAllocator(); + + // Execute the denoiser, which records commands to the command list. + _pDenoiser->denoise(denoiserState); + + // Submit the command list. + submitCommandList(); +} +#else +void PTRenderer::submitDenoising(bool /*isRestart*/) {} +#endif // ENABLE_DENOISER + +void PTRenderer::submitAccumulation(uint32_t sampleIndex) +{ + // Prepare accumulation settings. + updateAccumulationGPUStruct(sampleIndex); + + // Begin a command list. + ID3D12GraphicsCommandList4Ptr pCommandList = beginCommandList(); + + // Add a UAV barrier so that the direct texture can't be used until the previous dispatch is + // complete. + addUAVBarrier(_pTexDirect.Get()); + + // Prepare the pipeline for accumulation: descriptor heap, root signature, and pipeline + // state. + pCommandList->SetDescriptorHeaps(1, _pDescriptorHeap.GetAddressOf()); + pCommandList->SetComputeRootSignature(_pAccumulationRootSignature.Get()); + pCommandList->SetPipelineState(_pAccumulationPipelineState.Get()); + + // Set the root signature arguments for accumulation: the descriptor table and the accumulation + // settings. The descriptor table must start with the accumulation texture. Then dispatch the + // accumulation shader, which performs (optional) deferred shading and merges new path tracing + // samples with the previous results. + CD3DX12_GPU_DESCRIPTOR_HANDLE handle(_pDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), + kAccumulationDescriptorOffset, _handleIncrementSize); + pCommandList->SetComputeRootDescriptorTable(0, handle); + pCommandList->SetComputeRoot32BitConstants(1, sizeof(Accumulation) / 4, &_accumData, 0); + pCommandList->Dispatch(_outputDimensions.x, _outputDimensions.y, 1); + + // Submit the command list. + submitCommandList(); +} + +void PTRenderer::submitPostProcessing() +{ + // Update the post processing GPU struct by calling base class function. + updatePostProcessingGPUStruct(); + + // Begin a command list. + ID3D12GraphicsCommandList4Ptr pCommandList = beginCommandList(); + + // Add a UAV barrier so that the accumulation texture can't be used until the previous dispatch + // is complete. + addUAVBarrier(_pTexAccumulation.Get()); + + // Prepare the pipeline for post-processing: descriptor heap, root signature, and pipeline + // state. + pCommandList->SetDescriptorHeaps(1, _pDescriptorHeap.GetAddressOf()); + pCommandList->SetComputeRootSignature(_pPostProcessingRootSignature.Get()); + pCommandList->SetPipelineState(_pPostProcessingPipelineState.Get()); + + // Set the root signature arguments for post-processing: the descriptor table (at the start of + // the heap) and the post-processing settings. The descriptor table must start with the final + // texture. Then dispatch the post-processing shader, which tone maps (as needed) the + // accumulation texture (HDR) to the final texture (usually SDR). + // NOTE: Even if the settings mean no post-processing is performed, there is still an implicit + // format conversion, from the accumulation texture (UAV) format to the output texture format, + // e.g. floating-point to integer. + CD3DX12_GPU_DESCRIPTOR_HANDLE handle(_pDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), + kFinalDescriptorOffset, _handleIncrementSize); + pCommandList->SetComputeRootDescriptorTable(0, handle); + pCommandList->SetComputeRoot32BitConstants( + 1, sizeof(PostProcessing) / 4, &_postProcessingData, 0); + pCommandList->Dispatch(_outputDimensions.x, _outputDimensions.y, 1); + + // Copy the output textures to their associated targets, if any. + copyTextureToTarget(_pTexFinal.Get(), _pTargetFinal.get()); + if (_pTargetDepthNDC) + { + copyTextureToTarget(_pTexDepthNDC.Get(), _pTargetDepthNDC.get()); + } + + // Submit the command list. + submitCommandList(); +} + +void PTRenderer::createUAV(ID3D12Resource* pTexture, CD3DX12_CPU_DESCRIPTOR_HANDLE& handle) +{ + // Create two static descriptions: one for UAVs when the resource is present, and another + // for when the resource is not present. The latter is used to create a null descriptor; in that + // case, the UAV description must have an arbitrary format. + static D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {}; + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + static D3D12_UNORDERED_ACCESS_VIEW_DESC uavDescNull = {}; + uavDescNull.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + uavDescNull.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + + // Create a valid or null descriptor for the texture, depending on whether the texture is + // null. Then offset the handle by the increment, for creating a subsequent descriptor. + D3D12_UNORDERED_ACCESS_VIEW_DESC* pUAVDesc = pTexture ? &uavDesc : &uavDescNull; + _pDXDevice->CreateUnorderedAccessView(pTexture, nullptr, pUAVDesc, handle); + handle.Offset(_handleIncrementSize); +} + +void PTRenderer::copyTextureToTarget(ID3D12Resource* pTexture, PTTarget* pTarget) +{ + // Transition the final texture from writing (rendering to copying), then copy the final + // texture to the target, and then transition back. + addTransitionBarrier( + pTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE); + pTarget->copyFromResource(pTexture, _pCommandList.Get()); + addTransitionBarrier( + pTexture, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); +} + +bool PTRenderer::isDenoisingAOVsEnabled() const +{ + // Denoising AOVs are enabled when either denoising is enabled, or the debug mode involves + // rendering with a denoising AOV. + return _values.asBoolean(kLabelIsDenoisingEnabled) || + _values.asInt(kLabelDebugMode) > kDebugModeErrors; +} + +shared_ptr PTRenderer::generateMaterialX( + [[maybe_unused]] const string& document, [[maybe_unused]] map* pDefaultValuesOut) +{ +#if ENABLE_MATERIALX + // Generate the shader for the materialX document, along with its unique material name. + MaterialTypeSource materialTypeSource; + if (!_pMaterialXGenerator->generate(document, pDefaultValuesOut, materialTypeSource)) + { + return nullptr; + } + + // Set the shared definitions, this will only change anything if string is different to current + // one. + string definitions; + _pMaterialXGenerator->generateDefinitions(definitions); + _pShaderLibrary->setDefinitionsHLSL(definitions); + + // Acquire a material type for this shader. + // This will create a new one if needed (and trigger a rebuild), otherwise will it will return + // existing one. + bool typeCreated; + auto pType = _pShaderLibrary->acquireMaterialType(materialTypeSource, &typeCreated); + if (typeCreated) + { + if (AU_DEV_DUMP_PROCESSED_MATERIALX_DOCUMENTS) + { + string mltxPath = Foundation::getModulePath() + materialTypeSource.name + "Dumped.mtlx"; + AU_INFO("Dumping processed MTLX document to:%s", mltxPath.c_str()); + ofstream outputFile; + outputFile.open(mltxPath); + outputFile << document; + outputFile.close(); + } + } + + return pType; +#else + return nullptr; +#endif +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTRenderer.h b/Libraries/Aurora/Source/DirectX/PTRenderer.h new file mode 100644 index 0000000..e68e442 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTRenderer.h @@ -0,0 +1,231 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "PTEnvironment.h" +#include "PTGeometry.h" +#include "PTGroundPlane.h" +#include "PTMaterial.h" +#include "PTSampler.h" +#include "PTScene.h" +#include "PTTarget.h" +#include "RendererBase.h" + +// Include the denoiser if it is enabled. +#if defined(ENABLE_DENOISER) +#include "Denoiser.h" +#endif + +BEGIN_AURORA + +namespace MaterialXCodeGen +{ +class MaterialGenerator; +} // namespace MaterialXCodeGen + +// Forward references. +class AssetManager; +class PTDevice; +class PTMaterialType; +class PTShaderLibrary; +class ScratchBufferPool; +class VertexBufferPool; + +// An path tracing (PT) implementation for IRenderer. +class PTRenderer : public RendererBase +{ +public: + /*** Types ***/ + + template + using FillDataFunction = function; + + /*** Lifetime Management ***/ + + /// Path tracing renderer constructor. + /// + /// \param activeTaskCount Maximum number of tasks active at once. + PTRenderer(uint32_t activeTaskCount); + ~PTRenderer(); + + /*** IRenderer Functions ***/ + + IWindowPtr createWindow(WindowHandle handle, uint32_t width, uint32_t height) override; + IRenderBufferPtr createRenderBuffer(int width, int height, ImageFormat imageFormat) override; + IImagePtr createImagePointer(const IImage::InitData& initData) override; + ISamplerPtr createSamplerPointer(const Properties& props) override; + IMaterialPtr createMaterialPointer(const string& materialType = Names::MaterialTypes::kBuiltIn, + const string& document = "Default", const string& name = "") override; + IScenePtr createScene() override; + IEnvironmentPtr createEnvironmentPointer() override; + IGeometryPtr createGeometryPointer( + const GeometryDescriptor& desc, const string& name = "") override; + IGroundPlanePtr createGroundPlanePointer() override; + IRenderer::Backend backend() const override { return IRenderer::Backend::DirectX; } + void setScene(const IScenePtr& pScene) override; + void setTargets(const TargetAssignments& targetAssignments) override; + void render(uint32_t sampleStart, uint32_t sampleCount) override; + void waitForTask() override; + const vector& builtInMaterials() override; + void setLoadResourceFunction(LoadResourceFunction func) override; + + /*** Functions ***/ + + ID3D12Device5* dxDevice() const { return _pDXDevice.Get(); } + IDXGIFactory4* dxFactory() const { return _pDXFactory.Get(); } + ID3D12CommandQueue* commandQueue() const { return _pCommandQueue.Get(); } + ID3D12ResourcePtr createBuffer(size_t size, const string& name = "", + D3D12_HEAP_TYPE heapType = D3D12_HEAP_TYPE_UPLOAD, + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, + D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_GENERIC_READ); + template + void updateBuffer(ID3D12ResourcePtr& pBuffer, FillDataFunction fillDataFunction); + ID3D12ResourcePtr createTexture(uvec2 dimensions, DXGI_FORMAT format, const string& name = "", + bool isUnorderedAccess = false, bool shareable = false); + D3D12_GPU_VIRTUAL_ADDRESS getScratchBuffer(size_t size); + void getVertexBuffer(VertexBuffer& vertexBuffer, void* pData); + void flushVertexBufferPool(); + ID3D12CommandAllocator* getCommandAllocator(); + ID3D12GraphicsCommandList4* beginCommandList(); + void submitCommandList(); + void addTransitionBarrier(ID3D12Resource* pResource, D3D12_RESOURCE_STATES stateBefore, + D3D12_RESOURCE_STATES stateAfter); + void addUAVBarrier(ID3D12Resource* pResource); + void completeTask(); + PTSamplerPtr defaultSampler(); + PTGroundPlanePtr defaultGroundPlane(); + +private: + /*** Private Types ***/ + static const size_t _FRAME_DATA_SIZE = + ALIGNED_SIZE(sizeof(FrameData), D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + + /*** Private Functions ***/ + + bool initDevice(); + void initCommandList(); + void initFrameData(); + void initRayGenShaderTable(); + void initAccumulation(); + void initPostProcessing(); + void renderInternal(uint32_t sampleStart, uint32_t sampleCount); + void updateRayGenShaderTable(); + void updateFrameData(); + void updateSceneResources(); + void updateOutputResources(); + void updateDenoisingResources(); + void prepareRayDispatch(D3D12_DISPATCH_RAYS_DESC& dispatchRaysDesc); + void submitRayDispatch(const D3D12_DISPATCH_RAYS_DESC& dispatchRaysDesc, uint32_t sampleIndex, + uint32_t seedOffset); + void submitDenoising(bool isRestart); + void submitAccumulation(uint32_t sampleIndex); + void submitPostProcessing(); + void createUAV(ID3D12Resource* pTexture, CD3DX12_CPU_DESCRIPTOR_HANDLE& handle); + void copyTextureToTarget(ID3D12Resource* pTexture, PTTarget* pTarget); + bool isDenoisingAOVsEnabled() const; + shared_ptr generateMaterialX( + const string& document, map* pDefaultValuesOut); + PTScenePtr dxScene() { return static_pointer_cast(_pScene); } + + /*** Private Variables ***/ + + unique_ptr _pDevice; + bool _isCommandListOpen = false; + ID3D12ResourcePtr _pFrameDataBuffer; + PTEnvironmentPtr _pEnvironment; + uvec2 _outputDimensions; + bool _isDimensionsChanged = true; + uint32_t _seedOffset = 0; + PTTargetPtr _pTargetFinal; + PTTargetPtr _pTargetDepthNDC; + bool _isDescriptorHeapChanged = true; + PTSamplerPtr _pDefaultSampler; + PTGroundPlanePtr _pDefaultGroundPlane; + + // The shader library used to compile DXIL shader libraries. + unique_ptr _pShaderLibrary; + + // Code generator used to generate MaterialX files. +#if ENABLE_MATERIALX + unique_ptr _pMaterialXGenerator; +#endif + + /*** DirectX 12 Objects ***/ + + ID3D12Device5Ptr _pDXDevice; + IDXGIFactory4Ptr _pDXFactory; + ID3D12CommandQueuePtr _pCommandQueue; + vector _commandAllocators; + ID3D12GraphicsCommandList4Ptr _pCommandList; + ID3D12FencePtr _pTaskFence; + HANDLE _hTaskEvent = nullptr; + ID3D12PipelineStatePtr _pAccumulationPipelineState; + ID3D12RootSignaturePtr _pAccumulationRootSignature; + ID3D12PipelineStatePtr _pPostProcessingPipelineState; + ID3D12RootSignaturePtr _pPostProcessingRootSignature; + ID3D12DescriptorHeapPtr _pDescriptorHeap; + ID3D12DescriptorHeapPtr _pSamplerDescriptorHeap; + UINT _handleIncrementSize = 0; + ID3D12ResourcePtr _pTexFinal; // for tone-mapped final output (usually SDR) + ID3D12ResourcePtr _pTexDepthNDC; // for NDC depth output (usually float) + ID3D12ResourcePtr _pTexAccumulation; // for accumulation (HDR) + ID3D12ResourcePtr _pTexDirect; // for path tracing or direct lighting (HDR) + DXGI_FORMAT _finalFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + ID3D12ResourcePtr _pRayGenShaderTable; + size_t _rayGenShaderTableSize = 0; + unique_ptr _pScratchBufferCache; + unique_ptr _pVertexBufferPool; + unordered_map _mtlxSamplers; + /*** Denoising Variables ***/ + + // Include the denoiser if it is enabled. Other variables are still used for debug modes, even + // when denoising itself is not enabled. +#if defined(ENABLE_DENOISER) + unique_ptr _pDenoiser; +#endif + ID3D12ResourcePtr _pDenoisingTexDepthView; + ID3D12ResourcePtr _pDenoisingTexNormalRoughness; + ID3D12ResourcePtr _pDenoisingTexBaseColorMetalness; + ID3D12ResourcePtr _pDenoisingTexDiffuse; + ID3D12ResourcePtr _pDenoisingTexGlossy; + ID3D12ResourcePtr _pDenoisingTexDiffuseOut; + ID3D12ResourcePtr _pDenoisingTexGlossyOut; +}; + +// Creates (if needed) and an updates a GPU constant buffer with data supplied by the caller. +template +void PTRenderer::updateBuffer( + ID3D12ResourcePtr& pBuffer, FillDataFunction fillDataFunction) +{ + // Create a constant buffer for the data if it doesn't already exist. + static const size_t BUFFER_SIZE = sizeof(DataType); + if (!pBuffer) + { + pBuffer = createBuffer(BUFFER_SIZE); + } + + // Fill the data using the callback function. + DataType data; + fillDataFunction(data); + + // Copy the data to the constant buffer. + void* pMappedData = nullptr; + checkHR(pBuffer->Map(0, nullptr, &pMappedData)); + ::memcpy_s(pMappedData, BUFFER_SIZE, &data, BUFFER_SIZE); + pBuffer->Unmap(0, nullptr); // no HRESULT +} + +MAKE_AURORA_PTR(PTRenderer); + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTSampler.cpp b/Libraries/Aurora/Source/DirectX/PTSampler.cpp new file mode 100644 index 0000000..ff61f62 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTSampler.cpp @@ -0,0 +1,81 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTSampler.h" + +#include "PTRenderer.h" + +BEGIN_AURORA + +PTSampler::PTSampler(PTRenderer* /* pRenderer*/, const Properties& props) +{ + // Initailize with default values. + _desc = D3D12_SAMPLER_DESC(); + _desc.Filter = D3D12_FILTER_ANISOTROPIC; + _desc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + _desc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + _desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + _desc.MipLODBias = 0.0f; + _desc.MaxAnisotropy = 16; + _desc.ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; + _desc.BorderColor[0] = 1.0f; + _desc.BorderColor[1] = 1.0f; + _desc.BorderColor[2] = 1.0f; + _desc.BorderColor[3] = 1.0f; + _desc.MinLOD = 0.0f; + _desc.MaxLOD = D3D12_FLOAT32_MAX; + + // Set the U address mode from properties. + if (props.find(Names::SamplerProperties::kAddressModeU) != props.end()) + _desc.AddressU = valueToDXAddressMode(props.at(Names::SamplerProperties::kAddressModeU)); + + // Set the V address mode from properties. + if (props.find(Names::SamplerProperties::kAddressModeV) != props.end()) + _desc.AddressV = valueToDXAddressMode(props.at(Names::SamplerProperties::kAddressModeV)); + + // Compute hash for sampler (only consider address modes for now.) + uint32_t addressModes[] = { (uint32_t)_desc.AddressU, (uint32_t)_desc.AddressV }; + _hash = Foundation::hashInts((uint32_t*)&addressModes, sizeof(addressModes) / sizeof(uint32_t)); +} + +void PTSampler::createDescriptor( + const PTRenderer& renderer, PTSampler* pSampler, const D3D12_CPU_DESCRIPTOR_HANDLE& handle) +{ + // Create DX descriptor from sample description struct. + renderer.dxDevice()->CreateSampler(pSampler->desc(), handle); +} + +D3D12_TEXTURE_ADDRESS_MODE PTSampler::valueToDXAddressMode(const PropertyValue& value) +{ + + // Convert property string to DX addres mode. + string valStr = value.asString(); + if (valStr.compare(Names::AddressModes::kWrap) == 0) + return D3D12_TEXTURE_ADDRESS_MODE_WRAP; + if (valStr.compare(Names::AddressModes::kMirror) == 0) + return D3D12_TEXTURE_ADDRESS_MODE_MIRROR; + if (valStr.compare(Names::AddressModes::kClamp) == 0) + return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + if (valStr.compare(Names::AddressModes::kMirrorOnce) == 0) + return D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE; + if (valStr.compare(Names::AddressModes::kBorder) == 0) + return D3D12_TEXTURE_ADDRESS_MODE_BORDER; + + // Fail if address mode not found. + AU_FAIL("Unknown address mode:%s", value.asString().c_str()); + return (D3D12_TEXTURE_ADDRESS_MODE)-1; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTSampler.h b/Libraries/Aurora/Source/DirectX/PTSampler.h new file mode 100644 index 0000000..6a13e7d --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTSampler.h @@ -0,0 +1,46 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; + +// An internal implementation for ISampler. +class PTSampler : public ISampler +{ +public: + /*** Lifetime Management ***/ + + PTSampler(PTRenderer* pRenderer, const Properties& props); + ~PTSampler() {}; + + const D3D12_SAMPLER_DESC* desc() { return &_desc; } + + static void createDescriptor( + const PTRenderer& renderer, PTSampler* pSampler, const D3D12_CPU_DESCRIPTOR_HANDLE& handle); + + size_t hash() { return _hash; } + +private: + static D3D12_TEXTURE_ADDRESS_MODE valueToDXAddressMode(const PropertyValue& value); + + D3D12_SAMPLER_DESC _desc; + size_t _hash; +}; + +MAKE_AURORA_PTR(PTSampler); + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTScene.cpp b/Libraries/Aurora/Source/DirectX/PTScene.cpp new file mode 100644 index 0000000..db9d696 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTScene.cpp @@ -0,0 +1,844 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTScene.h" + +#include "PTEnvironment.h" +#include "PTGeometry.h" +#include "PTGroundPlane.h" +#include "PTMaterial.h" +#include "PTRenderer.h" +#include "PTShaderLibrary.h" + +BEGIN_AURORA +#define kBuiltInMissShaderCount 4 // "built-in" miss shaders: null, background, radiance, and shadow + +PTLayerIndexTable::PTLayerIndexTable(PTRenderer* pRenderer, const vector& indices) : + _pRenderer(pRenderer) +{ + if (!indices.empty()) + set(indices); +} + +void PTLayerIndexTable::set(const vector& indices) +{ + // Create a constant buffer for the material data if it doesn't already exist. + static const size_t BUFFER_SIZE = sizeof(int) * kMaxMaterialLayers; + if (!_pConstantBuffer) + { + _pConstantBuffer = _pRenderer->createBuffer(BUFFER_SIZE); + } + + _count = static_cast(indices.size()); + + // Copy the indices to the constant buffer. + int* pMappedData = nullptr; + checkHR(_pConstantBuffer->Map(0, nullptr, (void**)&pMappedData)); + for (int i = 0; i < kMaxMaterialLayers; i++) + { + if (i < indices.size()) + pMappedData[i] = indices[i]; + else + pMappedData[i] = -1; + } + _pConstantBuffer->Unmap(0, nullptr); // no HRESULT +} + +// A structure that contains the properties for a hit group shader record, laid out for direct +// copying to a shader table buffer. +struct HitGroupShaderRecord +{ + // Constructor. + HitGroupShaderRecord(const void* pShaderIdentifier, const PTGeometry::GeometryBuffers& geometry, + const PTMaterial& material, ID3D12Resource* pMaterialLayerIndexBuffer, + D3D12_GPU_DESCRIPTOR_HANDLE texturesHeapAddress, + D3D12_GPU_DESCRIPTOR_HANDLE samplersHeapAddress, int materialLayerCount) + { + ::memcpy_s(&ShaderIdentifer, SHADER_ID_SIZE, pShaderIdentifier, SHADER_ID_SIZE); + IndexBufferAddress = geometry.IndexBuffer; + PositionBufferAddress = geometry.PositionBuffer; + HasNormals = geometry.NormalBuffer ? 1 : 0; + NormalBufferAddress = HasNormals ? geometry.NormalBuffer : 0; + HasTexCoords = geometry.TexCoordBuffer ? 1 : 0; + TexCoordBufferAddress = HasTexCoords ? geometry.TexCoordBuffer : 0; + MaterialLayerCount = materialLayerCount; + MaterialBufferAddress = material.buffer()->GetGPUVirtualAddress(); + MaterialLayerIndexTableAddress = pMaterialLayerIndexBuffer + ? pMaterialLayerIndexBuffer->GetGPUVirtualAddress() + : (D3D12_GPU_VIRTUAL_ADDRESS) nullptr; + TexturesHeapAddress = texturesHeapAddress; + SamplersHeapAddress = samplersHeapAddress; + } + + // Copies the contents of the shader record to the specified mapped buffer. + void copyTo(void* mappedBuffer) + { + ::memcpy_s(mappedBuffer, sizeof(HitGroupShaderRecord), this, sizeof(HitGroupShaderRecord)); + } + + // Get the stride (aligned size) of a shader record. + static size_t stride() + { + return ALIGNED_SIZE(sizeof(HitGroupShaderRecord), SHADER_RECORD_ALIGNMENT); + } + + // These are the hit group arguments, in the order declared in the associated local root + // signature. These must be aligned by their size, e.g. a root descriptor must be aligned on an + // 8-byte (its size) boundary. + array ShaderIdentifer; + D3D12_GPU_VIRTUAL_ADDRESS IndexBufferAddress; + D3D12_GPU_VIRTUAL_ADDRESS PositionBufferAddress; + D3D12_GPU_VIRTUAL_ADDRESS NormalBufferAddress; + D3D12_GPU_VIRTUAL_ADDRESS TexCoordBufferAddress; + int HasNormals; + int HasTexCoords; + uint32_t MaterialLayerCount; + D3D12_GPU_VIRTUAL_ADDRESS MaterialBufferAddress; + D3D12_GPU_VIRTUAL_ADDRESS MaterialLayerIndexTableAddress; + D3D12_GPU_DESCRIPTOR_HANDLE TexturesHeapAddress; + D3D12_GPU_DESCRIPTOR_HANDLE SamplersHeapAddress; +}; + +PTInstance::PTInstance(PTScene* pScene, const PTGeometryPtr& pGeometry, + const PTMaterialPtr& pMaterial, const mat4& transform, const LayerDefinitions& layers) +{ + AU_ASSERT(pGeometry, "Geometry assigned to an cannot be null."); + + // Set the arguments directly; they are assumed to be prepared by the caller. + _pScene = pScene; + _pGeometry = pGeometry; + _transform = transform; + setMaterial(pMaterial); + + for (size_t i = 0; i < layers.size(); i++) + { + _layers.push_back(make_pair(dynamic_pointer_cast(layers[i].first), + dynamic_pointer_cast(layers[i].second))); + + _layers[i].first->materialType()->incrementRefCount(PTMaterialType::EntryPoint::kLayerMiss); + } +} + +PTInstance::~PTInstance() +{ + if (_pMaterial) + _pMaterial->materialType()->decrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + + for (size_t i = 0; i < _layers.size(); i++) + { + _layers[i].first->materialType()->decrementRefCount(PTMaterialType::EntryPoint::kLayerMiss); + } +} + +void PTInstance::setMaterial(const IMaterialPtr& pMaterial) +{ + if (_pMaterial) + _pMaterial->materialType()->decrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + + // Cast the (optional) material to the renderer implementation. Use the default material if one + // is / not specified. + _pMaterial = pMaterial + ? dynamic_pointer_cast(pMaterial) + : dynamic_pointer_cast(_pScene->defaultMaterialResource()->resource()); + + _pMaterial->materialType()->incrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + + // Set the instance as dirty. + _bIsDirty = true; +} + +void PTInstance::setTransform(const mat4& transform) +{ + // Create a GLM matrix from the (column-major) array if one is specified. Otherwise the + // default / (identity) matrix is used. + _transform = transform; + + // Set the instance as dirty. + _bIsDirty = true; +} + +void PTInstance::setObjectIdentifier(int /*objectId*/) +{ + // TODO: implement object id setting for AuroraPT +} + +bool PTInstance::update() +{ + // Update the geometry and material. + // NOTE: Whether these were dirty (i.e. return false) do not affect whether the instance was + // considered dirty. + _pMaterial->update(); + + // Update the material and geometry for layer materials. + for (size_t i = 0; i < _layers.size(); i++) + { + _layers[i].first->update(); + if (_layers[i].second) + _layers[i].second->update(); + } + + // Clear the dirty flag. + bool wasDirty = _bIsDirty; + _bIsDirty = false; + + return wasDirty; +} + +PTScene::PTScene( + PTRenderer* pRenderer, PTShaderLibrary* pShaderLibrary, uint32_t numRendererDescriptors) : + SceneBase(pRenderer) +{ + _pRenderer = pRenderer; + _pShaderLibrary = pShaderLibrary; + _numRendererDescriptors = numRendererDescriptors; + + // Compute the shader record strides. + _missShaderRecordStride = HitGroupShaderRecord::stride(); // shader ID, no other parameters + _missShaderRecordCount = kBuiltInMissShaderCount; // background, radiance, and shadow + _hitGroupShaderRecordStride = HitGroupShaderRecord::stride(); + + // Use the default environment and ground plane. + _pGroundPlane = pRenderer->defaultGroundPlane(); + + createDefaultResources(); +} + +IInstancePtr PTScene::addInstancePointer(const Path& /* path*/, const IGeometryPtr& pGeom, + const IMaterialPtr& pMaterial, const mat4& transform, const LayerDefinitions& materialLayers) +{ + // Cast the (optional) material to the device implementation. Use the default material if one is + // not specified. + PTMaterialPtr pPTMaterial = pMaterial + ? dynamic_pointer_cast(pMaterial) + : dynamic_pointer_cast(_pDefaultMaterialResource->resource()); + + // The remaining operations are not yet thread safe. + std::lock_guard lock(_mutex); + + // Create the instance object and add it to the list of instances for the scene. + PTInstancePtr pPTInstance = make_shared( + this, dynamic_pointer_cast(pGeom), pPTMaterial, transform, materialLayers); + + return pPTInstance; +} + +void PTScene::setGroundPlanePointer(const IGroundPlanePtr& pGroundPlane) +{ + // Use the renderer's default ground plane if a ground plane is not specified. + // NOTE: The default ground plane is disabled ("enabled" == false), so that setting a null + // pointer on this function will disable the ground plane. + _pGroundPlane = pGroundPlane ? dynamic_pointer_cast(pGroundPlane) + : _pRenderer->defaultGroundPlane(); +} + +ID3D12Resource* PTScene::getMissShaderTable(size_t& recordStride, uint32_t& recordCount) const +{ + // Set the shader record size and count. + recordStride = _missShaderRecordStride; + recordCount = _missShaderRecordCount; + + return _pMissShaderTable.Get(); +} + +ID3D12Resource* PTScene::getHitGroupShaderTable(size_t& recordStride, uint32_t& recordCount) const +{ + // Set the shader record size and count. + recordStride = HitGroupShaderRecord::stride(); + recordCount = static_cast(_lstInstanceData.size()); + + return _pHitGroupShaderTable.Get(); +} + +void PTScene::update() +{ + // Update base class. + SceneBase::update(); + + // Update the environment. + if (_environments.active().modified()) + { + _pEnvironment = static_pointer_cast(_pEnvironmentResource->resource()); + _pEnvironment->update(); + _isEnvironmentDescriptorsDirty = true; + } + + // Update the ground plane. + _pGroundPlane->update(); + + // If any active geometry resources have been modified, flush the vertex buffer pool in case + // there are any pending vertex buffers that are required to update the geometry, and then + // update the geometry (and update BLAS for "complete" geometry that has position data). + if (_geometry.changed()) + { + _pRenderer->flushVertexBufferPool(); + for (PTGeometry& geom : _geometry.active().resources()) + { + geom.update(); + if (!geom.isIncomplete()) + geom.updateBLAS(); + } + } + + // If any active material resources have been modified update them and build a list of unique + // samplers for all the active materials. + if (_materials.changed()) + { + map materialSamplerIndicesMap; + _samplerLookup.clear(); + for (PTMaterial& mtl : _materials.active().resources()) + { + mtl.update(); + + // Further ensure combination of samplers in material is unique. + _samplerLookup.add(mtl); + } + + // Wait for previous render tasks and then clear the descriptor heap. + // TODO: Only do this if any texture parameters have changed. + _pRenderer->waitForTask(); + clearDesciptorHeap(); + } + + // If any active instances have been modified, update all the active instances. + if (_instances.changed()) + { + for (PTInstance& instance : _instances.active().resources()) + { + instance.update(); + } + } + + // Update the acceleration structure if any geometry or instances have been modified. + if (_instances.active().modified() || _geometry.active().modified()) + { + + // Ensure the acceleration structure is no longer being accessed. + // TODO: Is there a less drastic stall we can do here? + _pRenderer->waitForTask(); + + // Release the acceleration structure. + _pAccelStructure.Reset(); + } + + // Update the scene resources: the acceleration structure, the descriptor heap, and the shader + // tables. Will only doanything if the relevant pointers have been cleared. + updateAccelerationStructure(); + updateDescriptorHeap(); + updateShaderTables(); +} + +void PTScene::clearDesciptorHeap() +{ + _pDescriptorHeap.Reset(); +} + +void PTScene::clearShaderData() +{ + // Delete the hit group and miss shader table. + _pHitGroupShaderTable.Reset(); + _pMissShaderTable.Reset(); +} + +void PTScene::updateAccelerationStructure() +{ + // Do nothing if the acceleration structure already exists. + if (_pAccelStructure) + { + return; + } + + // Build a list of *unique* instance data objects, i.e. unique combinations of geometry and + // material (but not transform). This can be used to build a minimal set of shader records, e.g. + // a large assembly with hundreds of identical screws can have the same shader record for all of + // the screws. At the same time, assign an identifier (index) so that each instance in the TLAS + // (built later) can refer to the correct shader record. + uint32_t nextIndex = 0; + InstanceDataMap instanceDataMap; + LayerIndicesMap layerIndicesMap; + vector instanceDataIndices; + map materialSamplerIndicesMap; + instanceDataIndices.reserve(_instances.active().count()); + _lstInstanceData.clear(); + _lstLayerData.clear(); + + // Iterate through all the active instances in the scene. + for (PTInstance& instance : _instances.active().resources()) + { + // Find the index of this instance's material within list of active materials. + uint32_t mtlIndex = _materials.active().findActiveIndex(instance.material()); + AU_ASSERT(mtlIndex != -1, "Material not active"); + + // Build the data required to implement material layers. + vector layerIndices; + PTLayerIndexTablePtr layerIndexTable; + if (instance.materialLayers().size()) + { + auto& materialLayers = instance.materialLayers(); + for (int i = 0; i < materialLayers.size(); i++) + { + // Find the index of this layer's material within list of active materials. + PTMaterialPtr pLayerMtl = materialLayers[i].first; + uint32_t layerMtlIndex = _materials.active().findActiveIndex(pLayerMtl); + + // Build an array of layer indices for each instance. + layerIndices.push_back( + static_cast(_lstLayerData.size()) + kBuiltInMissShaderCount); + + // For each layer material create the layer data required to build shader. + // TODO: We could remove duplicates here, as we do with the instance data. + LayerData layerData(materialLayers[i], layerMtlIndex); + _lstLayerData.push_back(layerData); + } + + if (layerIndicesMap.find(layerIndices) == layerIndicesMap.end()) + { + layerIndexTable = make_shared(_pRenderer, layerIndices); + layerIndicesMap[layerIndices] = layerIndexTable; + } + else + layerIndexTable = layerIndicesMap[layerIndices]; + } + + // If there is no matching instance data in the map, create a new index, add a new instance + // data object to the map, and add to the (ordered) list of instance data. Otherwise, use + // the the index for the existing instance data. + uint32_t instanceDataIndex = 0; + InstanceData instanceData(instance, layerIndexTable, mtlIndex); + if (instanceDataMap.find(instanceData) == instanceDataMap.end()) + { + instanceDataIndex = nextIndex++; + instanceDataMap[instanceData] = instanceDataIndex; + _lstInstanceData.push_back(instanceData); + } + else + { + instanceDataIndex = instanceDataMap[instanceData]; + } + + for (int i = 0; i < layerIndices.size(); i++) + { + int idx = layerIndices[i] - kBuiltInMissShaderCount; + _lstLayerData[idx].index = instanceDataIndex; + } + + // Add the instance data index to the list, i.e. one per instance in the scene. + instanceDataIndices.push_back(instanceDataIndex); + } + + // Build the top-level acceleration structure (TLAS). + _pAccelStructure = buildTLAS(instanceDataIndices); + + // If the acceleration structure was rebuilt, then the descriptor heap, as well as the miss and + // hit group shader tables must likewise be rebuilt, as they rely on the instance data. + _pDescriptorHeap.Reset(); + _pHitGroupShaderTable.Reset(); + _pMissShaderTable.Reset(); +} + +void PTScene::updateDescriptorHeap() +{ + // Create the descriptor heap if needed. + if (!_pDescriptorHeap) + { + // Get the device. + ID3D12Device5Ptr pDevice = _pRenderer->dxDevice(); + + // Determine the number of texture and sampler descriptors used for all the instances' + // materials. + UINT uniqueMaterialDescriptorCount = 0; + for (PTMaterial& mtl : _materials.active().resources()) + { + uniqueMaterialDescriptorCount += mtl.descriptorCount(); + } + + UINT uniqueMaterialSamplerDescriptorCount = 0; + for (PTMaterial& mtl : _samplerLookup.unique()) + { + uniqueMaterialSamplerDescriptorCount += mtl.samplerDescriptorCount(); + } + + // Create a descriptor heap for CBV/SRV/UAVs needed by shader records. Currently this means + // the descriptors for the instance materials, plus the number of descriptors needed by the + // renderer and environment. + D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; + heapDesc.NumDescriptors = uniqueMaterialDescriptorCount + _numRendererDescriptors + + _pEnvironment->descriptorCount(); + heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + checkHR(pDevice->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&_pDescriptorHeap))); + + // Create a descriptor heap for samplers needed by shader records. Currently this means + // the descriptors for the instance (and layer) material samplers. + D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc = {}; + samplerHeapDesc.NumDescriptors = uniqueMaterialSamplerDescriptorCount; + samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; + samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + checkHR(pDevice->CreateDescriptorHeap( + &samplerHeapDesc, IID_PPV_ARGS(&_pSamplerDescriptorHeap))); + + // If the descriptor heap was rebuilt, then the descriptors must likewise be recreated. + _isEnvironmentDescriptorsDirty = true; + _isHitGroupDescriptorsDirty = true; + } + + // Get a CPU handle to the start of the sampler descriptor heap, offset by the number of + // descriptors reserved for the renderer. + CD3DX12_CPU_DESCRIPTOR_HANDLE handle(_pDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); + UINT handleIncrement = _pRenderer->dxDevice()->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + handle.Offset(_numRendererDescriptors, handleIncrement); + + // Update the environment descriptors if needed. + if (_isEnvironmentDescriptorsDirty) + { + // Create the descriptors for the environment textures. + // NOTE: This will also increment the handle past the new descriptors. + _pEnvironment->createDescriptors(handle, handleIncrement); + + // Clear the dirty flag. + _isEnvironmentDescriptorsDirty = false; + } + + // Update the hit group descriptors if needed. + if (_isHitGroupDescriptorsDirty) + { + // Iterate the list of active materials, creating descriptors for each as needed from the + // material. + for (PTMaterial& mtl : _materials.active().resources()) + { + // Create the descriptors for the material's textures. + // NOTE: This will also increment the handle past the new descriptors. + mtl.createDescriptors(handle, handleIncrement); + } + } + + // Get a CPU handle to the start of the sampler descriptor heap. + CD3DX12_CPU_DESCRIPTOR_HANDLE samplerHandle( + _pSamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); + UINT samplerHandleIncrement = _pRenderer->dxDevice()->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + if (_isHitGroupDescriptorsDirty) + { + // Iterate the list of unique samplers, creating sampler descriptors for each as needed. + for (PTMaterial& mtl : _samplerLookup.unique()) + { + // Create the descriptors for the material's samplers. + // NOTE: This will also increment the handle past the new descriptors. + mtl.createSamplerDescriptors(samplerHandle, samplerHandleIncrement); + } + + // Clear the dirty flag. + _isHitGroupDescriptorsDirty = false; + } +} + +void PTScene::updateShaderTables() +{ + + // Create and populate the hit shader table if it doesn't exist and there are instances. + auto instanceCount = static_cast(_instances.active().count()); + + // Texture and sampler descriptors for unique materials in the scene. + vector lstUniqueMaterialTextureDescriptors; + vector lstUniqueMaterialSamplerDescriptors; + + // Get currently active materials. + auto& activeMaterialResources = _materials.active().resources(); + + if (!_pHitGroupShaderTable && instanceCount > 0) + { + // Build a list of texture and sampler GPU handles for all unique materials. + { + CD3DX12_GPU_DESCRIPTOR_HANDLE handle( + _pDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); + UINT handleIncrement = _pRenderer->dxDevice()->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + // Offset texture handle by the number of descriptors reserved for the renderer and + // environment. + handle.Offset( + _numRendererDescriptors + _pEnvironment->descriptorCount(), handleIncrement); + + for (size_t i = 0; i < _materials.active().count(); i++) + { + // Store the handle. + lstUniqueMaterialTextureDescriptors.push_back(handle); + + // Increment the handle by the number of descriptors for each material. + handle.Offset(PTMaterial::descriptorCount(), handleIncrement); + } + + CD3DX12_GPU_DESCRIPTOR_HANDLE samplerHandle( + _pSamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); + UINT samplerHandleIncrement = _pRenderer->dxDevice()->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + for (int i = 0; i < _samplerLookup.unique().size(); i++) + { + // Store the handle. + lstUniqueMaterialSamplerDescriptors.push_back(samplerHandle); + + // Increment the sampler handle by the number of sample descriptors for each + // material. + samplerHandle.Offset(PTMaterial::samplerDescriptorCount(), samplerHandleIncrement); + } + } + + size_t recordStride = HitGroupShaderRecord::stride(); + + // Create a buffer for the shader table, and map it for writing. + size_t shaderTableSize = recordStride * instanceCount; + _pHitGroupShaderTable = _pRenderer->createBuffer(shaderTableSize); + uint8_t* pShaderTableMappedData = nullptr; + checkHR(_pHitGroupShaderTable->Map( + 0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + + // Iterate the instance data objects, creating a hit group shader record for each one, and + // copying the shader record data to the shader table. + for (int i = 0; i < _lstInstanceData.size(); i++) + { + const auto& instanceData = _lstInstanceData[i]; + // Get the hit group shader ID from the material type, which will change if the shader + // library is rebuilt. + PTMaterial& instanceMtl = activeMaterialResources[instanceData.mtlIndex]; + const DirectXShaderIdentifier hitGroupShaderID = + instanceMtl.materialType()->getShaderID(); + + // Lookup texture and sampler handle for instance. + CD3DX12_GPU_DESCRIPTOR_HANDLE mtlTextureHandle = + lstUniqueMaterialTextureDescriptors[instanceData.mtlIndex]; + CD3DX12_GPU_DESCRIPTOR_HANDLE mtlSamplerHandle = + lstUniqueMaterialSamplerDescriptors[_samplerLookup.getUniqueIndex( + instanceData.mtlIndex)]; + + // Shader record data includes the geometry buffers, the material constant buffer, and + // the descriptor table (offset into the SRV heap) needed for textures. + PTGeometry::GeometryBuffers geometryBuffers = instanceData.pGeometry->buffers(); + ID3D12Resource* pMaterialLayerIndexBuffer = + instanceData.pLayerIndices ? instanceData.pLayerIndices->buffer() : nullptr; + int materialLayerCount = + instanceData.pLayerIndices ? instanceData.pLayerIndices->count() : 0; + HitGroupShaderRecord record(hitGroupShaderID, geometryBuffers, instanceMtl, + pMaterialLayerIndexBuffer, mtlTextureHandle, mtlSamplerHandle, materialLayerCount); + record.copyTo(pShaderTableMappedData); + pShaderTableMappedData += recordStride; + } + + // Close the shader table buffer. + _pHitGroupShaderTable->Unmap(0, nullptr); // no HRESULT + } + // Create and populate the miss shader table if necessary. + if (!_pMissShaderTable) + { + // Calculate miss shader record count (built-ins plus all the layer material miss shaders) + _missShaderRecordCount = kBuiltInMissShaderCount + (uint32_t)_lstLayerData.size(); + + // Create a buffer for the shader table and write the shader identifiers for the miss + // shaders. + // NOTE: There are no arguments (and indeed no local root signatures) for these shaders. + size_t shaderTableSize = _missShaderRecordStride * _missShaderRecordCount; + _pMissShaderTable = _pRenderer->createBuffer(shaderTableSize); + uint8_t* pShaderTableMappedData = nullptr; + checkHR( + _pMissShaderTable->Map(0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + uint8_t* pEndOfShaderTableMappedData = pShaderTableMappedData + shaderTableSize; + + // Get the shader identifiers from the shader library, which will change if the library is + // rebuilt. + // NOTE: The first miss shader is a null shader, used when a miss shader is not needed. + static array kNullShaderID = { 0 }; + ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, kNullShaderID.data(), SHADER_ID_SIZE); + pShaderTableMappedData += _missShaderRecordStride; + ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, + _pShaderLibrary->getBackgroundMissShaderID(), SHADER_ID_SIZE); + pShaderTableMappedData += _missShaderRecordStride; + ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, + _pShaderLibrary->getRadianceMissShaderID(), SHADER_ID_SIZE); + pShaderTableMappedData += _missShaderRecordStride; + ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, _pShaderLibrary->getShadowMissShaderID(), + SHADER_ID_SIZE); + pShaderTableMappedData += _missShaderRecordStride; + + // Fill in layer material miss shaders.. + for (int i = 0; i < _lstLayerData.size(); i++) + { + // Get the layer data and the parent instance data for this layer. + auto& layerData = _lstLayerData[i]; + auto& parentInstanceData = _lstInstanceData[layerData.index]; + PTMaterial& layerMtl = activeMaterialResources[layerData.instanceData.mtlIndex]; + + // Get the material type + auto pMtlType = layerMtl.materialType(); + + // Create the geometry by merging the layer geometry with parent instance's geometry. + PTGeometry::GeometryBuffers geometryLayerBuffers = + parentInstanceData.pGeometry->buffers(); + if (layerData.instanceData.pGeometry) + { + // Enure the layer geometry matches parent geometry + if (layerData.instanceData.pGeometry->vertexCount() != + parentInstanceData.pGeometry->vertexCount()) + { + // If vertex counts don't match raise an error and then fail. + AU_ERROR( + "Layer geometry %s vertex count (%d) does not match base geometry %s " + "vertex count " + "(%d) for instance at index %d", + layerData.instanceData.pGeometry->name().c_str(), + layerData.instanceData.pGeometry->vertexCount(), + parentInstanceData.pGeometry->name().c_str(), + parentInstanceData.pGeometry->vertexCount(), layerData.index); + // This will be caught by try statment around render. + AU_FAIL("Invalid geometry"); + } + + // Set any UVs from the layer geometry. + if (layerData.instanceData.pGeometry->buffers().TexCoordBuffer) + { + geometryLayerBuffers.TexCoordBuffer = + layerData.instanceData.pGeometry->buffers().TexCoordBuffer; + } + + // Set any normals from the layer geometry. + if (layerData.instanceData.pGeometry->buffers().NormalBuffer) + geometryLayerBuffers.NormalBuffer = + layerData.instanceData.pGeometry->buffers().NormalBuffer; + + // Set any positions from the layer geometry. + if (layerData.instanceData.pGeometry->buffers().PositionBuffer) + geometryLayerBuffers.PositionBuffer = + layerData.instanceData.pGeometry->buffers().PositionBuffer; + } + + // Lookup texture and sampler handle for layer. + CD3DX12_GPU_DESCRIPTOR_HANDLE mtlTextureHandle = + lstUniqueMaterialTextureDescriptors[layerData.instanceData.mtlIndex]; + CD3DX12_GPU_DESCRIPTOR_HANDLE mtlSamplerHandle = + lstUniqueMaterialSamplerDescriptors[_samplerLookup.getUniqueIndex( + layerData.instanceData.mtlIndex)]; + + // Create hit group (as layer miss shader has same layout as luminance closest hit + // shader) + HitGroupShaderRecord layerRecord(pMtlType->getLayerShaderID(), geometryLayerBuffers, + layerMtl, nullptr, mtlTextureHandle, mtlSamplerHandle, 0); + layerRecord.copyTo(pShaderTableMappedData); + pShaderTableMappedData += _missShaderRecordStride; + } + + // Ensure we didn't over run the shader table buffer. + AU_ASSERT(pShaderTableMappedData == pEndOfShaderTableMappedData, "Shader table overrun"); + + // Unmap the table. + _pMissShaderTable->Unmap(0, nullptr); // no HRESULT + } +} + +ID3D12ResourcePtr PTScene::buildTLAS(const vector& instanceDataIndices) +{ + // Create and populate a buffer with instance data, if there are any instances. + auto instanceCount = static_cast(_instances.active().count()); + ID3D12ResourcePtr pInstanceBuffer; + if (instanceCount > 0) + { + // Create a buffer for the instance data. + size_t bufferSize = sizeof(D3D12_RAYTRACING_INSTANCE_DESC) * instanceCount; + pInstanceBuffer = _pRenderer->createBuffer(bufferSize); + uint8_t* pInstanceMappedData = nullptr; + checkHR(pInstanceBuffer->Map(0, nullptr, reinterpret_cast(&pInstanceMappedData))); + + // Describe a set of instances with varying geometries and transforms. + uint32_t instanceIndex = 0; + for (PTInstance& instance : _instances.active().resources()) + { + // Get the bottom-level acceleration structure (BLAS) from the instance geometry. + ID3D12ResourcePtr pBLAS = instance.dxGeometry()->blas(); + + // Get the transpose of the transform matrix of the instance. GLM has column-major + // matrices, but DXR expects (4x3) row-major matrices for instance descriptions. + mat4 matrix = transpose(instance.transform()); + + // Describe the instance. Specifically this includes: + // - A pointer to the BLAS. + // - The transform for the instance. + // - An identifier for the hit group data to use when the instance is hit by a ray. + // NOTE: The instance is not set as opaque here on the instance flags, so that the any + // hit shader can be called if needed. If the any hit shader is not needed, the shader + // will use the opaque ray flag when calling TraceRay(). + // NOTE: Using memcpy() for the transform copy writes too much data (all 16 floats) in + // *release builds*, for an unknown reason. sizeof(instanceDesc.Transform) is only 12 + // floats. Using memcpy_s() does not cause this problem, and it should be used + // everywhere to be safe! + D3D12_RAYTRACING_INSTANCE_DESC instanceDesc = {}; + instanceDesc.AccelerationStructure = pBLAS->GetGPUVirtualAddress(); + instanceDesc.InstanceID = 0; + instanceDesc.InstanceMask = 0xFF; + instanceDesc.InstanceContributionToHitGroupIndex = instanceDataIndices[instanceIndex++]; + ::memcpy_s(instanceDesc.Transform, sizeof(instanceDesc.Transform), &matrix, + sizeof(instanceDesc.Transform)); + instanceDesc.Flags = D3D12_RAYTRACING_INSTANCE_FLAG_NONE; + + // Copy the instance description to the buffer. + ::memcpy_s(pInstanceMappedData, sizeof(D3D12_RAYTRACING_INSTANCE_DESC), &instanceDesc, + sizeof(D3D12_RAYTRACING_INSTANCE_DESC)); + pInstanceMappedData += sizeof(D3D12_RAYTRACING_INSTANCE_DESC); + } + + // Close the instance buffer. + pInstanceBuffer->Unmap(0, nullptr); // no HRESULT + } + + // Describe the top-level acceleration structure (TLAS). + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS tlasInputs = {}; + tlasInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + tlasInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + tlasInputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_NONE; + tlasInputs.NumDescs = instanceCount; + tlasInputs.InstanceDescs = pInstanceBuffer ? pInstanceBuffer->GetGPUVirtualAddress() : 0; + + // Get the sizes required for the TLAS scratch and result buffers, and create them. + // NOTE: The scratch buffer is obtained from the renderer and will be retained for the duration + // of the build task started below, and then it will be released. + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO tlasInfo = {}; + _pRenderer->dxDevice()->GetRaytracingAccelerationStructurePrebuildInfo(&tlasInputs, &tlasInfo); + D3D12_GPU_VIRTUAL_ADDRESS tlasScratchAddress = + _pRenderer->getScratchBuffer(tlasInfo.ScratchDataSizeInBytes); + ID3D12ResourcePtr pTLAS = _pRenderer->createBuffer(tlasInfo.ResultDataMaxSizeInBytes, + "TLAS Buffer", D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE); + + // Describe the build for the TLAS. + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC tlasDesc = {}; + tlasDesc.Inputs = tlasInputs; + tlasDesc.ScratchAccelerationStructureData = tlasScratchAddress; + tlasDesc.DestAccelerationStructureData = pTLAS->GetGPUVirtualAddress(); + + // Build the TLAS using a command list. Insert a UAV barrier so that it can't be used until + // it is generated. + ID3D12GraphicsCommandList4Ptr pCommandList = _pRenderer->beginCommandList(); + pCommandList->BuildRaytracingAccelerationStructure(&tlasDesc, 0, nullptr); + _pRenderer->addUAVBarrier(pTLAS.Get()); + + // Complete the command list and task, and wait for the work to complete. + // NOTE: This waits so that the instance buffer doesn't have to be retained beyond this + // function. Since there is only one TLAS for a scene, this is not a practical bottleneck. + _pRenderer->submitCommandList(); + _pRenderer->completeTask(); + _pRenderer->waitForTask(); + + return pTLAS; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTScene.h b/Libraries/Aurora/Source/DirectX/PTScene.h new file mode 100644 index 0000000..2ba0c1a --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTScene.h @@ -0,0 +1,223 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "PTEnvironment.h" +#include "PTGeometry.h" +#include "PTGroundPlane.h" +#include "PTMaterial.h" +#include "SceneBase.h" + +#include + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; +class PTScene; +class PTShaderLibrary; + +// Definition of a layer material (material+geometry) +using PTLayerDefinition = pair; + +// An internal implementation for the table of layer material indices. +class PTLayerIndexTable +{ +public: + /*** Lifetime Management ***/ + + PTLayerIndexTable(PTRenderer* pRenderer, const vector& indices = {}); + void set(const vector& indices); + + // The maximum number of supported material layers, must match value in PathTracingCommon.hlsl + // shader. + static const int kMaxMaterialLayers = 64; + + int count() { return _count; } + ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } + +private: + int _count = 0; + PTRenderer* _pRenderer = nullptr; + ID3D12ResourcePtr _pConstantBuffer; +}; +MAKE_AURORA_PTR(PTLayerIndexTable); + +// An internal implementation for IInstance. +class PTInstance : public IInstance +{ +public: + /*** Lifetime Management ***/ + + PTInstance(PTScene* pScene, const PTGeometryPtr& pGeometry, const PTMaterialPtr& pMaterial, + const mat4& transform, const LayerDefinitions& layers); + virtual ~PTInstance(); + + /*** IInstance Functions ***/ + + void setMaterial(const IMaterialPtr& pMaterial) override; + void setTransform(const mat4& transform) override; + void setObjectIdentifier(int objectId) override; + IGeometryPtr geometry() const override { return dynamic_pointer_cast(_pGeometry); } + void setVisible(bool /*val*/) override {} + + /*** Functions ***/ + + PTGeometryPtr dxGeometry() const { return _pGeometry; } + PTMaterialPtr material() const { return _pMaterial; } + const mat4& transform() const { return _transform; } + bool update(); + + const vector& materialLayers() const { return _layers; }; + +private: + /*** Private Variables ***/ + + PTScene* _pScene = nullptr; + bool _bIsDirty = true; + PTGeometryPtr _pGeometry; + PTMaterialPtr _pMaterial; + mat4 _transform; + + vector _layers; +}; +MAKE_AURORA_PTR(PTInstance); + +// An internal implementation for IScene. +class PTScene : public SceneBase +{ +public: + /*** Lifetime Management ***/ + PTScene( + PTRenderer* pRenderer, PTShaderLibrary* pShaderLibrary, uint32_t numRendererDescriptors); + ~PTScene() = default; + + /*** IScene Functions ***/ + void setGroundPlanePointer(const IGroundPlanePtr& pGroundPlane) override; + IInstancePtr addInstancePointer(const Path& path, const IGeometryPtr& geom, + const IMaterialPtr& pMaterial, const mat4& transform, + const LayerDefinitions& materialLayers) override; + + void update(); + + /*** Functions ***/ + + int instanceCount() const { return static_cast(_instances.active().count()); } + PTEnvironmentPtr environment() const { return _pEnvironment; } + PTGroundPlanePtr groundPlane() const { return _pGroundPlane; } + ID3D12DescriptorHeap* descriptorHeap() const { return _pDescriptorHeap.Get(); } + ID3D12DescriptorHeap* samplerDescriptorHeap() const { return _pSamplerDescriptorHeap.Get(); } + ID3D12Resource* accelerationStructure() const { return _pAccelStructure.Get(); } + ID3D12Resource* getMissShaderTable(size_t& recordStride, uint32_t& recordCount) const; + ID3D12Resource* getHitGroupShaderTable(size_t& recordStride, uint32_t& recordCount) const; + void clearShaderData(); + void clearDesciptorHeap(); + PTRenderer* renderer() { return _pRenderer; } + +private: + /*** Private Types ***/ + + // A structure containing the contents of instance that uniquely identify it, for purposes + // of using it in a shader table. + struct InstanceData + { + InstanceData() : pGeometry(nullptr), mtlIndex((uint32_t)-1), pLayerIndices(nullptr) {} + + InstanceData( + const PTInstance& instance, PTLayerIndexTablePtr pLayerIndices, uint32_t mtlIndex) : + pGeometry(instance.dxGeometry()), mtlIndex(mtlIndex), pLayerIndices(pLayerIndices) + { + } + + bool operator==(const InstanceData& other) const + { + return pGeometry == other.pGeometry && mtlIndex == other.mtlIndex && + pLayerIndices == other.pLayerIndices; + } + + PTGeometryPtr pGeometry; + PTLayerIndexTablePtr pLayerIndices; + // Index into array of unique materials for scsne. + uint32_t mtlIndex; + }; + + // Structure containing the contents of material layer, and an index back to parent instance + // data. + struct LayerData + { + LayerData(const PTLayerDefinition& def, uint32_t mtlIndex, int idx = -1) : index(idx) + { + instanceData.mtlIndex = mtlIndex; + instanceData.pGeometry = def.second; + } + + InstanceData instanceData; + int index; + }; + + // A functor that hashes the contents of an instance, i.e. the pointers to the geometry and + // material. + struct HashInstanceData + { + size_t operator()(const InstanceData& object) const + { + hash hasher1; + hash hasher2; + hash hasher3; + return hasher1(object.pGeometry.get()) ^ (hasher2(object.mtlIndex) << 1) ^ + (hasher3(object.pLayerIndices.get()) << 2); + } + }; + + using InstanceList = set; + using InstanceDataMap = unordered_map; + using LayerIndicesMap = map, PTLayerIndexTablePtr>; + using InstanceDataList = vector; // not needed; known to be unique + using LayerDataList = vector; + + /*** Private Functions ***/ + + void updateAccelerationStructure(); + void updateDescriptorHeap(); + void updateShaderTables(); + ID3D12ResourcePtr buildTLAS(const vector& instanceDataIndices); + static size_t GetSamplerHash(const PTMaterial& mtl) { return mtl.computeSamplerHash(); } + + /*** Private Variables ***/ + + PTRenderer* _pRenderer = nullptr; + PTShaderLibrary* _pShaderLibrary = nullptr; + InstanceDataList _lstInstanceData; + UniqueHashLookup _samplerLookup; + LayerDataList _lstLayerData; + PTGroundPlanePtr _pGroundPlane; + PTEnvironmentPtr _pEnvironment; + uint32_t _numRendererDescriptors = 0; + /*** DirectX 12 Objects ***/ + + ID3D12ResourcePtr _pAccelStructure; + ID3D12DescriptorHeapPtr _pDescriptorHeap; + ID3D12DescriptorHeapPtr _pSamplerDescriptorHeap; + ID3D12ResourcePtr _pMissShaderTable; + size_t _missShaderRecordStride = 0; + uint32_t _missShaderRecordCount = 0; + ID3D12ResourcePtr _pHitGroupShaderTable; + size_t _hitGroupShaderRecordStride = 0; + bool _isEnvironmentDescriptorsDirty = true; + bool _isHitGroupDescriptorsDirty = true; + std::mutex _mutex; +}; +MAKE_AURORA_PTR(PTScene); + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp new file mode 100644 index 0000000..27d2a2d --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp @@ -0,0 +1,1088 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTShaderLibrary.h" + +#include "CompiledShaders/CommonShaders.h" +#include "PTGeometry.h" +#include "PTImage.h" +#include "PTMaterial.h" +#include "PTRenderer.h" +#include "PTScene.h" +#include "PTShaderLibrary.h" +#include "PTTarget.h" +#include "Transpiler.h" + +// Development flag to enable/disable multithreaded compilation. +#define AU_DEV_MULTITHREAD_COMPILATION 0 + +#if AU_DEV_MULTITHREAD_COMPILATION +#include +#include +#endif + +BEGIN_AURORA + +// Development flag to entire HLSL library to disk. +// NOTE: This should never be enabled in committed code; it is only for local development. +#define AU_DEV_DUMP_SHADER_CODE 0 + +// Pack four bytes into a single unsigned int. +// Based on version in DirectX toolkit. +#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + (static_cast(static_cast(ch0)) | \ + (static_cast(static_cast(ch1)) << 8) | \ + (static_cast(static_cast(ch2)) << 16) | \ + (static_cast(static_cast(ch3)) << 24)) + +// Strings for shader entry points. +static const wchar_t* gRayGenEntryPoint = L"RayGenShader"; +static const wchar_t* gBackgroundMissEntryPoint = L"BackgroundMissShader"; +static const wchar_t* gRadianceMissEntryPoint = L"RadianceMissShader"; +static const wchar_t* gShadowMissEntryPoint = L"ShadowMissShader"; + +// Combine the source code for the reference and standard surface BSDF to produce the default built +// in material type. Use USE_REFERENCE_BSDF ifdef so that the material's BSDF can be selected via an +// option. + +// DXC include handler, used by PTShaderLibrary::compileLibrary +class IncludeHandler : public IDxcIncludeHandler +{ +public: + // Ctor takes the DXC library, and a map to lookup include files. + IncludeHandler(ComPtr pDXCLibrary, const map& includes) : + _pDXCLibrary(pDXCLibrary), _includes(includes) + { + } + +protected: + // The LoadSource method is called by the DXC compiler to load an include file. + HRESULT LoadSource(LPCWSTR pFilename, IDxcBlob** ppIncludeSource) override + { + // Convert filename to UTF8. + string fname = Foundation::w2s(wstring(pFilename)); + + // Strip the slashes from start of filename. + int pathChars = 0; + while (pathChars < fname.size() && + (fname[pathChars] == '/' || fname[pathChars] == '\\' || fname[pathChars] == '.')) + { + pathChars++; + } + if (pathChars) + fname.erase(0, pathChars); + + // If our include map doesn't contain file, set output to nullptr and return. + if (_includes.find(fname) == _includes.end()) + { + *ppIncludeSource = nullptr; + return S_OK; + } + + // Convert the source to a DXC blob. + const string& source = _includes.at(fname); + ComPtr pEncodingIncludeSource; + _pDXCLibrary->CreateBlobWithEncodingFromPinned( + source.c_str(), (UINT32)source.size(), CP_UTF8, &pEncodingIncludeSource); + + // Set the output to blob. + *ppIncludeSource = pEncodingIncludeSource.Detach(); + + // Return success code. + return S_OK; + } + + // Ref counting interface from IUnknown required, but just stubbed out. + HRESULT QueryInterface(const IID&, void**) override { return S_OK; } + ULONG AddRef(void) override { return 0; } + ULONG Release(void) override { return 0; } + +private: + ComPtr _pDXCLibrary; + const map& _includes; +}; + +PTMaterialType::PTMaterialType( + PTShaderLibrary* pShaderLibrary, int sourceIndex, const string& typeName) : + _pShaderLibrary(pShaderLibrary), _sourceIndex(sourceIndex), _name(typeName) +{ + // Set the closest hit, any hit, and layer miss hit entry point names, converted to wide string. + _closestHitEntryPoint = Foundation::s2w(typeName + "RadianceHitShader"); + _shadowAnyHitEntryPoint = Foundation::s2w(typeName + "ShadowAnyHitShader"); + _materialLayerMissEntryPoint = Foundation::s2w(typeName + "LayerMissShader"); + + // Set the hit group export name from the entry point name, converted to wide string. + _exportName = _closestHitEntryPoint + Foundation::s2w("Group"); + + // Initialize ref. counts to zero. + for (int i = 0; i < EntryPoint::kNumEntryPoints; i++) + _entryPointRefCount[i] = 0; +} + +PTMaterialType::~PTMaterialType() +{ + // If this material type is valid, when its destroyed (which will happen when no material holds + // a shared pointer to it) remove it source code from the library. + if (isValid()) + { + _pShaderLibrary->removeSource(_sourceIndex); + } +} + +void PTMaterialType::incrementRefCount(EntryPoint entryPoint) +{ + // Increment the ref. count and trigger rebuild if this is the flip case (that causes the count + // to go from zero to non-zero.) A shader rebuild is required as this will change the HLSL code. + _entryPointRefCount[entryPoint]++; + if (_pShaderLibrary && _entryPointRefCount[entryPoint] == 1) + _pShaderLibrary->triggerRebuild(); +} + +void PTMaterialType::decrementRefCount(EntryPoint entryPoint) +{ + // Ensure ref. count is non-zero. + AU_ASSERT(_entryPointRefCount[entryPoint] > 0, "Invalid ref count"); + + // Decrement the ref. count and trigger rebuild if this is the flip case (that causes the count + // to go from non-zero to zero). A shader rebuild is required as this will change the HLSL code. + _entryPointRefCount[entryPoint]--; + if (_pShaderLibrary && _entryPointRefCount[entryPoint] == 0) + _pShaderLibrary->triggerRebuild(); +} + +DirectXShaderIdentifier PTMaterialType::getShaderID() +{ + // Get the shader ID from the library. + return _pShaderLibrary->getShaderID(_exportName.c_str()); +} + +DirectXShaderIdentifier PTMaterialType::getLayerShaderID() +{ + // Get the shader ID from the library. + return _pShaderLibrary->getShaderID(_materialLayerMissEntryPoint.c_str()); +} + +bool PTShaderOptions::set(const string& name, int val) +{ + // Find name in lookup map. + size_t idx = static_cast(-1); + auto iter = _lookup.find(name); + + // If the option doesn't exist, add it. + // NOTE: A separate _lookup map and _data array are used to maintain the order of emitted + // #define statements. + if (iter == _lookup.end()) + { + _lookup[name] = _data.size(); + _data.push_back({ name, val }); + return true; + } + idx = iter->second; + + // Otherwise set existing value. + bool changed = _data[idx].second != val; + _data[idx].second = val; + + // Return true if value has changed. + return changed; +} + +void PTShaderOptions::remove(const string& name) +{ + // Remove from lookup map and clear entry in vector. + size_t idx = _lookup[name]; + _lookup.erase(name); + _data[idx] = { "", 0 }; +} + +void PTShaderOptions::clear() +{ + // Clear map and vector. + _data.clear(); + _lookup.clear(); +} + +string PTShaderOptions::toHLSL() const +{ + // Build HLSL string. + string hlslStr; + + // Add each option as #defines statement. + for (size_t i = 0; i < _data.size(); i++) + { + hlslStr += "#define " + _data[i].first + " " + to_string(_data[i].second) + "\n"; + } + + // Return HLSL. + return hlslStr; +} + +PTShaderLibrary::~PTShaderLibrary() +{ + // Invalidate any remaining valid material types, to avoid zombies. + for (auto pWeakMaterialType : _materialTypes) + { + PTMaterialTypePtr pMaterialType = pWeakMaterialType.second.lock(); + if (pMaterialType) + { + pMaterialType->invalidate(); + } + } +} + +bool PTShaderLibrary::compileLibrary(const ComPtr& pDXCLibrary, const string source, + const string& name, const string& target, const string& entryPoint, + const vector>& defines, bool debug, ComPtr& pOutput, + string* pErrorMessage) +{ + // Pass the auto-generated map of minified HLSL code to an include handler. + IncludeHandler includeHandler(pDXCLibrary, CommonShaders::g_sDirectory); + + // Create blob from HLSL source. + ComPtr pSource; + if (pDXCLibrary->CreateBlobWithEncodingFromPinned( + source.c_str(), (uint32)source.size(), CP_UTF8, pSource.GetAddressOf()) != S_OK) + { + AU_ERROR("Failed to create blob."); + return false; + } + + // Build vector of argument flags to pass to compiler. + vector args; + args.push_back(L"-WX"); // Warning as error. + args.push_back(L"-Zi"); // Debug info + if (debug) + { + args.push_back(L"-Qembed_debug"); // Embed debug info into the shader + args.push_back(L"-Od"); // Disable optimization + } + else + { + args.push_back(L"-O3"); // Optimization level 3 + } + + // Build DXC wide-string preprocessor defines from defines argument. + vector dxcDefines(defines.size()); + vector defValues(defines.size()); + for (size_t i = 0; i < defines.size(); ++i) + { + // Store the wide-string value (DxcDefine points to this). + defValues[i] = Foundation::s2w(defines[i].second.c_str()); + DxcDefine& m = dxcDefines[i]; + m.Name = defines[i].first.c_str(); + m.Value = defValues[i].c_str(); + } + + // Convert string arguments to wide-string. + wstring nameWide = Foundation::s2w(name); + wstring entryPointWide = Foundation::s2w(entryPoint); + wstring targetWide = Foundation::s2w(target); + + // Run the compiler and get the result. + ComPtr pCompileResult; + if (_pDXCompiler->Compile(pSource.Get(), nameWide.c_str(), entryPointWide.c_str(), + targetWide.c_str(), (LPCWSTR*)&args[0], (unsigned int)args.size(), dxcDefines.data(), + (uint32)dxcDefines.size(), &includeHandler, pCompileResult.GetAddressOf()) != S_OK) + { + // Print error and return false if DXC fails. + AU_ERROR("DXCompiler compile failed"); + return false; + } + + // Get the status of the compilation. + HRESULT status; + if (pCompileResult->GetStatus(&status) != S_OK) + { + AU_ERROR("Failed to get result."); + return false; + } + + // Get error buffer if status shows a failure. + if (status < 0) + { + // Get error buffer to provided pErrorMessage string. + ComPtr pPrintBlob; + if (pCompileResult->GetErrorBuffer(pPrintBlob.GetAddressOf()) != S_OK) + { + AU_ERROR("Failed to get error buffer."); + return false; + } + *pErrorMessage = string((char*)pPrintBlob->GetBufferPointer(), pPrintBlob->GetBufferSize()); + return false; + } + + // Get compiled shader as blob. + IDxcBlob** pBlob = reinterpret_cast(pOutput.GetAddressOf()); + pCompileResult->GetResult(pBlob); + return true; +} + +bool PTShaderLibrary::linkLibrary(const vector>& input, + const vector& inputNames, const string& target, const string& entryPoint, bool debug, + ComPtr& pOutput, string* pErrorMessage) +{ + + // Build vector of argument flags to pass to compiler. + vector args; + args.push_back(L"-WX"); // Warning as error. + args.push_back(L"-Zi"); // Debug info + if (debug) + { + args.push_back(L"-Qembed_debug"); // Embed debug info into the shader + args.push_back(L"-Od"); // Disable optimization + } + else + { + args.push_back(L"-O3"); // Optimization level 3 + } + + // Convert string arguments to wide-string. + wstring entryPointWide = Foundation::s2w(entryPoint); + wstring targetWide = Foundation::s2w(target); + + // Build vector of input names converted to wide string. + vector inputNamesStr; + for (int i = 0; i < input.size(); i++) + { + wstring libName = Foundation::s2w(inputNames[i]); + inputNamesStr.push_back(libName); + } + + // Register each of the named inputs with the associated binary blob. + vector inputNamesStrPtr; + for (int i = 0; i < input.size(); i++) + { + // Build vector of string points to input names. + LPWSTR strPtr = (LPWSTR)inputNamesStr[i].c_str(); + inputNamesStrPtr.push_back(strPtr); + + // Register the input binary with the given input name. + auto pBlob = input[i].Get(); + if (_pDXLinker->RegisterLibrary(strPtr, pBlob) != S_OK) + { + // Print error and return false if DXC fails. + AU_ERROR("Link RegisterLibrary failed"); + return false; + } + } + + // Run the linker and get the result. + ComPtr pLinkResult; + if (_pDXLinker->Link(entryPointWide.c_str(), targetWide.c_str(), inputNamesStrPtr.data(), + (unsigned int)inputNamesStrPtr.size(), (LPCWSTR*)&args[0], (unsigned int)args.size(), + pLinkResult.GetAddressOf()) != S_OK) + { + // Print error and return false if DXC fails. + AU_ERROR("DXCompiler link failed"); + return false; + } + + // Get the status of the compilation. + HRESULT status; + if (pLinkResult->GetStatus(&status) != S_OK) + { + AU_ERROR("Failed to get result."); + return false; + } + + // Get error buffer if status shows a failure. + if (status < 0) + { + // Get error buffer to provided pErrorMessage string. + ComPtr pPrintBlob; + if (pLinkResult->GetErrorBuffer(pPrintBlob.GetAddressOf()) != S_OK) + { + AU_ERROR("Failed to get error buffer."); + return false; + } + *pErrorMessage = string((char*)pPrintBlob->GetBufferPointer(), pPrintBlob->GetBufferSize()); + return false; + } + + // Get linked shader as blob. + IDxcBlob** pBlob = reinterpret_cast(pOutput.GetAddressOf()); + pLinkResult->GetResult(pBlob); + return true; +} + +ID3D12RootSignaturePtr PTShaderLibrary::createRootSignature(const D3D12_ROOT_SIGNATURE_DESC& desc) +{ + // Create a root signature from the specified description. + ID3D12RootSignaturePtr pSignature; + ID3DBlobPtr pSignatureBlob; + ID3DBlobPtr pErrorBlob; + checkHR(::D3D12SerializeRootSignature( + &desc, D3D_ROOT_SIGNATURE_VERSION_1, &pSignatureBlob, &pErrorBlob)); + checkHR(_pDXDevice->CreateRootSignature(0, pSignatureBlob->GetBufferPointer(), + pSignatureBlob->GetBufferSize(), IID_PPV_ARGS(&pSignature))); + + return pSignature; +} + +void PTShaderLibrary::initRootSignatures() +{ + // Specify the global root signature for all shaders. This includes a global static sampler + // which shaders can use by default, as well as dynamic samplers per-meterial. + CD3DX12_DESCRIPTOR_RANGE texRange; + CD3DX12_DESCRIPTOR_RANGE samplerRange; + + CD3DX12_ROOT_PARAMETER globalRootParameters[8] = {}; // NOLINT(modernize-avoid-c-arrays) + globalRootParameters[0].InitAsShaderResourceView(0); // gScene: acceleration structure + globalRootParameters[1].InitAsConstants(2, 0); // sampleIndex + seedOffset + globalRootParameters[2].InitAsConstantBufferView(1); // gFrameData: per-frame constant buffer + globalRootParameters[3].InitAsConstantBufferView(2); // gEnvironmentConstants + globalRootParameters[4].InitAsShaderResourceView(1); // gEnvironmentAliasMap + texRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, + 2); // gEnvironmentLight/BackgroundTexture (starting at t2) + CD3DX12_DESCRIPTOR_RANGE globalRootRanges[] = { texRange }; // NOLINT(modernize-avoid-c-arrays) + globalRootParameters[5].InitAsDescriptorTable(_countof(globalRootRanges), globalRootRanges); + globalRootParameters[6].InitAsConstantBufferView(3); // gGroundPlane + globalRootParameters[7].InitAsShaderResourceView(4); // gNullScene: null acceleration structure + CD3DX12_STATIC_SAMPLER_DESC samplerDesc(0, D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR); + CD3DX12_ROOT_SIGNATURE_DESC globalDesc( + _countof(globalRootParameters), globalRootParameters, 1, &samplerDesc); + _pGlobalRootSignature = createRootSignature(globalDesc); + _pGlobalRootSignature->SetName(L"Global Root Signature"); + + // Specify a local root signature for the ray gen shader. + // NOTE: The output UAVs are typed, and therefore can't be used with a root descriptor; they + // must come from a descriptor heap. + CD3DX12_DESCRIPTOR_RANGE uavRange; + uavRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 7, 0); // Output images (for AOV data) + CD3DX12_DESCRIPTOR_RANGE rayGenRanges[] = { uavRange }; // NOLINT(modernize-avoid-c-arrays) + CD3DX12_ROOT_PARAMETER rayGenRootParameters[1] = {}; // NOLINT(modernize-avoid-c-arrays) + rayGenRootParameters[0].InitAsDescriptorTable(_countof(rayGenRanges), rayGenRanges); + CD3DX12_ROOT_SIGNATURE_DESC rayGenDesc(_countof(rayGenRootParameters), rayGenRootParameters); + rayGenDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; + _pRayGenRootSignature = createRootSignature(rayGenDesc); + _pRayGenRootSignature->SetName(L"Ray Gen Local Root Signature"); + + // Specify a local root signature for the radiance hit group. + // NOTE: All shaders in the hit group must have the same local root signature. + CD3DX12_ROOT_PARAMETER radianceHitParameters[9] = {}; // NOLINT(modernize-avoid-c-arrays) + radianceHitParameters[0].InitAsShaderResourceView(0, 1); // gIndices: indices + radianceHitParameters[1].InitAsShaderResourceView(1, 1); // gPositions: positions + radianceHitParameters[2].InitAsShaderResourceView(2, 1); // gNormals: normals + radianceHitParameters[3].InitAsShaderResourceView(3, 1); // gTexCoords: texture coordinates + radianceHitParameters[4].InitAsConstants( + 3, 0, 1); // gHasNormals, gHasTexCoords, and gLayerMissShaderIndex + radianceHitParameters[5].InitAsConstantBufferView(1, 1); // gMaterial: material data + radianceHitParameters[6].InitAsConstantBufferView( + 2, 1); // gMaterialLayerIDs: indices for layer material shaders + + // Texture descriptors starting at register(t4, space1) + texRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, PTMaterial::descriptorCount(), 4, 1); + CD3DX12_DESCRIPTOR_RANGE radianceHitRanges[] = { texRange }; // NOLINT(modernize-avoid-c-arrays) + radianceHitParameters[7].InitAsDescriptorTable(_countof(radianceHitRanges), radianceHitRanges); + + // Sampler descriptors starting at register(s1) + samplerRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, PTMaterial::samplerDescriptorCount(), 1); + CD3DX12_DESCRIPTOR_RANGE radianceHitSamplerRanges[] = { + samplerRange + }; // NOLINT(modernize-avoid-c-arrays) + radianceHitParameters[8].InitAsDescriptorTable( + _countof(radianceHitSamplerRanges), radianceHitSamplerRanges); + + CD3DX12_ROOT_SIGNATURE_DESC radianceHitDesc( + _countof(radianceHitParameters), radianceHitParameters); + radianceHitDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; + _pRadianceHitRootSignature = createRootSignature(radianceHitDesc); + _pRadianceHitRootSignature->SetName(L"Radiance Hit Group Local Root Signature"); + + // Specify a local root signature for the layer miss shader. + // NOTE: All shaders in the hit group must have the same local root signature. + CD3DX12_ROOT_PARAMETER layerMissParameters[9] = {}; // NOLINT(modernize-avoid-c-arrays) + layerMissParameters[0].InitAsShaderResourceView(0, 1); // gIndices: indices + layerMissParameters[1].InitAsShaderResourceView(1, 1); // gPositions: positions + layerMissParameters[2].InitAsShaderResourceView(2, 1); // gNormals: normals + layerMissParameters[3].InitAsShaderResourceView(3, 1); // gTexCoords: texture coordinates + layerMissParameters[4].InitAsConstants( + 3, 0, 1); // gHasNormals, gHasTexCoords, and gLayerMissShaderIndex + layerMissParameters[5].InitAsConstantBufferView(1, 1); // gMaterial: material data + layerMissParameters[6].InitAsConstantBufferView( + 2, 1); // gMaterialLayerIDs: indices for layer material shaders + + // Texture descriptors starting at register(t4, space1) + texRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, PTMaterial::descriptorCount(), 4, 1); // Textures + CD3DX12_DESCRIPTOR_RANGE layerMissRanges[] = { texRange }; // NOLINT(modernize-avoid-c-arrays) + layerMissParameters[7].InitAsDescriptorTable(_countof(layerMissRanges), layerMissRanges); + + // Sampler descriptors starting at register(s1) + samplerRange.Init( + D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, PTMaterial::samplerDescriptorCount(), 1); // Samplers + CD3DX12_DESCRIPTOR_RANGE layerMissSamplerRanges[] = { + samplerRange + }; // NOLINT(modernize-avoid-c-arrays) + layerMissParameters[8].InitAsDescriptorTable( + _countof(layerMissSamplerRanges), layerMissSamplerRanges); + + // Create layer miss root signature. + CD3DX12_ROOT_SIGNATURE_DESC layerMissDesc(_countof(layerMissParameters), layerMissParameters); + layerMissDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; + _pLayerMissRootSignature = createRootSignature(layerMissDesc); + _pLayerMissRootSignature->SetName(L"Layer Miss Local Root Signature"); +} + +DirectXShaderIdentifier PTShaderLibrary::getShaderID(const wchar_t* entryPoint) +{ + // Assert if a rebuild is required, as the pipeline state will be invalid. + AU_ASSERT(!_rebuildRequired, + "Shader Library rebuild required, call rebuild() before accessing shaders"); + + // Get the shader ID from the pipeline state. + ID3D12StateObjectPropertiesPtr stateObjectProps; + _pPipelineState.As(&stateObjectProps); + return stateObjectProps->GetShaderIdentifier(entryPoint); +} + +DirectXShaderIdentifier PTShaderLibrary::getBackgroundMissShaderID() +{ + // Get the shader ID for the HLSL function name. + return getShaderID(gBackgroundMissEntryPoint); +} + +DirectXShaderIdentifier PTShaderLibrary::getRadianceMissShaderID() +{ + // Get the shader ID for the HLSL function name. + return getShaderID(gRadianceMissEntryPoint); +} + +DirectXShaderIdentifier PTShaderLibrary::getShadowMissShaderID() +{ + // Get the shader ID for the HLSL function name. + return getShaderID(gShadowMissEntryPoint); +} + +DirectXShaderIdentifier PTShaderLibrary::getRayGenShaderID() +{ + // Get the shader ID for the HLSL function name. + return getShaderID(gRayGenEntryPoint); +} + +vector PTShaderLibrary::getActiveTypeNames() +{ + vector res; + for (auto hgIter = _materialTypes.begin(); hgIter != _materialTypes.end(); hgIter++) + { + // Weak pointer is stored in shader library, ensure it has not been deleted. + PTMaterialTypePtr pMtlType = hgIter->second.lock(); + if (pMtlType) + { + res.push_back(pMtlType->name()); + } + } + return res; +} + +PTMaterialTypePtr PTShaderLibrary::getType(const string& name) +{ + return _materialTypes[name].lock(); +} + +void PTShaderLibrary::removeSource(int sourceIndex) +{ + // Push index in to vector, the actual remove only happens when library rebuilt. + _sourceToRemove.push_back(sourceIndex); +} +PTMaterialTypePtr PTShaderLibrary::acquireMaterialType( + const MaterialTypeSource& source, bool* pCreatedType) +{ + // The shared pointer to material type. + PTMaterialTypePtr pMtlType; + + // First see if this entry point already exists. + map>::iterator hgIter = _materialTypes.find(source.name); + if (hgIter != _materialTypes.end()) + { + // Weak pointer is stored in shader library, ensure it has not been deleted. + pMtlType = hgIter->second.lock(); + if (pMtlType) + { + // If the entry point exists, in debug mode do a string comparison to ensure the source + // also matches. + AU_ASSERT_DEBUG( + source.compareSource(_compiledMaterialTypes[pMtlType->_sourceIndex].source), + "Source mis-match for material type %s.", source.name.c_str()); + + // No type created. + if (pCreatedType) + *pCreatedType = false; + + // Return the existing material type. + return pMtlType; + } + } + + // Append the new source to the source vector, and calculate source index. + int sourceIdx = static_cast(_compiledMaterialTypes.size()); + _compiledMaterialTypes.push_back({ source, nullptr }); + + // Trigger rebuild. + _rebuildRequired = true; + + // Create new material type. + pMtlType = make_shared(this, sourceIdx, source.name); + + // Add weak reference to map. + _materialTypes[source.name] = weak_ptr(pMtlType); + + // New type created. + if (pCreatedType) + *pCreatedType = true; + + // Return the new material type. + return pMtlType; +} + +PTMaterialTypePtr PTShaderLibrary::getBuiltInMaterialType(const string& name) +{ + return _builtInMaterialTypes[name]; +} + +void PTShaderLibrary::initialize() +{ + _pTranspiler = make_shared(CommonShaders::g_sDirectory); + + // Initialize root signatures (these are shared by all material types, and don't change.) + initRootSignatures(); + + // Clear the source and built ins vector. Not strictly needed, but this function could be called + // repeatedly in the future. + _compiledMaterialTypes.clear(); + _builtInMaterialNames = {}; + + // Create the default material type. + MaterialTypeSource defaultMaterialSource( + "Default", CommonShaders::g_sInitializeDefaultMaterialType); + PTMaterialTypePtr pDefaultMaterialType = acquireMaterialType(defaultMaterialSource); + + // Ensure the radiance hit entry point is compiled for default material type. + pDefaultMaterialType->incrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + + // Add default material type to the built-in array. + _builtInMaterialNames.push_back(defaultMaterialSource.name); + _builtInMaterialTypes[defaultMaterialSource.name] = + pDefaultMaterialType; // Stores strong reference to the built-in's material type. +} + +bool PTShaderLibrary::setDefinitionsHLSL(const string& definitions) +{ + // If the MaterialX definitions HLSL has not changed do nothing. + if (_materialXDefinitionsSource.compare(definitions) == 0) + { + return false; + } + + // Otherwise set definitions and trigger rebuild. + _materialXDefinitionsSource = definitions; + _rebuildRequired = true; + return true; +} + +bool PTShaderLibrary::setOption(const string& name, int value) +{ + // Set the option and see if actually changed. + bool changed = _options.set(name, value); + + // If the option changed set the HLSL and trigger rebuild. + if (changed) + { + _optionsSource = _options.toHLSL(); + _rebuildRequired = true; + } + + return changed; +} + +void PTShaderLibrary::assembleShadersForMaterialType(const MaterialTypeSource& source, + const map& entryPoints, vector& shadersOut) +{ + // Add shared common code. + string shaderSource = _optionsSource; + shaderSource += CommonShaders::g_sGLSLToHLSL; + shaderSource += CommonShaders::g_sMaterialXCommon; + shaderSource += _materialXDefinitionsSource; + + // Clear the output shaders vector. + shadersOut.clear(); + + // Add the shaders for the radiance hit entry point, if needed. + if (entryPoints.at("RADIANCE_HIT")) + { + + // Create radiance hit entry point, by replacing template tags with the material type name. + string radianceHitEntryPointSource = regex_replace( + CommonShaders::g_sClosestHitEntryPointTemplate, regex("@MATERIAL_TYPE@"), source.name); + shadersOut.push_back(shaderSource + radianceHitEntryPointSource); + + // Create shadow hit entry point, by replacing template tags with the material type name. + string shadowHitEntryPointSource = regex_replace( + CommonShaders::g_sShadowHitEntryPointTemplate, regex("@MATERIAL_TYPE@"), source.name); + shadersOut.push_back(shaderSource + shadowHitEntryPointSource); + } + + // Add the shaders for the layer miss entry point, if needed. + if (entryPoints.at("LAYER_MISS")) + { + // Create layer miss entry point, by replacing template tags with the material type name. + string layerMissShaderSource = regex_replace( + CommonShaders::g_sLayerShaderEntryPointTemplate, regex("@MATERIAL_TYPE@"), source.name); + shadersOut.push_back(shaderSource + layerMissShaderSource); + } +} + +// Input to thread used to compile shaders. +struct CompileJob +{ + string code; + string libName; + ComPtr pBlob; + map includes; +}; + +void PTShaderLibrary::rebuild() +{ + // Start timer. + _timer.reset(); + + // Should only be called if required (rebuilding requires stalling the GPU pipeline.) + AU_ASSERT(_rebuildRequired, "Rebuild not needed"); + + // This creates the following subjects for a pipeline state object: + // - Pipeline configuration: the max trace recursion depth. + // - DXIL library: the compiled shaders in a bundle. + // - Shader configurations: the ray payload and intersection attributes sizes. + // - Associations between shader configurations and specific shaders (NOT DONE HERE). + // - The global root signature: inputs for all shaders. + // - Any local root signatures: inputs for specific shaders. + // - Associations between local root signatures and specific shaders. + // - Hit groups: combinations of closest hit / any hit / intersection shaders. + + // Prepare an empty pipeline state object description. + CD3DX12_STATE_OBJECT_DESC pipelineStateDesc(D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE); + + // Create a pipeline configuration subobject, which simply specifies the recursion depth. + // NOTE: Tracing beyond this depth leads to undefined behavior, e.g. incorrect rendering or + // device removal. The shaders track the depth to ensure it is not exceeded. We allow one more + // than the maximum to so that shadow rays can still be traced at the maximum trace depth. + auto* pPipelineConfigSuboject = + pipelineStateDesc.CreateSubobject(); + pPipelineConfigSuboject->Config(PTRenderer::kMaxTraceDepth + 1); + + // Create the DXC library, linker, and compiler. + // NOTE: DXCompiler.dll is set DELAYLOAD in the linker settings, so this will abort if DLL + // not available. + DxcCreateInstance(CLSID_DxcLibrary, IID_PPV_ARGS(_pDXCLibrary.GetAddressOf())); + DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(_pDXCompiler.GetAddressOf())); + DxcCreateInstance(CLSID_DxcLinker, IID_PPV_ARGS(_pDXLinker.GetAddressOf())); + + // Create a DXIL library subobject. + // NOTE: Shaders are not subobjects, but the library containing them is a subobject. All of the + // shader entry points are exported by default; if only specific entry points should be + // exported, then DefineExport() on the subobject should be used. + auto* pLibrarySubObject = pipelineStateDesc.CreateSubobject(); + + // Clear any source that has been removed. + // TODO: Allow re-use of source indices. + for (auto sourceIndex : _sourceToRemove) + { + _compiledMaterialTypes[sourceIndex].reset(); + } + _sourceToRemove.clear(); + + // Build vector of compile jobs to execute in paralell. + vector compileJobs; + + // Add code to be compiled for the common shared entry points. + // TODO: Just compile once at the start of day. + compileJobs.push_back({ CommonShaders::g_sBackgroundMissShader, "BackgroundMiss", nullptr }); + compileJobs.push_back({ CommonShaders::g_sRadianceMissShader, "RadianceMiss", nullptr }); + compileJobs.push_back({ CommonShaders::g_sShadowMissShader, "ShadowMiss", nullptr }); + compileJobs.push_back({ CommonShaders::g_sRayGenShader, "RayGen", nullptr }); + + // Assemble the shader code for for each material type. + for (int i = 0; i < _compiledMaterialTypes.size(); i++) + { + auto& compiledMtlType = _compiledMaterialTypes[i]; + if (!compiledMtlType.source.empty() && !compiledMtlType.binary) + { + // Get the material type. + PTMaterialType* pMtlType = _materialTypes[compiledMtlType.source.name].lock().get(); + + // Get entry points for material type (this may have changed since last rebuild) + map entryPoints; + pMtlType->getEntryPoints(entryPoints); + + // Assemble the shaders for all the entry points. + vector shaderCode; + assembleShadersForMaterialType(compiledMtlType.source, entryPoints, shaderCode); + + // Add a compile job for each entry point. + for (int j = 0; j < shaderCode.size(); j++) + { + // Add to compile jobs to be built, adding the material type source as includes map. + compileJobs.push_back({ shaderCode[j], compiledMtlType.source.name + to_string(j), + nullptr, { { "InitializeMaterial.slang", compiledMtlType.source.setup } } }); + } + } + } + + // Compile function called from parallel thread. + auto compileFunc = [this](CompileJob& job) { + // If development flag set dump HLSL library to a file. + if (AU_DEV_DUMP_SHADER_CODE) + { + string entryPointPath = Foundation::getModulePath() + job.libName + ".txt"; + AU_INFO("Dumping shader code to:%s", entryPointPath.c_str()); + ofstream outputFile; + outputFile.open(entryPointPath); + outputFile << job.code; + outputFile.close(); + } + + // Set the material type source as source code available as via #include in the compiler. + for (auto iter = job.includes.begin(); iter != job.includes.end(); iter++) + { + _pTranspiler->setSource(iter->first, iter->second); + } + + // Transpile the source. + string transpiledHLSL; + string transpilerErrors; + if (!_pTranspiler->transpileCode( + job.code, transpiledHLSL, transpilerErrors, Transpiler::Language::HLSL)) + { + AU_ERROR("Slang transpiling error log:\n%s", transpilerErrors.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Slang transpiling failed, see log in console for details."); + } + + // Compile the HLSL source containing all the material types. + ComPtr compiledShader; + vector> defines = { { L"DIRECTX", "1" } }; + string errorMessage; + if (!compileLibrary(_pDXCLibrary, + transpiledHLSL.c_str(), // Source code string. + "AuroraShaderLibrary", // Arbitrary shader name + "lib_6_3", // Used DXIL 6.3 shader target + "", // DXIL has empty entry point. + defines, // Defines (currently empty vector). + false, // Don't use debug mode. + compiledShader, // Result as blob. + &errorMessage // Error message (if compilation fails.) + )) + { + // Report a compile error and then break the debugger if attached. This gives the + // developer a chance to handle HLSL programming errors as early as possible. + AU_ERROR("HLSL compilation error log:\n%s", errorMessage.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("HLSL compilation failed, see log in console for details."); + } + job.pBlob = compiledShader; + }; + + // Compile all the material types. + vector> compiledShaders; + vector compiledShaderNames; + float compStart = _timer.elapsed(); +#if AU_DEV_MULTITHREAD_COMPILATION // Set to 1 to force single threaded. + tbb::parallel_for(tbb::blocked_range(0, (int)compileJobs.size()), + [&compileJobs, compileFunc](tbb::blocked_range r) { + for (int i = r.begin(); i < r.end(); ++i) + { + compileFunc(compileJobs[i]); + } + }); +#else + for (auto& job : compileJobs) + { + compileFunc(job); + } +#endif + + float compEnd = _timer.elapsed(); + + // Build array of shader binaries and names for linking. + for (auto& job : compileJobs) + { + compiledShaders.push_back(job.pBlob); + compiledShaderNames.push_back(job.libName); + } + + // Link the compiled shaders into single library. + float linkStart = _timer.elapsed(); + ComPtr linkedShader; + string linkErrorMessage; + if (!linkLibrary(compiledShaders, compiledShaderNames, + "lib_6_3", // Used DXIL 6.3 shader target + "", // DXIL has empty entry point. + false, // Don't use debug mode. + linkedShader, // Result as blob. + &linkErrorMessage // Error message (if compilation fails.) + )) + { + // Report a compile error and then break the debugger if attached. This gives the + // developer a chance to handle HLSL programming errors as early as possible. + AU_ERROR("HLSL linker error log:\n%s", linkErrorMessage.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("HLSL compilation failed, see log in console for details."); + } + float linkEnd = _timer.elapsed(); + + // Create bytecode from compiled blob. + D3D12_SHADER_BYTECODE shaderByteCode = + CD3DX12_SHADER_BYTECODE(linkedShader->GetBufferPointer(), linkedShader->GetBufferSize()); + + // Set DXIL library object bytecode. + pLibrarySubObject->SetDXILLibrary(&shaderByteCode); + + // Check shader material compatibility structure using reflection. + // Get the DXC blob compiled in compileShader. + IDxcBlob* pBlob = linkedShader.Get(); + + // Create a container reflection object from the blob. + ComPtr pContainerReflection; + DxcCreateInstance( + CLSID_DxcContainerReflection, IID_PPV_ARGS(pContainerReflection.GetAddressOf())); + pContainerReflection->Load(pBlob); + + // Find the DXIL reflection object within the container. + UINT32 shaderIdx; + pContainerReflection->FindFirstPartKind(MAKEFOURCC('D', 'X', 'I', 'L'), &shaderIdx); + pContainerReflection->GetPartReflection( + shaderIdx, __uuidof(ID3D12LibraryReflection), (void**)&_pShaderLibraryReflection); + + // Create a shader configuration subobject, which indicates the maximum sizes of the ray payload + // (as defined in the shaders; see "RadianceRayPayload" and "LayerData") and intersection + // attributes (UV barycentric coordinates). + const unsigned int kRayPayloadSize = 48 * sizeof(float); + const unsigned int kIntersectionSize = 2 * sizeof(float); + auto* pShaderConfigSubobject = + pipelineStateDesc.CreateSubobject(); + pShaderConfigSubobject->Config(kRayPayloadSize, kIntersectionSize); + + // NOTE: The shader configuration is assumed to apply to all shaders, so it is *not* associated + // with specific shaders (which would use CD3DX12_SUBOBJECT_TO_EXPORTS_ASSOCIATION_SUBOBJECT). + + // Create the global root signature subobject. + auto* pGlobalRootSignatureSuboject = + pipelineStateDesc.CreateSubobject(); + pGlobalRootSignatureSuboject->SetRootSignature(_pGlobalRootSignature.Get()); + + // Create the local root signature subobject associated with the ray generation shader. + auto* pRayGenRootSignatureSuboject = + pipelineStateDesc.CreateSubobject(); + pRayGenRootSignatureSuboject->SetRootSignature(_pRayGenRootSignature.Get()); + auto* pAssociationSubobject = + pipelineStateDesc.CreateSubobject(); + pAssociationSubobject->SetSubobjectToAssociate(*pRayGenRootSignatureSuboject); + pAssociationSubobject->AddExport(gRayGenEntryPoint); + + // Keep track of number of active material types. + int activeMaterialTypes = 0; + + // Create a DXR hit group for each material type. + for (auto pWeakMaterialType : _materialTypes) + { + PTMaterialTypePtr pMaterialType = pWeakMaterialType.second.lock(); + if (pMaterialType) + { + + if (pMaterialType->refCount(PTMaterialType::EntryPoint::kRadianceHit) == 0 && + pMaterialType->refCount(PTMaterialType::EntryPoint::kLayerMiss) == 0) + { + AU_WARN("Invalid material type %s: all entry point reference counts are zero!", + pMaterialType->name().c_str()); + } + + // Increment active material type. + activeMaterialTypes++; + + // Create the local root signature subobject associated with the hit group. + // All material types are based on the same radiance hit root signature currently. + auto* pRadianceHitRootSignatureSuboject = + pipelineStateDesc.CreateSubobject(); + pRadianceHitRootSignatureSuboject->SetRootSignature(_pRadianceHitRootSignature.Get()); + pAssociationSubobject = + pipelineStateDesc + .CreateSubobject(); + pAssociationSubobject->SetSubobjectToAssociate(*pRadianceHitRootSignatureSuboject); + + // Create the radiance hit group subobject, which aggregates closest hit, any hit, and + // intersection shaders as a group. In this case, there is a closest hit shader for + // radiance rays, and an any hit shader for shadow rays. + // NOTE: Normally a hit group corresponds to a single ray type, but here we are able to + // combine code for both radiance and shadow rays into a single hit group. If we needed + // an any hit shader for radiance rays, then a separate hit group for shadow rays would + // have to be created, and referenced with an offset in the related TraceRay() calls. + + // Create hit group (required even if only has miss shader.) + auto* pMaterialTypeSuboject = + pipelineStateDesc.CreateSubobject(); + pMaterialTypeSuboject->SetHitGroupExport(pMaterialType->exportName().c_str()); + pAssociationSubobject->AddExport(pMaterialType->exportName().c_str()); + + if (pMaterialType->refCount(PTMaterialType::EntryPoint::kRadianceHit) > 0) + { + + pMaterialTypeSuboject->SetClosestHitShaderImport( + pMaterialType->closestHitEntryPoint().c_str()); + pMaterialTypeSuboject->SetAnyHitShaderImport( + pMaterialType->shadowAnyHitEntryPoint().c_str()); + pMaterialTypeSuboject->SetHitGroupType(D3D12_HIT_GROUP_TYPE_TRIANGLES); + + pAssociationSubobject->AddExport(pMaterialType->exportName().c_str()); + } + + if (pMaterialType->refCount(PTMaterialType::EntryPoint::kLayerMiss) > 0) + { + + auto* pLayerMissRootSignatureSuboject = + pipelineStateDesc.CreateSubobject(); + pLayerMissRootSignatureSuboject->SetRootSignature(_pLayerMissRootSignature.Get()); + pAssociationSubobject = + pipelineStateDesc + .CreateSubobject(); + pAssociationSubobject->SetSubobjectToAssociate(*pLayerMissRootSignatureSuboject); + pAssociationSubobject->AddExport( + pMaterialType->materialLayerMissEntryPoint().c_str()); + } + } + } + + // Create the pipeline state object from the description. + float plStart = _timer.elapsed(); + checkHR(_pDXDevice->CreateStateObject(pipelineStateDesc, IID_PPV_ARGS(&_pPipelineState))); + float plEnd = _timer.elapsed(); + + // Ensure GPU material structure matches CPU version. + AU_ASSERT( + PTMaterial::validateOffsets(*this), "Mismatch between GPU and CPU material structure"); + + // Rebuild is no longer required. + _rebuildRequired = false; + + // Get time taken to rebuild. + // TODO: This should go into a stats property set and exposed to client properly. + float elapsedMillisec = _timer.elapsed(); + + AU_INFO("Compiled %d material types in %d ms", activeMaterialTypes, + static_cast(elapsedMillisec)); + AU_INFO(" - DXC compile took %d ms", static_cast(compEnd - compStart)); + AU_INFO(" - DXC link took %d ms", static_cast(linkEnd - linkStart)); + AU_INFO(" - Pipeline creation took %d ms", static_cast(plEnd - plStart)); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h new file mode 100644 index 0000000..c121dec --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h @@ -0,0 +1,320 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "MaterialBase.h" + +BEGIN_AURORA + +class Transpiler; + +// Alias for DirectX shader ID, used to access the shader functions in the library. +using DirectXShaderIdentifier = void*; + +/** + * \desc Class representing a material type, which maps directly to a DirectX hit group and a hit + * shader in the HLSL shader library. Material types are managed by the shader library, and delete + * via shared pointer when no longer referenced by any materials. + */ +class PTMaterialType +{ + // Shader library manages the material type, and accesses private properties. + friend class PTShaderLibrary; + +public: + /** + * \param pShaderLibrary The shader library this type is part of. + * \param sourceIndex The index for this type's shader code within the library's HLSL source. + * \param hitEntryPoint The entry point for this type's hit shader. + */ + PTMaterialType(PTShaderLibrary* pShaderLibrary, int sourceIndex, const string& typeName); + ~PTMaterialType(); + + // Entry point types. + enum EntryPoint + { + kRadianceHit, + kLayerMiss, + kNumEntryPoints + }; + + // Array of entry point names. + static constexpr string_view EntryPointNames[EntryPoint::kNumEntryPoints] = { "RADIANCE_HIT", + "LAYER_MISS" }; + + // Gets the DX shader identifier for this type's hit group. + DirectXShaderIdentifier getShaderID(); + + // Gets the DX shader identifier for this type's layer miss shader. + DirectXShaderIdentifier getLayerShaderID(); + + // Gets the unique material type name. + const string& name() { return _name; } + + // Gets the export name for this type's hit group. + const wstring& exportName() const { return _exportName; } + + // Gets the closest hit HLSL function entry point name for this type. + const wstring& closestHitEntryPoint() const { return _closestHitEntryPoint; } + + // Gets the shadow any hit HLSL function entry point name for this type. + const wstring& shadowAnyHitEntryPoint() const { return _shadowAnyHitEntryPoint; } + + // Gets the layer material miss shader HLSL function entry point name for this type. + const wstring& materialLayerMissEntryPoint() const { return _materialLayerMissEntryPoint; } + + // Gets the index for this type's hit shader within the library's HLSL source. + int sourceIndex() const { return _sourceIndex; } + + void incrementRefCount(EntryPoint entryPoint); + + void decrementRefCount(EntryPoint entryPoint); + + int refCount(EntryPoint entryPoint) const { return _entryPointRefCount[entryPoint]; } + + void getEntryPoints(map& entryPoints) + { + for (int i = 0; i < kNumEntryPoints; i++) + { + entryPoints[EntryPointNames[i].data()] = (refCount((EntryPoint)i) > 0); + } + } + +protected: + // Invalidate this type, by setting its shader library to null pointer. + // Called by shader library in its destructor to avoid orphaned material types. + void invalidate() { _pShaderLibrary = nullptr; } + + // Is this type still valid? + bool isValid() { return _pShaderLibrary != nullptr; } + + array _entryPointRefCount; + + // The shader library this type is part of. + PTShaderLibrary* _pShaderLibrary; + + // The index for this type's shader code within the library's HLSL source. + int _sourceIndex; + + // Hit group export name. + wstring _exportName; + + // The closest hit function entry point name for this type. + wstring _closestHitEntryPoint; + + // The shadow any hit function entry point name for this type. + wstring _shadowAnyHitEntryPoint; + + // The layer material miss shader entry for for this type. + // TODO: Should only exist if used as layer material. + wstring _materialLayerMissEntryPoint; + + // The unique material type name. + string _name; +}; + +// Shared pointer type for material types. +using PTMaterialTypePtr = shared_ptr; +struct CompiledMaterialType +{ + MaterialTypeSource source; + ComPtr binary = nullptr; + void reset() + { + binary = nullptr; + source.reset(); + } +}; + +// Shader options represented as set of HLSL #define statements. +class PTShaderOptions +{ +public: + // Remove named option. + void remove(const string& name); + + // Set named option as boolean (set as 0 or 1 int). + bool set(const string& name, bool val) { return set(name, val ? 1 : 0); } + + // Set named option as integer. + bool set(const string& name, int val); + + // Clear all the options. + void clear(); + + // Get all the options as HLSL string. + string toHLSL() const; + +private: + unordered_map _lookup; + vector> _data; +}; + +/** + * \desc Class representing a DXIL shader library, and its HLSL source code. Manages the material + * types that implement Aurora materials using the compiled code in the library. The DXIL library + * and its associated pipeline state must be rebuilt as the source code for the library changes. + */ +class PTShaderLibrary +{ + // Shader library manages the material type, and accesses private properties. + friend class PTMaterialType; + +public: + /*** Lifetime Management ***/ + + /** + * \param pDevice DirectX12 device used by the library. + */ + PTShaderLibrary(ID3D12Device5Ptr device) : _pDXDevice(device) { initialize(); } + ~PTShaderLibrary(); + + /// Get the named built-in material type. These are hand-coded and do not require runtime code + /// generation, so this will never trigger a rebuild. + PTMaterialTypePtr getBuiltInMaterialType(const string& name); + + void assembleShadersForMaterialType(const MaterialTypeSource& source, + const map& entryPoints, vector& hlslOut); + + /** + * \desc Acquire a material type for the provided source and type name. This + * will create new material type (and trigger a rebuild) if the named function does not already + * exist.. + * + * \param entryPoint HLSL function name of closest hit shader for this material type, if type + * with this name already exists will return that. + * \param hlslSource HLSL source code that implements this material type. If an existing + * material type for the entry point exists, the source code must match or this will trigger + * assert. + */ + PTMaterialTypePtr acquireMaterialType( + const MaterialTypeSource& source, bool* pCreateNewType = nullptr); + + /// Get the DX pipeline state for this library. This will assert if the library requires a + /// rebuild. + ID3D12StateObjectPtr pipelineState() + { + AU_ASSERT(!_rebuildRequired, + "Shader Library rebuild required, call rebuild() before accessing pipeline state."); + return _pPipelineState; + } + + /// Get the DX background miss shader identifier, that is shared by all material types.This will + /// assert if the library requires a rebuild. + DirectXShaderIdentifier getBackgroundMissShaderID(); + + /// Get the DX radiance miss shader identifier, that is shared by all material types.This will + /// assert if the library requires a rebuild. + DirectXShaderIdentifier getRadianceMissShaderID(); + + /// Get the DX shadow miss shader identifier, that is shared by all material types.This will + /// assert if the library requires a rebuild. + DirectXShaderIdentifier getShadowMissShaderID(); + + /// Get the DX ray generation shader identifier, that is shared by all material types.This will + /// assert if the library requires a rebuild. + DirectXShaderIdentifier getRayGenShaderID(); + + /// Get the names of the built-in material types. + const vector& builtInMaterials() const { return _builtInMaterialNames; } + + /// Get the global root signature used by all shaders. + ID3D12RootSignaturePtr globalRootSignature() const { return _pGlobalRootSignature; } + + /// Is a rebuild of the shader library and its pipeline state required? + bool rebuildRequired() { return _rebuildRequired; } + + /// Rebuild the shader library and its pipeline state. GPU must be idle before calling this. + void rebuild(); + + /// Set the MaterialX definition HLSL source. This will trigger rebuild if the new source is + /// different to the current definitions source. + bool setDefinitionsHLSL(const string& definitions); + + // Get the DirectX shader reflection for library. + ID3D12LibraryReflection* reflection() const { return _pShaderLibraryReflection; } + + // Set the named option to a given int value. + // Will be added to shader library as a #define statement. + bool setOption(const string& name, int value); + + PTMaterialTypePtr getType(const string& name); + + vector getActiveTypeNames(); + + void triggerRebuild() { _rebuildRequired = true; } + +private: + /*** Private Functions ***/ + + // Initialize the library. + void initialize(); + + // Compile HLSL source code using DXCompiler. + bool compileLibrary(const ComPtr& pDXCLibrary, const string source, + const string& name, const string& target, const string& entryPoint, + const vector>& defines, bool debug, ComPtr& pOutput, + string* pErrorMessage); + + bool linkLibrary(const vector>& input, const vector& inputNames, + const string& target, const string& entryPoint, bool debug, ComPtr& pOutput, + string* pErrorMessage); + + // Create a DirectX root signature. + ID3D12RootSignaturePtr createRootSignature(const D3D12_ROOT_SIGNATURE_DESC& desc); + + // Get the shader identifier for an entry point. + DirectXShaderIdentifier getShaderID(const wchar_t* entryPoint); + + // Initialize the shared root signatures. + void initRootSignatures(); + + // Remove the HLSL source for the associated index. Called by friend class PTMaterialType. + void removeSource(int sourceIndex); + + /*** Private Variables ***/ + vector _builtInMaterialNames; + + ID3D12Device5Ptr _pDXDevice; + + ID3D12RootSignaturePtr _pGlobalRootSignature; + ID3D12RootSignaturePtr _pRayGenRootSignature; + ID3D12RootSignaturePtr _pRadianceHitRootSignature; + ID3D12RootSignaturePtr _pLayerMissRootSignature; + ; + + ID3D12StateObjectPtr _pPipelineState; + + ID3D12LibraryReflection* _pShaderLibraryReflection; + ComPtr _pDXCLibrary; + ComPtr _pDXCompiler; + ComPtr _pDXLinker; + + vector _sourceToRemove; + + map> _materialTypes; + map _builtInMaterialTypes; + + bool _rebuildRequired = true; + + vector _compiledMaterialTypes; + string _materialXDefinitionsSource; + string _optionsSource; + PTShaderOptions _options; + shared_ptr _pTranspiler; + + Foundation::CPUTimer _timer; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTTarget.cpp b/Libraries/Aurora/Source/DirectX/PTTarget.cpp new file mode 100644 index 0000000..3952fc0 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTTarget.cpp @@ -0,0 +1,328 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTTarget.h" + +#include "PTImage.h" +#include "PTRenderer.h" + +BEGIN_AURORA + +PTWindow::PTWindow(PTRenderer* pRenderer, WindowHandle handle, uint32_t width, uint32_t height) : + PTTarget(pRenderer, width, height) +{ + // Initialize member variables. + _pRenderer = pRenderer; + _dimensions = uvec2(width, height); + + // Get DirectX objects from the renderer. + ID3D12Device5Ptr pDevice = _pRenderer->dxDevice(); + IDXGIFactory4Ptr pFactory = _pRenderer->dxFactory(); + + // Prepare a swap chain description. + // NOTE: Tearing (for variable refresh rate displays, or VRR) is enabled for the swap chain. + // This is always available with DX12 and Windows 10 after mid-2016. This actually takes effect + // when vsync is disabled and with an appropriate display. + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.BufferCount = _backBufferCount; + swapChainDesc.Width = _dimensions.x; + swapChainDesc.Height = _dimensions.y; + swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + // Create a swap chain. + // NOTE: The creation function only accepts ID3D12SwapChain1, so we query for ID3D12SwapChain3. + auto hwnd = static_cast(handle); + IDXGISwapChain1Ptr pSwapChain1; + checkHR(pFactory->CreateSwapChainForHwnd( + _pRenderer->commandQueue(), hwnd, &swapChainDesc, nullptr, nullptr, &pSwapChain1)); + pSwapChain1.As(&_pSwapChain); + + // Disable default handling of ALT-ENTER by DXGI to set the window into a full-screen exclusive + // mode. Instead, an Aurora client should style and place the window to be a full-screen + // borderless window. Full-screen exclusive mode is generally used for games, but it not + // appropriate (or necessary) for Aurora. + pFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER); + + // Create a descriptor heap for render target views. + D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; + rtvHeapDesc.NumDescriptors = _backBufferCount; + rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + checkHR(pDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&_pRTVHeap))); + + // Create render targets for the buffers in the swap chain. + createBackBuffers(); +} + +void PTWindow::resize(uint32_t width, uint32_t height) +{ + assert(width > 0 && height > 0); + + // Do nothing if the swap chain dimensions have not changed. + if (width == _dimensions.x && height == _dimensions.y) + { + return; + } + + // Flush the renderer to make sure the swap chain is no longer being used, and release the + // render targets. + // NOTE: All references (both direct and indirect) to the swap chain buffers must be released + // before the swap chain can be resized. + _pRenderer->waitForTask(); + _backBuffers.clear(); + + // Resize the swap chain, keeping the original buffer count and format. Also recreate the + // render targets. + _dimensions = uvec2(width, height); + _pSwapChain->ResizeBuffers( + 0, width, height, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING); + createBackBuffers(); +} +void PTWindow::copyFromResource(ID3D12Resource* pSource, ID3D12GraphicsCommandList4* pCommandList) +{ + // Get the resource for the current back buffer (target). The current back buffer changes on + // each swap chain Present() call. + ID3D12Resource* pBackBufferResource = + _backBuffers[_pSwapChain->GetCurrentBackBufferIndex()].resource(); + + // Transition the back buffer's resource from a presentable buffer to a copy destination, then + // copy the renderer's result to the resource, and transition back to presenting. + _pRenderer->addTransitionBarrier( + pBackBufferResource, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_COPY_DEST); + pCommandList->CopyResource(pBackBufferResource, pSource); + _pRenderer->addTransitionBarrier( + pBackBufferResource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PRESENT); +} + +void PTWindow::present() +{ + // Present the next buffer in the swap chain. Tearing (VRR) is set when vsync is disabled, i.e. + // sync interval of zero. + _pSwapChain->Present(_isVSyncEnabled ? 1 : 0, _isVSyncEnabled ? 0 : DXGI_PRESENT_ALLOW_TEARING); +} + +void PTWindow::createBackBuffers() +{ + ID3D12Device5Ptr pDevice = _pRenderer->dxDevice(); + + // Create render target views for each back buffer in the swap chain. + // NOTE: A non "_SRGB" format is deliberately used here. The renderer may use CopyResource() + // which does not perform gamma correction with a _SRGB RTV; only shader writes will do it. So + // to keep consistent behavior, the renderer must perform any gamma correction; this is very + // fast and simple to do anyway. + _backBuffers.resize(_backBufferCount); + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {}; + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rtvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // not _SRGB + rtvDesc.Texture2D.MipSlice = 0; + UINT rtvSize = pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(_pRTVHeap->GetCPUDescriptorHandleForHeapStart()); + for (uint32_t i = 0; i < _backBufferCount; i++) + { + ID3D12ResourcePtr pBuffer; + _pSwapChain->GetBuffer(static_cast(i), IID_PPV_ARGS(&pBuffer)); + pDevice->CreateRenderTargetView(pBuffer.Get(), &rtvDesc, rtvHandle); + _backBuffers[i] = BackBuffer(pBuffer.Get(), rtvHandle); + rtvHandle.Offset(rtvSize); + } +} + +PTRenderBuffer::PTRenderBuffer( + PTRenderer* pRenderer, uint32_t width, uint32_t height, ImageFormat format) : + PTTarget(pRenderer, width, height, format) +{ +} + +void PTRenderBuffer::resize(uint32_t width, uint32_t height) +{ + assert(width > 0 && height > 0); + + // Do nothing if the dimensions are unchanged. + if (width == _dimensions.x && height == _dimensions.y) + { + return; + } + + _dimensions = uvec2(width, height); + + // Wait for any previous work to be completed, so that the readback buffer is not released while + // in use. + _pRenderer->waitForTask(); + + // Simply clear the host data buffers and the resources; they will be recreated as needed. + _pData.clear(); + _pReadbackBuffer.Reset(); + _pShareableTexture.Reset(); + _sharedTextureHandle = nullptr; + + _hasPendingData = false; +} + +class PTReadbackBuffer : public IRenderBuffer::IBuffer +{ +public: + PTReadbackBuffer(ID3D12ResourcePtr pReadbackBuffer, size_t stride, size_t bytesPerPixel) : + _pReadbackBuffer(pReadbackBuffer), _stride(stride), _bytesPerPixel(bytesPerPixel) + { + if (pReadbackBuffer) + _pReadbackBuffer->Map(0, nullptr, &_pSrcData); + } + + ~PTReadbackBuffer() override + { + if (_pReadbackBuffer) + { + CD3DX12_RANGE writeRange(0, 0); + _pReadbackBuffer->Unmap(0, &writeRange); // no HRESULT + } + } + + const void* data() override { return _pSrcData; } + + const void data(size_t width, uint32_t height, void* pDataOut) + { + if (!_pReadbackBuffer) + return; + + // Pointer to start of row in source data + byte* srcPtr = static_cast(_pSrcData); + byte* destPtr = static_cast(pDataOut); + + size_t widthInBytes = width * _bytesPerPixel; + if (widthInBytes == _stride) + { + // faster path to copy the whole buffer + ::memcpy_s(destPtr, _stride * height, srcPtr, _stride * height); + } + else + { + // End of the destination buffer + byte* destPtrEnd = destPtr + (widthInBytes * height); + + // Copy the image row by row (excluding the padding) + for (size_t i = 0; i < height; i++) + { + // Calculate remaining bytes (required by memcpy_s to avoid buffer overruns) + size_t remBytes = destPtrEnd - destPtr; + // Copy start of current row (skipping any padding at the end) + ::memcpy_s(destPtr, remBytes, srcPtr, widthInBytes); + // Advance to next row in destination data (with no padding) + destPtr += widthInBytes; + // Advance to next row in source data (with padding) + srcPtr += _stride; + } + } + } + +private: + void* _pSrcData = nullptr; + ID3D12ResourcePtr _pReadbackBuffer; + size_t _stride, _bytesPerPixel; +}; + +size_t PTRenderBuffer::bytesPerPixel() +{ + return PTImage::getBytesPerPixel(_format); +} + +IRenderBuffer::IBufferPtr PTRenderBuffer::asReadable(size_t& stride) +{ + // set the row stride + stride = _dataStride; + + // Wait for any previous work to be completed, so that the copy to the readback buffer has + // the pending data. + _pRenderer->waitForTask(); + + return std::make_shared(_pReadbackBuffer, _dataStride, bytesPerPixel()); +} + +const void* PTRenderBuffer::data(size_t& stride, bool removePadding) +{ + // compute the row stride + stride = removePadding ? (size_t)(bytesPerPixel() * _dimensions.x) : _dataStride; + + if (_hasPendingData) + { + // Initialize the host data buffer if needed. + // NOTE: The _dataSize member (i.e. the "total bytes" property from GetCopyableFootprints()) + // excludes the padding for the last row, but we define the host data buffer to include that + // padding here, i.e. a full stride for every row, as that is what the client will expect. + if (_pData.empty()) + _pData.resize(stride * _dimensions.y); + + // Read the entire contents of the readback buffer into the host data buffer. + IBufferPtr readback = asReadable(stride); + PTReadbackBuffer* privateBuffer = static_cast(readback.get()); + privateBuffer->data(_dimensions.x, _dimensions.y, _pData.data()); + + _hasPendingData = false; + } + + // Return the data pointer. + return _pData.data(); +} + +void PTRenderBuffer::copyFromResource( + ID3D12Resource* pSource, ID3D12GraphicsCommandList4* pCommandList) +{ + // Determine the size and stride of the source data, as well as information for copying from it. + D3D12_RESOURCE_DESC desc = pSource->GetDesc(); + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; + _pRenderer->dxDevice()->GetCopyableFootprints( + &desc, 0, 1, 0, &layout, nullptr, nullptr, &_dataSize); + _dataStride = layout.Footprint.RowPitch; + + // Create the readback buffer if needed. + // NOTE: It is not possible to use a texture buffer for readback, so a plain buffer is used. + if (!_pReadbackBuffer) + { + _pReadbackBuffer = _pRenderer->createBuffer(_dataSize, "Readback Buffer", + D3D12_HEAP_TYPE_READBACK, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); + } + + // Use the command list to add a command to copy from the source texture to the readback buffer. + CD3DX12_TEXTURE_COPY_LOCATION copyBufferDest(_pReadbackBuffer.Get(), layout); + CD3DX12_TEXTURE_COPY_LOCATION copySrc(pSource); + pCommandList->CopyTextureRegion(©BufferDest, 0, 0, 0, ©Src, nullptr); + + // Create a shareable texture + if (!_pShareableTexture) + { + _pShareableTexture = _pRenderer->createTexture(this->_dimensions, + PTImage::getDXFormat(this->format()), "Shared Result Texture", false, true); + + // Create a shared handle for the shareable texture + auto hr = _pRenderer->dxDevice()->CreateSharedHandle( + _pShareableTexture.Get(), NULL, GENERIC_ALL, NULL, &_sharedTextureHandle); + if (hr != S_OK) + { + _sharedTextureHandle = nullptr; + AU_WARN("Failed to create a shared handle for PTRenderBuffer"); + } + } + // Use the command list to add a command to copy from the source texture to the shareable + // texture. + CD3DX12_TEXTURE_COPY_LOCATION copyTextureDest(_pShareableTexture.Get()); + pCommandList->CopyTextureRegion(©TextureDest, 0, 0, 0, ©Src, nullptr); + + _hasPendingData = true; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTTarget.h b/Libraries/Aurora/Source/DirectX/PTTarget.h new file mode 100644 index 0000000..c85089c --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTTarget.h @@ -0,0 +1,174 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// Forward declarations. +class PTRenderer; + +// An internal implementation for ITarget. +class PTTarget +{ +public: + /*** Lifetime Management ***/ + + PTTarget(PTRenderer* pRenderer, uint32_t width, uint32_t height, + ImageFormat format = ImageFormat::Integer_RGBA) : + _pRenderer(pRenderer), _dimensions(width, height), _format(format) + { + assert(width > 0 && height > 0); + } + + /*** Functions ***/ + + virtual const uvec2& dimensions() const { return _dimensions; }; + virtual void copyFromResource( + ID3D12Resource* pSource, ID3D12GraphicsCommandList4* pCommandList) = 0; + virtual void present() {}; + + /// Returns the format of the target. + ImageFormat format() { return _format; } + +protected: + /*** Protected Variables ***/ + + PTRenderer* _pRenderer = nullptr; + uvec2 _dimensions; + ImageFormat _format; +}; +MAKE_AURORA_PTR(PTTarget); + +// An internal implementation for IWindow. +class PTWindow : public IWindow, public PTTarget +{ +public: + /*** Lifetime Management ***/ + + PTWindow(PTRenderer* pRenderer, WindowHandle handle, uint32_t width, uint32_t height); + + /*** ITarget Functions ***/ + + void resize(uint32_t width, uint32_t height) override; + + /*** IWindow Functions ***/ + + void setVSyncEnabled(bool enabled) override { _isVSyncEnabled = enabled; } + + /*** PTTarget Functions ***/ + + void copyFromResource( + ID3D12Resource* pSource, ID3D12GraphicsCommandList4* pCommandList) override; + void present() override; + +private: + /*** Private Types ***/ + + class BackBuffer + { + public: + BackBuffer() {}; + BackBuffer(ID3D12Resource* pResource, D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle) + { + _pResource = pResource; + _rtvHandle = rtvHandle; + } + ID3D12Resource* resource() const { return _pResource.Get(); } + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle() const { return _rtvHandle; } + + private: + ID3D12ResourcePtr _pResource; + D3D12_CPU_DESCRIPTOR_HANDLE _rtvHandle = {}; + }; + + /*** Private Functions ***/ + + void createBackBuffers(); + + /*** Private Variables ***/ + + uint32_t _backBufferCount = 3; + IDXGISwapChain3Ptr _pSwapChain; + ID3D12DescriptorHeapPtr _pRTVHeap; + vector _backBuffers; + bool _isVSyncEnabled = false; +}; +MAKE_AURORA_PTR(PTWindow); + +// Implementation of IBuffer that just contains a reference to the shared handle for the texture +class PTSharedMemory : public IRenderBuffer::IBuffer +{ +public: + PTSharedMemory() = delete; + + // Handle to a buffer created with CreateSharedHandle + explicit PTSharedMemory(HANDLE sharedMemoryHandle) : _sharedMemoryHandle(sharedMemoryHandle) {} + + ~PTSharedMemory() override {} + + const void* data() override { return nullptr; } + const void* handle() override { return _sharedMemoryHandle; } + +private: + HANDLE _sharedMemoryHandle; +}; +MAKE_AURORA_PTR(PTSharedMemory); + +// An internal implementation for IRenderBuffer. +class PTRenderBuffer : public IRenderBuffer, public PTTarget +{ +public: + /*** Lifetime Management ***/ + + PTRenderBuffer(PTRenderer* pRenderer, uint32_t width, uint32_t height, ImageFormat format); + + /*** ITarget Functions ***/ + + void resize(uint32_t width, uint32_t height) override; + + /*** IRenderBuffer Functions ***/ + + const void* data(size_t& stride, bool removePadding) override; + IBufferPtr asReadable(size_t& stride) override; + IBufferPtr asShared() override + { + return std::make_shared(_sharedTextureHandle); + } + + /*** PTTarget Functions ***/ + + void copyFromResource( + ID3D12Resource* pSource, ID3D12GraphicsCommandList4* pCommandList) override; + +private: + size_t bytesPerPixel(); + + /*** Private Variables ***/ + + bool _hasPendingData = false; + vector _pData; + size_t _dataSize = 0; + size_t _dataStride = 0; + + // Buffer for data readback + ID3D12ResourcePtr _pReadbackBuffer; + + // Shareable texture representation of the source texture + ID3D12ResourcePtr _pShareableTexture; + + // Shared handle to the shareable texture + HANDLE _sharedTextureHandle = nullptr; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl b/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl new file mode 100644 index 0000000..3955108 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl @@ -0,0 +1,106 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Declare the root signature for the compute shader. +// NOTE: DESCRIPTORS_VOLATILE is specified for root signature 1.0 behavior, where descriptors do not +// have to be initialized in advance. +#define ROOT_SIGNATURE \ + "RootFlags(0)," \ + "DescriptorTable(UAV(u0, numDescriptors = 10, flags = DESCRIPTORS_VOLATILE)), " \ + "RootConstants(b0, num32BitConstants = 2)" + +// Source (input) and destination (output) textures. +RWTexture2D gAccumulation : register(u0); +RWTexture2D gDirect : register(u1); +RWTexture2D gDepthNDC : register(u2); +RWTexture2D gDepthView : register(u3); +RWTexture2D gNormalRoughness : register(u4); +RWTexture2D gBaseColorMetalness : register(u5); +RWTexture2D gDiffuse : register(u6); +RWTexture2D gGlossy : register(u7); +RWTexture2D gDiffuseDenoised : register(u8); +RWTexture2D gGlossyDenoised : register(u9); + +// Layout of accumulation properties. +struct Accumulation +{ + uint sampleIndex; + bool isDenoisingEnabled; +}; + +// Constant buffer of accumulation values. +ConstantBuffer gSettings : register(b0); + +// A compute shader that accumulates path tracing results, optionally with denoising. +[RootSignature(ROOT_SIGNATURE)][numthreads(1, 1, 1)] void Accumulation(uint3 threadID + : SV_DispatchThreadID) +{ + uint sampleIndex = gSettings.sampleIndex; + + // Get the screen coordinates (2D) from the thread ID, and the color / alpha as the result. + // Treat the color as the direct lighting value, optionally used below. + float2 screenCoords = threadID.xy; + float4 result = gDirect[screenCoords]; + float3 direct = result.rgb; + + // Combine data from textures if denoising is enabled. Otherwise the "direct" texture has the + // complete path tracing output. + if (gSettings.isDenoisingEnabled) + { + // Collect the necessary values. This includes a "hit factor" which is 0 for a background + // sample and 1 for a surface sample ("hit"). This is detected by testing for an infinite + // value in the view depth texture. + float hitFactor = gDepthView[screenCoords].r != 1.#INF; + float3 baseColor = gBaseColorMetalness[screenCoords].rgb; + float3 denoisedDiffuse = gDiffuseDenoised[screenCoords].rgb * hitFactor; + float3 denoisedGlossy = gGlossyDenoised[screenCoords].rgb * hitFactor; + + // Combine the following: + // - Direct lighting. This also includes the environment background. + // - The denoised diffuse radiance, modulated by the base color (albedo). + // - The denoised glossy radiance. + result.rgb = direct + denoisedDiffuse * baseColor + denoisedGlossy; + } + + // If the sample index is greater than zero, blend the new result color with the previous + // accumulation color. + if (sampleIndex > 0) + { + // Get the previous result. If it has an infinity component, then it represents an error + // pixel, and it should remain unmodified. Otherwise blend it with the new result. + float4 prevResult = gAccumulation[screenCoords]; + if (any(isinf(prevResult))) + { + result = prevResult; + } + else + { + // Compute a blend factor (between the previous and new result) based on the sample + // index, with the new result having less influence with an increasing sample index, + // e.g. with sample index #4 (the 5th sample), the final result is 4/5 of the previous + // (accumulated) result and 1/5 of the new result. If denoising is enabled, this should + // be treated as temporal accumulation, with the new result having a fixed influence, so + // that older results are eventually discarded. + static const float TEMPORAL_BLEND_FACTOR = 0.1f; + float t = + gSettings.isDenoisingEnabled ? TEMPORAL_BLEND_FACTOR : 1.0f / (sampleIndex + 1); + + // Blend between the previous result and the new result using the factor. + result = lerp(prevResult, result, t); + } + } + + // Write to the output (accumulation) buffer. + gAccumulation[screenCoords] = result; +} \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/Shaders/Colors.hlsli b/Libraries/Aurora/Source/DirectX/Shaders/Colors.hlsli new file mode 100644 index 0000000..c100076 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/Shaders/Colors.hlsli @@ -0,0 +1,55 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __COLORS_H__ +#define __COLORS_H__ + +// Linearizes an sRGB color. +// NOTE: Use this only for hardcoded colors in the shader. All other color inputs should already be +// linearized. +float3 sRGBtoLinear(float3 value) +{ + return value * (value * (value * 0.305306011f + 0.682171111f) + 0.012522878f); +} + +// Computes the perceived luminance of a color. +float luminance(float3 value) +{ + return dot(value, float3(0.2125f, 0.7154f, 0.0721f)); +} + +// Converts from linear color space to sRGB (gamma correction) for display. +// NOTE: Based on http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html. +float3 linearTosRGB(float3 color) +{ + float3 sq1 = sqrt(color); + float3 sq2 = sqrt(sq1); + float3 sq3 = sqrt(sq2); + + return 0.662002687f * sq1 + 0.684122060f * sq2 - 0.323583601f * sq3 - 0.0225411470f * color; +} + +// Applies the ACES filmic tone mapping curve to the color. +// NOTE: Based on https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve. +float3 toneMapACES(float3 color) +{ + float a = 2.51f; + float b = 0.03f; + float c = 2.43f; + float d = 0.59f; + float e = 0.14f; + + return saturate((color * (a * color + b)) / (color * (c * color + d) + e)); +} + +#endif // __COLORS_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl b/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl new file mode 100644 index 0000000..ad83972 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl @@ -0,0 +1,166 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Colors.hlsli" + +// Declare the root signature for the compute shader. +// NOTE: DESCRIPTORS_VOLATILE is specified for root signature 1.0 behavior, where descriptors do not +// have to be initialized in advance. +#define ROOT_SIGNATURE \ + "RootFlags(0)," \ + "DescriptorTable(UAV(u0, numDescriptors = 11, flags = DESCRIPTORS_VOLATILE)), " \ + "RootConstants(b0, num32BitConstants = 10)" + +// Debug display modes. +#define kDebugModeOff 0 +#define kDebugModeErrors 1 +#define kDebugModeDepthView 2 +#define kDebugModeNormal 3 +#define kDebugModeBaseColor 4 +#define kDebugModeRoughness 5 +#define kDebugModeMetalness 6 +#define kDebugModeDiffuse 7 +#define kDebugModeDiffuseHitDist 8 +#define kDebugModeGlossy 9 +#define kDebugModeGlossyHitDist 10 + +// Source (input) and destination (output) textures. +RWTexture2D gFinal : register(u0); +RWTexture2D gAccumulation : register(u1); +RWTexture2D gDirect : register(u2); +RWTexture2D gDepthNDC : register(u3); +RWTexture2D gDepthView : register(u4); +RWTexture2D gNormalRoughness : register(u5); +RWTexture2D gBaseColorMetalness : register(u6); +RWTexture2D gDiffuse : register(u7); +RWTexture2D gGlossy : register(u8); +RWTexture2D gDiffuseDenoised : register(u9); +RWTexture2D gGlossyDenoised : register(u10); + +// Layout of post-processing properties. +struct PostProcessing +{ + float3 brightness; + int debugMode; + float2 range; + int isDenoisingEnabled; + int isToneMappingEnabled; + int isGammaCorrectionEnabled; + int isAlphaEnabled; +}; + +// Constant buffer of post-processing values. +ConstantBuffer gSettings : register(b0); + +// Normalizes the specified view depth value to the scene range (front to back). +float normalizeDepthView(float depthView) +{ + return (depthView - gSettings.range.x) / (gSettings.range.y - gSettings.range.x); +} + +// A compute shader that applies post-processing to the source texture. +[RootSignature(ROOT_SIGNATURE)][numthreads(1, 1, 1)] void PostProcessing(uint3 threadID + : SV_DispatchThreadID) +{ + // Get the screen coordinates (2D) from the thread ID. + float2 coords = threadID.xy; + + // Use the appropriate texture for output if a debug mode is enabled. + float3 color = 0.0f; + float alpha = gAccumulation[coords].a; + bool isDenoisingEnabled = gSettings.isDenoisingEnabled; + bool isGammaCorrectionEnabled = gSettings.isGammaCorrectionEnabled; + switch (gSettings.debugMode) + { + // Output (accumulation). + case kDebugModeOff: + case kDebugModeErrors: + color = gAccumulation[coords].rgb; + break; + + // View depth. Normalize the R channel of the view depth texture (grayscale). + case kDebugModeDepthView: + color = normalizeDepthView(gDepthView[coords].r).rrr; + isGammaCorrectionEnabled = false; + break; + + // Normal. Use the RGB channels of the normal-roughness texture. + case kDebugModeNormal: + color = gNormalRoughness[coords].rgb; + isGammaCorrectionEnabled = false; + break; + + // Base color. Use the RGB channels of the base-color-metalness texture. + case kDebugModeBaseColor: + color = gBaseColorMetalness[coords].rgb; + break; + + // Roughness. Duplicate the A channel of the normal-roughness texture. + case kDebugModeRoughness: + color = gNormalRoughness[coords].aaa; + isGammaCorrectionEnabled = false; + break; + + // Metalness. Duplicate the A channel of the base-color-metalness texture. + case kDebugModeMetalness: + color = gBaseColorMetalness[coords].aaa; + isGammaCorrectionEnabled = false; + break; + + // Diffuse. Use the RGB channels of the diffuse texture. + case kDebugModeDiffuse: + color = (isDenoisingEnabled ? gDiffuseDenoised[coords] : gDiffuse[coords]).rgb; + break; + + // Diffuse Hit Distance. Duplicate the A channel of the diffuse texture. + case kDebugModeDiffuseHitDist: + color = (isDenoisingEnabled ? gDiffuseDenoised[coords] : gDiffuse[coords]).aaa; + isGammaCorrectionEnabled = false; + break; + + // Glossy. Use the RGB channels of the glossy texture. + case kDebugModeGlossy: + color = (isDenoisingEnabled ? gGlossyDenoised[coords] : gGlossy[coords]).rgb; + break; + + // Glossy Hit Distance. Duplicate the A channel of the glossy texture. + case kDebugModeGlossyHitDist: + color = (isDenoisingEnabled ? gGlossyDenoised[coords] : gGlossy[coords]).aaa; + isGammaCorrectionEnabled = false; + break; + } + + // Perform post-processing for any output based on the accumulation (beauty) texture. + if (gSettings.debugMode <= kDebugModeErrors) + { + // Apply brightness. + color *= gSettings.brightness; + + // Apply ACES tone mapping or simple saturation. + if (gSettings.isToneMappingEnabled) + { + color = toneMapACES(color); + } + } + + // Apply gamma correction. + // NOTE: Gamma correction must be performed here as UAV textures don't support sRGB write. + if (isGammaCorrectionEnabled) + { + color = linearTosRGB(saturate(color)); + } + + // Write to the final texture, optionally with alpha. + gFinal[coords] = float4(color, gSettings.isAlphaEnabled ? alpha : 1.0f); +} \ No newline at end of file diff --git a/Libraries/Aurora/Source/EnvironmentBase.cpp b/Libraries/Aurora/Source/EnvironmentBase.cpp new file mode 100644 index 0000000..d10dace --- /dev/null +++ b/Libraries/Aurora/Source/EnvironmentBase.cpp @@ -0,0 +1,73 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "EnvironmentBase.h" +#include "ImageBase.h" + +BEGIN_AURORA + +static PropertySetPtr g_pPropertySet; + +static PropertySetPtr propertySet() +{ + if (g_pPropertySet) + { + return g_pPropertySet; + } + + g_pPropertySet = make_shared(); + + // Add environment properties and default values to the property set. + static const vec3 LIGHT_TOP_COLOR = vec3(1.0f, 1.0f, 1.0f); + static const vec3 LIGHT_BOTTOM_COLOR = vec3(0.0f, 0.0f, 0.0f); + static const vec3 BACKGROUND_TOP_COLOR = vec3(0.02f, 0.25f, 0.60f); + static const vec3 BACKGROUND_BOTTOM_COLOR = vec3(0.32f, 0.79f, 1.0f); + g_pPropertySet->add("light_top", LIGHT_TOP_COLOR); + g_pPropertySet->add("light_bottom", LIGHT_BOTTOM_COLOR); + g_pPropertySet->add("light_image", IImagePtr()); + g_pPropertySet->add("light_transform", mat4()); + g_pPropertySet->add("background_top", BACKGROUND_TOP_COLOR); + g_pPropertySet->add("background_bottom", BACKGROUND_BOTTOM_COLOR); + g_pPropertySet->add("background_image", IImagePtr()); + g_pPropertySet->add("background_transform", mat4()); + g_pPropertySet->add("background_use_screen", false); + + return g_pPropertySet; +} + +EnvironmentBase::EnvironmentBase() : FixedValues(propertySet()) {} + +void EnvironmentBase::updateGPUStruct(EnvironmentData& data) +{ + data.lightTop = _values.asFloat3("light_top"); + data.lightBottom = _values.asFloat3("light_bottom"); + data.lightTransform = transpose(_values.asMatrix("light_transform")); + data.lightTransformInv = transpose(inverse(_values.asMatrix("light_transform"))); + data.backgroundTop = _values.asFloat3("background_top"); + data.backgroundBottom = _values.asFloat3("background_bottom"); + data.backgroundTransform = transpose(_values.asMatrix("background_transform")); + data.backgroundUseScreen = _values.asBoolean("background_use_screen") ? 1 : 0; + data.hasBackgroundTex = _values.asImage("background_image") ? 1 : 0; + + shared_ptr pLightTex = + dynamic_pointer_cast(_values.asImage("light_image")); + data.hasLightTex = pLightTex ? 1 : 0; + if (pLightTex) + { + data.lightTexLuminanceIntegral = pLightTex->luminanceIntegral(); + } +} + +END_AURORA diff --git a/Libraries/Aurora/Source/EnvironmentBase.h b/Libraries/Aurora/Source/EnvironmentBase.h new file mode 100644 index 0000000..2768ea9 --- /dev/null +++ b/Libraries/Aurora/Source/EnvironmentBase.h @@ -0,0 +1,54 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +// A base class for implementations of IEnvironment. +class EnvironmentBase : public IEnvironment, public FixedValues +{ +public: + /*** Lifetime Management ***/ + + EnvironmentBase(); + + /*** IEnvironment Functions ***/ + + FixedValues& values() override { return *this; } + +protected: + struct EnvironmentData + { + vec3 lightTop; + float _padding1; + vec3 lightBottom; + float lightTexLuminanceIntegral; + mat4 lightTransform; + mat4 lightTransformInv; + vec3 backgroundTop; + float _padding3; + vec3 backgroundBottom; + float _padding4; + mat4 backgroundTransform; + int backgroundUseScreen; + int hasLightTex; + int hasBackgroundTex; + }; + + void updateGPUStruct(EnvironmentData& data); +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/GeometryBase.cpp b/Libraries/Aurora/Source/GeometryBase.cpp new file mode 100644 index 0000000..5194bd7 --- /dev/null +++ b/Libraries/Aurora/Source/GeometryBase.cpp @@ -0,0 +1,125 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "GeometryBase.h" + +BEGIN_AURORA + +// Copies data from the specified array to the specified vector, with each element having a +// specified number of components, e.g. XYZ for a vertex position. +template +static void copyVertexChannelData(vector& dst, const AttributeData& src, + uint32_t vertexCount, uint32_t componentCount = 1) +{ + // If no source data is provided, clear the destination vector and return. + if (!src.address) + { + dst.clear(); + return; + } + + // Get start of source buffer. + const uint8_t* pSrc = reinterpret_cast(src.address) + src.offset; + + // Set the size of the vector with enough elements to include the vertex data, based on the + // specified data type (e.g. float) and number of components per vertex (e.g. three for a + // position, XYZ). + size_t bufferSize = vertexCount * componentCount * sizeof(ComponentType); + dst.resize(bufferSize); + + // If the stride of the source data is just the size of the element, just do a memcpy. + if (src.stride == componentCount * sizeof(ComponentType)) + { + memcpy(dst.data(), pSrc, bufferSize); + } + else + { + // If it doesn't match the input must be interleaved in some way. So iterate through each + // vertex. + ComponentType* pDstComp = dst.data(); + for (size_t i = 0; i < vertexCount; i++) + { + // Copy the individual element from the soure buffer to destination. + const ComponentType* pSrcComp = reinterpret_cast(pSrc); + for (uint32_t j = 0; j < componentCount; j++) + { + pDstComp[j] = pSrcComp[j]; + } + + // Advance the destination pointer by size of element. + pDstComp += componentCount; + + // Advance the source pointer by the stride provided by client. + pSrc += src.stride; + } + } +} + +GeometryBase::GeometryBase(const std::string& name, const GeometryDescriptor& descriptor) : + _name(name) +{ + // Position values must be provided, there must be at least three vertices. If indices are + // provided, there must be at least three of them. All other data is optional. + AU_ASSERT(descriptor.vertexDesc.count >= 3, "Invalid vertex data"); + + _incomplete = !descriptor.vertexDesc.hasAttribute(Names::VertexAttributes::kPosition); + + // Ensure correct index count. Must either not have any indices or have three or more of them. + AU_ASSERT(descriptor.indexCount == 0 || descriptor.indexCount >= 3, "Invalid index data"); + + if (!_incomplete) + { + auto positionAttributeType = + descriptor.vertexDesc.attributes.at(Names::VertexAttributes::kPosition); + AU_ASSERT(positionAttributeType == AttributeFormat::Float3, + "Unsupported type for position attribute: %d", positionAttributeType); + } + + // Get vertex and index count. + _vertexCount = static_cast(descriptor.vertexDesc.count); + _indexCount = static_cast(descriptor.indexCount); + + // Run the getAttributeData function to get the vertex and index data from client. + AttributeDataMap vertexBuffers; + descriptor.getAttributeData(vertexBuffers, 0, _vertexCount, 0, _indexCount); + + // Copy the vertex and index data. + copyVertexChannelData( + _positions, vertexBuffers[Names::VertexAttributes::kPosition], _vertexCount, 3); + copyVertexChannelData( + _normals, vertexBuffers[Names::VertexAttributes::kNormal], _vertexCount, 3); + copyVertexChannelData( + _texCoords, vertexBuffers[Names::VertexAttributes::kTexCoord0], _vertexCount, 2); + copyVertexChannelData(_indices, vertexBuffers[Names::VertexAttributes::kIndices], _indexCount); + + // Run the optional attributeUpdateComplete functoin to free any buffers being held by the + // client. + if (descriptor.attributeUpdateComplete) + descriptor.attributeUpdateComplete(vertexBuffers, 0, _vertexCount, 0, _indexCount); + + // TODO: We need a better UV generator, esspecially if we need tangents generated from them. + if (_texCoords.size() == 0) + { + _texCoords.resize(_vertexCount * 2); + for (uint32_t i = 0; i < _vertexCount; ++i) + { + float coord = static_cast(i) / _vertexCount; + _texCoords[i * 2u] = coord; + _texCoords[i * 2u + 1u] = coord; + } + } +} + +END_AURORA diff --git a/Libraries/Aurora/Source/GeometryBase.h b/Libraries/Aurora/Source/GeometryBase.h new file mode 100644 index 0000000..cd66357 --- /dev/null +++ b/Libraries/Aurora/Source/GeometryBase.h @@ -0,0 +1,55 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// An internal implementation for IGeometry. +class GeometryBase : public IGeometry +{ +public: + /*** Types ***/ + + /*** Lifetime Management ***/ + + GeometryBase(const std::string& name, const GeometryDescriptor& descriptor); + ~GeometryBase() {}; + + /*** Functions ***/ + + uint32_t vertexCount() { return _vertexCount; } + const string& name() { return _name; } + + // Is the geometry complete (has posiitons can be used as primary geometry for instance.) + bool isIncomplete() { return _incomplete; } + +protected: + /*** Private Functions ***/ + + /*** Private Variables ***/ + + std::string _name; + bool _bIsDirty = true; + uint32_t _vertexCount = 0; + vector _positions; + vector _normals; + vector _texCoords; + uint32_t _indexCount = 0; + vector _indices; + bool _incomplete = false; + + /*** DirectX 12 Objects ***/ +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIEnvironment.cpp b/Libraries/Aurora/Source/HGI/HGIEnvironment.cpp new file mode 100644 index 0000000..e4bd8ee --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIEnvironment.cpp @@ -0,0 +1,57 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HGIEnvironment.h" + +#include "HGIMaterial.h" +#include "HGIRenderer.h" + +using namespace pxr; + +BEGIN_AURORA + +HGIEnvironment::HGIEnvironment(HGIRenderer* pRenderer) : _pRenderer(pRenderer) +{ + // Create buffer descriptor, passing material as initial data. + HgiBufferDesc uboDesc; + uboDesc.debugName = "Environment UBO"; + uboDesc.usage = HgiBufferUsageUniform | HgiBufferUsageRayTracingExtensions | + HgiBufferUsageShaderDeviceAddress; + uboDesc.byteSize = sizeof(EnvironmentData); + + // Create UBO. + _ubo = + HgiBufferHandleWrapper::create(_pRenderer->hgi()->CreateBuffer(uboDesc), _pRenderer->hgi()); +} + +void HGIEnvironment::update() +{ + // Build a structure from values map in staging buffer. + EnvironmentData* pStaging = getStagingAddress(_ubo); + updateGPUStruct(*pStaging); + + // Transfer staging buffer to GPU. + pxr::HgiBlitCmdsUniquePtr blitCmds = _pRenderer->hgi()->CreateBlitCmds(); + pxr::HgiBufferCpuToGpuOp blitOp; + blitOp.byteSize = sizeof(EnvironmentData); + blitOp.cpuSourceBuffer = pStaging; + blitOp.sourceByteOffset = 0; + blitOp.gpuDestinationBuffer = _ubo->handle(); + blitOp.destinationByteOffset = 0; + blitCmds->CopyBufferCpuToGpu(blitOp); + _pRenderer->hgi()->SubmitCmds(blitCmds.get()); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIEnvironment.h b/Libraries/Aurora/Source/HGI/HGIEnvironment.h new file mode 100644 index 0000000..5ddd1b3 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIEnvironment.h @@ -0,0 +1,42 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "EnvironmentBase.h" +#include "HGIHandleWrapper.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +// An internal implementation for IEnvironment. +class HGIEnvironment : public EnvironmentBase +{ +public: + /*** Lifetime Management ***/ + + HGIEnvironment(HGIRenderer* pRenderer); + ~HGIEnvironment() {} + + void update(); + pxr::HgiBufferHandle ubo() { return _ubo->handle(); } + +private: + HGIRenderer* _pRenderer; + HgiBufferHandleWrapper::Pointer _ubo; +}; +MAKE_AURORA_PTR(HGIEnvironment); + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIGeometry.cpp b/Libraries/Aurora/Source/HGI/HGIGeometry.cpp new file mode 100644 index 0000000..410447e --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIGeometry.cpp @@ -0,0 +1,148 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pch.h" + +#include "HGIScene.h" + +#include "CompiledShaders/HGIShaders.h" +#include "HGIImage.h" +#include "HGIMaterial.h" +#include "HGIRenderBuffer.h" +#include "HGIRenderer.h" + +using namespace pxr; + +BEGIN_AURORA + +HGIGeometry::HGIGeometry( + HGIRenderer* pRenderer, const string& name, const GeometryDescriptor& geomData) : + GeometryBase(name, geomData), _pRenderer(pRenderer) +{ +} + +void HGIGeometry::update() +{ + // Create sequential indices if none provided. + if (_indices.empty()) + { + // Create sequential index data. + _indices.reserve(_vertexCount); + for (uint32_t i = 0; i < _vertexCount; i++) + { + _indices.push_back(i); + } + _indexCount = _vertexCount; + } + + // Create a vertex buffer for geometry. + HgiBufferDesc vertexBufferDesc; + vertexBufferDesc.debugName = _name + "VertexBuffer"; + vertexBufferDesc.usage = HgiBufferUsageStorage | + HgiBufferUsageAccelerationStructureBuildInputReadOnly | HgiBufferUsageRayTracingExtensions | + HgiBufferUsageShaderDeviceAddress; + vertexBufferDesc.initialData = _positions.data(); + vertexBufferDesc.vertexStride = sizeof(float) * 3; + vertexBufferDesc.byteSize = _vertexCount * vertexBufferDesc.vertexStride; + vertexBuffer = HgiBufferHandleWrapper::create( + _pRenderer->hgi()->CreateBuffer(vertexBufferDesc), _pRenderer->hgi()); + + // Create a normal buffer for geometry. + HgiBufferDesc normalBufferDesc; + normalBufferDesc.debugName = _name + "NormalBuffer"; + normalBufferDesc.usage = HgiBufferUsageStorage | + HgiBufferUsageAccelerationStructureBuildInputReadOnly | HgiBufferUsageRayTracingExtensions | + HgiBufferUsageShaderDeviceAddress; + normalBufferDesc.initialData = _normals.data(); + normalBufferDesc.vertexStride = sizeof(float) * 3; + normalBufferDesc.byteSize = _vertexCount * normalBufferDesc.vertexStride; + normalBuffer = HgiBufferHandleWrapper::create( + _pRenderer->hgi()->CreateBuffer(normalBufferDesc), _pRenderer->hgi()); + + // Create a tex coord buffer for geometry. + HgiBufferDesc texCoordBufferDesc; + texCoordBufferDesc.debugName = _name + "TexCoordBuffer"; + texCoordBufferDesc.usage = HgiBufferUsageStorage | + HgiBufferUsageAccelerationStructureBuildInputReadOnly | HgiBufferUsageRayTracingExtensions | + HgiBufferUsageShaderDeviceAddress; + texCoordBufferDesc.initialData = _texCoords.data(); + texCoordBufferDesc.vertexStride = sizeof(float) * 2; + texCoordBufferDesc.byteSize = _vertexCount * texCoordBufferDesc.vertexStride; + texCoordBuffer = HgiBufferHandleWrapper::create( + _pRenderer->hgi()->CreateBuffer(texCoordBufferDesc), _pRenderer->hgi()); + + // Create an index buffer for geometry. + HgiBufferDesc indexBufferDesc; + indexBufferDesc.debugName = _name + "IndexBuffer"; + indexBufferDesc.usage = HgiBufferUsageStorage | + HgiBufferUsageAccelerationStructureBuildInputReadOnly | HgiBufferUsageRayTracingExtensions | + HgiBufferUsageShaderDeviceAddress; + indexBufferDesc.initialData = _indices.data(); + indexBufferDesc.byteSize = _indexCount * sizeof(_indices[0]); + indexBuffer = HgiBufferHandleWrapper::create( + _pRenderer->hgi()->CreateBuffer(indexBufferDesc), _pRenderer->hgi()); + + // Get the device addresses for all the buffers to place in shader record. + bufferAddresses.indexBufferDeviceAddress = indexBuffer->handle()->GetDeviceAddress(); + bufferAddresses.vertexBufferDeviceAddress = vertexBuffer->handle()->GetDeviceAddress(); + bufferAddresses.normalBufferDeviceAddress = normalBuffer->handle()->GetDeviceAddress(); + bufferAddresses.texCoordBufferDeviceAddress = texCoordBuffer->handle()->GetDeviceAddress(); + + // Create an identity transform matrix buffer (this is for the geometry itself, not the + // instance.) + float transformMatrix[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f }; + HgiBufferDesc transformBufferDesc; + transformBufferDesc.debugName = _name + "GeometryTransformBuffer"; + transformBufferDesc.usage = HgiBufferUsageAccelerationStructureBuildInputReadOnly | + HgiBufferUsageRayTracingExtensions | HgiBufferUsageShaderDeviceAddress; + transformBufferDesc.initialData = &transformMatrix; + transformBufferDesc.byteSize = 1 * sizeof(transformMatrix); + transformBuffer = HgiBufferHandleWrapper::create( + _pRenderer->hgi()->CreateBuffer(transformBufferDesc), _pRenderer->hgi()); + + // create the BLAS triangle geometry description for this geometry. + uint32_t triangleCount = _indexCount / 3; + HgiAccelerationStructureTriangleGeometryDesc geometryDesc; + geometryDesc.debugName = "Bottom Level Geometry"; + geometryDesc.indexData = indexBuffer->handle(); + geometryDesc.vertexData = vertexBuffer->handle(); + geometryDesc.vertexStride = vertexBufferDesc.vertexStride; + geometryDesc.maxVertex = _vertexCount; + geometryDesc.transformData = transformBuffer->handle(); + geometryDesc.count = triangleCount; + geom = HgiAccelerationStructureGeometryHandleWrapper::create( + _pRenderer->hgi()->CreateAccelerationStructureGeometry(geometryDesc), _pRenderer->hgi()); + + // create the BLAS description for this geometry. + HgiAccelerationStructureDesc asDesc; + asDesc.debugName = "Bottom Level AS"; + asDesc.geometry.push_back(geom->handle()); + asDesc.type = HgiAccelerationStructureTypeBottomLevel; + accelStructure = HgiAccelerationStructureHandleWrapper::create( + _pRenderer->hgi()->CreateAccelerationStructure(asDesc), _pRenderer->hgi()); + + // Build the BLAS for this geometry. + HgiAccelerationStructureCmdsUniquePtr accelStructCmds = + _pRenderer->hgi()->CreateAccelerationStructureCmds(); + accelStructCmds->PushDebugGroup("Build BLAS cmds"); + accelStructCmds->Build({ accelStructure->handle() }, { { (uint32_t)geometryDesc.count } }); + accelStructCmds->PopDebugGroup(); + + // Buld the BLAS. + // TODO: Should not be done in geometry ctor. + _pRenderer->hgi()->SubmitCmds(accelStructCmds.get(), HgiSubmitWaitTypeWaitUntilCompleted); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIGeometry.h b/Libraries/Aurora/Source/HGI/HGIGeometry.h new file mode 100644 index 0000000..8c5fc9f --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIGeometry.h @@ -0,0 +1,52 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "GeometryBase.h" +#include "HGIHandleWrapper.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +struct HGIGeometryBuffers +{ + uint64_t indexBufferDeviceAddress = 0ull; + uint64_t vertexBufferDeviceAddress = 0ull; + uint64_t normalBufferDeviceAddress = 0ull; + uint64_t texCoordBufferDeviceAddress = 0ull; +}; + +class HGIGeometry : public GeometryBase +{ +public: + HGIGeometry(HGIRenderer* pRenderer, const string& name, const GeometryDescriptor& geomData); + ~HGIGeometry() {} + + void update(); + + HgiBufferHandleWrapper::Pointer vertexBuffer; + HgiBufferHandleWrapper::Pointer normalBuffer; + HgiBufferHandleWrapper::Pointer texCoordBuffer; + HgiBufferHandleWrapper::Pointer indexBuffer; + HgiBufferHandleWrapper::Pointer transformBuffer; + HgiAccelerationStructureGeometryHandleWrapper::Pointer geom; + HgiAccelerationStructureHandleWrapper::Pointer accelStructure; + + HGIGeometryBuffers bufferAddresses; + HGIRenderer* _pRenderer; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIGroundPlane.cpp b/Libraries/Aurora/Source/HGI/HGIGroundPlane.cpp new file mode 100644 index 0000000..2a40225 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIGroundPlane.cpp @@ -0,0 +1,51 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HGIGroundPlane.h" + +#include "HGIImage.h" +#include "HGIRenderer.h" + +BEGIN_AURORA + +// The global property set for ground plane data. +static PropertySetPtr gpPropertySet; + +// Creates (if needed) and get the property set for ground plane data. +static PropertySetPtr propertySet() +{ + // Return the property set, or create it if it doesn't exist. + if (gpPropertySet) + { + return gpPropertySet; + } + gpPropertySet = make_shared(); + + // Add ground plane properties and default values to the property set. + gpPropertySet->add("enabled", true); + gpPropertySet->add("position", vec3(0.0f, 0.0f, 0.0f)); + gpPropertySet->add("normal", vec3(0.0f, 1.0f, 0.0f)); + gpPropertySet->add("shadow_opacity", 1.0f); + gpPropertySet->add("shadow_color", vec3(0.0f, 0.0f, 0.0f)); + gpPropertySet->add("reflection_opacity", 0.5f); + gpPropertySet->add("reflection_color", vec3(0.5f, 0.5f, 0.5f)); + gpPropertySet->add("reflection_roughness", 0.1f); + + return gpPropertySet; +} + +HGIGroundPlane::HGIGroundPlane(HGIRenderer* /* pRenderer */) : FixedValues(propertySet()) {} + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIGroundPlane.h b/Libraries/Aurora/Source/HGI/HGIGroundPlane.h new file mode 100644 index 0000000..bd269b6 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIGroundPlane.h @@ -0,0 +1,36 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +// An internal implementation for IGroundPlane. +class HGIGroundPlane : public IGroundPlane, FixedValues +{ +public: + /*** Lifetime Management ***/ + + HGIGroundPlane(HGIRenderer* pRenderer); + ~HGIGroundPlane() {} + + IValues& values() override { return *this; } +}; +MAKE_AURORA_PTR(HGIGroundPlane); + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIHandleWrapper.cpp b/Libraries/Aurora/Source/HGI/HGIHandleWrapper.cpp new file mode 100644 index 0000000..c72457a --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIHandleWrapper.cpp @@ -0,0 +1,83 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "CompiledShaders/HGIShaders.h" +#include "HGIEnvironment.h" +#include "HGIGroundPlane.h" +#include "HGIImage.h" +#include "HGIMaterial.h" +#include "HGIRenderBuffer.h" +#include "HGIRenderer.h" +#include "HGIScene.h" +#include "HGIWindow.h" + +using namespace pxr; + +BEGIN_AURORA + +// Implementations for all the DestroyFunction types. + +void destroyHgiBuffer(pxr::HgiBufferHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyBuffer(pHdl); +} + +void destroyHgiResourceBindings(pxr::HgiResourceBindingsHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyResourceBindings(pHdl); +} + +void destroyHgiComputePipeline(pxr::HgiComputePipelineHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyComputePipeline(pHdl); +} + +void destroyHgiAccelerationStructureGeometry( + pxr::HgiAccelerationStructureGeometryHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyAccelerationStructureGeometry(pHdl); +} + +void destroyHgiAccelerationStructure(pxr::HgiAccelerationStructureHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyAccelerationStructure(pHdl); +} + +void destroyHgiTexture(pxr::HgiTextureHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyTexture(pHdl); +} + +void destroyHgiSampler(pxr::HgiSamplerHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroySampler(pHdl); +} + +void destroyHgiShaderFunction(pxr::HgiShaderFunctionHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyShaderFunction(pHdl); +} + +void destroyHgiShaderProgram(pxr::HgiShaderProgramHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyShaderProgram(pHdl); +} + +void destroyHgiRayTracingPipeline(pxr::HgiRayTracingPipelineHandle* pHdl, pxr::Hgi* pHgi) +{ + pHgi->DestroyRayTracingPipeline(pHdl); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIHandleWrapper.h b/Libraries/Aurora/Source/HGI/HGIHandleWrapper.h new file mode 100644 index 0000000..ddd6920 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIHandleWrapper.h @@ -0,0 +1,88 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// Wrapper class that handles memory management of HGI handles. +// DestroyFunction is invoked when an instance of this class is destroyed. +template +class HgiHandleWrapper +{ +public: + using Pointer = unique_ptr>; + + HgiHandleWrapper(HandleClass hdl, pxr::Hgi* pHgi) : _hdl(hdl), _pHgi(pHgi) {} + ~HgiHandleWrapper() { clear(); } + + // Create a unique point to this handle. DestroyFunction will be invoked when pointer is + // released. + static Pointer create(HandleClass hdl, pxr::HgiUniquePtr& pHgi) + { + return make_unique>(hdl, pHgi.get()); + } + + // Get HGI handle that is being wrapped. + const HandleClass& handle() const { return _hdl; } + +private: + void clear() + { + if (_pHgi) + DestroyFunction(&_hdl, _pHgi); + + _pHgi = nullptr; + _hdl = HandleClass(); + } + + HandleClass _hdl; + pxr::Hgi* _pHgi; +}; + +class HGIRenderBuffer; + +// Destroy functions for all the HGI handle types. +void destroyHgiBuffer(pxr::HgiBufferHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiResourceBindings(pxr::HgiResourceBindingsHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiComputePipeline(pxr::HgiComputePipelineHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiAccelerationStructureGeometry( + pxr::HgiAccelerationStructureGeometryHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiAccelerationStructure(pxr::HgiAccelerationStructureHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiTexture(pxr::HgiTextureHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiSampler(pxr::HgiSamplerHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiShaderFunction(pxr::HgiShaderFunctionHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiShaderProgram(pxr::HgiShaderProgramHandle* pHdl, pxr::Hgi* pHgi); +void destroyHgiRayTracingPipeline(pxr::HgiRayTracingPipelineHandle* pHdl, pxr::Hgi* pHgi); + +// Alias wrapper classes for all the HGI handle types. +using HgiBufferHandleWrapper = HgiHandleWrapper; +using HgiResourceBindingsHandleWrapper = + HgiHandleWrapper; +using HgiComputePipelineHandleWrapper = + HgiHandleWrapper; +using HgiAccelerationStructureGeometryHandleWrapper = + HgiHandleWrapper; +using HgiAccelerationStructureHandleWrapper = + HgiHandleWrapper; +using HgiTextureHandleWrapper = HgiHandleWrapper; +using HgiSamplerHandleWrapper = HgiHandleWrapper; +using HgiShaderFunctionHandleWrapper = + HgiHandleWrapper; +using HgiShaderProgramHandleWrapper = + HgiHandleWrapper; +using HgiRayTracingPipelineHandleWrapper = + HgiHandleWrapper; + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIImage.cpp b/Libraries/Aurora/Source/HGI/HGIImage.cpp new file mode 100644 index 0000000..a5c298a --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIImage.cpp @@ -0,0 +1,67 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HGIImage.h" +#include "HGIRenderer.h" + +using namespace pxr; + +BEGIN_AURORA + +HGIImage::HGIImage(HGIRenderer* pRenderer, const IImage::InitData& initData) +{ + // Convert Aurora image format to HGI image format. + uint32_t pixelSizeBytes; + HgiFormat hgiFormat = getHGIFormat(initData.format, initData.linearize, &pixelSizeBytes); + + // Create descriptor for image. + HgiTextureDesc imageTexDesc; + imageTexDesc.debugName = initData.name; + imageTexDesc.format = hgiFormat; + imageTexDesc.dimensions = GfVec3i(initData.width, initData.height, 1); + imageTexDesc.layerCount = 1; + imageTexDesc.mipLevels = 1; + imageTexDesc.usage = HgiTextureUsageBitsShaderRead; + imageTexDesc.pixelsByteSize = initData.width * initData.height * pixelSizeBytes; + imageTexDesc.initialData = initData.pImageData; + + // Create the texture. + _texture = HgiTextureHandleWrapper::create( + pRenderer->hgi()->CreateTexture(imageTexDesc), pRenderer->hgi()); +} + +HgiFormat HGIImage::getHGIFormat(ImageFormat format, bool linearize, uint32_t* pPixelByteSizeOut) +{ + // Convert the HGI format to HGI format enum. + switch (format) + { + case ImageFormat::Integer_RGBA: + *pPixelByteSizeOut = 4; + return linearize ? HgiFormat::HgiFormatUNorm8Vec4srgb : HgiFormat::HgiFormatUNorm8Vec4; + case ImageFormat::Float_RGBA: + *pPixelByteSizeOut = 16; + return HgiFormat::HgiFormatFloat32Vec4; + case ImageFormat::Float_RGB: + *pPixelByteSizeOut = 12; + return HgiFormat::HgiFormatFloat32Vec3; + default: + break; + } + + AU_FAIL("Unsupported image format:%x", format); + return (HgiFormat)-1; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIImage.h b/Libraries/Aurora/Source/HGI/HGIImage.h new file mode 100644 index 0000000..ac63630 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIImage.h @@ -0,0 +1,38 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "HGIHandleWrapper.h" +#include "ImageBase.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +// An internal implementation for IImage. +class HGIImage : public ImageBase +{ +public: + HGIImage(HGIRenderer* pRenderer, const IImage::InitData& initData); + ~HGIImage() {}; + const pxr::HgiTextureHandle& texture() { return _texture->handle(); } + pxr::HgiFormat getHGIFormat(ImageFormat format, bool linearize, uint32_t* pPixelByteSizeOut); + +private: + HgiTextureHandleWrapper::Pointer _texture; +}; +MAKE_AURORA_PTR(HGIImage); + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIMaterial.cpp b/Libraries/Aurora/Source/HGI/HGIMaterial.cpp new file mode 100644 index 0000000..18499ba --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIMaterial.cpp @@ -0,0 +1,55 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HGIMaterial.h" +#include "HGIRenderer.h" + +using namespace pxr; + +BEGIN_AURORA + +HGIMaterial::HGIMaterial(HGIRenderer* pRenderer) : _pRenderer(pRenderer) +{ + // Create buffer descriptor, passing material as initial data. + HgiBufferDesc uboDesc; + uboDesc.debugName = "Material UBO"; + uboDesc.usage = HgiBufferUsageUniform | HgiBufferUsageRayTracingExtensions | + HgiBufferUsageShaderDeviceAddress; + uboDesc.byteSize = sizeof(MaterialData); + + // Create UBO. + _ubo = + HgiBufferHandleWrapper::create(_pRenderer->hgi()->CreateBuffer(uboDesc), _pRenderer->hgi()); +} + +void HGIMaterial::update() +{ + // Build a structure from values map into staging buffer. + MaterialData* pStaging = getStagingAddress(_ubo); + updateGPUStruct(*pStaging); + + // Transfer staging buffer to GPU. + pxr::HgiBlitCmdsUniquePtr blitCmds = _pRenderer->hgi()->CreateBlitCmds(); + pxr::HgiBufferCpuToGpuOp blitOp; + blitOp.byteSize = sizeof(MaterialData); + blitOp.cpuSourceBuffer = pStaging; + blitOp.sourceByteOffset = 0; + blitOp.gpuDestinationBuffer = _ubo->handle(); + blitOp.destinationByteOffset = 0; + blitCmds->CopyBufferCpuToGpu(blitOp); + _pRenderer->hgi()->SubmitCmds(blitCmds.get()); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIMaterial.h b/Libraries/Aurora/Source/HGI/HGIMaterial.h new file mode 100644 index 0000000..d490994 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIMaterial.h @@ -0,0 +1,42 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "HGIHandleWrapper.h" +#include "MaterialBase.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +// An internal implementation for IMaterial. +class HGIMaterial : public MaterialBase +{ +public: + HGIMaterial(HGIRenderer* pRenderer); + ~HGIMaterial() {}; + + void update(); + + pxr::HgiBufferHandle ubo() { return _ubo->handle(); } + +private: + HGIRenderer* _pRenderer; + HgiBufferHandleWrapper::Pointer _ubo; +}; + +using HGIMaterialPtr = std::shared_ptr; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIRenderBuffer.cpp b/Libraries/Aurora/Source/HGI/HGIRenderBuffer.cpp new file mode 100644 index 0000000..049b890 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIRenderBuffer.cpp @@ -0,0 +1,78 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pch.h" + +#include "HGIRenderBuffer.h" +#include "pxr/imaging/hgi/blitCmds.h" +#include "pxr/imaging/hgi/blitCmdsOps.h" + +using namespace pxr; + +BEGIN_AURORA + +HGIRenderBuffer::HGIRenderBuffer( + HGIRenderer* pRenderer, uint32_t width, uint32_t height, ImageFormat /*format*/) : + _pRenderer(pRenderer), _width(width), _height(height) +{ + // All render buffers are RGBA8 unorm. + HgiFormat hgiFormat = HgiFormat::HgiFormatUNorm8Vec4; + + // Create descriptor for render buffer storage texture. + HgiTextureDesc rtStorageTexDesc; + rtStorageTexDesc.debugName = "RT Storage Texture"; + rtStorageTexDesc.format = hgiFormat; + rtStorageTexDesc.dimensions = GfVec3i(width, height, 1); + rtStorageTexDesc.layerCount = 1; + rtStorageTexDesc.mipLevels = 1; + rtStorageTexDesc.usage = HgiTextureUsageBitsShaderRead | HgiTextureUsageBitsShaderWrite; + + // Create storage texture itself. + _storageTex = HgiTextureHandleWrapper::create( + _pRenderer->hgi()->CreateTexture(rtStorageTexDesc), _pRenderer->hgi()); +} + +void HGIRenderBuffer::resize(uint32_t /*width*/, uint32_t /*height*/) {} + +const void* HGIRenderBuffer::data(size_t& stride, bool /*removePadding*/) +{ + // Stride is always just width*pixel-size. No row padding. + size_t pixelSizeBytes = 4; + stride = _width * pixelSizeBytes; + + // Ensure CPU buffer is big enough for pixels. + size_t dataByteSize = _width * _height * 4; + _mappedBuffer.resize(dataByteSize); + + // Setup commands to blit storage buffer contents to CPU buffer. + HgiBlitCmdsUniquePtr blitCmds = _pRenderer->hgi()->CreateBlitCmds(); + { + HgiTextureGpuToCpuOp copyOp; + copyOp.gpuSourceTexture = _storageTex->handle(); + copyOp.sourceTexelOffset = GfVec3i(0); + copyOp.mipLevel = 0; + copyOp.cpuDestinationBuffer = _mappedBuffer.data(); + copyOp.destinationByteOffset = 0; + copyOp.destinationBufferByteSize = dataByteSize; + blitCmds->CopyTextureGpuToCpu(copyOp); + } + + // Submit blocking commands. + _pRenderer->hgi()->SubmitCmds(blitCmds.get(), HgiSubmitWaitTypeWaitUntilCompleted); + + // Return pointer to CPU buffer. + return _mappedBuffer.data(); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIRenderBuffer.h b/Libraries/Aurora/Source/HGI/HGIRenderBuffer.h new file mode 100644 index 0000000..9e495f2 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIRenderBuffer.h @@ -0,0 +1,53 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include "HGIRenderer.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +// An internal implementation for IWindow. +class HGIRenderBuffer : public IRenderBuffer +{ +public: + // Constructor and destructor. + HGIRenderBuffer(HGIRenderer* pRenderer, uint32_t width, uint32_t height, ImageFormat format); + ~HGIRenderBuffer() {} + + // IRenderBuffer function implementations. + const void* data(size_t& stride, bool removePadding) override; + + // ITarget function implementations. + void resize(uint32_t width, uint32_t height) override; + + IBufferPtr asReadable(size_t& /*stride*/) override { return nullptr; } + + IBufferPtr asShared() override { return nullptr; } + + pxr::HgiTextureHandle storageTex() { return _storageTex->handle(); } + uint32_t width() { return _width; } + uint32_t height() { return _height; } + +private: + HgiTextureHandleWrapper::Pointer _storageTex; + HGIRenderer* _pRenderer; + uint32_t _width; + uint32_t _height; + std::vector _mappedBuffer; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIRenderer.cpp b/Libraries/Aurora/Source/HGI/HGIRenderer.cpp new file mode 100644 index 0000000..52666c1 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIRenderer.cpp @@ -0,0 +1,472 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "CompiledShaders/HGIShaders.h" +#include "HGIEnvironment.h" +#include "HGIGroundPlane.h" +#include "HGIImage.h" +#include "HGIMaterial.h" +#include "HGIRenderBuffer.h" +#include "HGIRenderer.h" +#include "HGIScene.h" +#include "HGIWindow.h" + +using namespace pxr; + +BEGIN_AURORA + +HGIRenderer::HGIRenderer(uint32_t activeFrameCount) : RendererBase(activeFrameCount) +{ + + _isValid = true; + + // Create the HGI instance. + _hgi = pxr::Hgi::CreatePlatformDefaultHgi(HgiDeviceCapabilitiesBitsRayTracing); + AU_ASSERT(hgi(), "Failed to create Hgi instance."); + + // Vulkan texture coordinates are upside down compared to DX. + _pAssetMgr->enableVerticalFlipOnImageLoad(!_values.asBoolean(kLabelIsFlipImageYEnabled)); +} +HGIRenderer::~HGIRenderer() +{ + // Ensure scene deleted before resources are destroyed. + _pScene.reset(); + + // Destroy all the GPU resources. + _frameDataUbo.reset(); + _sampleDataUbo.reset(); + _postProcessingUbo.reset(); + _accumulationComputeResourceBindings.reset(); + _accumulationComputePipeline.reset(); + _postProcessComputeResourceBindings.reset(); + _postProcessComputePipeline.reset(); + _accumulationComputeResourceBindings.reset(); + _accumComputeShader.reset(); + _accumComputeShaderProgram.reset(); + _postProcessComputeShader.reset(); + _postProcessComputeShaderProgram.reset(); + _pDirectTex.reset(); + _pAccumulationTex.reset(); + _sampler.reset(); + + // Destroy the HGI device. + _hgi.reset(); +} + +void HGIRenderer::createResources() +{ + HGIScenePtr pHGIScene = hgiScene(); + + // Create a default sampler, used by all textures. + HgiSamplerDesc samplerDesc; + _sampler = HgiSamplerHandleWrapper::create(hgi()->CreateSampler(samplerDesc), hgi()); + + // Create UBO buffer description for frame data. + // TODO: Allow modification of frame data. + HgiBufferDesc rtUboDesc; + rtUboDesc.debugName = "Raytracing frame data UBO"; + rtUboDesc.usage = HgiBufferUsageUniform; + rtUboDesc.byteSize = sizeof(FrameData); + + // Create UBO buffer object. + _frameDataUbo = HgiBufferHandleWrapper::create(hgi()->CreateBuffer(rtUboDesc), hgi()); + + // Create UBO buffer description for sample data. + HgiBufferDesc sampleDataUboDesc; + sampleDataUboDesc.debugName = "Raytracing sample data UBO"; + sampleDataUboDesc.usage = HgiBufferUsageUniform; + sampleDataUboDesc.byteSize = sizeof(SampleData); + + // Create UBO buffer object. + _sampleDataUbo = HgiBufferHandleWrapper::create(hgi()->CreateBuffer(sampleDataUboDesc), hgi()); + + // Create UBO buffer description for frame data. + // TODO: Allow modification of post-processing data. + HgiBufferDesc postProcUboDesc; + postProcUboDesc.debugName = "Post processing data UBO"; + postProcUboDesc.usage = HgiBufferUsageUniform; + postProcUboDesc.byteSize = sizeof(PostProcessing); + + // Create post processing UBO buffer object. + _postProcessingUbo = + HgiBufferHandleWrapper::create(hgi()->CreateBuffer(postProcUboDesc), hgi()); + + // All render buffers are RGBA32f + HgiFormat hgiFormat = HgiFormat::HgiFormatFloat32Vec4; + + // Create direct lighting buffer storage texture. + HgiTextureDesc directTexDesc; + directTexDesc.debugName = "Direct Light Result Texture"; + directTexDesc.format = hgiFormat; + directTexDesc.dimensions = GfVec3i(_pRenderBuffer->width(), _pRenderBuffer->height(), 1); + directTexDesc.layerCount = 1; + directTexDesc.mipLevels = 1; + directTexDesc.usage = HgiTextureUsageBitsShaderRead | HgiTextureUsageBitsShaderWrite; + _pDirectTex = HgiTextureHandleWrapper::create(hgi()->CreateTexture(directTexDesc), hgi()); + + // Create accumulation storage texture. + HgiTextureDesc accumTexDesc; + accumTexDesc.debugName = "Accumulation Result Texture"; + accumTexDesc.format = hgiFormat; + accumTexDesc.dimensions = GfVec3i(_pRenderBuffer->width(), _pRenderBuffer->height(), 1); + accumTexDesc.layerCount = 1; + accumTexDesc.mipLevels = 1; + accumTexDesc.usage = HgiTextureUsageBitsShaderRead | HgiTextureUsageBitsShaderWrite; + _pAccumulationTex = HgiTextureHandleWrapper::create(hgi()->CreateTexture(accumTexDesc), hgi()); + + // Description of shader function for post-processing compute shader. + HgiShaderFunctionDesc postProcessComputeDesc; + postProcessComputeDesc.shaderCode = HGIShaders::g_sPostProcessing.c_str(); + postProcessComputeDesc.debugName = "postProcessulationComputeShader"; + postProcessComputeDesc.shaderStage = HgiShaderStageCompute; + postProcessComputeDesc.computeDescriptor.localSize = GfVec3i(8, 8, 1); + + // Use the HGI code generation to add shader inputs to post-processing shader function. + HgiShaderFunctionAddWritableTexture( + &postProcessComputeDesc, "outTexture", 2, HgiFormatFloat32Vec4); + HgiShaderFunctionAddTexture(&postProcessComputeDesc, "accumulationTexture"); + HgiShaderFunctionAddStageInput(&postProcessComputeDesc, "hd_GlobalInvocationID", "uvec3", + HgiShaderKeywordTokens->hdGlobalInvocationID); + + // Create post-processing shader function, abort application if compilation fails. + _postProcessComputeShader = HgiShaderFunctionHandleWrapper::create( + hgi()->CreateShaderFunction(postProcessComputeDesc), hgi()); + if (!_postProcessComputeShader->handle()->IsValid()) + { + std::string logString = _postProcessComputeShader->handle()->GetCompileErrors(); + AU_INFO("%s", postProcessComputeDesc.shaderCode); + AU_FAIL("Compile error in post process compute shader:\n%s", logString.c_str()); + } + + // Create post-processing shader program, abort application if linking fails. + HgiShaderProgramDesc postProcessComputeProgramDesc; + postProcessComputeProgramDesc.shaderFunctions = { _postProcessComputeShader->handle() }; + postProcessComputeProgramDesc.debugName = "PostProcessingComputeShaderProgram"; + _postProcessComputeShaderProgram = HgiShaderProgramHandleWrapper::create( + hgi()->CreateShaderProgram(postProcessComputeProgramDesc), hgi()); + AU_ASSERT(_postProcessComputeShaderProgram->handle()->IsValid(), "Invalid shader program"); + + // Description of shader function for accumulation compute shader. + HgiShaderFunctionDesc accumComputeDesc; + accumComputeDesc.shaderCode = HGIShaders::g_sAccumulation.c_str(); + accumComputeDesc.debugName = "AccumulationComputeShader"; + accumComputeDesc.shaderStage = HgiShaderStageCompute; + accumComputeDesc.computeDescriptor.localSize = GfVec3i(8, 8, 1); + + // Use the HGI code generation to add shader inputs to accumulation shader function. + HgiShaderFunctionAddWritableTexture(&accumComputeDesc, "outTexture", 2, HgiFormatFloat32Vec4); + HgiShaderFunctionAddTexture(&accumComputeDesc, "accumulationTexture"); + HgiShaderFunctionAddTexture(&accumComputeDesc, "directLightTexture"); + HgiShaderFunctionAddStageInput(&accumComputeDesc, "hd_GlobalInvocationID", "uvec3", + HgiShaderKeywordTokens->hdGlobalInvocationID); + + // Create accumulation shader function, abort application if compilation fails. + _accumComputeShader = HgiShaderFunctionHandleWrapper::create( + hgi()->CreateShaderFunction(accumComputeDesc), hgi()); + if (!_accumComputeShader->handle()->IsValid()) + { + std::string logString = _accumComputeShader->handle()->GetCompileErrors(); + AU_INFO("%s", accumComputeDesc.shaderCode); + AU_FAIL("Compile error in accumulation compute shader:\n%s", logString.c_str()); + } + + // Create accumulation shader program, abort applicatoin if linking fails. + HgiShaderProgramDesc accumComputeProgramDesc; + accumComputeProgramDesc.shaderFunctions = { _accumComputeShader->handle() }; + accumComputeProgramDesc.debugName = "AccumulationComputeShaderProgram"; + _accumComputeShaderProgram = HgiShaderProgramHandleWrapper::create( + hgi()->CreateShaderProgram(accumComputeProgramDesc), hgi()); + AU_ASSERT(_accumComputeShaderProgram->handle()->IsValid(), "Invalid shader program"); + + // Create binding description for accumulation texture as input texture. + HgiTextureBindDesc accumInTexBind; + accumInTexBind.bindingIndex = 1; + accumInTexBind.stageUsage = HgiShaderStageCompute; + accumInTexBind.textures.push_back(_pAccumulationTex->handle()); + accumInTexBind.samplers.push_back(sampler()); + accumInTexBind.resourceType = HgiBindResourceTypeCombinedSamplerImage; + + // Create binding description for accumulation texture as output storage texture. + HgiTextureBindDesc accumTexBind; + accumTexBind.bindingIndex = 0; + accumTexBind.stageUsage = HgiShaderStageCompute; + accumTexBind.textures.push_back(_pAccumulationTex->handle()); + accumTexBind.resourceType = HgiBindResourceTypeStorageImage; + + // Create binding description for final render target as storage texture. + HgiTextureBindDesc finalTexBind; + finalTexBind.bindingIndex = 0; + finalTexBind.stageUsage = HgiShaderStageCompute; + finalTexBind.textures = { renderBuffer()->storageTex() }; + finalTexBind.resourceType = HgiBindResourceTypeStorageImage; + + // Create binding description for direct light texture as input texture. + HgiTextureBindDesc directTexBind; + directTexBind.bindingIndex = 2; + directTexBind.stageUsage = HgiShaderStageCompute; + directTexBind.textures.push_back(_pDirectTex->handle()); + directTexBind.samplers.push_back(sampler()); + directTexBind.resourceType = HgiBindResourceTypeCombinedSamplerImage; + + // Create binding description for sample data UBO. + HgiBufferBindDesc bufferDesc0; + bufferDesc0.bindingIndex = 3; + bufferDesc0.buffers = { _sampleDataUbo->handle() }; + bufferDesc0.offsets = { 0 }; + bufferDesc0.resourceType = HgiBindResourceTypeUniformBuffer; + bufferDesc0.stageUsage = HgiShaderStageCompute; + + // Create binding description for post processing UBO. + HgiBufferBindDesc bufferDesc1; + bufferDesc1.bindingIndex = 3; + bufferDesc1.buffers = { _postProcessingUbo->handle() }; + bufferDesc1.offsets = { 0 }; + bufferDesc1.resourceType = HgiBindResourceTypeUniformBuffer; + bufferDesc1.stageUsage = HgiShaderStageCompute; + + // Create resource description for accumulation compute shader. + HgiResourceBindingsDesc resourceDesc; + resourceDesc.debugName = "AccumulationCompueShaderResources"; + resourceDesc.buffers.push_back(std::move(bufferDesc0)); + resourceDesc.textures.push_back(accumTexBind); + resourceDesc.textures.push_back(accumInTexBind); + resourceDesc.textures.push_back(directTexBind); + _accumulationComputeResourceBindings = HgiResourceBindingsHandleWrapper::create( + hgi()->CreateResourceBindings(resourceDesc), hgi()); + + // Create resource descriptoin for post-processing compute shader. + HgiResourceBindingsDesc postProcessResourceDesc; + postProcessResourceDesc.debugName = "PostProcessComputeShaderResources"; + postProcessResourceDesc.buffers.push_back(std::move(bufferDesc1)); + postProcessResourceDesc.textures.push_back(finalTexBind); + postProcessResourceDesc.textures.push_back(accumInTexBind); + _postProcessComputeResourceBindings = HgiResourceBindingsHandleWrapper::create( + hgi()->CreateResourceBindings(postProcessResourceDesc), hgi()); + + // Create compute pipeline for accumulation shader. + HgiComputePipelineDesc pipelineDesc; + pipelineDesc.debugName = "AccumulationComputePipeline"; + pipelineDesc.shaderProgram = _accumComputeShaderProgram->handle(); + _accumulationComputePipeline = + HgiComputePipelineHandleWrapper::create(hgi()->CreateComputePipeline(pipelineDesc), hgi()); + + // Create compute pipeline for post-processing shader. + HgiComputePipelineDesc postProcessPipelineDesc; + postProcessPipelineDesc.debugName = "PostProcessComputePipeline"; + postProcessPipelineDesc.shaderProgram = _postProcessComputeShaderProgram->handle(); + _postProcessComputePipeline = HgiComputePipelineHandleWrapper::create( + hgi()->CreateComputePipeline(postProcessPipelineDesc), hgi()); +} + +IWindowPtr HGIRenderer::createWindow(WindowHandle window, uint32_t width, uint32_t height) +{ + // Create dummy window object. + // TODO: Support windows (or just remove windows from API) + return std::make_shared(this, window, width, height); +} + +IRenderBufferPtr HGIRenderer::createRenderBuffer(int width, int height, ImageFormat imageFormat) +{ + // create a render buffer. + return std::make_shared(this, width, height, imageFormat); +} + +ISamplerPtr HGIRenderer::createSamplerPointer(const Properties& /* props*/) +{ + // Return null. + // TODO: Support sampler. + return nullptr; +} + +IImagePtr HGIRenderer::createImagePointer(const IImage::InitData& initData) +{ + // Create a return a new image object. + return make_shared(this, initData); +} + +IMaterialPtr HGIRenderer::createMaterialPointer( + const std::string& materialType, const std::string& /*document*/, const std::string& name) +{ + if (materialType.compare(Names::MaterialTypes::kBuiltIn) != 0) + { + // Print error and return null material type if material type not found. + // TODO: Proper error handling for this case. + AU_ERROR( + "Unrecognized material type %s for material %s", materialType.c_str(), name.c_str()); + return nullptr; + } + + // Create and return a new material object. + return make_shared(this); +} + +IGeometryPtr HGIRenderer::createGeometryPointer( + const GeometryDescriptor& desc, const std::string& name) +{ + // Create and return a new geometry object. + return make_shared(this, name, desc); +} + +IEnvironmentPtr HGIRenderer::createEnvironmentPointer() +{ + // Create and return a new environment object. + return make_shared(this); +} + +IGroundPlanePtr HGIRenderer::createGroundPlanePointer() +{ + // Create dummy ground plane object. + // TODO: Support ground plane. + return make_shared(this); +} + +void HGIRenderer::render(uint32_t sampleStart, uint32_t sampleCount) +{ + HGIScenePtr pHGIScene = hgiScene(); + + // Create renderer resources if needed (only done once, even if scene changes). + if (!_frameDataUbo) + { + createResources(); + } + + // Update the scene (will update any GPU resources that have changed) + pHGIScene->update(); + + // Must have opaque shadows on HGI currently. + _values.setValue(kLabelIsOpaqueShadowsEnabled, true); + + // Render all the samples. + for (uint32_t i = 0; i < sampleCount; i++) + { + uint32_t sampleIndex = sampleStart + i; + + // Each sample is a separate HGI frame. + hgi()->StartFrame(); + + // Update the frame data UBO using staging buffer, if needed. + if (updateFrameDataGPUStruct(getStagingAddress(_frameDataUbo))) + { + pxr::HgiBlitCmdsUniquePtr blitCmds = hgi()->CreateBlitCmds(); + pxr::HgiBufferCpuToGpuOp blitOp; + blitOp.byteSize = sizeof(FrameData); + blitOp.cpuSourceBuffer = getStagingAddress(_frameDataUbo); + blitOp.sourceByteOffset = 0; + blitOp.gpuDestinationBuffer = _frameDataUbo->handle(); + blitOp.destinationByteOffset = 0; + blitCmds->CopyBufferCpuToGpu(blitOp); + hgi()->SubmitCmds(blitCmds.get()); + } + + // Update the post processing data UBO using staging buffer, if needed. + if (updatePostProcessingGPUStruct(getStagingAddress(_postProcessingUbo))) + { + pxr::HgiBlitCmdsUniquePtr blitCmds = hgi()->CreateBlitCmds(); + pxr::HgiBufferCpuToGpuOp blitOp; + blitOp.byteSize = sizeof(PostProcessing); + blitOp.cpuSourceBuffer = + getStagingAddress(_postProcessingUbo->handle()); + blitOp.sourceByteOffset = 0; + blitOp.gpuDestinationBuffer = _postProcessingUbo->handle(); + blitOp.destinationByteOffset = 0; + blitCmds->CopyBufferCpuToGpu(blitOp); + hgi()->SubmitCmds(blitCmds.get()); + } + + // Update the sample data with the current sample index. + SampleData* pSampleDataStaging = getStagingAddress(_sampleDataUbo); + pSampleDataStaging->sampleIndex = sampleIndex; + pSampleDataStaging->seedOffset = 0; + + // Update the sample data on the GPU for every iteration of loop. + pxr::HgiBlitCmdsUniquePtr blitCmds = hgi()->CreateBlitCmds(); + pxr::HgiBufferCpuToGpuOp blitOp; + blitOp.byteSize = sizeof(SampleData); + blitOp.cpuSourceBuffer = pSampleDataStaging; + blitOp.sourceByteOffset = 0; + blitOp.gpuDestinationBuffer = _sampleDataUbo->handle(); + blitOp.destinationByteOffset = 0; + blitCmds->CopyBufferCpuToGpu(blitOp); + hgi()->SubmitCmds(blitCmds.get()); + + // Create ray tracing commands for frame. + HgiRayTracingCmdsUniquePtr rtCmds = hgi()->CreateRayTracingCmds(); + rtCmds->PushDebugGroup("Frame Render RT commands"); + rtCmds->BindPipeline(pHGIScene->rayTracingPipeline()); + rtCmds->BindResources(pHGIScene->resourceBindings()); + rtCmds->TraceRays(_pRenderBuffer->width(), _pRenderBuffer->height(), 1); + rtCmds->PopDebugGroup(); + + // Submit the ray tracing commands for frame. + hgi()->SubmitCmds(rtCmds.get()); + + // Create accumulation compute commands for frame. + HgiComputeCmdsUniquePtr computeCmds = hgi()->CreateComputeCmds(); + computeCmds->PushDebugGroup("Accumulation Compute Commands"); + computeCmds->BindResources(_accumulationComputeResourceBindings->handle()); + computeCmds->BindPipeline(_accumulationComputePipeline->handle()); + computeCmds->Dispatch(_pRenderBuffer->width(), _pRenderBuffer->height()); + + // Submit the accumulation commands (which will update the accumulation buffer from the + // direct light buffer created by ray tracing) + hgi()->SubmitCmds(computeCmds.get()); + + // If this is the last frame, run the post processing compute shader, to get final image. + if (i == sampleCount - 1) + { + HgiComputeCmdsUniquePtr postProcessComputeCmds = hgi()->CreateComputeCmds(); + postProcessComputeCmds->PushDebugGroup("Post Process Compute Commands"); + postProcessComputeCmds->BindResources(_postProcessComputeResourceBindings->handle()); + postProcessComputeCmds->BindPipeline(_postProcessComputePipeline->handle()); + postProcessComputeCmds->Dispatch(_pRenderBuffer->width(), _pRenderBuffer->height()); + hgi()->SubmitCmds(postProcessComputeCmds.get()); + } + + // End HGI frame rendering. + hgi()->EndFrame(); + } +} + +void HGIRenderer::waitForTask() +{ + // TODO: Non-blocking commands. +} + +IScenePtr HGIRenderer::createScene() +{ + // Return new scene object. + return make_shared(this); +} + +void HGIRenderer::setScene(const IScenePtr& pScene) +{ + // Assign the new scene. + _pScene = dynamic_pointer_cast(pScene); +} + +void HGIRenderer::setTargets(const TargetAssignments& targetAssignments) +{ + // Only use kFinal render target currently. + _pRenderBuffer = (HGIRenderBuffer*)targetAssignments.at(AOV::kFinal).get(); +} + +const std::vector& HGIRenderer::builtInMaterials() +{ + // TODO: No builtins currently. + static const std::vector sBuiltins; + return sBuiltins; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIRenderer.h b/Libraries/Aurora/Source/HGI/HGIRenderer.h new file mode 100644 index 0000000..57f6aaa --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIRenderer.h @@ -0,0 +1,107 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "RendererBase.h" + +#include "HGIHandleWrapper.h" +#include "HGIMaterial.h" +#include "HGIScene.h" + +BEGIN_AURORA + +// Conveinence function to get cast pointer from buffer's staging buffers +template +BufferType* getStagingAddress(pxr::HgiBufferHandle pBuffer) +{ + AU_ASSERT(pBuffer->GetByteSizeOfResource() >= sizeof(BufferType), "Buffer too small"); + return static_cast(pBuffer->GetCPUStagingAddress()); +} + +template +BufferType* getStagingAddress(HgiBufferHandleWrapper::Pointer& pBuffer) +{ + return getStagingAddress(pBuffer->handle()); +} + +// An rasterization (HGI) implementation for IRenderer. +class HGIRenderer : public RendererBase +{ +public: + /*** Lifetime Management ***/ + + HGIRenderer(uint32_t activeFrameCount); + ~HGIRenderer(); + + /*** IRenderer Functions ***/ + + IWindowPtr createWindow(WindowHandle handle, uint32_t width, uint32_t height) override; + IRenderBufferPtr createRenderBuffer(int width, int height, ImageFormat imageFormat) override; + IImagePtr createImagePointer(const IImage::InitData& initData) override; + ISamplerPtr createSamplerPointer(const Properties& props) override; + IMaterialPtr createMaterialPointer(const string& materialType = Names::MaterialTypes::kBuiltIn, + const string& document = "Default", const string& name = "") override; + IScenePtr createScene() override; + IEnvironmentPtr createEnvironmentPointer() override; + IGeometryPtr createGeometryPointer( + const GeometryDescriptor& desc, const std::string& name = "") override; + IGroundPlanePtr createGroundPlanePointer() override; + + // TODO this is actually a bug. We should not hide IValues::type(). + IRenderer::Backend backend() const override { return IRenderer::Backend::HGI; } + + void setScene(const IScenePtr& pScene) override; + void setTargets(const TargetAssignments& targetAssignments) override; + void render(uint32_t sampleStart, uint32_t sampleCount) override; + void waitForTask() override; + void setLoadResourceFunction(LoadResourceFunction) override {} + + const std::vector& builtInMaterials() override; + pxr::HgiUniquePtr& hgi() { return _hgi; } + const pxr::HgiBufferHandle& frameDataUbo() { return _frameDataUbo->handle(); } + const pxr::HgiBufferHandle& sampleDataUbo() { return _sampleDataUbo->handle(); } + + HGIRenderBuffer* renderBuffer() { return _pRenderBuffer; } + + HGIScenePtr hgiScene() { return static_pointer_cast(_pScene); } + + pxr::HgiTextureHandle directTex() { return _pDirectTex->handle(); } + pxr::HgiTextureHandle accumulationTex() { return _pAccumulationTex->handle(); } + + pxr::HgiSamplerHandle sampler() { return _sampler->handle(); } + +private: + void createResources(); + pxr::HgiUniquePtr _hgi; + HGIRenderBuffer* _pRenderBuffer; + HgiBufferHandleWrapper::Pointer _frameDataUbo; + HgiBufferHandleWrapper::Pointer _sampleDataUbo; + HgiBufferHandleWrapper::Pointer _postProcessingUbo; + HgiResourceBindingsHandleWrapper::Pointer _accumulationComputeResourceBindings; + HgiComputePipelineHandleWrapper::Pointer _accumulationComputePipeline; + HgiResourceBindingsHandleWrapper::Pointer _postProcessComputeResourceBindings; + HgiComputePipelineHandleWrapper::Pointer _postProcessComputePipeline; + HgiShaderProgramHandleWrapper::Pointer _accumComputeShaderProgram; + HgiShaderProgramHandleWrapper::Pointer _postProcessComputeShaderProgram; + HgiShaderFunctionHandleWrapper::Pointer _accumComputeShader; + HgiShaderFunctionHandleWrapper::Pointer _postProcessComputeShader; + + HgiSamplerHandleWrapper::Pointer _sampler; + HgiTextureHandleWrapper::Pointer _pDirectTex; + HgiTextureHandleWrapper::Pointer _pAccumulationTex; +}; + +MAKE_AURORA_PTR(HGIRenderer); + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIScene.cpp b/Libraries/Aurora/Source/HGI/HGIScene.cpp new file mode 100644 index 0000000..fb32703 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIScene.cpp @@ -0,0 +1,660 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pch.h" + +#include "HGIScene.h" + +#include "CompiledShaders/CommonShaders.h" +#include "CompiledShaders/HGIShaders.h" +#include "HGIImage.h" +#include "HGIMaterial.h" +#include "HGIRenderBuffer.h" +#include "HGIRenderer.h" +#include "Transpiler.h" + +using namespace pxr; + +BEGIN_AURORA + +#define kInvalidTexureIndex -1 + +HGIInstance::HGIInstance(HGIRenderer* pRenderer, shared_ptr pMaterial, + shared_ptr pGeom, const mat4& transform) : + _pGeometry(pGeom), _pMaterial(pMaterial), _pRenderer(pRenderer) +{ + // Set the transform. + setTransform(transform); +} + +HGIScene::HGIScene(HGIRenderer* pRenderer) : SceneBase(pRenderer), _pRenderer(pRenderer) +{ + // Create the transpiler from the map of shader files. + _transpiler = make_shared(CommonShaders::g_sDirectory); + + createDefaultResources(); +} + +bool HGIScene::update() +{ + // Run the base class update (will update common resources) + SceneBase::update(); + + // Update the environment. + if (_environments.changed()) + { + HGIEnvironmentPtr pEnvironment = + static_pointer_cast(_pEnvironmentResource->resource()); + pEnvironment->update(); + } + + // If any active geometry resources have been modified, flush the vertex buffer pool in case + // there are any pending vertex buffers that are required to update the geometry, and then + // update the geometry (and update BLAS for "complete" geometry that has position data). + if (_geometry.changed()) + { + for (HGIGeometry& geom : _geometry.modified().resources()) + { + geom.update(); + } + } + + // If any active material resources have been modified update them and build a list of unique + // samplers for all the active materials. + if (_materials.changed()) + { + for (HGIMaterial& mtl : _materials.modified().resources()) + { + mtl.update(); + } + } + + // Update the acceleration structure if any geometry or instances have been modified. + if (_instances.changed() || _geometry.changed()) + { + _rayTracingPipeline.reset(); + } + + // If there is no ray tracing pipeliine (as it was never created or has been released) create + // the pipeline and associated resources. + if (!_rayTracingPipeline) + { + rebuildInstanceList(); + rebuildPipeline(); + rebuildAccelerationStructure(); + rebuildResourceBindings(); + + return true; + } + + return false; +} + +void HGIScene::rebuildInstanceList() +{ + // At the start of update loop (called if update required) clear the current instance data list. + _lstInstances.clear(); + _lstImages.clear(); + + // Get default material. + auto defaultMaterial = dynamic_pointer_cast(_pDefaultMaterialResource->resource()); + + // Add instance data for all the instances. + for (HGIInstance& instance : _instances.active().resources()) + { + // Get instacne and material. + auto pMtl = instance.material() ? instance.material() : defaultMaterial; + + // Create a hit group shader record for instance. + InstanceShaderRecord record; + record.geometry = instance.hgiGeometry()->bufferAddresses; + record.material = pMtl->ubo()->GetDeviceAddress(); + + // Update geometry flags. + record.hasNormals = record.geometry.normalBufferDeviceAddress != 0ull; + record.hasTexCoords = record.geometry.texCoordBufferDeviceAddress != 0ull; + + // Get baseColor image from material. + HGIImagePtr pBaseColorImage = nullptr; + if (pMtl->hasValue("base_color_image")) + { + pBaseColorImage = dynamic_pointer_cast(pMtl->asImage("base_color_image")); + } + if (pBaseColorImage) + { + record.baseColorTextureIndex = (int)_lstImages.size(); + _lstImages.push_back(pBaseColorImage); + } + else + record.baseColorTextureIndex = kInvalidTexureIndex; + + // Get specular roughness image from material. + HGIImagePtr pSpecularRoughnessImage = nullptr; + if (pMtl->hasValue("specular_roughness_image")) + { + pSpecularRoughnessImage = + dynamic_pointer_cast(pMtl->asImage("specular_roughness_image")); + } + if (pSpecularRoughnessImage) + { + record.specularRoughnessTextureIndex = (int)_lstImages.size(); + _lstImages.push_back(pSpecularRoughnessImage); + } + else + record.specularRoughnessTextureIndex = kInvalidTexureIndex; + + // Get opacity image from material. + HGIImagePtr pOpacityImage = nullptr; + if (pMtl->hasValue("opacity_image")) + { + pOpacityImage = dynamic_pointer_cast(pMtl->asImage("opacity_image")); + } + if (pOpacityImage) + { + record.opacityTextureIndex = (int)_lstImages.size(); + _lstImages.push_back(pOpacityImage); + } + else + record.opacityTextureIndex = kInvalidTexureIndex; + + // Get normal image from material. + HGIImagePtr pNormalImage = nullptr; + if (pMtl->hasValue("normal_image")) + { + pNormalImage = dynamic_pointer_cast(pMtl->asImage("normal_image")); + } + if (pNormalImage) + { + record.normalTextureIndex = (int)_lstImages.size(); + _lstImages.push_back(pNormalImage); + } + else + record.normalTextureIndex = kInvalidTexureIndex; + + _lstInstances.push_back({ instance, record }); + } +} + +void HGIScene::rebuildAccelerationStructure() +{ + // Create the TLAS decriptor. + HgiAccelerationStructureInstanceGeometryDesc instDesc; + + // Add descriptor for each instance. + for (size_t i = 0; i < _lstInstances.size(); i++) + { + InstanceData instData = _lstInstances[i]; + HGIInstance& instance = instData.instance; + shared_ptr pGeom = instance.hgiGeometry(); + + // Add transform, group index and BLAS (from geometry). + HgiAccelerationStructureInstanceDesc inst; + inst.transform = instance.transform(); + inst.groupIndex = static_cast(i); + inst.blas = pGeom->accelStructure->handle(); + + // Add to the descriptor. + instDesc.instances.push_back(inst); + } + + // Create the TLAS geometry (we place.all the instance in one geometry object.) + _tlasGeom = HgiAccelerationStructureGeometryHandleWrapper::create( + _pRenderer->hgi()->CreateAccelerationStructureGeometry(instDesc), _pRenderer->hgi()); + + // Create TLAS descriptor containing only one peice of TLAS geometry. + HgiAccelerationStructureDesc asDesc; + asDesc.debugName = "Top Level AS"; + asDesc.geometry.push_back(_tlasGeom->handle()); + asDesc.type = HgiAccelerationStructureTypeTopLevel; + _tlas = HgiAccelerationStructureHandleWrapper::create( + _pRenderer->hgi()->CreateAccelerationStructure(asDesc), _pRenderer->hgi()); + + // Create commands to build acceleration structure. + HgiAccelerationStructureCmdsUniquePtr accelStructCmds = + _pRenderer->hgi()->CreateAccelerationStructureCmds(); + accelStructCmds->PushDebugGroup("Build TLAS cmds"); + accelStructCmds->Build({ _tlas->handle() }, { { (uint32_t)instDesc.instances.size() } }); + accelStructCmds->PopDebugGroup(); + + // Run the commands to build acceleration structure. + // TODO: Should not be blocking. + _pRenderer->hgi()->SubmitCmds(accelStructCmds.get(), HgiSubmitWaitTypeWaitUntilCompleted); +} + +// TODO: Implement ground plane. +void HGIScene::setGroundPlanePointer(const IGroundPlanePtr& /*pGroundPlane*/) {} + +void HGIScene::rebuildResourceBindings() +{ + // Get the environment textures. + HGIEnvironmentPtr pEnvironment = + static_pointer_cast(_pEnvironmentResource->resource()); + HGIImagePtr pBackgroundImage = + static_pointer_cast(pEnvironment->values().asImage("background_image")); + HGIImagePtr pLightImage = + static_pointer_cast(pEnvironment->values().asImage("light_image")); + + // Create resource bindings (binding indices must match + // HgiRayTracingPipelineDescriptorSetLayoutDesc created in createPipeline.) + HgiResourceBindingsDesc resourceBindingsDesc; + resourceBindingsDesc.debugName = "Scene Resource Bindings"; + // One acceleration structure resource. + resourceBindingsDesc.accelerationStructures.resize(1); + resourceBindingsDesc.accelerationStructures[0].bindingIndex = 0; + resourceBindingsDesc.accelerationStructures[0].accelerationStructures = { tlas() }; + resourceBindingsDesc.accelerationStructures[0].resourceType = + HgiBindResourceTypeAccelerationStructure; + resourceBindingsDesc.accelerationStructures[0].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit; + + // 5 texture resources: + // - output image + // - array of textures for instances.) + // - Default sampler + // - Background image + // - Environment light image + resourceBindingsDesc.textures.resize(5); + + // Add resource for output image storage texture resource. + resourceBindingsDesc.textures[0].bindingIndex = 1; + resourceBindingsDesc.textures[0].textures = { _pRenderer->directTex() }; + resourceBindingsDesc.textures[0].resourceType = HgiBindResourceTypeStorageImage; + resourceBindingsDesc.textures[0].stageUsage = HgiShaderStageRayGen; + + // Add resources for array of textures and samplers for instances. + resourceBindingsDesc.textures[1].bindingIndex = 3; + if (_lstImages.empty()) + { + // Add default texture+sampler to bindings, to avoid empty texture array. + resourceBindingsDesc.textures[1].textures.push_back(_pDefaultImage->handle()); + resourceBindingsDesc.textures[1].samplers.push_back(_pRenderer->sampler()); + } + else + { + // Add textures+samplers to pipeline, to avoid empty texture array. + for (size_t i = 0; i < _lstImages.size(); i++) + { + resourceBindingsDesc.textures[1].textures.push_back(_lstImages[i]->texture()); + resourceBindingsDesc.textures[1].samplers.push_back(_pRenderer->sampler()); + } + } + resourceBindingsDesc.textures[1].resourceType = HgiBindResourceTypeCombinedSamplerImage; + resourceBindingsDesc.textures[1].stageUsage = HgiShaderStageClosestHit; + + // Add resource for default sampler resource. + resourceBindingsDesc.textures[2].bindingIndex = 6; + resourceBindingsDesc.textures[2].samplers = { _pRenderer->sampler() }; + resourceBindingsDesc.textures[2].resourceType = HgiBindResourceTypeSampler; + resourceBindingsDesc.textures[2].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + ; + // Add resource for background image. + resourceBindingsDesc.textures[3].bindingIndex = 7; + resourceBindingsDesc.textures[3].textures = { pBackgroundImage ? pBackgroundImage->texture() + : _pDefaultImage->handle() }; + resourceBindingsDesc.textures[3].resourceType = HgiBindResourceTypeSampledImage; + resourceBindingsDesc.textures[3].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + ; + // Add resource for light image. + resourceBindingsDesc.textures[4].bindingIndex = 8; + resourceBindingsDesc.textures[4].textures = { pLightImage ? pLightImage->texture() + : _pDefaultImage->handle() }; + resourceBindingsDesc.textures[4].resourceType = HgiBindResourceTypeSampledImage; + resourceBindingsDesc.textures[4].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + ; + + // 3 buffer resources for: + // - frame data UBO + // - sample data UBO + // - environment data UBO + resourceBindingsDesc.buffers.resize(3); + resourceBindingsDesc.buffers[0].bindingIndex = 2; + resourceBindingsDesc.buffers[0].buffers = { _pRenderer->frameDataUbo() }; + resourceBindingsDesc.buffers[0].offsets = { 0 }; + resourceBindingsDesc.buffers[0].resourceType = HgiBindResourceTypeUniformBuffer; + resourceBindingsDesc.buffers[0].stageUsage = HgiShaderStageRayGen | HgiShaderStageClosestHit; + resourceBindingsDesc.buffers[1].bindingIndex = 4; + resourceBindingsDesc.buffers[1].buffers = { _pRenderer->sampleDataUbo() }; + resourceBindingsDesc.buffers[1].offsets = { 0 }; + resourceBindingsDesc.buffers[1].resourceType = HgiBindResourceTypeUniformBuffer; + resourceBindingsDesc.buffers[1].stageUsage = HgiShaderStageRayGen | HgiShaderStageClosestHit; + resourceBindingsDesc.buffers[2].bindingIndex = 5; + resourceBindingsDesc.buffers[2].buffers = { pEnvironment->ubo() }; + resourceBindingsDesc.buffers[2].offsets = { 0 }; + resourceBindingsDesc.buffers[2].resourceType = HgiBindResourceTypeUniformBuffer; + resourceBindingsDesc.buffers[2].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + + // Create the resource bindings. + auto& hgi = _pRenderer->hgi(); + _resBindings = HgiResourceBindingsHandleWrapper::create( + hgi->CreateResourceBindings(resourceBindingsDesc), hgi); +} + +void HGIScene::createResources() +{ + // Get the HGI instance. + auto& hgi = _pRenderer->hgi(); + + // Create default 16x16 texture + const int defaultTexDims[] = { 16, 16 }; + unsigned int defaultTexData[16 * 16 * 4]; + memset(defaultTexData, 0xff, sizeof(defaultTexData)); + HgiTextureDesc defaultTexDesc; + defaultTexDesc.debugName = "Default Texture"; + defaultTexDesc.format = HgiFormat::HgiFormatUNorm8Vec4; + defaultTexDesc.dimensions = GfVec3i(16, 16, 1); + defaultTexDesc.layerCount = 1; + defaultTexDesc.mipLevels = 1; + defaultTexDesc.initialData = defaultTexData; + defaultTexDesc.pixelsByteSize = defaultTexDims[0] * defaultTexDims[1] * sizeof(uint32_t); + defaultTexDesc.usage = HgiTextureUsageBitsShaderRead; + _pDefaultImage = HgiTextureHandleWrapper::create(hgi->CreateTexture(defaultTexDesc), hgi); + + string transpiledGLSL; + string transpilerErrors; + + // Transpile the ray generation shader. + if (!_transpiler->transpile( + "RayGenShader.slang", transpiledGLSL, transpilerErrors, Transpiler::Language::GLSL)) + { + AU_ERROR("Slang transpiling error on RayGenShader.slang:\n%s", transpilerErrors.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Slang transpiling failed, see log in console for details."); + } + + // Create the ray generation shader description including transpiled GLSL source. + string rayGenShaderCode = transpiledGLSL; + HgiShaderFunctionDesc raygenShaderDesc; + raygenShaderDesc.debugName = "RayGenShader"; + raygenShaderDesc.shaderStage = HgiShaderStageRayGen; + raygenShaderDesc.shaderCode = rayGenShaderCode.c_str(); + + // Compile the shader itself. fail and print errors if the compilation doesn't succeed. + _rayGenShaderFunc = + HgiShaderFunctionHandleWrapper::create(hgi->CreateShaderFunction(raygenShaderDesc), hgi); + if (!_rayGenShaderFunc->handle()->IsValid()) + { + std::string logString = _rayGenShaderFunc->handle()->GetCompileErrors(); + AU_ERROR("Error creating shader function for RayGenShader.slang:\n%s", logString.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Shader function creation failed, see log in console for details."); + } + + // Transpile the radiance miss shader. + if (!_transpiler->transpile("BackgroundMissShader.slang", transpiledGLSL, transpilerErrors, + Transpiler::Language::GLSL)) + { + AU_ERROR( + "Slang transpiling error on BackgroundMissShader.slang:\n%s", transpilerErrors.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Slang transpiling failed, see log in console for details."); + } + + // Create the radiance miss shader description including transpiled GLSL source. + string backgroundMissShaderCode = transpiledGLSL; + HgiShaderFunctionDesc backgroundMissShaderDesc; + backgroundMissShaderDesc.debugName = "BackgroundMissShader"; + backgroundMissShaderDesc.shaderStage = HgiShaderStageMiss; + backgroundMissShaderDesc.shaderCode = backgroundMissShaderCode.c_str(); + + // Compile the shader itself. fail and print errors if the compilation doesn't succeed. + _backgroundMissShaderFunc = HgiShaderFunctionHandleWrapper::create( + hgi->CreateShaderFunction(backgroundMissShaderDesc), hgi); + if (!_backgroundMissShaderFunc->handle()->IsValid()) + { + std::string logString = _backgroundMissShaderFunc->handle()->GetCompileErrors(); + AU_ERROR("Error creating shader function for BackgroundMissShader.slang:\n%s", + logString.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Shader function creation failed, see log in console for details."); + } + + // Transpile the radiance miss shader. + if (!_transpiler->transpile("RadianceMissShader.slang", transpiledGLSL, transpilerErrors, + Transpiler::Language::GLSL)) + { + AU_ERROR( + "Slang transpiling error on RadianceMissShader.slang:\n%s", transpilerErrors.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Slang transpiling failed, see log in console for details."); + } + + // Create the radiance miss shader description including transpiled GLSL source. + string radianceMissShaderCode = transpiledGLSL; + HgiShaderFunctionDesc radianceMissShaderDesc; + radianceMissShaderDesc.debugName = "RadianceMissShader"; + radianceMissShaderDesc.shaderStage = HgiShaderStageMiss; + radianceMissShaderDesc.shaderCode = radianceMissShaderCode.c_str(); + + // Compile the shader itself. fail and print errors if the compilation doesn't succeed. + _radianceMissShaderFunc = HgiShaderFunctionHandleWrapper::create( + hgi->CreateShaderFunction(radianceMissShaderDesc), hgi); + if (!_radianceMissShaderFunc->handle()->IsValid()) + { + std::string logString = _radianceMissShaderFunc->handle()->GetCompileErrors(); + AU_ERROR( + "Error creating shader function for RadianceMissShader.slang:\n%s", logString.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Shader function creation failed, see log in console for details."); + } + + // Transpile the shadow miss shader. + if (!_transpiler->transpile( + "ShadowMissShader.slang", transpiledGLSL, transpilerErrors, Transpiler::Language::GLSL)) + { + AU_ERROR( + "Slang transpiling error on ShadowMissShader.slang:\n%s", transpilerErrors.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Slang transpiling failed, see log in console for details."); + } + + // Create the shadow miss shader description including GLSL source. + string shadowMissShaderCode = transpiledGLSL; + HgiShaderFunctionDesc shadowMissShaderDesc; + shadowMissShaderDesc.debugName = "ShadowMissShader"; + shadowMissShaderDesc.shaderStage = HgiShaderStageMiss; + shadowMissShaderDesc.shaderCode = shadowMissShaderCode.c_str(); + + // Compile the shader itself. fail and print errors if the compilation doesn't succeed. + _shadowMissShaderFunc = HgiShaderFunctionHandleWrapper::create( + hgi->CreateShaderFunction(shadowMissShaderDesc), hgi); + if (!_shadowMissShaderFunc->handle()->IsValid()) + { + std::string logString = _shadowMissShaderFunc->handle()->GetCompileErrors(); + AU_ERROR( + "Error creating shader function for ShadowMissShader.slang:\n%s", logString.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Shader function creation failed, see log in console for details."); + } + + // Create the closest hit shader from template text. + string closestHitEntryPointSource = regex_replace( + CommonShaders::g_sClosestHitEntryPointTemplate, regex("@MATERIAL_TYPE@"), "Default"); + _transpiler->setSource( + "InitializeMaterial.slang", CommonShaders::g_sInitializeDefaultMaterialType); + + // Transpaile the closest hit shader. + if (!_transpiler->transpileCode(closestHitEntryPointSource, transpiledGLSL, transpilerErrors, + Transpiler::Language::GLSL)) + { + AU_ERROR("Slang transpiling error on closest hit shdaer:\n%s", transpilerErrors.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Slang transpiling failed, see log in console for details."); + } + + // Create the closest hit shader description, appending the the raw instance data GLSL code. + string closestHitShaderCode = transpiledGLSL + HGIShaders::g_sInstanceData; + HgiShaderFunctionDesc closestHitShaderDesc; + closestHitShaderDesc.debugName = "ClosestHitShader"; + closestHitShaderDesc.shaderStage = HgiShaderStageClosestHit; + closestHitShaderDesc.shaderCode = closestHitShaderCode.c_str(); + + // Compile the shader itself. fail and print errors if the compilation doesn't succeed. + _closestHitShaderFunc = HgiShaderFunctionHandleWrapper::create( + hgi->CreateShaderFunction(closestHitShaderDesc), hgi); + if (!_closestHitShaderFunc->handle()->IsValid()) + { + std::string logString = _closestHitShaderFunc->handle()->GetCompileErrors(); + AU_ERROR("Error creating shader function for closest hit shader:\n%s", logString.c_str()); + AU_DEBUG_BREAK(); + AU_FAIL("Shader function creation failed, see log in console for details."); + } +} + +void HGIScene::rebuildPipeline() +{ + // Get the HGI instance. + auto& hgi = _pRenderer->hgi(); + + // Create the global resources if required. + if (!_pDefaultImage) + { + createResources(); + } + + // Create the descriptor set layour description for all the global resources. + // 9 global resources: + // - acceleration structure + // - output direct light image + // - frame data UBO + // - texture + sampler array + // - sample data UBO + // - default sampler + // - background image + // - light image + HgiRayTracingPipelineDescriptorSetLayoutDesc layoutBinding; + layoutBinding.resourceBinding.resize(9); + // Description of acceleration structure. + layoutBinding.resourceBinding[0].bindingIndex = 0; + layoutBinding.resourceBinding[0].count = 1; + layoutBinding.resourceBinding[0].resourceType = HgiBindResourceTypeAccelerationStructure; + layoutBinding.resourceBinding[0].stageUsage = HgiShaderStageRayGen | HgiShaderStageClosestHit; + // Description of output durect light image. + layoutBinding.resourceBinding[1].bindingIndex = 1; + layoutBinding.resourceBinding[1].count = 1; + layoutBinding.resourceBinding[1].resourceType = HgiBindResourceTypeStorageImage; + layoutBinding.resourceBinding[1].stageUsage = HgiShaderStageRayGen; + // Description of frame data UBO + layoutBinding.resourceBinding[2].bindingIndex = 2; + layoutBinding.resourceBinding[2].count = 1; + layoutBinding.resourceBinding[2].resourceType = HgiBindResourceTypeUniformBuffer; + layoutBinding.resourceBinding[2].stageUsage = HgiShaderStageRayGen | HgiShaderStageClosestHit; + // Description of array of textures and samplers (shared by all instances) + // Minimum size one. + layoutBinding.resourceBinding[3].bindingIndex = 3; + layoutBinding.resourceBinding[3].count = _lstImages.empty() ? 1 : (uint32_t)_lstImages.size(); + layoutBinding.resourceBinding[3].resourceType = HgiBindResourceTypeCombinedSamplerImage; + layoutBinding.resourceBinding[3].stageUsage = HgiShaderStageClosestHit; + // Description of sample data UBO + layoutBinding.resourceBinding[4].bindingIndex = 4; + layoutBinding.resourceBinding[4].count = 1; + layoutBinding.resourceBinding[4].resourceType = HgiBindResourceTypeUniformBuffer; + layoutBinding.resourceBinding[4].stageUsage = HgiShaderStageRayGen | HgiShaderStageClosestHit; + // Description of environment UBO + layoutBinding.resourceBinding[5].bindingIndex = 5; + layoutBinding.resourceBinding[5].count = 1; + layoutBinding.resourceBinding[5].resourceType = HgiBindResourceTypeUniformBuffer; + layoutBinding.resourceBinding[5].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + // Description of default sampler + layoutBinding.resourceBinding[6].bindingIndex = 6; + layoutBinding.resourceBinding[6].count = 1; + layoutBinding.resourceBinding[6].resourceType = HgiBindResourceTypeSampler; + layoutBinding.resourceBinding[6].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + // Description of background image + layoutBinding.resourceBinding[7].bindingIndex = 7; + layoutBinding.resourceBinding[7].count = 1; + layoutBinding.resourceBinding[7].resourceType = HgiBindResourceTypeSampledImage; + layoutBinding.resourceBinding[7].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + // Description of light image + layoutBinding.resourceBinding[8].bindingIndex = 8; + layoutBinding.resourceBinding[8].count = 1; + layoutBinding.resourceBinding[8].resourceType = HgiBindResourceTypeSampledImage; + layoutBinding.resourceBinding[8].stageUsage = + HgiShaderStageRayGen | HgiShaderStageClosestHit | HgiShaderStageMiss; + + // Create pipeline description. + HgiRayTracingPipelineDesc pipelineDesc; + pipelineDesc.debugName = "Main Raytracing Pipeline"; + pipelineDesc.maxRayRecursionDepth = 10; + pipelineDesc.descriptorSetLayouts.push_back(layoutBinding); + // 5 shaders (ray gen, background miss, radiance miss, shadow miss, and closest hit) + pipelineDesc.shaders.resize(5); + pipelineDesc.shaders[0].shader = _rayGenShaderFunc->handle(); + pipelineDesc.shaders[0].entryPoint = "main"; + pipelineDesc.shaders[1].shader = _backgroundMissShaderFunc->handle(); + pipelineDesc.shaders[1].entryPoint = "main"; + pipelineDesc.shaders[2].shader = _radianceMissShaderFunc->handle(); + pipelineDesc.shaders[2].entryPoint = "main"; + pipelineDesc.shaders[3].shader = _shadowMissShaderFunc->handle(); + pipelineDesc.shaders[3].entryPoint = "main"; + pipelineDesc.shaders[4].shader = _closestHitShaderFunc->handle(); + pipelineDesc.shaders[4].entryPoint = "main"; + + // Three general shader groups (for miss and ray gen) and one triangle shader group for each + // instance. + pipelineDesc.groups.resize(4 + _lstInstances.size()); + // Ray gen shader group. + pipelineDesc.groups[0].type = HgiRayTracingShaderGroupTypeGeneral; + pipelineDesc.groups[0].generalShader = 0; // Index within shader array above. + // Background miss shader group. + pipelineDesc.groups[1].type = HgiRayTracingShaderGroupTypeGeneral; + pipelineDesc.groups[1].generalShader = 1; // Index within shader array above. + // Radiance miss shader group. + pipelineDesc.groups[2].type = HgiRayTracingShaderGroupTypeGeneral; + pipelineDesc.groups[2].generalShader = 2; // Index within shader array above. + // Shadow miss shader group. + pipelineDesc.groups[3].type = HgiRayTracingShaderGroupTypeGeneral; + pipelineDesc.groups[3].generalShader = 3; // Index within shader array above. + // Triangle shader groups for each instance. + for (size_t i = 0; i < _lstInstances.size(); i++) + { + size_t shaderRecordStride = sizeof(_lstInstances[i].shaderRecord); + // Triangle miss shader group with closest hit shader. + pipelineDesc.groups[4 + i].type = HgiRayTracingShaderGroupTypeTriangles; + pipelineDesc.groups[4 + i].closestHitShader = 4; // Index within shader array above. + // Add the hit group record structure to the shader record (this will copied after the + // shader handle in the shader binding table.) + pipelineDesc.groups[4 + i].pShaderRecord = &_lstInstances[i].shaderRecord; + pipelineDesc.groups[4 + i].shaderRecordLength = shaderRecordStride; + } + + // Create the pipeline. + _rayTracingPipeline = HgiRayTracingPipelineHandleWrapper::create( + hgi->CreateRayTracingPipeline(pipelineDesc), hgi); +} + +IInstancePtr HGIScene::addInstancePointer(const Path& /* path*/, const IGeometryPtr& pGeom, + const IMaterialPtr& pMaterial, const mat4& transform, + const LayerDefinitions& /* materialLayers*/) +{ + // Cast the (optional) material to the device implementation. Use the default material if one is + // not specified. + HGIMaterialPtr pHGIMaterial = pMaterial + ? dynamic_pointer_cast(pMaterial) + : dynamic_pointer_cast(_pDefaultMaterialResource->resource()); + + // Create the instance object and add it to the list of instances for the scene. + HGIInstancePtr pHGIInstance = make_shared( + _pRenderer, pHGIMaterial, dynamic_pointer_cast(pGeom), transform); + + return pHGIInstance; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIScene.h b/Libraries/Aurora/Source/HGI/HGIScene.h new file mode 100644 index 0000000..0f81e2d --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIScene.h @@ -0,0 +1,138 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "HGIEnvironment.h" +#include "HGIGeometry.h" +#include "HGIImage.h" +#include "HGIMaterial.h" +#include "SceneBase.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; +struct Consolidator; +class Transpiler; + +// An internal implementation for IInstance. +class HGIInstance : public IInstance +{ +public: + // Constructor. + HGIInstance(HGIRenderer* pRenderer, shared_ptr pMaterial, + shared_ptr pGeom, const mat4& transform); + + void setMaterial(const IMaterialPtr& pMaterial) override + { + _pMaterial = dynamic_pointer_cast(pMaterial); + } + void setTransform(const mat4& transform) override + { + const pxr::GfMatrix4f* pGFMtx = reinterpret_cast(&transform); + _transform = pGFMtx->GetTranspose(); + } + void setObjectIdentifier(int /*objectId*/) override {} + + void setVisible(bool /*val*/) override {} + + IGeometryPtr geometry() const override { return dynamic_pointer_cast(_pGeometry); } + + const pxr::GfMatrix4f transform() { return _transform; } + shared_ptr hgiGeometry() { return _pGeometry; } + shared_ptr material() { return _pMaterial; } + +private: + shared_ptr _pGeometry; + shared_ptr _pMaterial; + [[maybe_unused]] HGIRenderer* _pRenderer; + pxr::GfMatrix4f _transform; +}; + +using HGIInstancePtr = std::shared_ptr; + +// Shader record for hit shaders. +struct InstanceShaderRecord +{ + // Geometry data. + HGIGeometryBuffers geometry; + + // Index into material array. + uint64_t material; + + // Index into texture sampler array for material's textures. + int baseColorTextureIndex = -1; + + int specularRoughnessTextureIndex = -1; + int normalTextureIndex = -1; + int opacityTextureIndex = -1; + + // Geometry flags. + unsigned int hasNormals = true; + unsigned int hasTexCoords = true; +}; + +// Per-instance data. +struct InstanceData +{ + HGIInstance& instance; + InstanceShaderRecord shaderRecord; +}; + +// An internal implementation for IScene. +class HGIScene : public SceneBase +{ +public: + // Constructor and destructor. + HGIScene(HGIRenderer* pRenderer); + + /*** IScene Functions ***/ + void setGroundPlanePointer(const IGroundPlanePtr& pGroundPlane) override; + + pxr::HgiRayTracingPipelineHandle rayTracingPipeline() { return _rayTracingPipeline->handle(); } + + bool update(); + void rebuildInstanceList(); + void createResources(); + void rebuildPipeline(); + void rebuildAccelerationStructure(); + void rebuildResourceBindings(); + + pxr::HgiAccelerationStructureHandle tlas() { return _tlas->handle(); } + + pxr::HgiResourceBindingsHandle resourceBindings() { return _resBindings->handle(); } + + IInstancePtr addInstancePointer(const Path& /* path*/, const IGeometryPtr& pGeom, + const IMaterialPtr& pMaterial, const mat4& transform, + const LayerDefinitions& materialLayers) override; + +private: + HGIRenderer* _pRenderer = nullptr; + shared_ptr _transpiler; + HgiRayTracingPipelineHandleWrapper::Pointer _rayTracingPipeline; + HgiAccelerationStructureHandleWrapper::Pointer _tlas; + HgiAccelerationStructureGeometryHandleWrapper::Pointer _tlasGeom; + vector _lstInstances; + vector> _lstImages; + HgiResourceBindingsHandleWrapper::Pointer _resBindings; + HgiTextureHandleWrapper::Pointer _pDefaultImage; + HgiShaderFunctionHandleWrapper::Pointer _backgroundMissShaderFunc; + HgiShaderFunctionHandleWrapper::Pointer _rayGenShaderFunc; + HgiShaderFunctionHandleWrapper::Pointer _shadowMissShaderFunc; + HgiShaderFunctionHandleWrapper::Pointer _radianceMissShaderFunc; + HgiShaderFunctionHandleWrapper::Pointer _closestHitShaderFunc; +}; +using HGIScenePtr = std::shared_ptr; + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIWindow.cpp b/Libraries/Aurora/Source/HGI/HGIWindow.cpp new file mode 100644 index 0000000..75c9e6b --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIWindow.cpp @@ -0,0 +1,27 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HGIWindow.h" + +BEGIN_AURORA + +HGIWindow::HGIWindow( + HGIRenderer* /*pRenderer*/, WindowHandle /*window*/, uint32_t /*width*/, uint32_t /*height*/) +{ +} + +void HGIWindow::resize(uint32_t /*width*/, uint32_t /*height*/) {} + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/HGIWindow.h b/Libraries/Aurora/Source/HGI/HGIWindow.h new file mode 100644 index 0000000..04af52d --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGIWindow.h @@ -0,0 +1,34 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// Forward declarations. +class HGIRenderer; + +// An internal implementation for IWindow. +class HGIWindow : public IWindow +{ +public: + // Constructor and destructor. + HGIWindow(HGIRenderer* pRenderer, WindowHandle window, uint32_t width, uint32_t height); + ~HGIWindow() {} + + void resize(uint32_t width, uint32_t height) override; + + void setVSyncEnabled(bool /*enabled*/) override {} +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/Shaders/Accumulation.glsl b/Libraries/Aurora/Source/HGI/Shaders/Accumulation.glsl new file mode 100644 index 0000000..d31d77e --- /dev/null +++ b/Libraries/Aurora/Source/HGI/Shaders/Accumulation.glsl @@ -0,0 +1,39 @@ +// Sample data UBO. +// NOTE should be passed as push constants, but this is currently broken in HGI. +layout(binding = 3) layout(std140) uniform SampleData +{ + int sampleIndex; + int seedOffset; +} gSampleData; + +// Get texture UV from input coordinate. +vec2 GetTexCoords(ivec2 outCoords) +{ + vec2 outDims = vec2(HgiGetSize_outTexture()); + // apply a (0.5, 0.5) offset to use pixel centers and not pixel corners + vec2 texCoords = (vec2(outCoords) + vec2(0.5, 0.5)) / outDims; + return texCoords; +} + +void main(void) +{ + // Sample input direct light texture. + ivec2 outCoords = ivec2(hd_GlobalInvocationID.xy); + vec2 texCoords = GetTexCoords(outCoords); + vec4 result = HgiTextureLod_directLightTexture(texCoords, 0.0); + vec3 direct = result.rgb; + + // For all samples except the first, combine direct light texture with previous frame. + if (gSampleData.sampleIndex > 0) + { + // Get result for previous frame. + vec4 prevResult = HgiTextureLod_accumulationTexture(texCoords, 0.0); + + // Blend between the previous result based on sample index. + float t = 1.0 / (gSampleData.sampleIndex + 1); + result = mix(prevResult, result, t); + } + + // Write result back to accumulation. + HgiSet_outTexture(outCoords, result); +} diff --git a/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl b/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl new file mode 100644 index 0000000..19b7b0c --- /dev/null +++ b/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl @@ -0,0 +1,102 @@ + +// Extensions required (should be in prefix) +#extension GL_EXT_ray_tracing : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require +#extension GL_EXT_buffer_reference : require + +// Buffer types for vertex data. +layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Indices { uint i[]; }; +layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Positions { vec3 v[]; }; +layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Normals { vec3 n[]; }; +layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer TexCoords { vec2 t[]; }; + +// Buffer type for material. +layout(buffer_reference, std430, scalar) buffer Materials { MaterialConstants_0 m[]; }; + +// Array of textures and samplers for all instances. +layout(binding = 3) uniform sampler2D textureSamplers[]; + +// Shader record for hit shaders. Must match HitGroupShaderRecord struct in HGIScene.h. +layout(shaderRecordEXT, std430) buffer InstanceShaderRecord +{ + // Geometry data. + Indices indices; + Positions positions; + Normals normals; + TexCoords texcoords; + + // Material data. + Materials material; + + // Index into texture sampler array for material's textures. + int baseColorTextureIndex; + int specularRoughnessTextureIndex; + int normalTextureIndex; + int opacityTextureIndex; + + // Geometry flags. + uint hasNormals; + uint hasTexCoords; +} instance; + + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +uvec3 getIndicesForTriangle_0(int triangleIndex) { + uint v0 = instance.indices.i[triangleIndex*3+0]; + uint v1 = instance.indices.i[triangleIndex*3+1]; + uint v2 = instance.indices.i[triangleIndex*3+2]; + + return uvec3(v0,v1,v2); +} + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +vec3 getPositionForVertex_0(int vertexIndex) { + return instance.positions.v[vertexIndex]; +} + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +vec3 getNormalForVertex_0(int vertexIndex) { + return instance.normals.n[vertexIndex]; +} + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +vec2 getTexCoordForVertex_0(int vertexIndex) { + return instance.texcoords.t[vertexIndex]; +} + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +bool instanceHasNormals_0() { + return true;//instance.hasNormals; +} + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +bool instanceHasTexCoords_0() { + return true;//instance.hasTexCoords; +} + +// Implementation for forward declared material accessor function in Material.hlsli. +MaterialConstants_0 getMaterial_0() { + return instance.material.m[0]; +} + +// Implementation for forward declared texture sample function in Material.hlsli. +vec4 sampleBaseColorTexture_0(vec2 uv, float level) { + return texture(textureSamplers[nonuniformEXT(instance.baseColorTextureIndex)], uv); +} + +// Implementation for forward declared texture sample function in Material.hlsli. +vec4 sampleSpecularRoughnessTexture_0(vec2 uv, float level) { + return texture(textureSamplers[nonuniformEXT(instance.specularRoughnessTextureIndex)], uv); +} + +// Implementation for forward declared texture sample function in Material.hlsli. +vec4 sampleNormalTexture_0(vec2 uv, float level) { + return texture(textureSamplers[nonuniformEXT(instance.normalTextureIndex)], uv); +} + +// Implementation for forward declared texture sample function in Material.hlsli. +vec4 sampleOpacityTexture_0(vec2 uv, float level) { + return texture(textureSamplers[nonuniformEXT(instance.opacityTextureIndex)], uv); +} \ No newline at end of file diff --git a/Libraries/Aurora/Source/HGI/Shaders/PostProcessing.glsl b/Libraries/Aurora/Source/HGI/Shaders/PostProcessing.glsl new file mode 100644 index 0000000..3aa82f1 --- /dev/null +++ b/Libraries/Aurora/Source/HGI/Shaders/PostProcessing.glsl @@ -0,0 +1,73 @@ +// Post processing data UBO. +// NOTE should be passed as push constants, but this is currently broken in HGI. +layout(binding = 3) layout(std140) uniform PostProcesssing +{ + vec3 brightness; + int debugMode; + vec2 range; + bool isDenoisingEnabled; + bool isToneMappingEnabled; + bool isGammaCorrectionEnabled; + bool isAlphaEnabled; +} gSettings; + +// Get texture UV from input coordinate. +vec2 GetTexCoords(ivec2 outCoords) +{ + vec2 outDims = vec2(HgiGetSize_outTexture()); + // apply a (0.5, 0.5) offset to use pixel centers and not pixel corners + vec2 texCoords = (vec2(outCoords) + vec2(0.5, 0.5)) / outDims; + return texCoords; +} + +// Applies the ACES filmic tone mapping curve to the color. +// NOTE: Based on https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve. +vec3 toneMapACES(vec3 color) +{ + float a = 2.51f; + float b = 0.03f; + float c = 2.43f; + float d = 0.59f; + float e = 0.14f; + + return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0); +} + +// Converts from linear color space to sRGB (gamma correction) for display. +// NOTE: Based on http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html. +vec3 linearTosRGB(vec3 color) +{ + vec3 sq1 = sqrt(color); + vec3 sq2 = sqrt(sq1); + vec3 sq3 = sqrt(sq2); + + return 0.662002687 * sq1 + 0.684122060 * sq2 - 0.323583601 * sq3 - 0.0225411470 * color; +} + +void main(void) +{ + // Sample input accumulation texture. + ivec2 outCoords = ivec2(hd_GlobalInvocationID.xy); + vec2 texCoords = GetTexCoords(outCoords); + vec3 color = HgiTextureLod_accumulationTexture(texCoords, 0.0).rgb; + + // Apply brightness. + color *= gSettings.brightness; + + // Apply ACES tone mapping or simple saturation. + if (gSettings.isToneMappingEnabled) + { + color = toneMapACES(color); + } + + // Apply gamma correction. + // NOTE: Gamma correction must be performed here as UAV textures don't support sRGB write. + if (gSettings.isGammaCorrectionEnabled) + { + color = linearTosRGB(clamp(color,0.0,1.0)); + } + + // For now just return input directly. Force alpha to one. + // TODO: Correct alpha output. + HgiSet_outTexture(outCoords, vec4(color,1.0)); +} diff --git a/Libraries/Aurora/Source/ImageBase.h b/Libraries/Aurora/Source/ImageBase.h new file mode 100644 index 0000000..b37bae8 --- /dev/null +++ b/Libraries/Aurora/Source/ImageBase.h @@ -0,0 +1,32 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// A base class for implementations of IEnvironment. +class ImageBase : public IImage +{ +public: + /*** Lifetime Management ***/ + + ImageBase() {} + + float luminanceIntegral() { return _luminanceIntegral; } + +protected: + float _luminanceIntegral = 0.0f; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/MaterialBase.cpp b/Libraries/Aurora/Source/MaterialBase.cpp new file mode 100644 index 0000000..071547d --- /dev/null +++ b/Libraries/Aurora/Source/MaterialBase.cpp @@ -0,0 +1,231 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "MaterialBase.h" + +BEGIN_AURORA + +static PropertySetPtr g_pPropertySet; + +static PropertySetPtr propertySet() +{ + if (g_pPropertySet) + { + return g_pPropertySet; + } + + g_pPropertySet = make_shared(); + + // Constants. + // NOTE: Default values and order come from the Standard surface reference document: + // https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx + g_pPropertySet->add("base", 0.8f); + g_pPropertySet->add("base_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("diffuse_roughness", 0.0f); + g_pPropertySet->add("metalness", 0.0f); + g_pPropertySet->add("specular", 1.0f); + g_pPropertySet->add("specular_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("specular_roughness", 0.2f); + g_pPropertySet->add("specular_IOR", 1.5f); + g_pPropertySet->add("specular_anisotropy", 0.0f); + g_pPropertySet->add("specular_rotation", 0.0f); + g_pPropertySet->add("transmission", 0.0f); + g_pPropertySet->add("transmission_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("transmission_depth", 0.0f); + g_pPropertySet->add("transmission_scatter", vec3(0.0f, 1.0f, 1.0f)); + g_pPropertySet->add("transmission_scatter_anisotropy", 0.0f); + g_pPropertySet->add("transmission_dispersion", 0.0f); + g_pPropertySet->add("transmission_extra_roughness", 0.0f); + g_pPropertySet->add("subsurface", 0.0f); + g_pPropertySet->add("subsurface_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("subsurface_radius", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("subsurface_scale", 1.0f); + g_pPropertySet->add("subsurface_anisotropy", 0.0f); + g_pPropertySet->add("sheen", 0.0f); + g_pPropertySet->add("sheen_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("sheen_roughness", 0.3f); + g_pPropertySet->add("coat", 0.0f); + g_pPropertySet->add("coat_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("coat_roughness", 0.1f); + g_pPropertySet->add("coat_anisotropy", 0.0f); + g_pPropertySet->add("coat_rotation", 0.0f); + g_pPropertySet->add("coat_IOR", 1.5f); + g_pPropertySet->add("coat_affect_color", 0.0f); + g_pPropertySet->add("coat_affect_roughness", 0.0f); + g_pPropertySet->add("thin_film_thickness", 0.0f); + g_pPropertySet->add("thin_film_IOR", 1.5f); + g_pPropertySet->add("emission", 0.0f); + g_pPropertySet->add("emission_color", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("opacity", vec3(1.0f, 1.0f, 1.0f)); + g_pPropertySet->add("thin_walled", false); + + // Images (textures) and associated transforms. + // NOTE: Default values must be nullptr, as the property set has a lifetime that could exceed + // the renderer, and images can't be retained outside their renderer. + g_pPropertySet->add("base_color_image", IImagePtr()); + g_pPropertySet->add("base_color_image_sampler", ISamplerPtr()); + g_pPropertySet->add("base_color_image_offset", vec2()); + g_pPropertySet->add("base_color_image_scale", vec2(1, 1)); + g_pPropertySet->add("base_color_image_pivot", vec2()); + g_pPropertySet->add("base_color_image_rotation", 0.0f); + g_pPropertySet->add("specular_roughness_image", IImagePtr()); + g_pPropertySet->add("specular_roughness_image_offset", vec2()); + g_pPropertySet->add("specular_roughness_image_scale", vec2(1, 1)); + g_pPropertySet->add("specular_roughness_image_pivot", vec2()); + g_pPropertySet->add("specular_roughness_image_rotation", 0.0f); + g_pPropertySet->add("specular_color_image", IImagePtr()); + g_pPropertySet->add("specular_color_image_transform", mat4()); + g_pPropertySet->add("coat_color_image", IImagePtr()); + g_pPropertySet->add("coat_color_image_transform", mat4()); + g_pPropertySet->add("coat_roughness_image", IImagePtr()); + g_pPropertySet->add("coat_roughness_image_transform", mat4()); + g_pPropertySet->add("opacity_image", IImagePtr()); + g_pPropertySet->add("opacity_image_offset", vec2()); + g_pPropertySet->add("opacity_image_scale", vec2(1, 1)); + g_pPropertySet->add("opacity_image_pivot", vec2()); + g_pPropertySet->add("opacity_image_rotation", 0.0f); + g_pPropertySet->add("opacity_image_sampler", ISamplerPtr()); + g_pPropertySet->add("normal_image", IImagePtr()); + g_pPropertySet->add("normal_image_offset", vec2()); + g_pPropertySet->add("normal_image_scale", vec2(1, 1)); + g_pPropertySet->add("normal_image_pivot", vec2()); + g_pPropertySet->add("normal_image_rotation", 0.0f); + g_pPropertySet->add("displacement_image", IImagePtr()); + + return g_pPropertySet; +} + +void MaterialBase::updateGPUStruct(MaterialData& data) +{ + // Get print offsets into struct for debugging purposes. +#if 0 + int id = 0; + AU_INFO("Offset %02d - base:%d", id++, offsetof(MaterialData, base)); + AU_INFO("Offset %02d - baseColor:%d", id++, offsetof(MaterialData, baseColor)); + AU_INFO("Offset %02d - diffuseRoughness:%d", id++, offsetof(MaterialData, diffuseRoughness)); + AU_INFO("Offset %02d - metalness:%d", id++, offsetof(MaterialData, metalness)); + AU_INFO("Offset %02d - specular:%d", id++, offsetof(MaterialData, specular)); + AU_INFO("Offset %02d - specularColor:%d", id++, offsetof(MaterialData, specularColor)); + AU_INFO("Offset %02d - specularRoughness:%d", id++, offsetof(MaterialData, specularRoughness)); + AU_INFO("Offset %02d - specularIOR:%d", id++, offsetof(MaterialData, specularIOR)); + AU_INFO("Offset %02d - specularAnisotropy:%d", id++, offsetof(MaterialData, specularAnisotropy)); + AU_INFO("Offset %02d - specularRotation:%d", id++, offsetof(MaterialData, specularRotation)); + AU_INFO("Offset %02d - transmission:%d", id++, offsetof(MaterialData, transmission)); + AU_INFO("Offset %02d - transmissionColor:%d", id++, offsetof(MaterialData, transmissionColor)); + AU_INFO("Offset %02d - subsurface:%d", id++, offsetof(MaterialData, subsurface)); + AU_INFO("Offset %02d - subsurfaceColor:%d", id++, offsetof(MaterialData, subsurfaceColor)); + AU_INFO("Offset %02d - subsurfaceRadius:%d", id++, offsetof(MaterialData, subsurfaceRadius)); + AU_INFO("Offset %02d - subsurfaceScale:%d", id++, offsetof(MaterialData, subsurfaceScale)); + AU_INFO("Offset %02d - subsurfaceAnisotropy:%d", id++, offsetof(MaterialData, subsurfaceAnisotropy)); + AU_INFO("Offset %02d - sheen:%d", id++, offsetof(MaterialData, sheen)); + AU_INFO("Offset %02d - sheenColor:%d", id++, offsetof(MaterialData, sheenColor)); + AU_INFO("Offset %02d - sheenRoughness:%d", id++, offsetof(MaterialData, sheenRoughness)); + AU_INFO("Offset %02d - coat:%d", id++, offsetof(MaterialData, coat)); + AU_INFO("Offset %02d - coatColor:%d", id++, offsetof(MaterialData, coatColor)); + AU_INFO("Offset %02d - coatRoughness:%d", id++, offsetof(MaterialData, coatRoughness)); + AU_INFO("Offset %02d - coatAnisotropy:%d", id++, offsetof(MaterialData, coatAnisotropy)); + AU_INFO("Offset %02d - coatRotation:%d", id++, offsetof(MaterialData, coatRotation)); + AU_INFO("Offset %02d - coatIOR:%d", id++, offsetof(MaterialData, coatIOR)); + AU_INFO("Offset %02d - coatAffectColor:%d", id++, offsetof(MaterialData, coatAffectColor)); + AU_INFO( + "Offset %02d - coatAffectRoughness:%d", id++, offsetof(MaterialData, coatAffectRoughness)); + AU_INFO("Offset %02d - _padding4:%d", id++, offsetof(MaterialData, _padding4)); + AU_INFO("Offset %02d - opacity:%d", id++, offsetof(MaterialData, opacity)); + AU_INFO("Offset %02d - thinWalled:%d", id++, offsetof(MaterialData, thinWalled)); + AU_INFO("Offset %02d - hasBaseColorTex:%d", id++, offsetof(MaterialData, hasBaseColorTex)); + AU_INFO("Offset %02d - baseColorTexTransform:%d", id++, offsetof(MaterialData, baseColorTexTransform)); + AU_INFO("Offset %02d - hasSpecularRoughnessTex:%d", id++, offsetof(MaterialData, hasSpecularRoughnessTex)); + AU_INFO("Offset %02d - specularRoughnessTexTransform:%d", id++, offsetof(MaterialData, specularRoughnessTexTransform)); + AU_INFO("Offset %02d - hasOpacityTex:%d", id++, offsetof(MaterialData, hasOpacityTex)); + AU_INFO("Offset %02d - opacityTexTransform:%d", id++, offsetof(MaterialData, opacityTexTransform)); + AU_INFO("Offset %02d - hasNormalTex:%d", id++, offsetof(MaterialData, hasNormalTex)); + AU_INFO("Offset %02d - normalTexTransform:%d", id++, offsetof(MaterialData, normalTexTransform)); + AU_INFO("Offset %02d - isOpaque:%d", id++, offsetof(MaterialData, isOpaque)); +#endif + + // Update the GPU struct from the values map. + data.base = _values.asFloat("base"); + data.baseColor = _values.asFloat3("base_color"); + data.diffuseRoughness = _values.asFloat("diffuse_roughness"); + data.metalness = _values.asFloat("metalness"); + data.specular = _values.asFloat("specular"); + data.specularColor = _values.asFloat3("specular_color"); + data.specularRoughness = _values.asFloat("specular_roughness"); + data.specularIOR = _values.asFloat("specular_IOR"); + data.specularAnisotropy = _values.asFloat("specular_anisotropy"); + data.specularRotation = _values.asFloat("specular_rotation"); + data.transmission = _values.asFloat("transmission"); + data.transmissionColor = _values.asFloat3("transmission_color"); + data.subsurface = _values.asFloat("subsurface"); + data.subsurfaceColor = _values.asFloat3("subsurface_color"); + data.subsurfaceRadius = _values.asFloat3("subsurface_radius"); + data.subsurfaceScale = _values.asFloat("subsurface_scale"); + data.subsurfaceAnisotropy = _values.asFloat("subsurface_anisotropy"); + data.sheen = _values.asFloat("sheen"); + data.sheenColor = _values.asFloat3("sheen_color"); + data.sheenRoughness = _values.asFloat("sheen_roughness"); + data.coat = _values.asFloat("coat"); + data.coatColor = _values.asFloat3("coat_color"); + data.coatRoughness = _values.asFloat("coat_roughness"); + data.coatAnisotropy = _values.asFloat("coat_anisotropy"); + data.coatRotation = _values.asFloat("coat_rotation"); + data.coatIOR = _values.asFloat("coat_IOR"); + data.coatAffectColor = _values.asFloat("coat_affect_color"); + data.coatAffectRoughness = _values.asFloat("coat_affect_roughness"); + data.opacity = _values.asFloat3("opacity"); + data.thinWalled = _values.asBoolean("thin_walled") ? 1 : 0; + data.hasBaseColorTex = _values.asImage("base_color_image") ? 1 : 0; + data.baseColorTexTransform.offset = _values.asFloat2("base_color_image_offset"); + data.baseColorTexTransform.scale = _values.asFloat2("base_color_image_scale"); + data.baseColorTexTransform.pivot = _values.asFloat2("base_color_image_pivot"); + data.baseColorTexTransform.rotation = _values.asFloat("base_color_image_rotation"); + data.hasSpecularRoughnessTex = _values.asImage("specular_roughness_image") ? 1 : 0; + data.specularRoughnessTexTransform.offset = _values.asFloat2("specular_roughness_image_offset"); + data.specularRoughnessTexTransform.scale = _values.asFloat2("specular_roughness_image_scale"); + data.specularRoughnessTexTransform.pivot = _values.asFloat2("specular_roughness_image_pivot"); + data.specularRoughnessTexTransform.rotation = + _values.asFloat("specular_roughness_image_rotation"); + data.hasOpacityTex = _values.asImage("opacity_image") ? 1 : 0; + data.opacityTexTransform.offset = _values.asFloat2("opacity_image_offset"); + data.opacityTexTransform.scale = _values.asFloat2("opacity_image_scale"); + data.opacityTexTransform.pivot = _values.asFloat2("opacity_image_pivot"); + data.opacityTexTransform.rotation = _values.asFloat("opacity_image_rotation"); + data.hasNormalTex = _values.asImage("normal_image") ? 1 : 0; + data.normalTexTransform.offset = _values.asFloat2("normal_image_offset"); + data.normalTexTransform.scale = _values.asFloat2("normal_image_scale"); + data.normalTexTransform.pivot = _values.asFloat2("normal_image_pivot"); + data.normalTexTransform.rotation = _values.asFloat("normal_image_rotation"); + + // Record whether the material is opaque with its current values. + data.isOpaque = computeIsOpaque(); +} + +MaterialBase::MaterialBase() : FixedValues(propertySet()) {} + +bool MaterialBase::computeIsOpaque() const +{ + // A material is considered opaque if all the opacity components are 1.0, there is no opacity + // image, and transmission is zero. + // TODO: This should also consider whether the material type has overridden opacity or + // transmission, likely with a shader graph attached to that input. + static vec3 kOpaque(1.0f); + vec3 opacity = _values.asFloat3("opacity"); + bool hasOpacityImage = _values.asImage("opacity_image") ? 1 : 0; + float transmission = _values.asFloat("transmission"); + + return opacity == kOpaque && !hasOpacityImage && transmission == 0.0f; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialBase.h b/Libraries/Aurora/Source/MaterialBase.h new file mode 100644 index 0000000..98a4c51 --- /dev/null +++ b/Libraries/Aurora/Source/MaterialBase.h @@ -0,0 +1,140 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +// Texture UV transform. +struct TextureTransform +{ + vec2 pivot; + vec2 scale; + vec2 offset; + float rotation; +}; + +// Representation of the shader source for a material type. +struct MaterialTypeSource +{ + MaterialTypeSource(const string& typeName = "", const string& setupSource = "", + const string& bsdfSource = "") : + name(typeName), setup(setupSource), bsdf(bsdfSource) + { + } + + // Compare the source itself. + bool compareSource(const MaterialTypeSource& other) const + { + return (setup.compare(other.setup) == 0 && bsdf.compare(other.bsdf) == 0); + } + + // Compare the name. + bool compare(const MaterialTypeSource& other) const { return (name.compare(other.name) == 0); } + + // Reset the contents of the + void reset() + { + name = ""; + setup = ""; + bsdf = ""; + } + + // Is there actually source associated with this material type? + bool empty() const { return name.empty(); } + + // Unique name. + string name; + + // Shader source for material setup. + string setup; + + // Optional shader source for bsdf. + string bsdf; +}; + +// A base class for implementations of IMaterial. +class MaterialBase : public IMaterial, public FixedValues +{ +public: + /*** Lifetime Management ***/ + + MaterialBase(); + + /*** IMaterial Functions ***/ + + IValues& values() override { return *this; } + +protected: + // The CPU representation of material constant buffer. The layout of this struct must match the + // MaterialConstants struct in the shader. + // NOTE: Members should be kept in the same order as the Standard Surface reference document. + // https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx + // Because it is a CPU representation of a GPU constant buffer, it must be padded to conform to + // DirectX packing conventions: + // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules + struct MaterialData + { + float base; + vec3 baseColor; + float diffuseRoughness; + float metalness; + float specular; + float _padding1; + vec3 specularColor; + float specularRoughness; + float specularIOR; + float specularAnisotropy; + float specularRotation; + float transmission; + vec3 transmissionColor; + float subsurface; + vec3 subsurfaceColor; + float _padding2; + vec3 subsurfaceRadius; + float subsurfaceScale; + float subsurfaceAnisotropy; + float sheen; + vec2 _padding3; + vec3 sheenColor; + float sheenRoughness; + float coat; + vec3 coatColor; + float coatRoughness; + float coatAnisotropy; + float coatRotation; + float coatIOR; + float coatAffectColor; + float coatAffectRoughness; + vec2 _padding4; + vec3 opacity; + int thinWalled; + int hasBaseColorTex; + vec3 _padding5; + TextureTransform baseColorTexTransform; + int hasSpecularRoughnessTex; + TextureTransform specularRoughnessTexTransform; + int hasOpacityTex; + TextureTransform opacityTexTransform; + int hasNormalTex; + TextureTransform normalTexTransform; + int isOpaque; + }; + + void updateGPUStruct(MaterialData& data); + bool computeIsOpaque() const; +}; + +END_AURORA \ No newline at end of file diff --git a/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp new file mode 100644 index 0000000..a78316b --- /dev/null +++ b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp @@ -0,0 +1,949 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include +#include + +#include "BSDFCodeGenerator.h" + +// MaterialX headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Aurora +{ +namespace MaterialXCodeGen +{ + +shared_ptr BSDFCodeGenerator::s_pStdLib; + +// Custom shader class used by BSDFCodeGenerator. +class BSDFCodeGeneratorShader : public MaterialX::Shader +{ +public: + // Same ctor arguments as parent class. + BSDFCodeGeneratorShader(const string& name, MaterialX::ShaderGraphPtr graph) : + MaterialX::Shader(name, graph) + { + // Hard coded GLSL syntax. + // TODO: Investigate creating a HlslSyntax class. + _pSyntax = make_shared(); + + // A dummy "pixel" stage is used to generate the BSDF code. + _pStage = make_shared(MaterialX::Stage::PIXEL, _pSyntax); + _stagesMap[MaterialX::Stage::PIXEL] = _pStage; + _stages.push_back(_pStage.get()); + + // Create an input block for the vertex data (stored in global struct) + auto vd = _pStage->createInputBlock(MaterialX::HW::VERTEX_DATA); + vd->setInstance("vertexData"); + + // Add the required vertex attributes. + // TODO: These are just educated guesses. How do we work out which attributes are used? + auto tc0 = vd->add(MaterialX::TypeDesc::get("vector2"), MaterialX::HW::T_TEXCOORD + "_0"); + tc0->setVariable("texCoord"); + auto nw = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_NORMAL_WORLD); + nw->setVariable("normal"); + auto tw = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_TANGENT_WORLD); + tw->setVariable("tangent"); + // TODO: Should absolutely not just map world position to object position. + auto po = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_POSITION_OBJECT); + po->setVariable("position"); + auto pw = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_POSITION_WORLD); + pw->setVariable("position"); + } + + // Get the shader stage created by this shader class. + shared_ptr stage() { return _pStage; } + + // Get the syntax object created by this shader class. + shared_ptr syntax() { return _pSyntax; } + + // Get the source that was generated by the last emit call. + string getNewSource() + { + const string& allSource = _pStage->getSourceCode(); + size_t p = _currSourcePosition; + _currSourcePosition = allSource.size(); + return allSource.substr(p); + } + +private: + size_t _currSourcePosition = 0; + shared_ptr _pStage; + shared_ptr _pSyntax; +}; + +// Custom shader generator class used by BSDFCodeGenerator. +class BSDFShaderGenerator : public MaterialX::GlslShaderGenerator +{ +public: + BSDFShaderGenerator(const string& mtlxLibPath) : _mtlxLibPath(mtlxLibPath) + { + _pGeneratedIncludes = make_unique>(); + _pGeneratedDefinitions = make_unique>(); + } + + virtual void emitFunctionDefinition(const MaterialX::ShaderNode& node, + MaterialX::GenContext& context, MaterialX::ShaderStage& stage) const override + { + // Get implementation for node. + const MaterialX::ShaderNodeImpl& impl = node.getImplementation(); + size_t implHash = impl.getHash(); + + // Check if we've already generated this implementation. + // There are lots of checks like this around, but the only way to reliably ensure we don't + // have duplicated functions is to do it here in the code generator. + // TODO: Work out a nicer way of doing this. + if (_pGeneratedDefinitions->find(implHash) != _pGeneratedDefinitions->end()) + { + return; + } + + // Add to the map so not emitted next time. + _pGeneratedDefinitions->insert({ implHash, node.getName() }); + + // If not a duplicate just pass to the + MaterialX::GlslShaderGenerator::emitFunctionDefinition(node, context, stage); + } + + virtual void emitBlock(const string& str, const MaterialX::FilePath& sourceFilename, + MaterialX::GenContext& context, MaterialX::ShaderStage& stage) const override + { + // Build processed block of source from original. + string processedBlock = ""; + + // Stream the source from original string. + MaterialX::StringStream stream(str); + for (string line; std::getline(stream, line);) + { + + // Look for #include lines, so we can have a custom include functionality. + size_t pos = line.find("#include"); + if (pos != string::npos) + { + // Decode the include line. + size_t startQuote = line.find_first_of("\""); + size_t endQuote = line.find_last_of("\""); + if (startQuote != string::npos && endQuote != string::npos && endQuote > startQuote) + { + size_t length = (endQuote - startQuote) - 1; + if (length) + { + // Get filename from line. + const string filename = line.substr(startQuote + 1, length); + + // Ensure this include has not been included before, if it has ignore this + // line. There are lots of checks like this around, but the only way to + // reliably ensure we don't have duplicated functions is to do it here in + // the code generator. + // TODO: Work out a nicer way of doing this. + if (_pGeneratedIncludes->find(filename) == _pGeneratedIncludes->end()) + { + // Read the include file and add to processed block. + const string path = _includeFilePaths.at(filename); + const string resolvedPath = + context.resolveSourceFile(path, _mtlxLibPath); + _pGeneratedIncludes->insert({ filename, resolvedPath }); + string source = MaterialX::readFile(resolvedPath); + if (source.empty()) + AU_ERROR("Failed to load MaterialX GLSL include file:%s", + resolvedPath.c_str()); + processedBlock += "// Included from " + filename + "\n" + source + "\n"; + } + } + } + } + else + { + // All other lines are added with + processedBlock += line + "\n"; + } + } + + // Pass the processed block (without include lines) to the parent class emitBlock function. + MaterialX::GlslShaderGenerator::emitBlock(processedBlock, sourceFilename, context, stage); + } + + // Replace the upstream result with a temp variable if node+input name match. + string getUpstreamResult( + const MaterialX::ShaderInput* input, MaterialX::GenContext& context) const override + { + // Use temp variable if full name matches. + string fullName = input->getNode()->getName() + "_" + input->getName(); + if (_tempVariables.find(fullName) != _tempVariables.end()) + { + return _tempVariables.at(fullName); + } + + // Otherwise call parent class method. + string result = MaterialX::GlslShaderGenerator::getUpstreamResult(input, context); + return result; + } + + // Add a temp variable for given input. + void addTempInputVariable(const MaterialX::ShaderInput* input, string tempName) + { + string fullName = input->getNode()->getName() + "_" + input->getName(); + + _tempVariables[fullName] = tempName; + } + + // Clear all the temp variables. + void clearTempVariables() { _tempVariables.clear(); } + +protected: + map _tempVariables; + unique_ptr> _pGeneratedDefinitions; + unique_ptr> _pGeneratedIncludes; + string _mtlxLibPath; + + // Map of include paths. + // TOOO: How to know which of these we should have? + map _includeFilePaths = { { "lib/$fileTransformUv", + "stdlib/genglsl/lib/mx_transform_uv.glsl" } }; +}; + +void BSDFCodeGenerator::createStdLib() +{ + // Setup search path. + MaterialX::FilePathVec libraryFolders; + libraryFolders.push_back(_mtlxLibPath); + MaterialX::FileSearchPath searchPath; + searchPath.append(_mtlxLibPath.c_str()); + + // Create a document to store standard library. + s_pStdLib = MaterialX::createDocument(); + + // Load the standard library. + auto res = MaterialX::loadLibraries(libraryFolders, searchPath, s_pStdLib); + AU_ASSERT( + res.size(), "Failed to load MaterialX standard libraries from %s\n", _mtlxLibPath.c_str()); +} + +BSDFCodeGenerator::BSDFCodeGenerator( + const string& mtlxPath, const string& surfaceShaderNodeCategory) : + _surfaceShaderNodeCategory(surfaceShaderNodeCategory) +{ + // Get the MaterialX library path within the MaterialX folder. + _mtlxLibPath = mtlxPath + "/libraries"; + + // Create the MaterialX standard library if it doesn't exist. + if (!s_pStdLib) + { + createStdLib(); + } + + // Create a GLSL generator and generator context. + _pGenerator = make_shared(_mtlxLibPath); + _pGeneratorContext = make_unique(_pGenerator); + + // Create unit system. + _unitSystem = MaterialX::UnitSystem::create(_pGenerator->getTarget()); + _unitRegistry = MaterialX::UnitConverterRegistry::create(); + MaterialX::UnitTypeDefPtr distanceTypeDef = s_pStdLib->getUnitTypeDef("distance"); + + // Add create and register unit converter. + MaterialX::UnitConverterPtr uconverter = + MaterialX::LinearUnitConverter::create(distanceTypeDef); + _unitRegistry->addUnitConverter(distanceTypeDef, uconverter); + + // Setup unit system. + _unitSystem->loadLibrary(s_pStdLib); + _unitSystem->setUnitConverterRegistry(_unitRegistry); + + // Get the names of the unit definitions and their indices so client code can access them. + auto unitDefs = distanceTypeDef->getUnitDefs(); + _units.names.clear(); + _units.indices.clear(); + while (true) + { + // Get the name for current index.. + int idx = (int)_units.names.size(); + string unit = uconverter->getUnitFromInteger(idx); + if (unit.empty()) + break; + + // Add to array and map. + _units.indices[unit] = idx; + _units.names.push_back(unit); + } + + // Set the unit system in the generator. + _pGenerator->setUnitSystem(_unitSystem); + _pGeneratorContext->getOptions().targetDistanceUnit = "centimeter"; // NOTE: this is not used. + + // Setup library search path to find libraries. + MaterialX::FileSearchPath searchPath; + searchPath.append(_mtlxLibPath.c_str()); + _pGeneratorContext->registerSourceCodeSearchPath(searchPath); + + _defaultOutputParamMapping = { { "base", "base" }, { "base_color", "baseColor" }, + { "diffuse_roughness", "diffuseRoughness" }, { "specular_roughness", "specularRoughness" }, + { "specular", "specular" }, { "sheen", "sheen" }, { "sheen_roughness", "sheenRoughness" }, + { "sheen_color", "sheenColor" }, { "specular_color", "specularColor" }, + { "metalness", "metalness" } }; + _defaultOutputMapper = [&](const string& name, Aurora::IValues::Type /*type*/, + const string& /*topLevelShaderName*/) { + auto iter = _defaultOutputParamMapping.find(name); + if (iter != _defaultOutputParamMapping.end()) + return iter->second; + return string(""); + }; +} + +// Empty dtor in C++ file, to avoid issues with forward declaring MaterialX types. +BSDFCodeGenerator::~BSDFCodeGenerator() {} + +void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, + shared_ptr pBSDFGenShader, const string& outputVariable, + string* pSourceOut) +{ + // Do we need local scope to avoid name collisions? + bool addScope = false; + + // Get the shader stage. + MaterialX::ShaderStage& ps = pBSDFGenShader->getStage(MaterialX::Stage::PIXEL); + + // Get the connection for the input (warn if null.) + auto connection = input->getConnection(); + + // Ignore input if no connection. + // TODO: Handle this case. + if (!connection) + { + AU_WARN("Input %s has no connection, ignoring.", input->getFullName().c_str()); + return; + } + + // Compare connection name. + string variableName = connection->getFullName(); + if (input->getConnection()->getNode()->getName() == "BSDFCodeGeneratorShaderGraph") + { + // If this a connection from the high-level shader graph, its a material input. + auto inputName = input->getFullName(); + + IValues::Type auroraInputType = + glslTypeToAuroraType(pBSDFGenShader->syntax()->getTypeName(input->getType())); + + string mappedName = + _currentInputParameterMapper(inputName, auroraInputType, _topLevelShaderNodeName); + pSourceOut->append("\t// Graph input " + inputName + "\n"); + if (mappedName.empty()) + { + // MtlX code gen requires a scope, but we comment it out so variable scope not effected. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .beginScope(MaterialX::Syntax::CURLY_BRACKETS); + + // If this is not one of the material inputs from _materialInputs emit with hard-coded + // default value. + _pGenerator->emitVariableDeclaration(input, "", *_pGeneratorContext, ps, true); + + // End the scope for declaration. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .endScope(MaterialX::Syntax::CURLY_BRACKETS); + pSourceOut->append("\t" + pBSDFGenShader->getNewSource() + ";\n"); + } + else + { + // MtlX code gen requires a scope, but we comment it out so variable scope not effected. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .beginScope(MaterialX::Syntax::CURLY_BRACKETS); + + // If this is one of the material inputs from _materialInputs, set it using the function + // parameter for that input. + _pGenerator->emitVariableDeclaration(input, "", *_pGeneratorContext, ps, false); + + // End the decleration scope. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .endScope(MaterialX::Syntax::CURLY_BRACKETS); + + pSourceOut->append("\t" + pBSDFGenShader->getNewSource()); + pSourceOut->append(" = " + mappedName + _materialInputSuffix + ";\n"); + + // Mark this input as active. + if (_activeInputs.find(mappedName) == _activeInputs.end()) + _activeInputNames.push_back(mappedName); + + _activeInputs[mappedName] = make_pair( + pBSDFGenShader->syntax()->getTypeName(input->getType()), input->getValue()); + } + + // Set the variable name that will used to generate the output.result. + variableName = input->getVariable(); + } + else + { + // Get the node this connection is from and its implementation string. + auto node = connection->getNode(); + string implName = node->getImplementation().getName(); + + // Create a temp variable to store the node ouput, if this node has already been generated + // then this will be reused without generating again. + // TODO: Handle multiple outputs. + string outputTempVariable = + "nodeOutTmp_" + connection->getNode()->getName() + "_" + connection->getName(); + + // Only process the input if it hasn't already been processed. + if (_processedNodes.find(node) == _processedNodes.end()) + { + _processedNodes.insert(node); + + // Generate temp variables for the inputs, to avoid name collisions. + auto inputs = node->getInputs(); + pSourceOut->append("\t//Temp input variables for " + node->getName() + " \n"); + + // Create a temp output variable to store any externally-facing outputs. + pSourceOut->append("\t" + pBSDFGenShader->syntax()->getTypeName(connection->getType()) + + " " + outputTempVariable + "; //Temp output variable for " + connection->getName() + + " \n"); + + vector outputVars; + for (int i = 0; i < inputs.size(); i++) + { + // Get the input name and connection name it is coming from.. + auto nodeInput = inputs[i]; + string inputTempVariable = + "nodeTmp_" + nodeInput->getNode()->getName() + "_" + nodeInput->getName(); + + // Create temp variable + string inputType = pBSDFGenShader->syntax()->getTypeName(nodeInput->getType()); + pSourceOut->append("\t" + inputType + " " + inputTempVariable + + "; //Temp input variable for " + nodeInput->getName() + " \n"); + + // Add to output variables.(passed to upstream input nodes) and code generator. + outputVars.push_back(inputTempVariable); + _pGenerator->addTempInputVariable(nodeInput, inputTempVariable); + } + + // Recursively process inputs. + for (int i = 0; i < inputs.size(); i++) + { + auto nodeInput = inputs[i]; + processInput(nodeInput, pBSDFGenShader, outputVars[i], pSourceOut); + } + + // Check if we already have a definition for this node's implementation. + // The definitions are shared between documents, and calls to generate. + auto defIter = _definitionMap.find(implName); + size_t definitionIndex = 0xFFFFFFFF; + if (defIter != _definitionMap.end()) + { + definitionIndex = defIter->second; + } + else + { + // MtlX code gen requires a scope, but we comment it out so variable scope not + // effected. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .beginScope(MaterialX::Syntax::CURLY_BRACKETS); + + // If not already generated (and not an empty string), store the function + // definition. + _pGenerator->emitFunctionDefinition(*node, *_pGeneratorContext, ps); + + // End scope for definition. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .endScope(MaterialX::Syntax::CURLY_BRACKETS); + + string defSource = pBSDFGenShader->getNewSource(); + if (defSource.size()) + { + definitionIndex = _definitions.size(); + _definitionMap[implName] = definitionIndex; + _definitions.push_back( + "\n// Definition for implementation " + implName + "\n" + defSource); + } + } + + // Begin with comment (referencing definition if there is one) + pSourceOut->append("\t// Graph input function call " + input->getName()); + if (definitionIndex != 0xFFFFFFFF) + { + pSourceOut->append(" (See definition " + implName + ")"); + } + pSourceOut->append("\n"); + + // To avoid name collisions add local scope. + // TODO: Fix indentation. + pSourceOut->append("{\n"); + addScope = true; + + // MtlX code gen requires a scope, but we comment it out so variable scope not + // effected. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .beginScope(MaterialX::Syntax::CURLY_BRACKETS); + + // Emit function call for this input. + _pGenerator->emitFunctionCall(*node, *_pGeneratorContext, ps); + + // End scope for function call. + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); + pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) + .endScope(MaterialX::Syntax::CURLY_BRACKETS); + + pSourceOut->append("\t" + pBSDFGenShader->getNewSource()); + + // + pSourceOut->append("\t" + outputTempVariable); + pSourceOut->append(" = " + variableName + ";// Output connection\n"); + } + else + { + variableName = outputTempVariable; + } + } + + // Copy this output to the provided + if (outputVariable.size()) + { + pSourceOut->append("\t" + outputVariable); + pSourceOut->append(" = " + variableName + ";// Output connection\n"); + } + + // End local scope (ensuring output variable setter is enclosed.) + if (addScope) + { + pSourceOut->append("}\n"); + } +} + +void BSDFCodeGenerator::clearDefinitions() +{ + // Clear the definitions, which are accumulated after each generate call. + _definitions.clear(); + _definitionMap.clear(); +} + +int BSDFCodeGenerator::generateDefinitions(string* pResultOut) +{ + // Combine the GLSL code stored in the definitions vector. + *pResultOut = ""; + for (int i = 0; i < _definitions.size(); i++) + { + pResultOut->append(_definitions[i]); + } + + // Return number of definitions. + return (int)_definitions.size(); +} + +bool BSDFCodeGenerator::materialXValueToAuroraValue( + Value* pValueOut, shared_ptr pMtlXValue) +{ + *pValueOut = Value(); + + string glslType = pMtlXValue->getTypeString(); + if (glslType.compare("color3") == 0) + { + MaterialX::Color3 valData = pMtlXValue->asA(); + *pValueOut = glm::vec3(valData[0], valData[1], valData[2]); + return true; + } + if (glslType.compare("vector2") == 0) + { + MaterialX::Vector2 valData = pMtlXValue->asA(); + *pValueOut = glm::vec2(valData[0], valData[1]); + return true; + } + if (glslType.compare("boolean") == 0) + { + bool valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + if (glslType.compare("integer") == 0) + { + int valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + if (glslType.compare("float") == 0) + { + float valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + if (glslType.compare("string") == 0) + { + string valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + + return false; +} + +IValues::Type BSDFCodeGenerator::glslTypeToAuroraType(const string glslType) +{ + // Set the aurora type based on GLSL type string. + if (glslType.compare("vec3") == 0) + return IValues::Type::Float3; + if (glslType.compare("vec2") == 0) + return IValues::Type::Float2; + if (glslType.compare("float") == 0) + return IValues::Type::Float; + if (glslType.compare("sampler2D") == 0) + return IValues::Type::Image; + if (glslType.compare("int") == 0) + return IValues::Type::Int; + if (glslType.compare("bool") == 0) + return IValues::Type::Boolean; + + // Fail if no valid mapping found. + AU_FAIL("Unsupported GLSL type %s", glslType.c_str()); + return IValues::Type::Undefined; +} + +bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Result* pResultOut, + ParameterMappingFunction inputParameterMapper, ParameterMappingFunction outputParameterMapper, + const string& overrideDocumentName) +{ + // Processed MaterialX document string. + string processedMtlXDocument; + + // Process the document string to replace document name if override name provided. + if (!overrideDocumentName.empty()) + { + // Regular expression to find strings in the form: + // ]+>\\s*<\\s*\\S+\\s+name=\"([^\"]+)\""); + std::smatch match; + + // Create a copy of document string. + processedMtlXDocument = document; + + // Search for matches to the document name reg exp. + if (std::regex_search(processedMtlXDocument, match, rgx)) + { + // Retreive the sub-expression with the document name. + string documentName = match[1]; + + // Replace all occurrences of document name with the overide value. + processedMtlXDocument = + Foundation::replace(processedMtlXDocument, documentName, overrideDocumentName); + } + else + { + // Warn if the regular expression fails. + AU_WARN("Failed to find document name in MaterialX XML"); + } + } + + // Get reference to original or processed materialX document string. + const string& mtlXDocument = processedMtlXDocument.empty() ? document : processedMtlXDocument; + + // Set the current input an output mappers to provided values. + _currentInputParameterMapper = inputParameterMapper; + _currentOutputParameterMapper = + outputParameterMapper ? outputParameterMapper : _defaultOutputMapper; + + _pGenerator->clearTempVariables(); + _processedNodes.clear(); + + // Clear the setup code string. + pResultOut->materialSetupCode = ""; + + // Create new document object. + MaterialX::DocumentPtr mtlxDocument = MaterialX::createDocument(); + + // Read the XML into the document. + try + { + MaterialX::readFromXmlString(mtlxDocument, mtlXDocument); + } + catch (const MaterialX::Exception& exception) + { + AU_ERROR("Failed to read MaterialX from document:\n%s\n", exception.what()); + return false; + } + + _unitRegistry->write(mtlxDocument); + + string errorMessage; + if (!mtlxDocument->validate(&errorMessage)) + { + AU_ERROR("Invalid MaterialX document:\n%s", errorMessage.c_str()); + return false; + } + + // Import the standard library. + mtlxDocument->importLibrary(s_pStdLib); + + // Setup include paths. + // TODO: This is just a guess. How do we ensure all required includes are provided? + MaterialX::StringMap& tokenSub = + const_cast(_pGenerator->getTokenSubstitutions()); + tokenSub["$fileTransformUv"] = "stdlib/genglsl/lib/mx_transform_uv.glsl"; + + // Find renderable nodes. + vector elements; + unordered_set processedOutputs; + MaterialX::findRenderableMaterialNodes(mtlxDocument, elements, false, processedOutputs); + + // Return false if no renderable nodes. + // TODO: Better error handling. + if (elements.size() == 0) + { + AU_ERROR("No renderable nodes in MaterialX document"); + return false; + } + + // Assume material is first element in the XML file. + string materialNodeName = elements[0]->getName(); + + // Get the material nodes, return false if empty. + // TODO: Better error handling. + auto materialNodes = mtlxDocument->getMaterialNodes(); + if (elements.size() == 0) + { + AU_ERROR("Empty material in MaterialX document"); + return false; + } + + // Find the material node using name from element. + MaterialX::NodePtr materialNode = nullptr; + for (int i = 0; i < materialNodes.size(); i++) + { + if (materialNodes[i]->getName() == materialNodeName) + { + materialNode = materialNodes[i]; + break; + } + } + + // Return false if find failed. + // TODO: Better error handling. + if (materialNode == nullptr) + { + AU_ERROR("Failed to find material node"); + return false; + } + + // Get the shader nodes. + vector shaderNodes = MaterialX::getShaderNodes(materialNode); + if (shaderNodes.empty()) + { + AU_ERROR("No shader nodes in MaterialX document"); + return false; + } + + // Find the surface node for shader. + MaterialX::TypedElementPtr shaderNodeElement = nullptr; + MaterialX::InterfaceElementPtr shaderNodeDefImpl = nullptr; + for (MaterialX::NodePtr shaderNode : shaderNodes) + { + if ((shaderNode->getType() == MaterialX::SURFACE_SHADER_TYPE_STRING) && + (shaderNode->getCategory() == _surfaceShaderNodeCategory)) + { + shaderNodeElement = shaderNode; + + // Find the nodegraph implementation of the surface node. + shaderNodeDefImpl = shaderNodeElement->asA()->getImplementation(); + + if (shaderNodeDefImpl) + break; + } + } + if (!shaderNodeElement) + { + AU_ERROR("Failed to find surface shader in material"); + return false; + } + if (!shaderNodeDefImpl) + { + AU_ERROR("Failed to find nodegraph implementation for the surface shader"); + return false; + } + + string surfaceShaderNodeType = shaderNodeDefImpl->getName(); + + // Create a shader graph from the surface node. + MaterialX::ShaderGraphPtr graph = MaterialX::ShaderGraph::create( + nullptr, "BSDFCodeGeneratorShaderGraph", shaderNodeElement, *_pGeneratorContext); + shared_ptr pBSDFGenShader = + make_shared("BSDFCodeGeneratorShader", graph); + MaterialX::ShaderPtr shader = dynamic_pointer_cast(pBSDFGenShader); + + // Find the surface shader node. + MaterialX::ShaderNode* surfaceShaderNode = nullptr; + for (MaterialX::ShaderNode* node : graph->getNodes()) + { + if (node->getImplementation().getName() == surfaceShaderNodeType) + { + surfaceShaderNode = node; + break; + } + } + if (!surfaceShaderNode) + { + AU_ERROR("No surface shader node of type %s found", surfaceShaderNodeType.c_str()); + return false; + } + + _topLevelShaderNodeName = surfaceShaderNode->getName(); + + // Create the active inputs and outputs. + _activeOutputs.clear(); + _activeInputs.clear(); + _activeInputNames.clear(); + _activeOutputNames.clear(); + + // Build the body of the setup function from the surface shader node inputs. + string functionBody = ""; + auto surfaceShaderInputs = surfaceShaderNode->getInputs(); + for (int ssi = 0; ssi < surfaceShaderInputs.size(); ssi++) + { + + // Is this shader input one of the outputs we are interested in ? + auto surfaceShaderInput = surfaceShaderInputs[ssi]; + + IValues::Type auroraoOutputType = glslTypeToAuroraType( + pBSDFGenShader->syntax()->getTypeName(surfaceShaderInput->getType())); + string outputVariable = _currentOutputParameterMapper( + surfaceShaderInput->getName(), auroraoOutputType, _topLevelShaderNodeName); + + if (!outputVariable.empty()) + { + // Process the shader input. + processInput(surfaceShaderInput, pBSDFGenShader, outputVariable, &functionBody); + + // Add type to active outputs. + _activeOutputNames.push_back(outputVariable); + _activeOutputs[outputVariable] = + pBSDFGenShader->syntax()->getTypeName(surfaceShaderInput->getType()); + }; + } + + // Create a hash from the contents of the setup function. This is used to uniquely identify the + // shader code generated for this material, without any of the parameters or other surrounding + // data. + pResultOut->functionHash = hash {}(functionBody); + + // Create setup function name from hash. + stringstream sstream; + sstream << "setupMaterial_" << Foundation::sHash(pResultOut->functionHash); + pResultOut->setupFunctionName = sstream.str(); + + // Create the function prototype from the function name, active inputs and outputs. + pResultOut->materialSetupCode = "void " + pResultOut->setupFunctionName + "(\n"; + + // Keep track of total input and output parameter count. + int numParams = 0; + + // Clear the argument and default values vectors. + pResultOut->argumentsUsed.clear(); + pResultOut->defaultInputValues.clear(); + pResultOut->defaultInputValues.resize(_activeInputNames.size()); + + // Add the material inputs to the function prototype. + for (int i = 0; i < _activeInputNames.size(); i++) + { + auto inputVar = _activeInputNames[i]; + + auto activeTypeIter = _activeInputs.find(inputVar); + if (activeTypeIter != _activeInputs.end()) + { + auto activeInput = activeTypeIter->second; + string activeInputType = activeInput.first; + + // Create variable name using input name and suffix. + string inputVarName = inputVar + _materialInputSuffix; + + // Append comma to previous line, if any. + if (numParams) + pResultOut->materialSetupCode.append(",\n"); + + // Convert GLSL type to Aurora type enum. + IValues::Type auroraType = glslTypeToAuroraType(activeInputType); + + // Convert MaterialX value for input into Aurora Value. + if (activeInput.second) + { + materialXValueToAuroraValue(&pResultOut->defaultInputValues[i], activeInput.second); + } + Value* pDefaultValue = &pResultOut->defaultInputValues[i]; + + // Add this input to the results. + pResultOut->argumentsUsed.push_back({ inputVar, auroraType, false, pDefaultValue }); + + // Add the code for this input to the prototype. + pResultOut->materialSetupCode.append("\t" + activeInputType + " " + inputVarName); + + // Increment parameter count. + numParams++; + } + } + + // Add the material outputs to the function prototype. + for (auto outputVar : _activeOutputNames) + { + auto activeTypeIter = _activeOutputs.find(outputVar); + if (activeTypeIter != _activeOutputs.end()) + { + // Create variable name using output name and suffix. + string outputVarName = outputVar + _materialOutputSuffix; + + // Append comma to previous line, if any. + if (numParams) + pResultOut->materialSetupCode.append(",\n"); + + // Convert GLSL type to Aurora type enum. + IValues::Type auroraType = glslTypeToAuroraType(activeTypeIter->second); + + // Add this output to the results. + pResultOut->argumentsUsed.push_back({ outputVar, auroraType, true }); + + // Add the code for this output to the prototype. + pResultOut->materialSetupCode.append( + "\tout " + activeTypeIter->second + " " + outputVarName); + + // Increment parameter count. + numParams++; + } + } + + // Add body wrapped in braces. + pResultOut->materialSetupCode.append(")\n{\n"); + pResultOut->materialSetupCode.append(functionBody); + pResultOut->materialSetupCode.append("}\n"); + + return true; +}; + +} // namespace MaterialXCodeGen +} // namespace Aurora diff --git a/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h new file mode 100644 index 0000000..35fbd06 --- /dev/null +++ b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h @@ -0,0 +1,182 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include + +// Forward declare MaterialX types. +MATERIALX_NAMESPACE_BEGIN + +class Document; +class FileSearchPath; +class ShaderGenerator; +class GenContext; +class ShaderStage; +class ShaderInput; +class Value; +class UnitConverterRegistry; +class UnitSystem; +class ShaderNode; + +MATERIALX_NAMESPACE_END + +#include "Properties.h" + +BEGIN_AURORA + +namespace MaterialXCodeGen +{ + +// Forward declare shader class (used internally for codegen) +class BSDFCodeGeneratorShader; +class BSDFShaderGenerator; + +/// MaterialX code generator for BSDF and material setup.GLSL code for pathtracing. +/// TODO: Currently only generates the material setup function and associated metadata, not the +/// BSDF. +class BSDFCodeGenerator +{ +public: + // The MaterialX units. + struct Units + { + // Unit names. + std::vector names; + // Indices for units used in shaders. + std::map indices; + }; + + /// Material argument details. + struct MaterialArgument + { + /// Argument name + string name; + /// Argument type as GLSL string + IValues::Type type; + /// True if output argument. + bool isOutput; + /// Pointer to default value (nullptr for output values) + Value* pDefaultValue; + }; + + /// Code generation result. + struct Result + { + /// Unique hash for the generated code. + size_t functionHash; + /// \brief The generated GLSL code for material setup function. + /// \desc The setup function takes a set of material inputs from CPU and outputs the + /// parameters of the standard surface material, based on a materialX network. + string materialSetupCode; + /// \desc The name of the setup function . + string setupFunctionName; + /// \brief Vector of input and output arguments used by the setup function. + vector argumentsUsed; + /// \brief Vector of default values for the material inputs. + vector defaultInputValues; + }; + + /// Maps a MaterialX parameter (either input or output) to an argument in the generated setup + /// function. Returns empty string to indicate parameter is not mapped. + using ParameterMappingFunction = function; + + /// \param surfaceShaderNodeCategory The surface shader category to code generate material + /// inputs for. \param mtlxPath The search path for the materialX assets, including the library + /// assets in the 'libraries' folder. + BSDFCodeGenerator(const string& mtlxPath = "./MaterialX", const string& = "standard_surface"); + + ~BSDFCodeGenerator(); + + /// \desc Run the code generator on a MaterialX document. + /// \param.document MaterialX XML document string. + /// \param inputParameterMapper Function used to map a parameter in the MaterialX network an + /// input parameter in the setup function. \param outputParameterMapper Function used to map a + /// parameter in the MaterialX network an ouput parameter in the setup function. \param + /// pResultOut Code generation result output. + bool generate(const string& document, Result* pResultOut, + ParameterMappingFunction inputParameterMapper, + ParameterMappingFunction outputParameterMapper = nullptr, + const string& overrideDocumentName = ""); + + /// \desc Generate the shared GLSL definitions for all the documents generated by this + /// generator. Will clear the shared definitions. + /// \param pDefinitionCodeOut GLSL definition string. + int generateDefinitions(string* pDefinitionCodeOut); + + /// Clear the definition shader code, which is accumulated after each generateDefinitions call. + void clearDefinitions(); + + // Get the units used by materialX. + const Units& units() { return _units; } + +protected: + // Convert GLSL type string to Aurora type (asserts if conversion fails.) + IValues::Type glslTypeToAuroraType(const string glslType); + + // Convert materialX value to Aurora value (asserts if conversion fails.) + bool materialXValueToAuroraValue(Value* pValueOut, shared_ptr pMtlXValue); + + // Process a MaterialX shader input. + void processInput(MaterialX::ShaderInput* input, + shared_ptr pBSDFGenShader, const string& outputVariable, + string* pSourceOut); + + // Create the static MaterialX standard library. + void createStdLib(); + + // Static MaterialX standard library. + static shared_ptr s_pStdLib; + + // The GLSL shader generator used internally. + shared_ptr _pGenerator; + + // The shader context used internally. + unique_ptr _pGeneratorContext; + + // Current active material inputs and outputs. + map _activeOutputs; + map>> _activeInputs; + vector _activeInputNames; + vector _activeOutputNames; + + // Definition look-up. + map _definitionMap; + vector _definitions; + + // Suffix added to material inputs. + string _materialInputSuffix = "In"; + string _materialOutputSuffix = ""; + + // The surface shader category to code generate inputs for. + string _surfaceShaderNodeCategory; + + // The units for MaterialX; + Units _units; + + shared_ptr _unitSystem; + shared_ptr _unitRegistry; + set _processedNodes; + ParameterMappingFunction _defaultOutputMapper; + map _defaultOutputParamMapping; + string _topLevelShaderNodeName; + string _mtlxLibPath; + + ParameterMappingFunction _currentInputParameterMapper; + ParameterMappingFunction _currentOutputParameterMapper; +}; + +} // namespace MaterialXCodeGen + +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp new file mode 100644 index 0000000..f7aa4fe --- /dev/null +++ b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp @@ -0,0 +1,561 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "MaterialGenerator.h" + +#include +#include + +#include "MaterialBase.h" + +namespace Aurora +{ +namespace MaterialXCodeGen +{ + +static string mapMaterialXTextureName(const string& name) +{ + string mappedName = ""; + + // Any image input containing string 'diffuse' or 'base' is mapped to the + // base_color_image texture (e.g. "generic_diffuse_file" or "base_color_image".). + if (name.find("diffuse") != string::npos || name.find("base") != string::npos) + { + mappedName = "base_color_image"; + } + // Any image input containing string 'roughness' or 'specular' is mapped to the + // specular_roughness_image texture. + else if (name.find("roughness") != string::npos || name.find("specular") != string::npos) + { + mappedName = "specular_roughness_image"; + } + // Any image input containing string 'normal' or 'bump' is mapped to the + // normal_image texture. + else if (name.find("normal") != string::npos || name.find("bump") != string::npos) + { + mappedName = "normal_image"; + } + // Any image input containing string 'opacity', 'alpha', 'cutout', or 'transmission' + // is mapped to the opacity_image texture. + else if (name.find("opacity") != string::npos || name.find("alpha") != string::npos || + name.find("cutout") != string::npos || name.find("transmission") != string::npos) + { + mappedName = "opacity_image"; + } + // If nothing else matches, then map any image containing color to base_color_image + // texture. + else if (name.find("color") != string::npos) + { + mappedName = "base_color_image"; + } + + return mappedName; +} + +// Extremely primitive GLSL-to-HLSL conversion function, that handles cases not dealt with in shader +// code prefix. +// TODO: This is a very basic placeholder, we need a better solution for this. +static string GLSLToHLSL(const string& glslStr) +{ + // Replace vectors initialized by scalar 0.0. + string hlslStr = regex_replace(glslStr, regex("vec4\\(0.0\\)"), "vec4(0.0,0.0,0.0,0.0)"); + hlslStr = regex_replace(hlslStr, regex("vec3\\(0.0\\)"), "vec3(0.0,0.0,0.0)"); + hlslStr = regex_replace(hlslStr, regex("vec2\\(0.0\\)"), "vec2(0.0,0.0)"); + + // Replace GLSL float array initializers. + hlslStr = regex_replace(hlslStr, regex("float\\[.*\\]\\((.+)\\);\\n"), "{$1};\n"); + + return hlslStr; +} + +MaterialGenerator::MaterialGenerator(IRenderer* pRenderer, const string& mtlxFolder) : + _pRenderer(pRenderer) +{ + // Create code generator. + _pCodeGenerator = make_unique(mtlxFolder); + + // Map of MaterialX output parameters to Aurora material property. + // Used by PTRenderer::generateMaterialX. + // clang-format off + _materialXOutputParamMapping = + { + { "base", "base" }, + { "base_color", "baseColor" }, + { "coat", "coat" }, + { "coat_color", "coatColor" }, + { "coat_roughness", "coatRoughness" }, + { "coat_anisotropy", "coatAnisotropy" }, + { "coat_rotation", "coatRotation" }, + { "coat_IOR", "coatIOR" }, + { "diffuse_roughness", "diffuseRoughness" }, + { "metalness", "metalness" }, + { "specular", "specular" }, + { "specular_color", "specularColor" }, + { "specular_roughness", "specularRoughness" }, + { "specular_anisotropy", "specularAnisotropy" }, + { "specular_IOR", "specularIOR" }, + { "specular_rotation", "specularRotation" }, + { "transmission", "transmission" }, + { "transmission_color", "transmissionColor" }, + { "subsurface", "subsurface" }, + { "subsurfaceColor", "subsurfaceColor" }, + { "subsurface_scale", "subsurfaceScale" }, + { "subsurface_radius", "subsurfaceRadius" }, + { "sheen", "sheen" }, + { "sheen_color", "sheenColor" }, + { "sheen_roughness", "sheenRoughness" }, + { "coat", "coat" }, + { "coat_color", "coatColor" }, + { "coat_roughness", "coatRoughness" }, + { "coat_anisotropy", "coatAnisotropy" }, + { "coat_rotation", "coatRotation" }, + { "coat_IOR", "coatIOR" }, + { "coat_affect_color", "coatAffectColor" }, + { "coat_affect_roughness", "coatAffectRoughness" }, + { "opacity", "opacity" }, + { "base_color_image_scale", "baseColorTexTransform.scale" }, + { "base_color_image_offset", "baseColorTexTransform.offset" }, + { "base_color_image_rotation", "baseColorTexTransform.rotation" }, + { "opacity_image_scale", "opacityTexTransform.scale" }, + { "opacity_image_offset", "opacityTexTransform.offset" }, + { "opacity_image_rotation", "opacityTexTransform.rotation" }, + { "normal_image_scale", "normalTexTransform.scale" }, + { "normal_image_offset", "normalTexTransform.offset" }, + { "normal_image_rotation", "normalTexTransform.rotation" }, + { "specular_roughness_image_scale", "specularRoughnessTexTransform.scale" }, + { "specular_roughness_image_offset", "specularRoughnessTexTransform.offset" }, + { "specular_roughness_image_rotation", "specularRoughnessTexTransform.rotation" }, + { "thin_walled", "thinWalled" } + }; + // clang-format on +} + +bool MaterialGenerator::generate( + const string& document, map* pDefaultValuesOut, MaterialTypeSource& sourceOut) +{ + // Create code generator result struct. + MaterialXCodeGen::BSDFCodeGenerator::Result res; + + // The hardcoded inputs are added manually to the generated HLSL and passed to the + // code-generated set up function. This is filled in by the inputMapper lamdba when the code is + // generated. + set hardcodedInputs; + + // Callback function to map MaterialX inputs to Aurora uniforms and textures. + // This function will be called for each input parameter in the MaterialX document that is + // connected to a mapped output. Each "top level" input parameter will be prefixed with the top + // level shader name for the MaterialX document, other input parameters will be named for the + // node graph they are part of (e.g. generic_diffuse) + MaterialXCodeGen::BSDFCodeGenerator::ParameterMappingFunction inputMapper = + [&](const string& name, Aurora::IValues::Type type, const string& topLevelShaderName) { + // Top-level inputs are prefixed with the top level material name. + if (name.size() > topLevelShaderName.size()) + { + // Strip the top level material name (and following underscore) from the input name. + string nameStripped = name.substr(topLevelShaderName.size() + 1); + + // If this is one of the standard surface inputs then map to that. + auto iter = _materialXOutputParamMapping.find(nameStripped); + if (iter != _materialXOutputParamMapping.end()) + return iter->second; + } + + // By default return an empty string (which will leave input unmapped.) + string mappedName = ""; + + // Map anything containing the string 'unit_unit_to' to the distance unit. + if (name.find("unit_unit_to") != string::npos) + { + mappedName = "distanceUnit"; + } + + // Map any image inputs using a heuristic that should guess the right texture mapping + // for any textures we care about. + if (type == Aurora::IValues::Type::Image) + { + mappedName = mapMaterialXTextureName(name); + if (mappedName.empty()) + { + // Leaving a texture unassigned will result in a HLSL compilation error, so map + // unrecognized textures to gBaseColorTexture and print a warning. This avoids + // the shader compile error, but will render incorrectly. + AU_WARN("Unrecognized texture variable %s, setting to gBaseColorTexture.", + name.c_str()); + mappedName = "base_color_image"; + } + } + + // Map any int parameters as sampler properties. + if (type == Aurora::IValues::Type::Int) + { + string mappedTextureName = mapMaterialXTextureName(name); + + // Any int input that can be mapped to hardcoded texture name is treated as address + // mode. + // TODO: Only base color and opacity image currently supports samplers. + if (!mappedTextureName.empty() && + (mappedTextureName.compare("base_color_image") == 0 || + mappedTextureName.compare("opacity_image") == 0)) + { + // Map uaddressmode to the image's AddressModeU property. + if (name.find("uaddressmode") != string::npos) + mappedName = mappedTextureName + "_sampler_uaddressmode"; + // Map uaddressmode to the image's AddressModeV property. + else if (name.find("vaddressmode") != string::npos) + mappedName = mappedTextureName + "_sampler_vaddressmode"; + } + } + if (type == Aurora::IValues::Type::Float2) + { + string mappedTextureName = mapMaterialXTextureName(name); + + // Any vec2 input that can be mapped to hardcoded texture name is treated as texture + // transform property. + if (!mappedTextureName.empty()) + { + // Map uv_offset to the image transform's offset property. + if (name.find("uv_offset") != string::npos) + mappedName = mappedTextureName + "_offset"; + // Map uv_scale to the image transform's scale property. + else if (name.find("uv_scale") != string::npos) + mappedName = mappedTextureName + "_scale"; + } + } + if (type == Aurora::IValues::Type::Float) + { + string mappedTextureName = mapMaterialXTextureName(name); + + // Any float input that can be mapped to hardcoded texture name as texture rotation. + if (!mappedTextureName.empty()) + { + // Map uv_scale to the image transform's rotaiton property. + if (name.find("rotation_angle") != string::npos) + mappedName = mappedTextureName + "_rotation"; + } + } + + // Add to the hardcoded inputs. + if (!mappedName.empty()) + hardcodedInputs.insert(mappedName); + + // Return the mapped name + return mappedName; + }; + + // Set to true by lambda if the normal has been modified in code-generated material setup. + bool modifiedNormal = false; + + // Callback function to map MaterialX setup outputs to the Aurora material parameter. + MaterialXCodeGen::BSDFCodeGenerator::ParameterMappingFunction outputMapper = + [&](const string& name, Aurora::IValues::Type /*type*/, + const string& /*topLevelShaderName*/) { + // If there is direct mapping to aurora material use that. + auto iter = _materialXOutputParamMapping.find(name); + if (iter != _materialXOutputParamMapping.end()) + return iter->second; + + // If the setup code has generated a normal, set the modified flag. + if (name.compare("normal") == 0) + { + modifiedNormal = true; + return name; + } + + // Otherwise don't code generate this output. + return string(""); + }; + + // Remap the parameters *back* to the materialX name, as the material inputs are in snake case, + // not camel case. + // TODO: We should unify camel-vs-snake case for material parameters. + map remappedInputs; + for (auto iter : _materialXOutputParamMapping) + { + remappedInputs[iter.second] = iter.first; + } + + // Run the code generator overriding materialX document name, so that regardless of the name in + // the document, the generated HLSL is based on a hardcoded name string for caching purposes. + // NOTE: This will immediate run the code generator and invoke the inputMapper and outputMapper + // function populate hardcodedInputs and set modifiedNormal. + if (!_pCodeGenerator->generate(document, &res, inputMapper, outputMapper, "MaterialXDocument")) + { + // Fail if code generation fails. + // TODO: Proper error handling here. + AU_ERROR("Failed to generate MaterialX code."); + return false; + } + + // Map of samplers to collate sampler properties. + map samplerProperties; + + // Get the default values. + pDefaultValuesOut->clear(); + for (int i = 0; i < res.argumentsUsed.size(); i++) + { + auto arg = res.argumentsUsed[i]; + // Code generator returns input and output values, we only care about inputs. + if (!arg.isOutput) + { + // For all standard surface inputs, remap back to snake case. + string remappedName = arg.name; + + // We need to collate the seperate sampler properties (vaddressmode or uaddressmode) + // into sampler object. + bool isSamplerParam = false; + if (arg.name.find("uaddressmode") != string::npos || + arg.name.find("vaddressmode") != string::npos) + { + isSamplerParam = true; + + // Compute sampler name from property name. + string samplerName = arg.name; + samplerName.erase(samplerName.length() - string("_uaddressmode").length()); + + // If no sampler properties, create one. + if (samplerProperties.find(samplerName) == samplerProperties.end()) + { + samplerProperties[samplerName] = {}; + } + + // Map int value to address mode. + // These int values are abrirarily defined in MaterialX source: + // source\MaterialXRender\ImageHandler.h + // TODO: Make this less error prone. + string addressMode = Names::AddressModes::kWrap; + switch (arg.pDefaultValue->asInt()) + { + case 1: + addressMode = Names::AddressModes::kClamp; + break; + case 2: + addressMode = Names::AddressModes::kWrap; + break; + case 3: + addressMode = Names::AddressModes::kMirror; + break; + default: + break; + } + + // Set the sampler properties for U/V address mode. + if (arg.name.find("uaddressmode") != string::npos) + { + samplerProperties[samplerName][Names::SamplerProperties::kAddressModeU] = + addressMode; + } + else if (arg.name.find("vaddressmode") != string::npos) + { + samplerProperties[samplerName][Names::SamplerProperties::kAddressModeV] = + addressMode; + } + } + + // Don't map individual sampler parameters to default values, as they are added as + // sampler objects below. + if (!isSamplerParam) + { + // Remap if needed. + auto hardcodedIter = hardcodedInputs.find(arg.name); + if (hardcodedIter != hardcodedInputs.end()) + { + // If this is a hard-oded input, use the hardcoded name directly. + remappedName = arg.name; + } + else + { + // For standard surface inputs, remap back to snake case. + remappedName = remappedInputs[arg.name]; + } + + // Add the default value to the output map. + pDefaultValuesOut->insert(make_pair(remappedName, *arg.pDefaultValue)); + } + } + } + + // Create sampler objects for the sampler properties. + // TODO: Cache these to avoid unnessacary sampler objects. + for (auto iter = samplerProperties.begin(); iter != samplerProperties.end(); iter++) + { + pDefaultValuesOut->insert( + make_pair(iter->first, _pRenderer->createSamplerPointer(iter->second))); + } + + // Create material name from the hash provided by code generator. + string materialName = "MaterialX_" + Foundation::sHash(res.functionHash); + + // Begin with the setup code (converted to HLSL) + string generatedMtlxSetupFunction = GLSLToHLSL(res.materialSetupCode); + + // Create a wrapper function that is called by the ray hit entry point to initialize material. + generatedMtlxSetupFunction += + "\nMaterial initializeMaterial(ShadingData shading, float3x4 objToWorld, out float3 " + "materialNormal, out bool " + "isGeneratedNormal) {\n"; + + // Fill in the global vertexData struct (used by generated code to access vertex attributes). + generatedMtlxSetupFunction += "\tvertexData = shading;\n"; + + // Fill in the isGeneratedNormal output (set to true if the code-generated setup function + // generated a normal) + generatedMtlxSetupFunction += "\tisGeneratedNormal = " + to_string(modifiedNormal) + ";\n"; + + // The hardcoded initializeDefaultMaterial() function requires a normal parameter, this should + // never be used here. + generatedMtlxSetupFunction += + "\nfloat3 unusedObjectSpaceNormal = shading.normal; bool unusedIsGeneratedNormal;\n"; + + // Add code to call default initialize material function. + // NOTE: Most of this will get overwritten, but the compiler should be clever enough to know + // that and remove dead code. + generatedMtlxSetupFunction += + "\tMaterial material = initializeDefaultMaterial(shading, ObjectToWorld3x4(), " + "unusedObjectSpaceNormal, " + "unusedIsGeneratedNormal);\n"; + + // MaterialX accepts addresmode parameters as dummy integers that are passed down to the shader + // code, but then never used. So we need to define them as arbritary ints to avoid compile + // errors. + // TODO: Less hacky way to do this. + if (hardcodedInputs.find("base_color_image_sampler_uaddressmode") != hardcodedInputs.end()) + { + generatedMtlxSetupFunction += "\tint base_color_image_sampler_uaddressmode = 0;\n"; + } + if (hardcodedInputs.find("base_color_image_sampler_vaddressmode") != hardcodedInputs.end()) + { + generatedMtlxSetupFunction += "\tint base_color_image_sampler_vaddressmode = 0;\n"; + } + if (hardcodedInputs.find("opacity_image_sampler_uaddressmode") != hardcodedInputs.end()) + { + generatedMtlxSetupFunction += "\tint opacity_image_sampler_uaddressmode = 0;\n"; + } + if (hardcodedInputs.find("opacity_image_sampler_vaddressmode") != hardcodedInputs.end()) + { + generatedMtlxSetupFunction += "\tint opacity_image_sampler_vaddressmode = 0;\n"; + } + + // Add code to create local texture variables for the textures used by generated code. + // TODO: Add more and be more rigorous about finding which textures are used. + if (hardcodedInputs.find("base_color_image") != hardcodedInputs.end()) + { + // Ensure the correct sampler is used for the base color sampler (not default sampler). + generatedMtlxSetupFunction += + "\tsampler2D base_color_image = createSampler2D(gBaseColorTexture, " + "gBaseColorSampler);\n"; + } + if (hardcodedInputs.find("specular_roughness_image") != hardcodedInputs.end()) + { + generatedMtlxSetupFunction += + "\tsampler2D specular_roughness_image = createSampler2D(gSpecularRoughnessTexture, " + "gDefaultSampler);\n"; + } + if (hardcodedInputs.find("normal_image") != hardcodedInputs.end()) + { + generatedMtlxSetupFunction += + "\tsampler2D normal_image = createSampler2D(gNormalTexture, " + "gDefaultSampler);\n"; + } + if (hardcodedInputs.find("opacity_image") != hardcodedInputs.end()) + { + // Ensure the correct sampler is used for the opacity sampler (not default sampler). + generatedMtlxSetupFunction += + "\tsampler2D opacity_image = createSampler2D(gOpacityTexture, " + "gOpacitySampler);\n"; + } + + // Use the define DISTANCE_UNIT which is set in the shader library options. + generatedMtlxSetupFunction += "\tint distanceUnit = DISTANCE_UNIT;\n"; + + // Add code to call the generate setup function. + generatedMtlxSetupFunction += "\t" + res.setupFunctionName + "(\n"; + + // Add code for all the required arguments. + for (int i = 0; i < res.argumentsUsed.size(); i++) + { + // Current argument. + auto arg = res.argumentsUsed[i]; + + // Append comma if needed. + if (i > 0) + generatedMtlxSetupFunction += ",\n"; + + if (arg.isOutput) + { + if (arg.name.compare("normal") == 0) + { + // Output the generated normal straight into the materialNormal output parameter. + generatedMtlxSetupFunction += "\t\tmaterialNormal"; + } + else + { + // Output arguments are written to the material struct. + generatedMtlxSetupFunction += "\t\tmaterial." + arg.name; + } + } + else + { + if (hardcodedInputs.find(arg.name) != hardcodedInputs.end()) + { + if (_materialXOutputParamMapping.find(arg.name) != + _materialXOutputParamMapping.end()) + { + generatedMtlxSetupFunction += + "\t\tgMaterialConstants." + _materialXOutputParamMapping[arg.name]; + } + else + { + // Hardcoded inputs are specified in the HLSL above and passed in directly. + generatedMtlxSetupFunction += "\t\t" + arg.name; + } + } + else + { + // Other arguments are read from constant buffer transfered from CPU. + generatedMtlxSetupFunction += "\t\tgMaterialConstants." + arg.name; + } + } + } + + // Finish function call. + generatedMtlxSetupFunction += ");\n"; + + // Return the generated material struct. + generatedMtlxSetupFunction += "\treturn material;\n"; + + // Finish setup wrapper function. + generatedMtlxSetupFunction += "}\n"; + + // Output the material name + sourceOut.bsdf = ""; + sourceOut.setup = generatedMtlxSetupFunction; + sourceOut.name = materialName; + + return true; +} + +void MaterialGenerator::generateDefinitions(string& definitionHLSLOut) +{ + + // Generate the shared definitions used by all MaterialX material types. + string definitionGLSL; + _pCodeGenerator->generateDefinitions(&definitionGLSL); + + // Set the shared definitions; + definitionHLSLOut = (GLSLToHLSL(definitionGLSL)); +} + +} // namespace MaterialXCodeGen +} // namespace Aurora diff --git a/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h new file mode 100644 index 0000000..24a8277 --- /dev/null +++ b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h @@ -0,0 +1,52 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "BSDFCodeGenerator.h" + +BEGIN_AURORA + +struct MaterialTypeSource; + +namespace MaterialXCodeGen +{ + +class MaterialGenerator +{ +public: + MaterialGenerator(IRenderer* pRenderer, const string& mtlxFolder); + + /// Generate shader code for material. + bool generate(const string& document, map* pDefaultValuesOut, + MaterialTypeSource& sourceOut); + + // Generate shared definition functions. + void generateDefinitions(string& definitionHLSLOut); + + // Get the code generator used to generate material shader code. + MaterialXCodeGen::BSDFCodeGenerator& codeGenerator() { return *_pCodeGenerator; } + +private: + // Code generator used to generate MaterialX files. + unique_ptr _pCodeGenerator; + + // Mapping from MaterialX output parameter to Aurora standard surface parameter. + map _materialXOutputParamMapping; + + IRenderer* _pRenderer; +}; + +} // namespace MaterialXCodeGen + +END_AURORA diff --git a/Libraries/Aurora/Source/Properties.h b/Libraries/Aurora/Source/Properties.h new file mode 100644 index 0000000..f467e0d --- /dev/null +++ b/Libraries/Aurora/Source/Properties.h @@ -0,0 +1,535 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +// A value that can take on one of the available types, suitable for use in containers. +// NOTE: This could be replaced with std::variant or boost::variant if available. +class Value +{ +public: + /*** Lifetime Management ***/ + + // Constructor: begin with an undefined type (no value). + Value() : _type(IValues::IValues::Type::Undefined) {} + + // Destructor: clear the value. + ~Value() { clear(); } + + // Copy constructor: invoke the assignment operator. + Value(const Value& other) { *this = other; } + + /*** Operators ***/ + + // Assignment operator. + Value& operator=(const Value& other) + { + // This value must either be undefined, or match the incoming type, i.e. no changing types. + AU_ASSERT(_type == IValues::Type::Undefined || other._type == _type, + "Aurora Value cannot be assigned to a Value of a different type."); + + // Clear the value, and assign the new value, using placement new for non-trivial storage + // types. + clear(); + _type = other._type; + switch (_type) + { + case IValues::Type::Boolean: + _boolean = other._boolean; + break; + case IValues::Type::Int: + _int = other._int; + break; + case IValues::Type::Float: + _float = other._float; + break; + case IValues::Type::Float2: + new (&_float2) vec2(other._float2); + break; + case IValues::Type::Float3: + new (&_float3) vec3(other._float3); + break; + case IValues::Type::Matrix: + new (&_matrix) mat4(other._matrix); + break; + case IValues::Type::Image: + new (&_pImage) IImagePtr(other._pImage); + break; + case IValues::Type::Sampler: + new (&_pSampler) ISamplerPtr(other._pSampler); + break; + case IValues::Type::String: + new (&_string) std::string(other._string); + break; + default: + break; + } + + return *this; + } + + /*** Functions ***/ + + // Clears the value. + void clear() + { + // Call the destructor for non-trivial types that have a destructor. + switch (_type) + { + case IValues::Type::Image: + _pImage.~IImagePtr(); + break; + case IValues::Type::Sampler: + _pSampler.~ISamplerPtr(); + break; + default: + break; + } + + // Clear the type. + _type = IValues::Type::Undefined; + } + + // Gets the type of the value. + IValues::Type type() const { return _type; } + + // A macro for implementing a constructor (set) and get accessor for the specified type, storage + // type, and member name. +#define IMPLEMENT_TYPE_FOR_AURORA(TYPE, STORAGE_TYPE, MEMBER) \ + Value(STORAGE_TYPE value) : MEMBER(value), _type(IValues::Type::TYPE) {} \ + STORAGE_TYPE as##TYPE() const \ + { \ + AU_ASSERT(_type == IValues::Type::TYPE, "Invalid type"); \ + return MEMBER; \ + } + + // Implement the constructors (set) and get accessors for the supported types. + IMPLEMENT_TYPE_FOR_AURORA(Boolean, bool, _boolean) + IMPLEMENT_TYPE_FOR_AURORA(Int, int, _int) + IMPLEMENT_TYPE_FOR_AURORA(Float, float, _float) + IMPLEMENT_TYPE_FOR_AURORA(Float2, const vec2&, _float2) + IMPLEMENT_TYPE_FOR_AURORA(Float3, const vec3&, _float3) + IMPLEMENT_TYPE_FOR_AURORA(Matrix, const mat4&, _matrix) + IMPLEMENT_TYPE_FOR_AURORA(Image, const IImagePtr&, _pImage) + IMPLEMENT_TYPE_FOR_AURORA(Sampler, const ISamplerPtr&, _pSampler) + IMPLEMENT_TYPE_FOR_AURORA(String, const std::string&, _string) + +private: + /*** Private Variables ***/ + // NOTE: Together these variables and the functions above implement a tagged anonymous union. + + union + { + bool _boolean; + int _int; + float _float; + vec2 _float2; + vec3 _float3; + mat4 _matrix; + IImagePtr _pImage; + ISamplerPtr _pSampler; + std::string _string; + }; + + IValues::Type _type = IValues::Type::Undefined; +}; + +// A description of a property, with a name, type, and default value. +class Property +{ +public: + /*** Lifetime Management ***/ + Property() : _type(IValues::Type::Undefined) {} + Property(const string& name, size_t index, const Value& defaultValue) : + _name(name), _index(index), _type(defaultValue.type()), _default(defaultValue) + { + } + + /*** Functions ***/ + + // Gets the index assigned to the property, used for tracking it in a property set. + size_t index() const { return _index; } + + // Get the type of the property. + IValues::Type type() const { return _type; } + + // Gets the default value of the property, which must have the same type as the property. + const Value& defaultValue() const { return _default; } + + // Gets the property's name. + const string& name() const { return _name; } + +private: + /*** Private Variables ***/ + + string _name; + size_t _index; + IValues::Type _type; + Value _default; +}; + +// A collection of properties, that can be retrieved by name. +class PropertySet : private unordered_map +{ +public: + /*** Functions ***/ + + // Adds a property to the property set, with the specified name and default value. + void add(const string& name, const Value& defaultValue) + { + (*this)[name] = Property(name, count(), defaultValue); + } + + // Gets the property with the specified name, which must exist in the property set. + const Property& get(CONST string& name) const + { + AU_ASSERT(find(name) != end(), "An invalid property name (%s) was specified", name.c_str()); + return at(name); + } + + // Gets the property with the specified name, which must exist in the property set. + Property& get(CONST string& name) + { + AU_ASSERT(find(name) != end(), "An invalid property name (%s) was specified", name.c_str()); + return at(name); + } + + // Does set contain this named value? + bool hasValue(const string& name) const { return find(name) != end(); } + + // Gets the number of properties in the property set. + size_t count() const { return size(); } + + unordered_map::const_iterator begin() const + { + return unordered_map::begin(); + } + unordered_map::const_iterator end() const + { + return unordered_map::end(); + } + +private: + friend class ValueSet; +}; +MAKE_AURORA_PTR(PropertySet); + +// A collection of values for a property set. +class FixedValueSet : private vector +{ + +public: + /*** Lifetime Management ***/ + + // Constructor. + FixedValueSet(const PropertySetPtr& pPropertySet) + { + AU_ASSERT(pPropertySet, "The property set cannot be null."); + _pPropertySet = pPropertySet; + resize(pPropertySet->count()); + } + + // Sets a value for the property with the specified name. + void setValue(const string& name, const Value& value) + { + Property& property = _pPropertySet->get(name); + bool typesMatch = value.type() == property.type(); + AU_ASSERT(typesMatch, "The property %s exists, but the type does not match.", name.c_str()); + at(property.index()) = value; + } + + // A macro for implementing a get value accessor for the specified type and storage type. + // NOTE: When an value does not have a assignment (i.e. has the Undefined type), the get value + // accessor returns the property default value instead. +#define MAKE_VALUE_GET_FUNC(TYPE, STORAGE_TYPE) \ + STORAGE_TYPE as##TYPE(const string& name) const \ + { \ + const Property& property = _pPropertySet->get(name); \ + const Value& value = at(property.index()); \ + return value.type() == IValues::Type::Undefined ? property.defaultValue().as##TYPE() \ + : value.as##TYPE(); \ + } + + // Implement the get value accessors for the supported types. + MAKE_VALUE_GET_FUNC(Boolean, bool) + MAKE_VALUE_GET_FUNC(Int, int) + MAKE_VALUE_GET_FUNC(Float, float) + MAKE_VALUE_GET_FUNC(Float2, const vec2&) + MAKE_VALUE_GET_FUNC(Float3, const vec3&) + MAKE_VALUE_GET_FUNC(Matrix, const mat4&) + MAKE_VALUE_GET_FUNC(Image, IImagePtr) + MAKE_VALUE_GET_FUNC(Sampler, ISamplerPtr) + MAKE_VALUE_GET_FUNC(String, const std::string&) + + // Clears the value for the property with the specified name. + void clearValue(const string& name) { at(_pPropertySet->get(name).index()).clear(); } + + unordered_map::const_iterator begin() const { return _pPropertySet->begin(); } + unordered_map::const_iterator end() const { return _pPropertySet->end(); } + + IValues::Type type(const string& name) { return _pPropertySet->get(name).type(); } + + bool hasValue(const string& name) const { return _pPropertySet->hasValue(name); } + +protected: + /*** Private Variables ***/ + + PropertySetPtr _pPropertySet; + + friend ostream& operator<<(ostream& os, const FixedValueSet& values); +}; + +// A collection of values for a property set. The property set is initially empty and can be added +// to at any time. +class DynamicValueSet : public unordered_map +{ +public: + /*** Lifetime Management ***/ + + // Constructor. + DynamicValueSet() {} + + // A macro for implementing a get value accessor for the specified type and storage type. + // NOTE: When an value does not have a assignment (i.e. has the Undefined type), the get value + // accessor returns the property default value instead. +#define MAKE_VALUE_GET_FUNC_DYNAMIC(TYPE, STORAGE_TYPE) \ + STORAGE_TYPE as##TYPE(const string& name) const \ + { \ + AU_ASSERT(this->find(name) != this->end(), "Invalid property name."); \ + const Value& value = at(name); \ + return value.as##TYPE(); \ + } + + // Implement the get value accessors for the supported types. + MAKE_VALUE_GET_FUNC_DYNAMIC(Boolean, bool) + MAKE_VALUE_GET_FUNC_DYNAMIC(Int, int) + MAKE_VALUE_GET_FUNC_DYNAMIC(Float, float) + MAKE_VALUE_GET_FUNC_DYNAMIC(Float2, const vec2&) + MAKE_VALUE_GET_FUNC_DYNAMIC(Float3, const vec3&) + MAKE_VALUE_GET_FUNC_DYNAMIC(Matrix, const mat4&) + MAKE_VALUE_GET_FUNC_DYNAMIC(Image, IImagePtr) + MAKE_VALUE_GET_FUNC_DYNAMIC(Sampler, ISamplerPtr) + MAKE_VALUE_GET_FUNC_DYNAMIC(String, const std::string&) + + // Sets a value for the property with the specified name. + void setValue(const string& name, const Value& value) + { + bool typesMatch = (*this)[name].type() == IValues::Type::Undefined || + value.type() == (*this)[name].type(); + AU_ASSERT(typesMatch, "The property %s exists, but the type does not match.", name.c_str()); + (*this)[name] = value; + } + + bool hasValue(const std::string& name) const { return find(name) != end(); } + + // Clears the value for the property with the specified name. + void clearValue(const string& name) { this->erase(name); } + + // Get the type of the provided property. + IValues::Type type(const string& name) { return this->at(name).type(); } + +protected: +}; + +inline ostream& writeToStream(ostream& os, const Value& value) +{ + switch (value.type()) + { + case IValues::Type::Boolean: + os << (value.asBoolean() ? "true" : "false"); + break; + case IValues::Type::Float: + os << value.asFloat(); + break; + case IValues::Type::Int: + os << value.asInt(); + break; + case IValues::Type::Float3: + os << value.asFloat3().x << ", " << value.asFloat3().y << ", " << value.asFloat3().z; + break; + case IValues::Type::Float2: + os << value.asFloat2().x << ", " << value.asFloat2().y; + break; + case IValues::Type::Matrix: + os << "{ " << value.asMatrix()[0].x << ", " << value.asMatrix()[0].y << ", " + << value.asMatrix()[0].z << ", " << value.asMatrix()[0].w << " }, "; + os << "{ " << value.asMatrix()[1].x << ", " << value.asMatrix()[1].y << ", " + << value.asMatrix()[1].z << ", " << value.asMatrix()[1].w << " }, "; + os << "{ " << value.asMatrix()[2].x << ", " << value.asMatrix()[2].y << ", " + << value.asMatrix()[2].z << ", " << value.asMatrix()[2].w << " }, "; + os << "{ " << value.asMatrix()[3].x << ", " << value.asMatrix()[3].y << ", " + << value.asMatrix()[3].z << ", " << value.asMatrix()[3].w << " }"; + break; + case IValues::Type::Image: + os << "Image:0x" << value.asImage().get(); + break; + case IValues::Type::Sampler: + os << "Sampler:0x" << value.asSampler().get(); + break; + case IValues::Type::String: + os << value.asString(); + break; + default: + os << "Undefined"; + break; + } + + os << std::endl; + return os; +} + +inline ostream& operator<<(ostream& os, const FixedValueSet& values) +{ + os.precision(5); + os << std::fixed; + + for (auto it = values._pPropertySet->begin(); it != values._pPropertySet->end(); it++) + { + auto prop = it->second; + auto value = values.at(prop.index()); + os << prop.name() << " = "; + if (value.type() == IValues::Type::Undefined) + value = prop.defaultValue(); + + writeToStream(os, value); + } + return os; +} + +inline ostream& operator<<(ostream& os, const DynamicValueSet& values) +{ + os.precision(5); + os << std::fixed; + + for (auto it = values.begin(); it != values.end(); it++) + { + os << it->first << " = "; + writeToStream(os, it->second); + } + return os; +} + +// An implementation for IValues, based on a ValueSet member. +template +class ValuesImpl : public IValues +{ +public: + /*** Lifetime Management ***/ + ValuesImpl() {} + ValuesImpl(const PropertySetPtr& pPropertySet) : _values(pPropertySet) {}; + + /*** IValues Functions ***/ + + void setBoolean(const string& name, bool value) override + { + _values.setValue(name, value); + _bIsDirty = true; + } + + void setInt(const string& name, int value) override + { + _values.setValue(name, value); + _bIsDirty = true; + } + + void setFloat(const string& name, float value) override + { + _values.setValue(name, value); + _bIsDirty = true; + } + + void setFloat2(const string& name, const float* value) override + { + _values.setValue(name, make_vec2(value)); + _bIsDirty = true; + } + + void setFloat3(const string& name, const float* value) override + { + _values.setValue(name, make_vec3(value)); + _bIsDirty = true; + } + + void setMatrix(const string& name, const float* value) override + { + _values.setValue(name, make_mat4(value)); + _bIsDirty = true; + } + + void setImage(const string& name, const IImagePtr& value) override + { + _values.setValue(name, value); + _bIsDirty = true; + } + + void setSampler(const string& name, const ISamplerPtr& value) override + { + _values.setValue(name, value); + _bIsDirty = true; + } + + void setString(const string& name, const std::string& value) override + { + _values.setValue(name, value); + _bIsDirty = true; + } + + void clearValue(const string& name) override + { + _values.clearValue(name); + _bIsDirty = true; + } + + bool hasValue(const string& name) const { return _values.hasValue(name); } + + bool asBoolean(const string& name) const { return _values.asBoolean(name); } + + int asInt(const string& name) const { return _values.asInt(name); } + + float asFloat(const string& name) const { return _values.asFloat(name); } + + const vec3& asFloat3(const string& name) const { return _values.asFloat3(name); } + + const mat4& asMatrix(const string& name) const { return _values.asMatrix(name); } + + IImagePtr asImage(const string& name) const { return _values.asImage(name); } + + ISamplerPtr asSampler(const string& name) const { return _values.asSampler(name); } + + const string& asString(const string& name) const { return _values.asString(name); } + + IValues::Type type(const string& name) override { return _values.type(name); } + +protected: + T _values; + bool _bIsDirty = true; +}; + +// An implementation for IValues, based on a ValueSet member. +class FixedValues : public ValuesImpl +{ +public: + FixedValues() = delete; + FixedValues(const PropertySetPtr& pPropertySet) : ValuesImpl(pPropertySet) {} +}; + +// An implementation for IValues, based on a ValueSet member. +class DynamicValues : public ValuesImpl +{ +public: + DynamicValues() : ValuesImpl() {} +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/RendererBase.cpp b/Libraries/Aurora/Source/RendererBase.cpp new file mode 100644 index 0000000..87561e3 --- /dev/null +++ b/Libraries/Aurora/Source/RendererBase.cpp @@ -0,0 +1,250 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "AssetManager.h" +#include "RendererBase.h" +#include "SceneBase.h" + +BEGIN_AURORA + +// Create or get the property set (options) for the renderer. +static PropertySetPtr gpPropertySet; +static PropertySetPtr propertySet() +{ + if (gpPropertySet) + { + return gpPropertySet; + } + + gpPropertySet = make_shared(); + + // Add the renderer options to the property set. + gpPropertySet->add(kLabelIsResetHistoryEnabled, false); + gpPropertySet->add(kLabelIsDenoisingEnabled, false); + gpPropertySet->add(kLabelIsDiffuseOnlyEnabled, false); + gpPropertySet->add(kLabelDebugMode, 0); + gpPropertySet->add(kLabelMaxLuminance, 1000.0f); + gpPropertySet->add(kLabelTraceDepth, 5); + gpPropertySet->add(kLabelIsToneMappingEnabled, false); + gpPropertySet->add(kLabelIsGammaCorrectionEnabled, true); + gpPropertySet->add(kLabelIsAlphaEnabled, false); + gpPropertySet->add(kLabelBrightness, vec3(1.0f, 1.0f, 1.0f)); + gpPropertySet->add(kLabelUnits, string("centimeter")); + gpPropertySet->add(kLabelImportanceSamplingMode, kImportanceSamplingModeMIS); + gpPropertySet->add(kLabelIsFlipImageYEnabled, true); + gpPropertySet->add(kLabelIsReferenceBSDFEnabled, false); + gpPropertySet->add(kLabelIsOpaqueShadowsEnabled, false); + + return gpPropertySet; +} + +RendererBase::RendererBase(uint32_t taskCount) : FixedValues(propertySet()), _taskCount(taskCount) +{ + // Initialize the asset manager. + _pAssetMgr = make_unique(); + _pAssetMgr->enableVerticalFlipOnImageLoad(_values.asBoolean(kLabelIsFlipImageYEnabled)); + + assert(taskCount > 0); +} + +void RendererBase::setOptions(const Properties& options) +{ + propertiesToValues(options, *this); +} + +void RendererBase::setCamera( + const mat4& view, const mat4& projection, float focalDistance, float lensRadius) +{ + assert(focalDistance > 0.0f && lensRadius >= 0.0f); + + _cameraView = view; + _cameraProj = projection; + _focalDistance = focalDistance; + _lensRadius = lensRadius; +} + +void RendererBase::setCamera( + const float* view, const float* proj, float focalDistance, float lensRadius) +{ + assert(focalDistance > 0.0f && lensRadius >= 0.0f); + + _cameraView = make_mat4(view); + _cameraProj = make_mat4(proj); + _focalDistance = focalDistance; + _lensRadius = lensRadius; +} + +// Note that this handles strings differently than the implementation in SceneBase. +void RendererBase::propertiesToValues(const Properties& properties, IValues& values) +{ + for (auto& property : properties) + { + switch (property.second.type) + { + default: + case PropertyValue::Type::Undefined: + values.clearValue(property.first); + break; + + case PropertyValue::Type::Bool: + values.setBoolean(property.first, property.second._bool); + break; + + case PropertyValue::Type::Int: + values.setInt(property.first, property.second._int); + break; + + case PropertyValue::Type::Float: + values.setFloat(property.first, property.second._float); + break; + + case PropertyValue::Type::Float2: + AU_WARN("Cannot convert Float2 property."); + break; + + case PropertyValue::Type::Float3: + values.setFloat3(property.first, value_ptr(property.second._float3)); + break; + + case PropertyValue::Type::Float4: + AU_WARN("Cannot convert Float4 property."); + break; + + case PropertyValue::Type::String: + values.setString(property.first, property.second._string); + break; + + case PropertyValue::Type::Matrix4: + values.setMatrix(property.first, value_ptr(property.second._matrix4)); + break; + } + } +} + +bool RendererBase::updateFrameDataGPUStruct(FrameData* pStaging) +{ + FrameData frameData; + + // Get the camera properties: + // - The view-projection matrix. + // - The inverse view matrix, transposed. + // - The dimensions of the view (in world units) at a distance of 1.0 from the camera, derived + // from the [0,0] and [1,1] elements of the projection matrix. + // - Whether the camera uses an orthographic projection, defined by the [3,3] element of the + // projection matrix. + // - Focal distance and lens radius, for depth of field. + frameData.cameraViewProj = _cameraProj * _cameraView; + frameData.cameraInvView = transpose(inverse(_cameraView)); + frameData.viewSize = vec2(2.0f / _cameraProj[0][0], 2.0f / _cameraProj[1][1]); + frameData.isOrthoProjection = _cameraProj[3][3] == 1.0f; + frameData.focalDistance = _focalDistance; + frameData.lensRadius = _lensRadius; + + // Get the scene size, specifically the maximum distance between any two points in the scene. + // This is computed as the distance between the min / max corners of the bounding box. + const Foundation::BoundingBox& bounds = _pScene->bounds(); + frameData.sceneSize = glm::length(bounds.max() - bounds.min()); + + // Get the light properties. + // NOTE: The light direction is inverted, as expected by the shaders. The light size is + // converted from a diameter in radians to the cosine of the radius. + frameData.lightDir = -_pScene->lightDirection(); + frameData.lightColorAndIntensity = make_vec4(_pScene->lightColor()); + frameData.lightColorAndIntensity.w = _pScene->lightIntensity(); + frameData.lightCosRadius = cos(0.5f * _pScene->lightAngularDiameter()); + + int debugMode = _values.asInt(kLabelDebugMode); + int traceDepth = _values.asInt(kLabelTraceDepth); + traceDepth = glm::max(1, glm::min(kMaxTraceDepth, traceDepth)); + frameData.traceDepth = traceDepth; + frameData.isDenoisingEnabled = _values.asBoolean(kLabelIsDenoisingEnabled) ? 1 : 0; + frameData.isOpaqueShadowsEnabled = _values.asBoolean(kLabelIsOpaqueShadowsEnabled) ? 1 : 0; + frameData.isDiffuseOnlyEnabled = _values.asBoolean(kLabelIsDiffuseOnlyEnabled) ? 1 : 0; + frameData.maxLuminance = _values.asFloat(kLabelMaxLuminance); + frameData.isDisplayErrorsEnabled = debugMode == kDebugModeErrors ? 1 : 0; + + // If there are no changes compared local CPU copy, then do nothing and return false. + if (memcmp(&_frameData, &frameData, sizeof(FrameData)) == 0) + return false; // No changes. + + // Set the local copy of frame data to use for next comparison. + _frameData = frameData; + + // If staging buffer pointer was passed in, copy to that. + if (pStaging) + *pStaging = frameData; + + return true; +} + +bool RendererBase::updatePostProcessingGPUStruct(PostProcessing* pStaging) +{ + PostProcessing settings; + + // Compute the scene range, i.e. the near and far distance of the scene bounding box from the + // current view. Since the view has a direction along the -Z axis, the range is determined as + // [-maxZ, -minZ]. + Foundation::BoundingBox viewBox = _pScene->bounds().transform(_cameraView); + vec2 sceneRange(-viewBox.max().z, -viewBox.min().z); + + // Prepare post-processing settings. + int debugMode = _values.asInt(kLabelDebugMode); + settings.debugMode = glm::max(0, glm::min(debugMode, kMaxDebugMode)); + settings.isDenoisingEnabled = _values.asBoolean(kLabelIsDenoisingEnabled) ? 1 : 0; + settings.isToneMappingEnabled = _values.asBoolean(kLabelIsToneMappingEnabled); + settings.isGammaCorrectionEnabled = _values.asBoolean(kLabelIsGammaCorrectionEnabled); + settings.isAlphaEnabled = _values.asBoolean(kLabelIsAlphaEnabled); + settings.brightness = _values.asFloat3(kLabelBrightness); + settings.range = sceneRange; + + // If there are no changes compared local CPU copy, then do nothing and return false. + if (memcmp(&_postProcessingData, &settings, sizeof(PostProcessing)) == 0) + return false; // No changes. + + // Update local CPU copy. + _postProcessingData = settings; + + // Update staging buffer, if one provided. + if (pStaging) + *pStaging = settings; + + return true; +} + +bool RendererBase::updateAccumulationGPUStruct(uint32_t sampleIndex, Accumulation* pStaging) +{ + Accumulation settings; + // Prepare accumulation settings. + settings.sampleIndex = sampleIndex; + settings.isDenoisingEnabled = _values.asBoolean(kLabelIsDenoisingEnabled) ? 1 : 0; + + // If there are no changes compared local CPU copy, then do nothing and return false. + if (memcmp(&_accumData, &settings, sizeof(PostProcessing)) == 0) + return false; // No changes. + + // Update local CPU copy. + _accumData = settings; + + // Update staging buffer, if one provided. + if (pStaging) + *pStaging = settings; + + return true; +} + +// The maximum trace depth for recursion, set on the ray tracing pipeline and not to be exceeded. +const int RendererBase::kMaxTraceDepth = 10; + +END_AURORA diff --git a/Libraries/Aurora/Source/RendererBase.h b/Libraries/Aurora/Source/RendererBase.h new file mode 100644 index 0000000..2481a70 --- /dev/null +++ b/Libraries/Aurora/Source/RendererBase.h @@ -0,0 +1,186 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "AssetManager.h" +#include "Properties.h" + +BEGIN_AURORA + +class SceneBase; + +// Property names as constants. +static const string kLabelIsResetHistoryEnabled = "isResetHistoryEnabled"; +static const string kLabelIsDenoisingEnabled = "isDenoisingEnabled"; +static const string kLabelIsDiffuseOnlyEnabled = "isDiffuseOnlyEnabled"; +static const string kLabelDebugMode = "debugMode"; +static const string kLabelMaxLuminance = "maxLuminance"; +static const string kLabelTraceDepth = "traceDepth"; +static const string kLabelIsToneMappingEnabled = "isToneMappingEnabled"; +static const string kLabelIsGammaCorrectionEnabled = "isGammaCorrectionEnabled"; +static const string kLabelIsAlphaEnabled = "alphaEnabled"; +static const string kLabelBrightness = "brightness"; +static const string kLabelUnits = "units"; +static const string kLabelImportanceSamplingMode = "importanceSamplingMode"; +static const string kLabelIsFlipImageYEnabled = "isFlipImageYEnabled"; +static const string kLabelIsReferenceBSDFEnabled = "isReferenceBSDFEnabled"; +static const string kLabelIsOpaqueShadowsEnabled = "isOpaqueShadowsEnabled"; + +// The debug modes include: +// - 0 Output (accumulation) +// - 1 Output with Errors +// - 2 View Depth +// - 3 Normal +// - 4 Base Color +// - 5 Roughness +// - 6 Metalness +// - 7 Diffuse +// - 8 Diffuse Hit Distance +// - 9 Glossy +// - 10 Glossy Hit Distance +static const int kDebugModeErrors = 1; +static const int kMaxDebugMode = 10; + +// Importance sampling mode options as constants. +static const int kImportanceSamplingModeBSDF = 0; +static const int kImportanceSamplingModeEnvironment = 1; +static const int kImportanceSamplingModeMIS = 2; + +// A base class for implementations of IRenderer. +class RendererBase : public IRenderer, public FixedValues +{ +public: + /*** Lifetime Management ***/ + + RendererBase(uint32_t activeTaskCount); + + /*** IRenderer Functions ***/ + + void setOptions(const Properties& option) override; + IValues& options() override { return *this; }; + void setCamera(const mat4& view, const mat4& projection, float focalDistance = 1.0f, + float lensRadius = 0.0f) override; + void setCamera( + const float* view, const float* proj, float focalDistance, float lensRadius) override; + + /*** Functions ***/ + + bool isValid() { return _isValid; } + void valuesToProperties(const FixedValueSet& values, Properties& props) const; + void propertiesToValues(const Properties& properties, IValues& values); + + unique_ptr& assetManager() { return _pAssetMgr; } + +// TODO: Destruction via shared_ptr is not safe, we should have some kind of kill list system, but +// can't seem to get it to work. +#if 0 + void destroyImage(IImagePtr& pImg) + { + _imageDestroyList.push_back(pImg); + pImg.reset(); + } +#endif + + // The max trace depth, for recursion in ray tracing. + static const int kMaxTraceDepth; + +protected: + // Per-frame GPU uniform data. + struct FrameData + { + mat4 cameraViewProj; + mat4 cameraInvView; + vec2 viewSize; + int isOrthoProjection; + float focalDistance; + float lensRadius; + float sceneSize; + vec2 _padding1; + vec3 lightDir; + float _padding2; + vec4 lightColorAndIntensity; + float lightCosRadius; + int isOpaqueShadowsEnabled; + int isDepthNDCEnabled; + int isDiffuseOnlyEnabled; + int isDisplayErrorsEnabled; + int isDenoisingEnabled; + int isDenoisingAOVsEnabled; + int traceDepth; + float maxLuminance; + }; + + // Accumulation settings GPU data. + struct Accumulation + { + unsigned int sampleIndex; + unsigned int isDenoisingEnabled; + }; + + // Post-processing settings GPU data. + struct PostProcessing + { + vec3 brightness; + int debugMode; + vec2 range; + int isDenoisingEnabled; + int isToneMappingEnabled; + int isGammaCorrectionEnabled; + int isAlphaEnabled; + }; + + // Sample settings GPU data. + struct SampleData + { + // The sample index (iteration) for the frame, for progressive rendering. + uint sampleIndex; + + // An offset to apply to the sample index for seeding a random number generator. + uint seedOffset; + }; + + FrameData _frameData; + Accumulation _accumData; + SampleData _sampleData; + PostProcessing _postProcessingData; + + bool updateFrameDataGPUStruct(FrameData* pStaging = nullptr); + bool updatePostProcessingGPUStruct(PostProcessing* pStaging = nullptr); + bool updateAccumulationGPUStruct(uint32_t sampleIndex, Accumulation* pStaging = nullptr); + + /*** Protected Variables ***/ + +// TODO: Destruction via shared_ptr is not safe, we should have some kind of kill list system, but +// can't seem to get it to work. +// See OGSMOD-1912 +#if 0 + vector _imageDestroyList; +#endif + shared_ptr _pScene; + + bool _isValid = false; + uint32_t _taskCount = 0; + uint32_t _taskIndex = 0; + uint64_t _taskNumber = 0; + mat4 _cameraView; + mat4 _cameraProj; + float _focalDistance = 1.0f; + float _lensRadius = 0.0f; + + // Asset manager for loading external assets. + unique_ptr _pAssetMgr; +}; +MAKE_AURORA_PTR(RendererBase); + +END_AURORA diff --git a/Libraries/Aurora/Source/ResourceStub.cpp b/Libraries/Aurora/Source/ResourceStub.cpp new file mode 100644 index 0000000..fcc3618 --- /dev/null +++ b/Libraries/Aurora/Source/ResourceStub.cpp @@ -0,0 +1,459 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "ResourceStub.h" + +BEGIN_AURORA + +const string ResourceStub::kDefaultPropName = ""; + +void ResourceStub::shutdown() +{ + // Remove all permanent reference counts. + while (_permanentReferenceCount) + decrementPermanentRefCount(); + + // Clear all references to other resource stubs. + clearReferences(); +} + +void ResourceStub::clearReferences() +{ + // Set any reference properties to the empty string. + Properties props; + for (auto iter = _references.begin(); iter != _references.end(); iter++) + { + props[iter->first] = ""; + } + setProperties(props); +} + +void ResourceStub::setProperties(const Properties& props) +{ + + // Iterate through all the supplied properties. + for (auto iter = props.begin(); iter != props.end(); iter++) + { + // Get name of property. + const Path& propName = iter->first; + + // Is this a string property (as in, it does *not* have a string applicator function + // assoicated with it)? + bool isStringProperty = + _stringPropertyApplicators.find(propName) != _stringPropertyApplicators.end(); + + // We assume any string properies that do not have an explicit string applicator function + // are actually paths not strings. + if (iter->second.type == PropertyValue::Type::String && !isStringProperty) + { + // Setup a reference for this path property. + const Path& path = iter->second.asString(); + setReference(propName, path); + } + + // We assume any string array properies are actually paths not strings. + else if (iter->second.type == PropertyValue::Type::Strings) + { + Strings paths = iter->second.asStrings(); + + // Set references to all the paths in form "propName[n]" + for (int i = 0; i < static_cast(paths.size()); i++) + { + string refName = getIndexedPropertyName(propName, i); + setReference(refName, paths[i]); + } + + // Clear any previous references that exist past end of array. + for (int i = static_cast(paths.size());; i++) + { + string refName = getIndexedPropertyName(propName, i); + if (_references.find(refName) == _references.end()) + break; + else + setReference(refName, ""); + } + } + + // Apply the property (will apply to the resource itself if we are active.) + applyProperty(iter->first, iter->second); + } + + if (isActive() && _tracker.resourceModified) + _tracker.resourceModified(*this, props); +} + +void ResourceStub::applyProperty(const string& name, const PropertyValue& prop) +{ + // Set the property in property map. + _properties[name] = prop; + + // If inactive do nothing else. + if (!isActive()) + return; + + // Execute string or path applicator functions for this property. + if (prop.type == PropertyValue::Type::String) + { + // Execute explicit string applicator, if any. This will mean this property is treated as + // string not path. + if (_stringPropertyApplicators.find(name) != _stringPropertyApplicators.end()) + { + _stringPropertyApplicators[name](name, prop.asString()); + } + // Execute explicit path applicator, if any. + else if (_pathPropertyApplicators.find(name) != _pathPropertyApplicators.end()) + { + _pathPropertyApplicators[name](name, prop.asString()); + } + // Execute default path applicator, if any. (NOTE, execute *before* any string default + // applicator, even though explicit applicator exectued *after*) + else if (_pathPropertyApplicators.find(kDefaultPropName) != _pathPropertyApplicators.end()) + { + _pathPropertyApplicators[kDefaultPropName](name, prop.asString()); + } + // Execute default string applicator, if any. + else if (_stringPropertyApplicators.find(kDefaultPropName) != + _stringPropertyApplicators.end()) + { + _stringPropertyApplicators[kDefaultPropName](name, prop.asString()); + } + // Fail if no string or path applicator found. + else + AU_FAIL("Unknown string property %s (and no default string applicator)", name.c_str()); + } + // Execute path array applicator functions for this property. + else if (prop.type == PropertyValue::Type::Strings) + { + if (_pathArrayPropertyApplicators.find(name) != _pathArrayPropertyApplicators.end()) + { + _pathArrayPropertyApplicators[name](name, prop.asStrings()); + } + // Execute default string array applicator, if any. + else if (_pathArrayPropertyApplicators.find(kDefaultPropName) != + _pathArrayPropertyApplicators.end()) + { + _pathArrayPropertyApplicators[kDefaultPropName](name, prop.asStrings()); + } + // Fail if no string or path applicator found. + else + AU_FAIL("Unknown strings property %s (and no default string applicator)", name.c_str()); + } + // Execute bool applicator functions for this property. + else if (prop.type == PropertyValue::Type::Bool) + { + // Execute explicit bool applicator, if any. + if (_boolPropertyApplicators.find(name) != _boolPropertyApplicators.end()) + { + _boolPropertyApplicators[name](name, prop.asBool()); + } + // Execute default bool applicator, if any. + else if (_boolPropertyApplicators.find(kDefaultPropName) != _boolPropertyApplicators.end()) + { + _boolPropertyApplicators[kDefaultPropName](name, prop.asBool()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown bool propert %s (and no default string applicator)", name.c_str()); + } + else if (prop.type == PropertyValue::Type::Int) + { + // Execute explicit applicator, if any. + if (_intPropertyApplicators.find(name) != _intPropertyApplicators.end()) + { + _intPropertyApplicators[name](name, prop.asInt()); + } + // Execute default applicator, if any. + else if (_intPropertyApplicators.find(kDefaultPropName) != _intPropertyApplicators.end()) + { + _intPropertyApplicators[kDefaultPropName](name, prop.asInt()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown int property %s (and no default string applicator)", name.c_str()); + } + else if (prop.type == PropertyValue::Type::Float) + { + // Execute explicit applicator, if any. + if (_floatPropertyApplicators.find(name) != _floatPropertyApplicators.end()) + { + _floatPropertyApplicators[name](name, prop.asFloat()); + } + // Execute default applicator, if any. + else if (_floatPropertyApplicators.find(kDefaultPropName) != + _floatPropertyApplicators.end()) + { + _floatPropertyApplicators[kDefaultPropName](name, prop.asFloat()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown float property %s (and no default string applicator)", name.c_str()); + } + else if (prop.type == PropertyValue::Type::Float2) + { + // Execute explicit applicator, if any. + if (_vec2PropertyApplicators.find(name) != _vec2PropertyApplicators.end()) + { + _vec2PropertyApplicators[name](name, prop.asFloat2()); + } + // Execute default applicator, if any. + else if (_vec2PropertyApplicators.find(kDefaultPropName) != _vec2PropertyApplicators.end()) + { + _vec2PropertyApplicators[kDefaultPropName](name, prop.asFloat2()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown vec2 property %s (and no default string applicator)", name.c_str()); + } + else if (prop.type == PropertyValue::Type::Float3) + { + // Execute explicit applicator, if any. + if (_vec3PropertyApplicators.find(name) != _vec3PropertyApplicators.end()) + { + _vec3PropertyApplicators[name](name, prop.asFloat3()); + } + // Execute default applicator, if any. + else if (_vec3PropertyApplicators.find(kDefaultPropName) != _vec3PropertyApplicators.end()) + { + _vec3PropertyApplicators[kDefaultPropName](name, prop.asFloat3()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown vec3 property %s (and no default string applicator)", name.c_str()); + } + else if (prop.type == PropertyValue::Type::Float4) + { + // Execute explicit applicator, if any. + if (_vec4PropertyApplicators.find(name) != _vec4PropertyApplicators.end()) + { + _vec4PropertyApplicators[name](name, prop.asFloat4()); + } + // Execute default applicator, if any. + else if (_vec4PropertyApplicators.find(kDefaultPropName) != _vec4PropertyApplicators.end()) + { + _vec4PropertyApplicators[kDefaultPropName](name, prop.asFloat4()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown vec4 property %s (and no default string applicator)", name.c_str()); + } + else if (prop.type == PropertyValue::Type::Matrix4) + { + // Execute explicit applicator, if any. + if (_mat4PropertyApplicators.find(name) != _mat4PropertyApplicators.end()) + { + _mat4PropertyApplicators[name](name, prop.asMatrix4()); + } + // Execute default applicator, if any. + else if (_mat4PropertyApplicators.find(kDefaultPropName) != _mat4PropertyApplicators.end()) + { + _mat4PropertyApplicators[kDefaultPropName](name, prop.asMatrix4()); + } + // Fail if no applicator found. + else + AU_FAIL("Unknown mat4 property %s (and no default string applicator)", name.c_str()); + } + // Execute clear applicator (property is cleared if undefined value passed as property) + else if (prop.type == PropertyValue::Type::Undefined) + { + // Execute explicit clear applicator, if any. + if (_clearedPropertyApplicators.find(name) != _clearedPropertyApplicators.end()) + { + _clearedPropertyApplicators[name](name); + } + // Execute default clear applicator, if any. + else if (_clearedPropertyApplicators.find(kDefaultPropName) != + _clearedPropertyApplicators.end()) + { + _clearedPropertyApplicators[kDefaultPropName](name); + } + // Fail if no applicator found. + else + AU_FAIL("Unsupported cleared property %s", name.c_str()); + } + else + { + AU_FAIL("Unknown property type %d for %s", prop.type, name.c_str()); + } +} + +ResourceStubPtr ResourceStub::getReference(const string& name) +{ + // Find property name for in references map. + auto iter = _references.find(name); + + // Return null if none found. + if (iter == _references.end()) + return nullptr; + + // Return pointer to referenced resource stub. + return iter->second; +} + +void ResourceStub::setReference(const string& name, const Path& path) +{ + ResourceStubPtr pRes = nullptr; + + // If path is not empty (the null case), find the referenced resource in the parent container. + if (!path.empty()) + { + auto iter = _container.find(path); + AU_ASSERT(iter != _container.end(), + "Failed to set reference in resource %s, path %s not found for property %s", + _path.c_str(), path.c_str(), name.c_str()); + + pRes = iter->second; + } + + // If the reference hasn't change do nothing. + if (getReference(name) == pRes) + return; + + // If this resource is active increment the active ref count on the referenced resource, and + // decrement the exisiting reference if any. + if (isActive()) + { + if (pRes) + pRes->incrementActiveRefCount(); + if (getReference(name)) + _references[name]->decrementActiveRefCount(); + } + + // Set the reference. + _references[name] = pRes; +} + +bool ResourceStub::incrementActiveRefCount() +{ + // Is the resource stub currently active? + bool currentlyActive = isActive(); + + // Increment ref count. + _activeReferenceCount++; + + // Activate if it has gone from inactive to active. + if (currentlyActive != isActive()) + { + activate(); + return true; + } + return false; +} + +bool ResourceStub::decrementActiveRefCount() +{ + // Is the resource stub currently active? + bool currentlyActive = isActive(); + + // Can't decrement below zero. + AU_ASSERT(_activeReferenceCount > 0, "Invalid reference count"); + + // Decrement ref count. + _activeReferenceCount--; + + // Deactivate if it has gone from inactive to active. + if (currentlyActive != isActive()) + { + deactivate(); + return true; + } + + return false; +} + +bool ResourceStub::incrementPermanentRefCount() +{ + // Is the resource stub currently active? + bool currentlyActive = isActive(); + + // Decrement ref count. + _permanentReferenceCount++; + + // Deactivate if it has gone from inactive to active. + if (currentlyActive != isActive()) + { + activate(); + return true; + } + return false; +} + +bool ResourceStub::decrementPermanentRefCount() +{ + // Is the resource stub currently active? + bool currentlyActive = isActive(); + + // Can't decrement below zero. + AU_ASSERT(_permanentReferenceCount > 0, "Invalid reference count"); + + // Decrement ref count. + _permanentReferenceCount--; + + // Deactivate if it has gone from inactive to active. + if (currentlyActive != isActive()) + { + deactivate(); + return true; + } + return false; +} + +void ResourceStub::activate() +{ + // Iterate through all the referenced resource stubs. + for (auto iter = _references.begin(); iter != _references.end(); iter++) + { + // Increment refenced resource's active reference count. + if (iter->second) + { + iter->second->incrementActiveRefCount(); + } + } + + // Create the actual renderer resource for this resource stub. + createResource(); + + if (_tracker.resourceActivated) + _tracker.resourceActivated(*this); + + // Apply the properties to the newly created resource. + for (auto iter = _properties.begin(); iter != _properties.end(); iter++) + { + applyProperty(iter->first, iter->second); + } + + if (_tracker.resourceModified) + _tracker.resourceModified(*this, _properties); +} + +void ResourceStub::deactivate() +{ + // Iterate through all the referenced resource stubs. + for (auto iter = _references.begin(); iter != _references.end(); iter++) + { + // Decrement refenced resource's active reference count. + if (iter->second) + { + iter->second->decrementActiveRefCount(); + } + } + + if (_tracker.resourceDeactivated) + _tracker.resourceDeactivated(*this); + + // Destroy the actual renderer resource for this resource stub. + destroyResource(); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/ResourceStub.h b/Libraries/Aurora/Source/ResourceStub.h new file mode 100644 index 0000000..631a771 --- /dev/null +++ b/Libraries/Aurora/Source/ResourceStub.h @@ -0,0 +1,333 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" +#include + +BEGIN_AURORA + +class ResourceStub; + +// Define resource stub pointer and map type. +using ResourceStubPtr = shared_ptr; +using ResourceMap = map; +// TODO: Should be tbb::concurrent_hash_map; + +// Function applicator types, used to apply resource stub properties to actual renderer resource +// object. These should be defined by the resource sub-class to actually implement the renderer +// resource. +/// Apply a path property from resource stub to actual resource. +using ApplyPathPropertyFunction = function; +/// Apply a path array property from resource stub to actual resource. +using ApplyPathArrayPropertyFunction = function)>; +/// Apply a string property from resource stub to actual resource. +using ApplyStringPropertyFunction = function; +/// Apply a bool property from resource stub to actual resource. +using ApplyBoolPropertyFunction = function; +/// Apply a float property from resource stub to actual resource. +using ApplyFloatPropertyFunction = function; +/// Apply a int property from resource stub to actual resource. +using ApplyIntPropertyFunction = function; +/// Apply a vec2 property from resource stub to actual resource. +using ApplyVec2PropertyFunction = function; +/// Apply a vec3 property from resource stub to actual resource. +using ApplyVec3PropertyFunction = function; +/// Apply a vec4 property from resource stub to actual resource. +using ApplyVec4PropertyFunction = function; +/// Apply a matrix4 property from resource stub to actual resource. +using ApplyMat4PropertyFunction = function; +/// Clear a property that has been cleared in resource stub in the actual resource. +using ApplyClearedPropertyFunction = function; + +class ResourceStub; + +/// A resource stub is the CPU representation of a renderer resource that remains permanently in +/// memory. The resource stub holds all the properties associated with the resource, and a set of +/// references to the resource stubs for any references (e.g. a Material resource stub will hold a +/// reference to the resource stub of a referenced image.). +/// +/// A resource stub becomes active when it is added to the scene, or something referencing it +/// becomes active (e.g. an image becomes active when it is added to a material, that is set on an +/// instance in the scene.) When a resource stub is activated the actual renderer resource +/// associated with it is created. A resource stub becomes inactive when it is no longer refenced by +/// something in the scene, and its renderer resource is destroyed. +/// +/// It is also possible to manually activate a resource stub before it is added to the scene, by +/// incrementing the permanent reference count of the stub. Any stub with a permanent reference +/// count of greater than one is always active, regardless of whether it is added to the scene or +/// not. +/// +/// The stub contains all the required data to create the actual resource when the stub becomes +/// active but none of the large data buffers, which should be retreived via callback function. +/// +/// This class should be sub-classed by the various resource types (Material, Image, Geometry, +/// Instance, etc.) to implement the specifics of creating and destroying that resource. The sub +/// class should also set up the applicator funcitons that define how the stubs properties are +/// applied to the actual renderer resource. +class ResourceStub +{ +public: + /// \param path The unique path for this resource. + /// \param container The parent container for this resource and any resources it references. + ResourceStub( + const Path& path, const ResourceMap& container, const ResourceTracker& tracker = {}) : + _path(path), _container(container), _tracker(tracker) + { + } + ResourceStub(const ResourceStub& s) = delete; + + /// Creates the actual renderer resource for this resource stub when activated. Should be + /// overriden by specific resource sub-class. + virtual void createResource() = 0; + + /// Destroys the actual renderer resource for this resource stub when deactivated. Should be + /// overriden by specific resource sub-class. + virtual void destroyResource() = 0; + + virtual const ResourceType& type() = 0; + + /// Set some or all of the properties of this resource. + /// + /// Modifiying path properties will also result adding a reference from this resource stub to + /// the one referenced by the path. + /// + /// If the resource stub is currently active this will also apply to the properties to the + /// actual renderer resource. + void setProperties(const Properties& props); + + /// Is this resource stub currently active? + bool isActive() { return _permanentReferenceCount > 0 || _activeReferenceCount > 0; } + + /// Increment the permanent reference count of this stub. If the permanent reference count is + /// currently zero, this will cause the resource stub to be activated. + bool incrementPermanentRefCount(); + + /// Decrement the permanent reference count of this stub. If the permanent reference count is + /// currently one, this will cause the resource stub to be activated. + bool decrementPermanentRefCount(); + + /// Get a pointer the resource stub referenced by the provided path property name. + /// + /// \return Shared pointer to the referenced resource stub, or null pointer if not found. + ResourceStubPtr getReference(const string& pathPropertyName); + + /// Get a pointer the specific type of resource stub referenced by the provided path property + /// name. + /// + /// \param name The property name associated with the reference. + /// \return Shared pointer to the referenced resource stub, or null pointer if not found or is + /// not of type ResourceClass. + template + shared_ptr getReference(const string& name) + { + ResourceStubPtr pBase = getReference(name); + if (!pBase) + return nullptr; + return dynamic_pointer_cast(pBase); + } + + /// Get the resource from the resource stub referenced by the + /// provided path property name. + /// + /// \param name The property name associated with the reference. + /// \return Shared pointer to the referenced resource object, or null pointer if not found or is + /// not of type ResourceClass, or the referenced resource is not active. + template + shared_ptr getReferenceResource(const string& name) + { + auto pRes = getReference(name); + return pRes ? pRes->resource() : nullptr; + } + + /// Get the resource from the resource stub referenced by the + /// provided path property name. + /// + /// \param name The property name associated with the reference. + /// \return Shared pointer to the referenced resource object, or null pointer if not found or is + /// not of type ResourceClass, or the referenced resource is not active. + template + vector> getReferenceResources(const string& name) + { + if (!_properties[name].hasValue()) + return {}; + + vector> res; + const auto& arrayProp = _properties[name].asStrings(); + for (size_t i = 0; i < arrayProp.size(); i++) + { + string propName = getIndexedPropertyName(name, static_cast(i)); + res.push_back(getReferenceResource(propName)); + } + return res; + } + + /// String used to define default property applicator function. + /// Any applicator function for this property name is applied to any property of a given type + /// that has no specific applicator function applied. + static const string kDefaultPropName; + + /// Get the unique path. + const Path& path() const { return _path; } + +protected: + /// Invalidate this resource stub (will trigger resource destruction and recreation if currently + /// active). + void invalidate() + { + // TODO: This needs to be implemented or commands like setImageDescriptor, and + // setMaterialType will have no effect on activated resources. + } + + /// Shutdown this resource stub. + /// \note Due to destructor ordering issues, must be called in the dtor of any resource + /// sub-classes. + void shutdown(); + + /// Initialize resource stub properties (called from sub-class constructor to set default + /// properties for resource stub.) + void initializeProperties(const Properties& properties) { _properties = properties; } + + /// Get the properties map. + Properties& properties() { return _properties; } + + /// Initialize the map of applicator callback functions used to apply path properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializePathApplicators(const map& applicators) + { + _pathPropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply path array properties in + /// the renderer resource. Should be called by the constructor of any sub-class to define + /// resource properties. + void initializePathArrayApplicators( + const map& applicators) + { + _pathArrayPropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply string properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + /// + /// \note As path and string properties use identical types, all string properties are assumed + /// to be paths (and have an associated reference), rather than plain strings, unless a string + /// applicator function is defined for them. + void initializeStringApplicators(const map& applicators) + { + _stringPropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply bool properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeBoolApplicators(const map& applicators) + { + _boolPropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply float properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeFloatApplicators(const map& applicators) + { + _floatPropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply int properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeIntApplicators(const map& applicators) + { + _intPropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply vec2 properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeVec2Applicators(const map& applicators) + { + _vec2PropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply vec3 properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeVec3Applicators(const map& applicators) + { + _vec3PropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply vec4 properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeVec4Applicators(const map& applicators) + { + _vec4PropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to apply mat4 properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeMat4Applicators(const map& applicators) + { + _mat4PropertyApplicators = applicators; + } + + /// Initialize the map of applicator callback functions used to clear properties in the + /// renderer resource. Should be called by the constructor of any sub-class to define resource + /// properties. + void initializeClearedApplicators(const map& applicators) + { + _clearedPropertyApplicators = applicators; + } + +private: + void activate(); + void deactivate(); + void setReference(const string& name, const Path& path); + void applyProperty(const string& name, const PropertyValue& prop); + bool incrementActiveRefCount(); + bool decrementActiveRefCount(); + void clearReferences(); + string getIndexedPropertyName(string propName, int index) + { + return propName + "[" + to_string(index) + "]"; + } + + map _pathPropertyApplicators; + map _pathArrayPropertyApplicators; + map _stringPropertyApplicators; + map _boolPropertyApplicators; + map _floatPropertyApplicators; + map _intPropertyApplicators; + map _vec2PropertyApplicators; + map _vec3PropertyApplicators; + map _vec4PropertyApplicators; + map _mat4PropertyApplicators; + map _clearedPropertyApplicators; + + int _permanentReferenceCount = 0; + int _activeReferenceCount = 0; + Path _path; + Properties _properties; + ResourceMap _references; + const ResourceMap& _container; + const ResourceTracker _tracker; + static shared_ptr _spDefaultTracker; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/ResourceTracker.h b/Libraries/Aurora/Source/ResourceTracker.h new file mode 100644 index 0000000..c94d1c6 --- /dev/null +++ b/Libraries/Aurora/Source/ResourceTracker.h @@ -0,0 +1,291 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +BEGIN_AURORA + +class ResourceStub; + +/// Set of functions to track activation and modification of resources in resource system. +struct ResourceTracker +{ + function resourceModified = + nullptr; + function resourceActivated = nullptr; + function resourceDeactivated = nullptr; +}; + +template +class PointerWrapper +{ +public: + PointerWrapper(PointerType* ptr = nullptr) : m_ptr(ptr) {} + + PointerType& get() { return *m_ptr; } + const PointerType& get() const { return *m_ptr; } + + operator PointerType&() { return get(); } + operator const PointerType&() const { return get(); } + + PointerType* ptr() { return m_ptr; } + const PointerType* ptr() const { return m_ptr; } + +private: + PointerType* m_ptr; +}; + +// Noitifier, used to provide a list of active GPU resources to the renderer backend. +template +class ResourceNotifier +{ + +public: + ResourceNotifier() {} + + // Is active resource list empty? + bool empty() const { return _resourceData.empty(); } + + // Have changes been made to any resouces this frame? + bool modified() const { return _modified; } + + // Get the index for the provided resource implentation within active list. + // Will return -1 if resource not currently active. + uint32_t findActiveIndex(shared_ptr pData) const + { + auto iter = _indexLookup.find(pData.get()); + if (iter == _indexLookup.end()) + return static_cast(-1); + return static_cast(iter->second); + } + + // Get the active resource implementations. + template + vector>& resources() + { + // Return resource data cast to the requested sub-class. + // TODO: Can we get rid of dodgy casting? + return *reinterpret_cast>*>(&_resourceData); + } + + // Get number of currently active resources. + size_t count() const { return _resourceData.size(); } + + void clearModifiedFlag() { _modified = false; } + void clear() + { + _resourceData.clear(); + _indexLookup.clear(); + _modified = true; + } + + void add(ImplementationClass* pDataPtr) + { + _modified = true; + // Add resource implementation to data list, and add index to lookup. + _indexLookup[pDataPtr] = _resourceData.size(); + _resourceData.push_back(PointerWrapper(pDataPtr)); + } + +private: + vector> _resourceData; + map _indexLookup; + bool _modified = false; +}; + +/// Typed tracker that will maintain list of active resource stubs of a given type. +template +class TypedResourceTracker +{ +public: + TypedResourceTracker() : _active(true) + { + // Setup the tracker callbacks to maintain resource lists. + _tracker.resourceActivated = [this](const ResourceStub& baseRes) { + if (!_active) + return; + const ResourceClass* pRes = static_cast(&baseRes); + _activatedResources.push_back(pRes); + _currentlyActiveResources.emplace(pRes->path(), pRes); + }; + _tracker.resourceDeactivated = [this](const ResourceStub& baseRes) { + if (!_active) + return; + const ResourceClass* pRes = static_cast(&baseRes); + _deactivatedResources.push_back(pRes); + _currentlyActiveResources.erase(pRes->path()); + }; + _tracker.resourceModified = [this](const ResourceStub& baseRes, const Properties& props) { + if (!_active) + return; + const ResourceClass* pRes = static_cast(&baseRes); + _modifiedResources.push_back(make_pair(pRes, props)); + }; + } + + void shutdown() + { + _activatedResources.clear(); + _deactivatedResources.clear(); + _modifiedResources.clear(); + _currentlyActiveResources.clear(); + _active = false; + } + + // Update the list of active resource implementations for this frame. + // Will clear the resources for this frame, will do nothing (but will maintain the active + // resouce list) if no changes recorded in the tracker for this frame. + bool update() + { + _modifiedNotifier.clear(); + + // If tracker not changed, do nothing. + // This keeps active resouce list from previous frame but clears the modified flag. + if (!changed()) + { + _activeNotifier.clearModifiedFlag(); + return false; + } + + for (auto iter = _modifiedResources.begin(); iter != _modifiedResources.end(); iter++) + { + // Get the GPU implementation for the resource stub (sometimes will be null if error in + // activation) + const ResourceClass* pRes = iter->first; + ImplementationClass* pDataPtr = pRes->resource().get(); + if (pDataPtr) + { + _modifiedNotifier.add(pDataPtr); + } + } + + // Set the modified flag and clear lists of resources. + _activeNotifier.clear(); + + // Iterate through all the active resources in tracker. + for (auto iter = _currentlyActiveResources.begin(); iter != _currentlyActiveResources.end(); + iter++) + { + // Get the GPU implementation for the resource stub (sometimes will be null if error in + // activation) + const ResourceClass* pRes = iter->second; + ImplementationClass* pDataPtr = pRes->resource().get(); + if (pDataPtr) + { + _activeNotifier.add(pDataPtr); + } + } + + // Clear the tracker for this frame. + clear(); + return true; + } + + // Get the tracker callbacks. + const ResourceTracker& tracker() const { return _tracker; } + + // Get the list of resource stubs activated this frame. + vector activated() { return _activatedResources; } + + // Get the list of resources stubs deactivated this frame. + vector deactivated() { return _deactivatedResources; } + + // Clear the lists for this frame. + void clear() + { + _activatedResources.clear(); + _deactivatedResources.clear(); + _modifiedResources.clear(); + } + + // Have any changes (modifications, activations or deactivations) been made this frame? + bool changed() const { return _modifiedNotifier.modified() || _activeNotifier.modified(); } + + // Get the currently active set of resource implementations, for this frame. + ResourceNotifier& active() { return _activeNotifier; } + const ResourceNotifier& active() const { return _activeNotifier; } + + // Get the set of resources that have been modified this frame. + ResourceNotifier& modified() { return _modifiedNotifier; } + const ResourceNotifier& modified() const { return _modifiedNotifier; } + + size_t activeCount() { return _currentlyActiveResources.size(); } + +private: + ResourceTracker _tracker; + vector _activatedResources; + vector _deactivatedResources; + vector> _modifiedResources; + map _currentlyActiveResources; + + ResourceNotifier _activeNotifier; + ResourceNotifier _modifiedNotifier; + bool _active; +}; + +// Hash lookup to build subset of unique objects using provided hash function, building list of +// objects mapped to unique subset. +template +class UniqueHashLookup +{ +public: + // Add object to lookup. + void add(ObjectClass& obj) + { + // Get hash for object and lookup in map. + size_t hash = HashFunction(obj); + auto iter = _indexLookup.find(hash); + size_t index; + + // If hash does not exist add it. + if (iter == _indexLookup.end()) + { + index = _uniqueObjects.size(); + _indexLookup[hash] = index; + _uniqueObjects.push_back(&obj); + } + else + { + // If hash already exists use existing index. + index = iter->second; + } + _indices.push_back(index); + } + + // Get unique object at provided index. + vector>& unique() { return _uniqueObjects; } + + // Get count of objects in list. + size_t count() { return _indices.size(); } + + // Get Nth object in list. + ObjectClass& get(size_t n) { return _uniqueObjects[getUniqueIndex(n)]; } + + // Get the unique index for Nth object in list. + size_t getUniqueIndex(size_t n) { return _indices[n]; } + + // Clear all the contents. + void clear() + { + _indices.clear(); + _uniqueObjects.clear(); + _indexLookup.clear(); + } + +private: + vector> _uniqueObjects; + vector _indices; + unordered_map _indexLookup; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/Resources.cpp b/Libraries/Aurora/Source/Resources.cpp new file mode 100644 index 0000000..9674a55 --- /dev/null +++ b/Libraries/Aurora/Source/Resources.cpp @@ -0,0 +1,322 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "RendererBase.h" +#include "Resources.h" + +BEGIN_AURORA + +EnvironmentResource::EnvironmentResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer) : + ResourceStub(path, container, tracker.tracker()), _pRenderer(pRenderer) +{ + // Initialize the path applicator functions that apply path properties in the environment + // resource. + initializePathApplicators({ // Apply the background image (get the associated image resource and + // set in environment resource.) + { Names::EnvironmentProperties::kBackgroundImage, + [this](string propName, Aurora::Path) { + IImagePtr pImage = getReferenceResource(propName); + _resource->values().setImage(propName, pImage); + } }, + // Apply the light image (get the associated image resource and set in environment + // resource.) + { Names::EnvironmentProperties::kLightImage, [this](string propName, Aurora::Path) { + IImagePtr pImage = getReferenceResource(propName); + _resource->values().setImage(propName, pImage); + } } }); + + // Initialize the vec3 applicator functions that apply vec3 properties in the environment + // resource. + initializeVec3Applicators({ // Apply the top light color. + { Names::EnvironmentProperties::kLightTop, + [this](string propName, glm::vec3 val) { + _resource->values().setFloat3(propName, (const float*)&val); + } }, + // Apply the bottom light color. + { Names::EnvironmentProperties::kLightBottom, + [this](string propName, glm::vec3 val) { + _resource->values().setFloat3(propName, (const float*)&val); + } }, + // Apply the top backgound color. + { Names::EnvironmentProperties::kBackgroundTop, + [this](string propName, glm::vec3 val) { + _resource->values().setFloat3(propName, (const float*)&val); + } }, + // Apply the bottom backgound color. + { Names::EnvironmentProperties::kBackgroundBottom, [this](string propName, glm::vec3 val) { + _resource->values().setFloat3(propName, (const float*)&val); + } } }); + + // Initialize the mat4 applicator functions that apply vec3 properties in the environment + // resource. + initializeMat4Applicators({ // Apply the background transform matrix. + { Names::EnvironmentProperties::kBackgroundTransform, + [this](string propName, glm::mat4 val) { + _resource->values().setMatrix(propName, (const float*)&val); + } }, + // Apply the light transform matrix. + { Names::EnvironmentProperties::kLightTransform, [this](string propName, glm::mat4 val) { + _resource->values().setMatrix(propName, (const float*)&val); + } } }); + + // Initialize the bool applicator functions that apply bool properties in the environment + // resource. + initializeBoolApplicators( + // Apply the background use screen flag. + { { Names::EnvironmentProperties::kBackgroundUseScreen, [this](string propName, bool val) { + _resource->values().setBoolean(propName, val); + } } }); +} + +void EnvironmentResource::createResource() +{ + // Create the actual renderer environment resource. + _resource = _pRenderer->createEnvironmentPointer(); +} + +MaterialResource::MaterialResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer) : + ResourceStub(path, container, tracker.tracker()), _pRenderer(pRenderer) +{ + // Setup a default applicator function so all image properties are applied to the underlying + // material resource. + initializePathApplicators( + // material resource. + { { ResourceStub::kDefaultPropName, [this](string propName, Aurora::Path) { + // If the material resource's parameter is a sampler, treat path as sampler + // reference. + if (_resource->values().type(propName) == IValues::Type::Sampler) + { + ISamplerPtr pSampler = getReferenceResource(propName); + _resource->values().setSampler(propName, pSampler); + } + // All other paths are treated as image references. + else + { + IImagePtr pImage = getReferenceResource(propName); + _resource->values().setImage(propName, pImage); + } + } } }); + + // Setup a default applicator function so all int properties are applied to the underlying + // material resource. + initializeIntApplicators({ { ResourceStub::kDefaultPropName, + [this](string propName, int value) { _resource->values().setInt(propName, value); } } }); + + // Setup a default applicator function so all bool properties are applied to the underlying + // material resource. + initializeBoolApplicators( + { { ResourceStub::kDefaultPropName, [this](string propName, bool value) { + _resource->values().setBoolean(propName, value); + } } }); + + // Setup a default applicator function so all bool properties are applied to the underlying + // material resource. + initializeFloatApplicators( + { { ResourceStub::kDefaultPropName, [this](string propName, float value) { + _resource->values().setFloat(propName, value); + } } }); + + // Setup a default applicator function so all vec3 properties are applied to the underlying + // material resource. + initializeVec3Applicators( + { { ResourceStub::kDefaultPropName, [this](string propName, vec3 value) { + _resource->values().setFloat3(propName, (float*)&value); + } } }); + + // Setup a default applicator function so all mat4 properties are applied to the underlying + // material resource. + initializeMat4Applicators( + { { ResourceStub::kDefaultPropName, [this](string propName, mat4 value) { + _resource->values().setMatrix(propName, (float*)&value); + } } }); + + // Setup a default applicator function so all cleared properties are applied to the underlying + // material resource. + initializeClearedApplicators({ { ResourceStub::kDefaultPropName, + [this](string propName) { _resource->values().clearValue(propName); } } }); +} + +void MaterialResource::createResource() +{ + // Create actual renderer material resource. + _resource = _pRenderer->createMaterialPointer(_type, _document, path()); +} + +SamplerResource::SamplerResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer) : + ResourceStub(path, container, tracker.tracker()), _pRenderer(pRenderer) +{ + initializeStringApplicators({ + // All the sampler properties are immutable, so in the applicator functions just invalidate + // the sampler resource, causing it to be recreated. + { Names::SamplerProperties::kAddressModeU, [this](string, Aurora::Path) { invalidate(); } }, + { Names::SamplerProperties::kAddressModeV, [this](string, Aurora::Path) { invalidate(); } }, + }); +} + +void SamplerResource::createResource() +{ + // Create sampler directly from the resouce properties. + _resource = _pRenderer->createSamplerPointer(this->properties()); +} + +ImageResource::ImageResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer) : + ResourceStub(path, container, tracker.tracker()), _pRenderer(pRenderer) +{ +} + +void ImageResource::createResource() +{ + // Ensure descriptor as been set. + AU_ASSERT(_hasDescriptor, "Can't create image, no descriptor"); + + // Setup InitData object for image. + // TODO: This is legacy struct, replace with descriptor in pointer inteface. + IImage::InitData initData; + initData.format = _descriptor.format; + initData.width = _descriptor.width; + initData.height = _descriptor.height; + initData.isEnvironment = _descriptor.isEnvironment; + initData.linearize = _descriptor.linearize; + initData.name = path(); + + // Use the getPixelData callback to get the contents of the image. + PixelData pixelData; + _descriptor.getPixelData(pixelData, ivec2(0, 0), ivec2(_descriptor.width, _descriptor.height)); + initData.pImageData = pixelData.address; + + // Create the actual renderer image resource. + _resource = _pRenderer->createImagePointer(initData); + + // If we have a completion callback, execute that as pixels ahve been passed to renderer and can + // be deleted. + if (_descriptor.pixelUpdateComplete) + _descriptor.pixelUpdateComplete( + pixelData, ivec2(0, 0), ivec2(_descriptor.width, _descriptor.height)); +} + +void ImageResource::destroyResource() +{ + // TODO: Aurora cannot currently delete an image that is in use, even if synchronized with the + // renderer. +#if 0 + RendererBase* pBaseRenderer = (RendererBase*)_pRenderer; + pBaseRenderer->destroyImage(_resource); +#endif +} + +GeometryResource::GeometryResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer) : + ResourceStub(path, container, tracker.tracker()), _pRenderer(pRenderer) +{ +} + +void GeometryResource::createResource() +{ + AU_ASSERT(_hasDescriptor, "No descriptor, can't create geometry"); + _resource = _pRenderer->createGeometryPointer(_descriptor, path()); +} + +InstanceResource::InstanceResource(const Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IScene* pScene) : + ResourceStub(path, container, tracker.tracker()), _pScene(pScene) +{ + + // Initialize the default properties of an instance resource. + initializeProperties({ { Names::InstanceProperties::kMaterialLayers, vector({}) }, + { Names::InstanceProperties::kGeometryLayers, vector({}) }, + { Names::InstanceProperties::kMaterial, "" }, { Names::InstanceProperties::kVisible, true }, + { Names::InstanceProperties::kTransform, mat4() } }); + + // Initialize the path applicator functions that apply path properties in the instance + // resource. + initializePathApplicators( + { // Apply the geometry property. This is treated as a property internally, but will trigger + // resource invalidation if changed (i.e. will force recreation of the resouce) + { Names::InstanceProperties::kGeometry, + [this](string propName, Aurora::Path) { + IGeometryPtr pGeom = + getReferenceResource(propName); + if (_resource->geometry() != pGeom) + invalidate(); + } }, + // Apply the material property to the instance. + { Names::InstanceProperties::kMaterial, [this](string propName, Aurora::Path) { + IMaterialPtr pMtl = getReferenceResource(propName); + _resource->setMaterial(pMtl); + } } }); + + initializePathArrayApplicators({ { Names::InstanceProperties::kMaterialLayers, + [this](string, vector) { invalidate(); } }, + { Names::InstanceProperties::kGeometryLayers, + [this](string, vector) { invalidate(); } } }); + + // Initialize the mat4 applicator functions that apply mat4 properties in the instance + // resource. + initializeMat4Applicators({ { Names::InstanceProperties::kTransform, + [this](string, glm::mat4 val) { _resource->setTransform(val); } } }); + + // Initialize the int applicator functions that apply int path properties in the instance + // resource. + initializeIntApplicators({ { Names::InstanceProperties::kObjectID, + [this](string, int val) { _resource->setObjectIdentifier(val); } } }); + + // Initialize the bool applicator functions that apply bpp path properties in the instance + // resource. + initializeBoolApplicators({ { Names::InstanceProperties::kVisible, + [this](string, bool val) { _resource->setVisible(val); } } }); +} + +void InstanceResource::createResource() +{ + // Get the referenced material resource. + IMaterialPtr pMaterial = + getReferenceResource(Names::InstanceProperties::kMaterial); + + // Get the referenced geometry resource. + IGeometryPtr pGeometry = + getReferenceResource(Names::InstanceProperties::kGeometry); + + vector materialLayers = getReferenceResources( + Names::InstanceProperties::kMaterialLayers); + vector geometryLayers = getReferenceResources( + Names::InstanceProperties::kGeometryLayers); + + vector materialLayerDefs; + for (size_t i = 0; i < materialLayers.size(); i++) + { + IGeometryPtr pGeom = nullptr; + if (i < geometryLayers.size()) + pGeom = geometryLayers[i]; + materialLayerDefs.push_back(make_pair(materialLayers[i], pGeom)); + } + + // Get the transform matrix. + const mat4 mtx = properties()[Names::InstanceProperties::kTransform].asMatrix4(); + + // Create instance. + _resource = _pScene->addInstancePointer(path(), pGeometry, pMaterial, mtx, materialLayerDefs); +} + +void InstanceResource::destroyResource() +{ + // Destroy the instance resource. + _resource.reset(); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/Resources.h b/Libraries/Aurora/Source/Resources.h new file mode 100644 index 0000000..dc5cc8c --- /dev/null +++ b/Libraries/Aurora/Source/Resources.h @@ -0,0 +1,217 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "ResourceStub.h" + +BEGIN_AURORA + +/// ResourceStub sub-class that implements a renderer material resource. +class MaterialResource : public ResourceStub +{ +public: + MaterialResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer); + + // Call shutdown in the dtor to avoid issues with destructor ordering. + virtual ~MaterialResource() { shutdown(); } + + const ResourceType& type() override { return resourceType; } + + /// Override the createResource method to implement material creation functionality. + void createResource() override; + + /// Override the destroyResource method to reset resource pointer and free resource. + void destroyResource() override { _resource.reset(); } + + /// Get the resource pointer. + IMaterialPtr resource() const { return _resource; } + + /// Set the material type and document. + /// If resource is active will make it invalid, forcing recreation of resource. + /// \param type The Aurora material type string. + /// \param type The Aurora material document string. + void setType(const string& type, const string& document) + { + // Set type and document. + _type = type; + _document = document; + + // Make resource stub invalid. + invalidate(); + } + + static constexpr ResourceType resourceType = ResourceType::Material; + +private: + IMaterialPtr _resource; + IRenderer* _pRenderer; + string _type = Names::MaterialTypes::kBuiltIn; + string _document = "Default"; +}; + +/// ResourceStub sub-class that implements a renderer environment resource. +class EnvironmentResource : public ResourceStub +{ +public: + EnvironmentResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, + IRenderer* pRenderer); + + // Call shutdown in the dtor to avoid issues with destructor ordering. + virtual ~EnvironmentResource() { shutdown(); } + + /// Override the createResource method to implement material creation functionality. + void createResource() override; + + /// Override the destroyResource method to reset resource pointer and free resource. + void destroyResource() override { _resource.reset(); } + + const ResourceType& type() override { return resourceType; } + + /// Get the resource pointer. + IEnvironmentPtr resource() const { return _resource; } + + static constexpr ResourceType resourceType = ResourceType::Environment; + +private: + IEnvironmentPtr _resource; + IRenderer* _pRenderer; +}; + +/// ResourceStub sub-class that implements a renderer image resource. +class ImageResource : public ResourceStub +{ +public: + ImageResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer); + + virtual ~ImageResource() { shutdown(); } + + /// Override the createResource method to implement image creation functionality. + void createResource() override; + + /// Override the destroyResource method to reset resource pointer and free resource. + void destroyResource() override; + + const ResourceType& type() override { return resourceType; } + + /// Get the resource pointer. + IImagePtr resource() const { return _resource; } + + /// Set the material type and document. + /// If resource is active will make it invalid, forcing recreation of resource. + /// \param descriptor The Aurora image descriptor. + void setDescriptor(const ImageDescriptor& descriptor) + { + _descriptor = descriptor; + _hasDescriptor = true; + invalidate(); + } + + static constexpr ResourceType resourceType = ResourceType::Image; + +private: + IImagePtr _resource; + IRenderer* _pRenderer; + ImageDescriptor _descriptor; + bool _hasDescriptor = false; +}; + +/// ResourceStub sub-class that implements a renderer sampler resource. +class SamplerResource : public ResourceStub +{ +public: + SamplerResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer); + + virtual ~SamplerResource() { shutdown(); } + + /// Override the createResource method to implement sampler creation functionality. + void createResource() override; + + /// Override the destroyResource method to reset resource pointer and free resource. + void destroyResource() override { _resource.reset(); } + + const ResourceType& type() override { return resourceType; } + + /// Get the resource pointer. + ISamplerPtr resource() const { return _resource; } + + static constexpr ResourceType resourceType = ResourceType::Sampler; + +private: + ISamplerPtr _resource; + IRenderer* _pRenderer; +}; + +/// ResourceStub sub-class that implements a renderer geometry resource. +class GeometryResource : public ResourceStub +{ +public: + GeometryResource(const Aurora::Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IRenderer* pRenderer); + + virtual ~GeometryResource() { shutdown(); } + + void createResource() override; + void destroyResource() override { _resource.reset(); } + + const ResourceType& type() override { return resourceType; } + + IGeometryPtr resource() const { return _resource; } + + void setDescriptor(const GeometryDescriptor& descriptor) + { + _descriptor = descriptor; + _hasDescriptor = true; + invalidate(); + } + + static constexpr ResourceType resourceType = ResourceType::Geometry; + +private: + IGeometryPtr _resource; + IRenderer* _pRenderer; + GeometryDescriptor _descriptor; + bool _hasDescriptor = false; +}; + +/// ResourceStub sub-class that implements a renderer instance resource. +class InstanceResource : public ResourceStub +{ +public: + InstanceResource(const Path& path, const ResourceMap& container, + const TypedResourceTracker& tracker, IScene* pScene); + + virtual ~InstanceResource() + { + // Just reset pointer. Don't remove from scene (as this can be called from scene dtor when + // the scene is no longer valid.) + _resource.reset(); + } + + void createResource() override; + void destroyResource() override; + const ResourceType& type() override { return resourceType; } + IInstancePtr resource() const { return _resource; } + + static constexpr ResourceType resourceType = ResourceType::Instance; + +private: + IInstancePtr _resource; + IScene* _pScene; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/SceneBase.cpp b/Libraries/Aurora/Source/SceneBase.cpp new file mode 100644 index 0000000..e1391b8 --- /dev/null +++ b/Libraries/Aurora/Source/SceneBase.cpp @@ -0,0 +1,438 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "AssetManager.h" +#include "RendererBase.h" +#include "Resources.h" +#include "SceneBase.h" + +BEGIN_AURORA + +// Resource paths for default resources. +Path SceneBase::kDefaultEnvironmentName = "__AuroraDefaultEnvironment"; +Path SceneBase::kDefaultMaterialName = "__AuroraDefaultMaterial"; +Path SceneBase::kDefaultGeometryName = "__AuroraDefaultInstance"; +Path SceneBase::kDefaultInstanceName = "__AuroraDefaultGeometry"; + +RendererBase* SceneBase::rendererBase() +{ + return static_cast(_pRenderer); +} + +void SceneBase::createDefaultResources() +{ + // Create a default environment resource, and set it as current environment. + _pDefaultEnvironmentResource = make_shared( + kDefaultEnvironmentName, _resources, _environments, _pRenderer); + _resources[kDefaultEnvironmentName] = _pDefaultEnvironmentResource; + setEnvironment(""); + + _pErrorImageData = make_shared(); + _pErrorImageData->data.format = ImageFormat::Integer_RGBA; + _pErrorImageData->data.width = 1; + _pErrorImageData->data.height = 1; + _pErrorImageData->data.isEnvironment = false; + _pErrorImageData->data.linearize = false; + _pErrorImageData->pixels = make_unique(4); + _pErrorImageData->data.pImageData = _pErrorImageData->pixels.get(); + _pErrorImageData->pixels[0] = 0xff; + _pErrorImageData->pixels[1] = 0xff; + _pErrorImageData->pixels[2] = 0xff; + _pErrorImageData->pixels[3] = 0xff; + + // Create default material resource, and make it permanent, so underlying GPU material always + // available. + _pDefaultMaterialResource = + make_shared(kDefaultMaterialName, _resources, _materials, _pRenderer); + _resources[kDefaultMaterialName] = _pDefaultMaterialResource; + _pDefaultMaterialResource->incrementPermanentRefCount(); + + const Path kDefaultQuadPath = "DefaultQuadGeometry"; + GeometryDescriptor geomDesc; + geomDesc.type = PrimitiveType::Triangles; + geomDesc.vertexDesc.attributes[Names::VertexAttributes::kPosition] = AttributeFormat::Float3; + geomDesc.vertexDesc.count = _defaultGeometryVerts.size() / 3; + geomDesc.indexCount = _defaultGeometryIndices.size(); + geomDesc.getAttributeData = [this](AttributeDataMap& buffers, size_t firstVertex, + size_t vertexCount, size_t firstIndex, size_t indexCount) { + AU_ASSERT(firstVertex == 0, "Partial update not supported"); + AU_ASSERT(vertexCount == _defaultGeometryVerts.size() / 3, "Partial update not supported"); + + buffers[Names::VertexAttributes::kPosition].address = _defaultGeometryVerts.data(); + buffers[Names::VertexAttributes::kPosition].size = + _defaultGeometryVerts.size() * sizeof(vec3); + + AU_ASSERT(firstIndex == 0, "Partial update not supported"); + AU_ASSERT(indexCount == _defaultGeometryIndices.size(), "Partial update not supported"); + + buffers[Names::VertexAttributes::kIndices].address = _defaultGeometryIndices.data(); + buffers[Names::VertexAttributes::kIndices].size = + _defaultGeometryIndices.size() * sizeof(uint32_t); + buffers[Names::VertexAttributes::kIndices].stride = sizeof(uint32_t); + + return true; + }; + + setGeometryDescriptor(kDefaultGeometryName, geomDesc); + + _pDefaultInstanceResource = + make_shared(kDefaultInstanceName, _resources, _instances, this); + _pDefaultInstanceResource->setProperties( + { { Names::InstanceProperties::kGeometry, kDefaultGeometryName } }); + _resources[kDefaultInstanceName] = _pDefaultInstanceResource; +} + +SceneBase::~SceneBase() +{ + _environments.shutdown(); + _instances.shutdown(); + _geometry.shutdown(); + _environments.shutdown(); + _images.shutdown(); + _samplers.shutdown(); + _materials.shutdown(); +} +void SceneBase::setBounds(const vec3& min, const vec3& max) +{ + _bounds.reset(); + _bounds.add(min); + _bounds.add(max); + + assert(_bounds.isValid()); +} + +void SceneBase::setBounds(const float* min, const float* max) +{ + _bounds.reset(); + _bounds.add(make_vec3(min)); + _bounds.add(make_vec3(max)); + + assert(_bounds.isValid()); +} + +void SceneBase::setLight( + float intensity, const rgb& color, const vec3& direction, float angularDiameter) +{ + _lightIntensity = intensity; + _lightColor = color; + _lightDirection = normalize(direction); + _lightAngularDiameter = angularDiameter; +} + +void SceneBase::setLight( + float intensity, const float* color, const float* direction, float angularDiameter) +{ + _lightIntensity = intensity; + _lightColor = make_vec3(color); + _lightDirection = normalize(make_vec3(direction)); + _lightAngularDiameter = angularDiameter; +} + +void SceneBase::addPermanent(const Path& path) +{ + _resources[path]->incrementPermanentRefCount(); +} + +void SceneBase::removePermanent(const Path& path) +{ + _resources[path]->decrementPermanentRefCount(); +} + +void SceneBase::setSamplerProperties(const Path& atPath, const Properties& props) +{ + // Get sampler resource. + auto pSamplerRes = getResource(atPath); + + // If no existing resource create one. + if (!pSamplerRes) + { + pSamplerRes = make_shared(atPath, _resources, _samplers, _pRenderer); + _resources[atPath] = pSamplerRes; + } + + // Set the sampler . + pSamplerRes->setProperties(props); +} + +void SceneBase::setImageDescriptor(const Path& atPath, const ImageDescriptor& desc) +{ + // Get image resource. + auto pImageRes = getResource(atPath); + + // If no existing resource create one. + if (!pImageRes) + { + pImageRes = make_shared(atPath, _resources, _images, _pRenderer); + _resources[atPath] = pImageRes; + } + + // Set the image descriptor. + pImageRes->setDescriptor(desc); +} + +ResourceType SceneBase::getResourceType(const Path& path) +{ + auto iter = _resources.find(path); + if (iter == _resources.end()) + { + return ResourceType::Invalid; + } + + return iter->second->type(); +} + +void SceneBase::setImageFromFilePath( + const Path& atPath, const string& filePath, bool forceLinear, bool isEnvironment) +{ + // Set file path to Aurora path if empty. + string imagePath = filePath; + if (imagePath.empty()) + imagePath = atPath; + + // Lookup in loaded images map, return + shared_ptr pImageAsset; + auto iter = _loadedImages.find(filePath); + if (iter != _loadedImages.end()) + { + pImageAsset = iter->second; + } + else + { + pImageAsset = rendererBase()->assetManager()->acquireImage(imagePath); + if (!pImageAsset) + { + pImageAsset = _pErrorImageData; + AU_ERROR("Failed to load image %s", imagePath.c_str()); + } + _loadedImages[filePath] = pImageAsset; + } + + Aurora::ImageDescriptor imageDesc; + imageDesc.format = pImageAsset->data.format; + imageDesc.width = pImageAsset->data.width; + imageDesc.height = pImageAsset->data.height; + imageDesc.linearize = forceLinear ? false : pImageAsset->data.linearize; + imageDesc.isEnvironment = isEnvironment; + + // Setup pixel data callback to get address and size from buffer from cache entry. + string pathToLoad = filePath; + imageDesc.getPixelData = [this, pathToLoad]( + Aurora::PixelData& dataOut, glm::ivec2, glm::ivec2) { + shared_ptr pAsset = _loadedImages[pathToLoad]; + dataOut.address = pAsset->pixels.get(); + dataOut.size = pAsset->sizeBytes; + return true; + }; + imageDesc.pixelUpdateComplete = [this, pathToLoad](const Aurora::PixelData&, glm::ivec2, + glm::ivec2) { _loadedImages.erase(pathToLoad); }; + + setImageDescriptor(atPath, imageDesc); +} + +void SceneBase::setMaterialType( + const Path& atPath, const std::string& materialType, const std::string& document) +{ + // Get material resource. + auto pMaterialRes = getResource(atPath); + + // If no existing resource create one. + if (!pMaterialRes) + { + pMaterialRes = make_shared(atPath, _resources, _materials, _pRenderer); + _resources[atPath] = pMaterialRes; + } + + // Set the material type. + pMaterialRes->setType(materialType, document); +} + +Paths SceneBase::addInstances(const Path& geometry, const InstanceDefinitions& definitions) +{ + // Returned paths vectors. + Paths paths; + + // Iterate through all the instance definitions. + for (size_t i = 0; i < definitions.size(); i++) + { + // Add path to return value. + paths.push_back(definitions[i].path); + + // Add the instance to the scene. + addInstance(definitions[i].path, geometry, definitions[i].properties); + } + + return paths; +} + +bool SceneBase::setEnvironmentProperties(const Path& atPath, const Properties& properties) +{ + // Get environment resource. + auto pEnvRes = getResource(atPath); + + // If no existing resource create one. + if (!pEnvRes) + { + pEnvRes = make_shared(atPath, _resources, _environments, _pRenderer); + _resources[atPath] = pEnvRes; + } + + // Set environment properties. + pEnvRes->setProperties(properties); + + return true; +} + +bool SceneBase::setEnvironment(const Path& environment) +{ + // If this is just empty path, set to the default environment. + if (environment.empty()) + { + return setEnvironment(kDefaultEnvironmentName); + } + + // If we already have an environment decrement it's permanent ref count (as it's no longer + // attached to the scene). + if (_pEnvironmentResource) + _pEnvironmentResource->decrementPermanentRefCount(); + + // Get environment resource stub. + _pEnvironmentResource = getResource(environment); + + // Fail if no environment exists. + AU_ASSERT(_pEnvironmentResource, "Environment not found at path %s", environment.c_str()); + + // Increment permanent reference count (as environment is attached to the scene.) + _pEnvironmentResource->incrementPermanentRefCount(); + + return true; +} + +void SceneBase::setMaterialProperties(const Path& atPath, const Properties& materialProperties) +{ + // Get material resource + auto pMaterialRes = getResource(atPath); + + // Create resource if it doesn't exist yet. + if (!pMaterialRes) + { + pMaterialRes = make_shared(atPath, _resources, _materials, _pRenderer); + _resources[atPath] = pMaterialRes; + } + + // Set properties. + pMaterialRes->setProperties(materialProperties); +} + +void SceneBase::setGeometryDescriptor(const Path& atPath, const GeometryDescriptor& desc) +{ + // Look for existing geometry resource. + auto pGeom = getResource(atPath); + + // Create one if it doesn't exist. + if (!pGeom) + { + pGeom = make_shared( + atPath, _resources, _geometry, reinterpret_cast(_pRenderer)); + _resources[atPath] = pGeom; + } + + // Set descriptor on resource stub (this will trigger resource invalidation if it have an actual + // geometry pointer) + pGeom->setDescriptor(desc); +} + +bool SceneBase::addInstance(const Path& atPath, const Path& geometry, const Properties& properties) +{ + // Ensure resource does not already exist with this path. + AU_ASSERT(!isPathValid(atPath), + "Resource already exists with path %s, can't create instance with that path", + atPath.c_str()); + + // Create new resource stub and put in resource map. + auto pInstRes = make_shared(atPath, _resources, _instances, this); + _resources[atPath] = pInstRes; + + // Set geometry (geometry is treated as a special property internally) + pInstRes->setProperties({ { Names::InstanceProperties::kGeometry, geometry } }); + // Set the other properties. + pInstRes->setProperties(properties); + + // Increment the permanent ref counts, as instances are activated as soon as they are added to + // the scene. This will create resources for this instance and any resources it references + // directly or indirectly (geometry, material, images, etc.) + pInstRes->incrementPermanentRefCount(); + + return true; +} + +void SceneBase::setInstanceProperties(const Path& instance, const Properties& instanceProperties) +{ + _resources[instance]->setProperties(instanceProperties); +} + +void SceneBase::removeInstance(const Path& path) +{ + _resources[path]->decrementPermanentRefCount(); + _resources.erase(path); +} + +void SceneBase::removeInstances(const Paths& paths) +{ + // Remove all the paths. + for (const Path& path : paths) + { + removeInstance(path); + } +} + +void SceneBase::setInstanceProperties(const Paths& paths, const Properties& instanceProperties) +{ + + // Process all paths/instances. + for (const Path& path : paths) + { + _resources[path]->setProperties(instanceProperties); + } +} + +void SceneBase::update() +{ + if (_instances.activeCount() == 0) + { + if (!_pDefaultInstanceResource->isActive()) + _pDefaultInstanceResource->incrementPermanentRefCount(); + } + else + { + if (_pDefaultInstanceResource->isActive()) + _pDefaultInstanceResource->decrementPermanentRefCount(); + } + + // Update all the active resources, this will build the ResourceNotifier that stores the set of + // active resources for this frame. + _instances.update(); + _geometry.update(); + _environments.update(); + _materials.update(); + _samplers.update(); + _images.update(); +} + +bool SceneBase::isPathValid(const Path& path) +{ + return _resources.find(path) != _resources.end(); +} + +END_AURORA diff --git a/Libraries/Aurora/Source/SceneBase.h b/Libraries/Aurora/Source/SceneBase.h new file mode 100644 index 0000000..76402b0 --- /dev/null +++ b/Libraries/Aurora/Source/SceneBase.h @@ -0,0 +1,131 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" +#include "Resources.h" + +BEGIN_AURORA + +class EnvironmentResource; +class RendererBase; +struct ImageAsset; + +// A base class for implementations of IScene. +class SceneBase : public IScene +{ +public: + SceneBase(IRenderer* pRenderer) : _pRenderer(pRenderer) {} + ~SceneBase(); + + /*** IScene Functions ***/ + + ResourceType getResourceType(const Path& path) override; + void setBounds(const vec3& min, const vec3& max) override; + void setBounds(const float* min, const float* max) override; + void setLight(float intensity, const rgb& color, const vec3& direction, + float angularDiameter = 0.1f) override; + void setLight(float intensity, const float* color, const float* direction, + float angularDiameter) override; + void setImageDescriptor(const Path& atPath, const ImageDescriptor& desc) override; + void setImageFromFilePath( + const Path& atPath, const string& filePath, bool forceLinear, bool isEnvironment) override; + void setSamplerProperties(const Path& atPath, const Properties& props) override; + void setMaterialType( + const Path& atPath, const std::string& materialType, const std::string& document) override; + Paths addInstances(const Path& geometry, const InstanceDefinitions& definitions) override; + bool setEnvironmentProperties( + const Path& environment, const Properties& environmentProperties) override; + bool setEnvironment(const Path& environment) override; + void setMaterialProperties(const Path& path, const Properties& materialProperties) override; + void setInstanceProperties(const Path& path, const Properties& instanceProperties) override; + void setGeometryDescriptor(const Path& atPath, const GeometryDescriptor& desc) override; + + void addPermanent(const Path& resource) override; + void removePermanent(const Path& resource) override; + + bool addInstance( + const Path& atPath, const Path& geometry, const Properties& properties = {}) override; + void removeInstance(const Path& path) override; + void removeInstances(const Paths& paths) override; + void setInstanceProperties(const Paths& paths, const Properties& instanceProperties) override; + + /*** Functions ***/ + + void update(); + shared_ptr defaultEnvironment(); + + const Foundation::BoundingBox& bounds() const { return _bounds; } + float lightIntensity() const { return _lightIntensity; } + const vec3& lightColor() const { return _lightColor; } + const vec3& lightDirection() const { return _lightDirection; } + float lightAngularDiameter() const { return _lightAngularDiameter; } + + shared_ptr defaultMaterialResource() { return _pDefaultMaterialResource; } + + RendererBase* rendererBase(); + +protected: + // Is the path a valid resource path. + virtual bool isPathValid(const Path& path); + + void createDefaultResources(); + + template + shared_ptr getResource(const Path& path) + { + auto iter = _resources.find(path); + if (iter == _resources.end()) + return nullptr; + return dynamic_pointer_cast(iter->second); + } + + /*** Protected Variables ***/ + + IRenderer* _pRenderer = nullptr; + Foundation::BoundingBox _bounds; + float _lightIntensity = 1.0f; + rgb _lightColor = rgb(1.0f, 1.0f, 1.0f); + vec3 _lightDirection = normalize(vec3(-1.0f, -0.5f, -1.0f)); + float _lightAngularDiameter = 0.1f; + + ResourceMap _resources; + + // Trackers for all resource types. + TypedResourceTracker _instances; + TypedResourceTracker _geometry; + TypedResourceTracker _environments; + TypedResourceTracker _images; + TypedResourceTracker _samplers; + TypedResourceTracker _materials; + + shared_ptr _pEnvironmentResource; + shared_ptr _pDefaultEnvironmentResource; + shared_ptr _pDefaultMaterialResource; + shared_ptr _pDefaultInstanceResource; + + // Images loaded with setImageFromFilePath. + map> _loadedImages; + + static Path kDefaultEnvironmentName; + static Path kDefaultMaterialName; + static Path kDefaultGeometryName; + static Path kDefaultInstanceName; + vector _defaultGeometryVerts = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + vector _defaultGeometryIndices = { 0, 1, 2 }; + + shared_ptr _pErrorImageData; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/Shaders/BSDF.slang b/Libraries/Aurora/Source/Shaders/BSDF.slang new file mode 100644 index 0000000..13bdb3d --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/BSDF.slang @@ -0,0 +1,5 @@ +#if USE_REFERENCE_BSDF +#include "ReferenceBSDF.slang" +#else +#include "StandardSurfaceBSDF.slang" +#endif diff --git a/Libraries/Aurora/Source/Shaders/BSDFCommon.slang b/Libraries/Aurora/Source/Shaders/BSDFCommon.slang new file mode 100644 index 0000000..c14b804 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/BSDFCommon.slang @@ -0,0 +1,363 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __BSDFCOMMON_H__ +#define __BSDFCOMMON_H__ + +#include "Globals.slang" +#include "Sampling.slang" + +// A typedef for the result of a BSDF, i.e. the ratio of outgoing radiance to incoming irradiance. +// Some functions include the cosine term with the BSDF, so another definition is used for clarity. +#define BSDF float3 +#define BSDF_AND_COSINE float3 + +// Integer lobe IDs used for debug purposes. +// NOTE: These are in order defined by the closure tree in the Standard Surface specification. The +// names used there are shown as EOL comments below. https://autodesk.github.io/standard-surface +#define TRANSPARENCY_LOBE 1 // transparency +#define COAT_LOBE 2 // coat_brdf +#define METAL_LOBE 3 // metal_brdf +#define GLOSSY_LOBE 4 // spec_brdf +#define TRANSMISSION_LOBE 5 // spec_btdf +#define SHEEN_LOBE 6 // sheen_brdf +#define DIFFUSE_LOBE 7 // diffuse_brdf + +// ================================================================================================= +// Vector Conventions +// ================================================================================================= +// +// The following names are typically used for vectors: +// - V: The view direction. This is away from the shading point, or toward the viewer. This is +// sometimes called the outgoing direction or "wo" in research. +// - L: The light direction. This is away from the shading point, or toward the light. This is +// sometimes called the incoming direction or "wi" in research. +// - N, X, Y: The normal, tangent, and bitangent vectors. These usually refer to a surface point. +// Note that the name "binormal" in inaccurate and should not be used. +// - H: The half-vector between two other vectors. This usually refers to a microfacet normal, or +// "m" in research. It can be constructed from V and L vectors as H = normalize(V + L). +// +// The following conventions apply to the use of vectors for shading: +// - BSDFs are called with and return tangent-space vectors. The caller must convert to / from +// world-space vectors as needed with worldToTangent() and tangentToWorld(). This simplifies +// computation, e.g. you can use V.z in tangent space instead of dot(V, N). +// - Materials must handle the possibility that V and L are in opposite hemispheres. Specifically, +// use a BRDF (reflection) when they are in the same hemisphere, and use a BTDF (transmission) +// otherwise. This is not detected in the BSDF evaluation functions here, but is handled by the +// BSDF sampling functions that call the evaluation functions. +// - From this, double-sided shading is supported by simply using the absolute value of any dot +// products involving the shading normal N, instead of testing or clamping. +// - The normal direction should not be flipped, so that it can used to determine the "outside" of +// the surface, and therefore whether directions are entering (from the outside) or exiting (from +// the inside). +// - Sampled directions that rely on a canonical (0.0, 0.0, 1.0) normal must be flipped as needed, +// depending on the view direction. +// - See PBRT 3E 9.1 for more information on these conventions. + +// ================================================================================================= +// Utility Functions +// ================================================================================================= + +// Remaps a random variable to [0.0, 1.0] after passing the specified test value. +float remapPass(float random, float test) +{ + return random / test; +} + +// Remaps a random variable to [0.0, 1.0] after failing the specified test value. +float remapFail(float random, float test) +{ + return (random - test) / (1.0f - test); +} + +// Transforms the specified input vector in world space to the tangent space defined by [X, Y, N]. +float3 worldToTangent(float3 input, float3 X, float3 Y, float3 N) +{ + return float3(dot(input, X), dot(input, Y), dot(input, N)); +} + +// Transforms the specified input vector in tangent space defined by [X, Y, N] to world space. +float3 tangentToWorld(float3 input, float3 X, float3 Y, float3 N) +{ + return input.x * X + input.y * Y + input.z * N; +} + +// Returns whether the two tangent-space vectors in the same hemisphere. +bool sameHemisphere(float3 a, float3 b) +{ + return a.z * b.z > 0.0f; +} + +// Converts an index of refraction (IOR) to reflectance at normal incidence (F0). +// NOTE: The standard dielectric IOR of 1.5 yields 4%. +float3 iorToF0(float ior) +{ + return sqr((1.0f - ior) / (1.0f + ior)); +} + +// Evaluates the Frensel equation for a conductor with the specified f0 value (i.e. reflectance at +// normal incidence), using the Schlick approximation. +float3 evaluateSchlickFresnel(float3 f0, float cosTheta) +{ + return lerp(f0, 1.0f, pow(1.0f - cosTheta, 5.0f)); +} + +// Computes a refraction direction from the specified tangent-space view direction and IOR. This +// also returns whether total internal reflection occurs. +float3 computeRefraction(float3 V, float ior, out bool reflected) +{ + reflected = false; + + // If the view direction (toward the viewer) is above the normal, then we assume that the ray is + // *entering* the material with the specified IOR from the outside (air). Otherwise it is + // *leaving* the material. + bool isEnteringMaterial = V.z > 0.0f; + + // Invert the tangent-space normal if the ray is exiting the material. This is needed for the + // refract() call to work as expected. + float3 N = float3(0.0f, 0.0f, isEnteringMaterial ? 1.0f : -1.0f); + + // The refract() function expects an IOR ratio ("eta") as exiting IOR over entering IOR, so + // compute that ratio based on whether the ray is entering the material or exiting it. The other + // medium is assumed to be air (IOR 1.0). + float eta = isEnteringMaterial ? 1.0f / ior : ior / 1.0f; + + // Compute the refraction direction from the ray direction and normal. If the refraction + // direction has zero length, then perform total internal reflection (TIR) instead, returning + // that reflection (true) was performed instead of refraction. + float3 L = refract(-V, N, eta); + if (length(L) == 0.0f) + { + // Compute a tangent-space reflection vector. + L = float3(-V.x, -V.y, V.z); + reflected = true; + } + + return L; +} + +// ================================================================================================= +// Lambertian BRDF +// ================================================================================================= + +// Evaluates the Lambertian diffuse BRDF, with the specified albedo. +float3 evaluateLambertianBRDF(float3 albedo) +{ + return albedo * M_PI_INV; +} + +// Samples and evaluates the Lambertian diffuse BRDF, returning the BRDF value along with the +// sampled light direction and the PDF for that direction. +BSDF sampleLambertianBRDF(float3 V, float3 albedo, float2 random, out float3 L, out float pdf) +{ + // Sample a cosine-weighted direction from a hemisphere above the normal. If the view direction + // is not in the same hemisphere as the normal, flip the normal before sampling to allow proper + // shading of the backface. + float3 N = float3(0.0f, 0.0f, V.z > 0.0f ? 1.0f : -1.0f); + L = sampleHemisphere(random, N, pdf); + + // Return black if the view and light directions are not in the same hemisphere. + if (!sameHemisphere(V, L)) + { + return BLACK; + } + + // Evaluate the Lambertian BRDF. + return evaluateLambertianBRDF(albedo); +} + +// ================================================================================================= +// GGX-Smith BRDF +// ================================================================================================= +// +// This section refers to two papers by Eric Heitz: +// - 2014: Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. +// - 2018: Sampling the GGX Distribution of Visible Normals. + +// Converts roughness to alpha for the GGX NDF and Smith geometry term. +// NOTE: See "Revisiting Physically Based Shading at Imageworks" from SIGGRAPH 2017. +float2 roughnessToAlpha(float roughness, float anisotropy) +{ + // Specify a minimum alpha value, as the GGX NDF does not support a zero alpha. + static const float MIN_ALPHA = 0.0001f; + + // Square the roughness value and combine with anisotropy to produce X and Y alpha values. + float alpha = max(MIN_ALPHA, sqr(roughness)); + float2 alpha2 = float2(alpha * (1.0f + anisotropy), alpha * (1.0f - anisotropy)); + + return alpha2; +} + +// Evaluates the "lambda" expression used in Smith G1/G2 implementations, for the specified vector. +// NOTE: See 2018, equation #2. +float lambda(float3 vec, float2 alpha) +{ + float squared = (sqr(alpha.x) * sqr(vec.x) + sqr(alpha.y) * sqr(vec.y)) / sqr(vec.z); + + return (-1.0f + sqrt(1.0f + squared)) * 0.5f; +} + +// Evaluates the Smith masking function (G1) for the specified view direction and alpha. +// NOTE: See 2014, equation #43. +float evaluateSmithG1(float3 V, float3 H, float2 alpha) +{ + // Return zero if the view direction is below the half-vector, i.e. fully masked. + if (dot(V, H) <= 0.0f) + { + return 0.0f; + } + + return 1.0f / (1.0f + lambda(V, alpha)); +} + +// Evaluates the Smith masking and shadowing function (G2) for the specified view and light +// directions and alpha. +// NOTE: See 2014, equation #43. +float evaluateSmithG2(float3 V, float3 L, float3 H, float2 alpha) +{ + // Return zero if the view or light direction is below the half-vector, i.e. fully masked and / + // or shadowed. + if (dot(V, H) <= 0.0f || dot(L, H) < 0.0f) + { + return 0.0f; + } + + return 1.0f / (1.0f + lambda(V, alpha) + lambda(L, alpha)); +} + +// Evaluates the GGX NDF (D), for the given half-vector and alpha. +// NOTE: See 2018, equation #1. +float evaluateGGXNDF(float3 H, float2 alpha) +{ + float ellipse = sqr(H.x) / sqr(alpha.x) + sqr(H.y) / sqr(alpha.y) + sqr(H.z); + + return 1.0f / (M_PI * alpha.x * alpha.y * sqr(ellipse)); +} + +// Evaluates the GGX-Smith VNDF (Dv), for the given view direction, microfacet normal, and alpha. +// NOTE: See 2018, equation #1. This is used when sampling the GGX-Smith VNDF, and should not be +// used in place of the GGX NDF. +float evaluateGGXSmithVNDF(float3 V, float3 H, float2 alpha) +{ + // Return zero if the view direction is below the half-vector, i.e. fully masked. + float VDotH = dot(V, H); + if (VDotH <= 0.0f) + { + return 0.0f; + } + + // Evaluate the D and G1 components for the VNDF. + float D = evaluateGGXNDF(H, alpha); + float G1 = evaluateSmithG1(V, H, alpha); + + return D * G1 * VDotH / abs(V.z); +} + +// Samples the GGX-Smith VNDF with the specified view direction, returning a microfacet normal and +// the PDF for that direction. +// NOTE: See 2018, appendix A. +float3 sampleGGXSmithVNDF(float3 V, float2 alpha, float2 random) +{ + // Section 3.2: Transform the view direction to the hemisphere configuration. + float3 Vh = normalize(float3(alpha.x * V.x, alpha.y * V.y, V.z)); + + // Section 4.1: Orthonormal basis (with special case if cross product is zero). + float lensq = Vh.x * Vh.x + Vh.y * Vh.y; + float3 T1 = lensq > 0.0f ? float3(-Vh.y, Vh.x, 0.0f) / sqrt(lensq) : float3(1.0f, 0.0f, 0.0f); + float3 T2 = cross(Vh, T1); + + // Section 4.2: Parameterization of the projected area. + float r = sqrt(random.x); + float phi = 2.0f * M_PI * random.y; + float t1 = r * cos(phi); + float t2 = r * sin(phi); + float s = 0.5f * (1.0f + Vh.z); + t2 = (1.0f - s) * sqrt(1.0f - t1 * t1) + s * t2; + + // Section 4.3: Reprojection onto hemisphere. + float3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0, 1.0f - t1 * t1 - t2 * t2)) * Vh; + + // Section 3.4: Transform the normal back to the ellipsoid configuration. + float3 H = normalize(float3(alpha.x * Nh.x, alpha.y * Nh.y, max(0.0f, Nh.z))); + + return H; +} + +// Compute the PDF for the GGX-Smith VNDF, with the specified view direction and microfacet normal. +// NOTE: See 2018, appendix B. +float computeGGXSmithPDF(float3 V, float3 H, float2 alpha) +{ + // The PDF is simply the evaluation of the VNDF. + return evaluateGGXSmithVNDF(V, H, alpha); +} + +// Evaluates the GGX-Smith glossy BRDF, returning the BRDF value along with the Fresnel term used. +BSDF evaluateGGXSmithBRDF( + float3 V, float3 L, float3 f0, float roughness, float anisotropy, out float3 F) +{ + // Convert roughness and anisotropy to alpha values. + float2 alpha = roughnessToAlpha(roughness, anisotropy); + + // Compute the D (NDF), G (visibility), and F (Fresnel) terms, along with the microfacet BRDF + // denominator. + float3 H = normalize(V + L); + float D = evaluateGGXNDF(H, alpha); + float G2 = evaluateSmithG2(V, L, H, alpha); + F = evaluateSchlickFresnel(f0, dot(V, H)); + float denom = 4.0f * abs(V.z) * abs(L.z); + + // Return the combined microfacet expression. + return D * G2 * F / denom; +} + +// Samples and evaluates the GGX-Smith glossy BRDF, returning the BRDF value along with the sampled +// light direction and the PDF for that direction. +// NOTE: See 2018, appendix B. +BSDF sampleGGXSmithBRDF(float3 V, float3 f0, float roughness, float anisotropy, float2 random, + out float3 L, out float pdf) +{ + // Convert roughness and anisotropy to alpha values. + float2 alpha = roughnessToAlpha(roughness, anisotropy); + + // Sample the GGX-Smith VNDF to determine a microfacet normal (half-vector). If the view + // direction is in the lower hemisphere in tangent space, flip the view direction normal before + // sampling the VNDF, then flip the resulting microfacet normal. + // NOTE: The VNDF expects the view direction to be in the upper hemisphere (with the normal). + bool flip = V.z <= 0.0f; + float3 H = sampleGGXSmithVNDF(flip ? -V : V, alpha, random); + H = flip ? -H : H; + + // Reflect the view direction across the microfacet normal to get the sample direction. + L = reflect(-V, H); + + // Compute the PDF, divided by a factor from using a reflected vector. See 2018 equation #17. + // NOTE: 2018 equation #19 shows that a number of terms can be cancelled out to produce a simple + // estimator: just F * G2 / G1. Note that the PDF and cosine terms from the rendering equation + // are cancelled out. However, doing this means the call sites must change: you will need + // special handling for this BRDF compared to other BRDFs. There is no discernible performance + // improvement by using the simpler estimator. + float denom = 4.0f * dot(V, H); + pdf = computeGGXSmithPDF(V, H, alpha) / denom; + + // Return black if the view and light directions are not in the same hemisphere. + if (!sameHemisphere(V, L)) + { + return BLACK; + } + + // Evaluate the GGX-Smith BRDF with view direction and sampled light direction. + float3 F; // output not used + return evaluateGGXSmithBRDF(V, L, f0, roughness, anisotropy, F); +} + +#endif // __BSDF_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang b/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang new file mode 100644 index 0000000..675ef02 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang @@ -0,0 +1,36 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Prefix containing the common code used by all material types. +// NOTE: Does not contain a hit shader. Hit shaders for the different material types must be +// appended to this file. +#include "PathTracingCommon.slang" + +// The background miss shader, which evaluates the environment as a background. This is +// typically done from primary, transmission, and transparency rays. +[shader("miss")] void BackgroundMissShader(inout RayPayload rayPayload) +{ + // Initialize the radiance ray payload for a miss. + rayPayload.radianceRay.clear(); + + // Evaluate the environment, as a background. + Environment environment = prepareEnvironmentValues(); + float3 color = evaluateEnvironment(environment, WorldRayDirection(), true); + + // Store the environment color. + // NOTE: The miss result is considered to be part of direct lighting, to allow for simpler logic + // during accumulation. + rayPayload.radianceRay.color = color; + rayPayload.radianceRay.direct = color; +} diff --git a/Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang b/Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang new file mode 100644 index 0000000..8ac50bf --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang @@ -0,0 +1,200 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Entry point template that defines a hit shader for a material type. +// NOTE: This file is not valid HLSL as is. This template must be configured at runtime by +// replacing the tags surrounded by the @ character: +// -@MATERIAL_TYPE@ with the unique material type name for this shader. + +// Closest hit shader for radiance rays. +#include "PathTracingCommon.slang" +#include "InitializeMaterial.slang" +#include "ShadeFunctions.slang" + +[shader("closesthit")] void @MATERIAL_TYPE@RadianceHitShader(inout RayPayload rayPayload, in BuiltInTriangleIntersectionAttributes hit) +{ + int depth = rayPayload.radianceRay.depth; + int maxDepth = gFrameData.traceDepth; + bool isOpaqueShadowsEnabled = gFrameData.isOpaqueShadowsEnabled; + + // Get the interpolated vertex data for the hit triangle, at the hit barycentric coordinates. + uint triangleIndex = PrimitiveIndex(); + float3 barycentrics = computeBarycentrics(hit); + ShadingData shading = computeShadingData( + gGeometry, triangleIndex, barycentrics, ObjectToWorld3x4()); + + // Initialize the view direction. + float3 V = -WorldRayDirection(); + float3 materialNormal = shading.normal; + bool isGeneratedNormal; + + // Initialize the material values for repeated evaluations at the current shading point. Also + // initialize the environment. + Material material = initializeMaterial( + shading, + ObjectToWorld3x4(), + materialNormal, + isGeneratedNormal); + Environment environment = prepareEnvironmentValues(); + + // If a new normal has been generated, transform it to world space and build a corresponding + // basis from it. + if (isGeneratedNormal) + { + shading.normal = materialNormal; + buildBasis(shading.normal, shading.tangent, shading.bitangent); + } + + // Modify the material properties if only the diffuse component should be renderered. + if (gFrameData.isDiffuseOnlyEnabled) + { + material.base = 1.0f; + material.specular = 0.0f; + material.metalness = 0.0f; + } + + // Shade any material layers (for front facing hits only) if the ENABLE_LAYERS option is + // defined. + // TODO: Should this be done after the transparency evaluation below, i.e. it is possible this + // work will be thrown away for transparent samples. +#if ENABLE_LAYERS + if (dot(shading.normal, V) > 0.0) + { + // Iterate in reverse order, so innermost layer is shaded last. + for (int layer = gMaterialLayerCount - 1; layer >= 0; layer--) + { + int layerMissShaderIdx = getMaterialLayerIndex(layer); + + // Shade the layer, and return if the ray was absorbed. + if (shadeMaterialLayer(gNullScene, layerMissShaderIdx, rayPayload.radianceRay, shading, + hit, depth, maxDepth)) + { + return; + } + } + } +#endif + + // Handle opacity (transparency) by stochastically determining whether the hit should be + // skipped, based on the luminance of the material opacity. If so, use the ray payload from a + // ray traced at the hit point, i.e. passing straight through the geometry, and return. + // NOTE: This could also be done with an any hit shader, with possibly better or worse + // performance. + float3 transparency = 1.0f - material.opacity; + float P = luminance(transparency); + if (random2D(rayPayload.radianceRay.rng).x < P) + { + // Trace a radiance ray with the unmodified ray direction, and the hit position. Use the + // background miss shader so that the background is sampled, since this is for transparency. + // NOTE: The geometric position (geomPosition) is used to avoid self-intersection. + rayPayload.radianceRay = traceRadianceRay(gScene, environment, shading.geomPosition, -V, + M_RAY_TMIN, depth, maxDepth, true, rayPayload.radianceRay.rng); + + // Scale the color components of the ray payload by transparency, and normalized by the + // probability of this segment being considered transparent. + rayPayload.radianceRay.scaleColor(transparency / P); + + // Nothing more to do. + return; + } + + // Compute the NDC depth and view depth of the hit position: + // - NDC depth: Compute the clip space position using the supplied view-projection matrix, then + // divide the Z component by W, and remap from [-1.0, 1.0] to [0.0, 1.0]. + // - View Depth: For a primary ray, this is simply the T value of the ray. We don't use this + // value for non-primary rays; it is recorded here but not used. + float4 positionClip = mul(gFrameData.cameraViewProj, float4(shading.geomPosition, 1.0f)); + float depthNDC = (positionClip.z / positionClip.w + 1.0f) / 2.0f; + float depthView = RayTCurrent(); + + // Clamp roughness for the ray payload to a minimum, because the denoiser handles materials with + // low (near-zero) roughness poorly, leading to a noisy result. This addresses two separate but + // related issues: low-roughness metallic materials reflecting noisy surroundings, and low- + // roughness dielectric materials where the glossy lobe is sparsely sampled. + // NOTE: The roughness in the ray payload is used for denoising only. The material specular + // roughness (used in shading) is not modified here. + static const float kMinRoughness = 0.05f; + float clampedRoughness = max(material.specularRoughness, kMinRoughness); + + // Store initial data in the ray payload. + rayPayload.radianceRay.color = BLACK; + rayPayload.radianceRay.alpha = 1.0f; + rayPayload.radianceRay.direct = BLACK; + rayPayload.radianceRay.depthNDC = depthNDC; + rayPayload.radianceRay.depthView = depthView; + rayPayload.radianceRay.normal = shading.normal; + rayPayload.radianceRay.baseColor = material.baseColor; + rayPayload.radianceRay.roughness = clampedRoughness; + rayPayload.radianceRay.metalness = material.metalness; + rayPayload.radianceRay.indirect.clear(); + + // Shade with the global directional light. Skip this if the light intensity is zero. + if (gFrameData.lightColorAndIntensity.a > 0.0f) + { + rayPayload.radianceRay.direct = shadeDirectionalLight(gFrameData.lightDir, + gFrameData.lightColorAndIntensity, gFrameData.lightCosRadius, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + + // Add the direct lighting to the color result. + // NOTE: For denoising purposes, "direct" lighting only includes the directional light, and + // not the environment light computed below. + rayPayload.radianceRay.color += rayPayload.radianceRay.direct; + } + + // When denoising, primary rays for indirect lighting should not include the base color (also + // known as albedo, as used by diffuse lobes) in order to avoid blurring out color details. So + // the base color is set to white here and the color is added back after denoising. + if (gFrameData.isDenoisingEnabled && rayPayload.radianceRay.depth == 1) + { + material.baseColor = WHITE; + } + + // Shade with the environment light depending on the importance sampling type: + // - BSDF: The environment is treated as indirect light, and is evaluated in the miss shader. + // - Environment: Sample the environment light as direct lighting. + // - MIS: Use multiple importance sampling on both the material and the light. + // NOTE: For denoising purposes, "indirect" lighting includes the environment light. + if (IMPORTANCE_SAMPLING_MODE != IMPORTANCE_SAMPLING_BSDF) + { + float3 environmentRadiance = BLACK; + if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_ENVIRONMENT) + { + // TODO: This does not currently contribute to indirect lighting for denoising purposes. + // That will require having the material evaluation return separate diffuse and glossy + // components. + environmentRadiance = + shadeEnvironmentLightDirect(environment, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + } + else if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_MIS) + { + environmentRadiance = shadeEnvironmentLightMIS(environment, gScene, material, shading, V, isOpaqueShadowsEnabled, depth, + maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.indirect); + } + + // Add the radiance from the environment light to the color result. + rayPayload.radianceRay.color += environmentRadiance; + } + + // Shade with indirect light from the surrounding scene (i.e. path tracing). + rayPayload.radianceRay.color += shadeIndirectLight( + gScene, environment, material, shading, V, depth, maxDepth, rayPayload.radianceRay.rng, + rayPayload.radianceRay.alpha, rayPayload.radianceRay.indirect); + + // Scale the color components of the ray payload by opacity, and normalized by the probability + // of this segment being considered opaque. + // NOTE: The shading functions do not individually consider opacity, so that it can be handled + // in one place here. + rayPayload.radianceRay.scaleColor(material.opacity / (1.0f - P)); +} diff --git a/Libraries/Aurora/Source/Shaders/Colors.slang b/Libraries/Aurora/Source/Shaders/Colors.slang new file mode 100644 index 0000000..c100076 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Colors.slang @@ -0,0 +1,55 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __COLORS_H__ +#define __COLORS_H__ + +// Linearizes an sRGB color. +// NOTE: Use this only for hardcoded colors in the shader. All other color inputs should already be +// linearized. +float3 sRGBtoLinear(float3 value) +{ + return value * (value * (value * 0.305306011f + 0.682171111f) + 0.012522878f); +} + +// Computes the perceived luminance of a color. +float luminance(float3 value) +{ + return dot(value, float3(0.2125f, 0.7154f, 0.0721f)); +} + +// Converts from linear color space to sRGB (gamma correction) for display. +// NOTE: Based on http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html. +float3 linearTosRGB(float3 color) +{ + float3 sq1 = sqrt(color); + float3 sq2 = sqrt(sq1); + float3 sq3 = sqrt(sq2); + + return 0.662002687f * sq1 + 0.684122060f * sq2 - 0.323583601f * sq3 - 0.0225411470f * color; +} + +// Applies the ACES filmic tone mapping curve to the color. +// NOTE: Based on https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve. +float3 toneMapACES(float3 color) +{ + float a = 2.51f; + float b = 0.03f; + float c = 2.43f; + float d = 0.59f; + float e = 0.14f; + + return saturate((color * (a * color + b)) / (color * (c * color + d) + e)); +} + +#endif // __COLORS_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/Environment.slang b/Libraries/Aurora/Source/Shaders/Environment.slang new file mode 100644 index 0000000..ce517e3 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Environment.slang @@ -0,0 +1,220 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __ENVIRONMENT_H__ +#define __ENVIRONMENT_H__ + +#include "Colors.slang" +#include "Globals.slang" +#include "Sampling.slang" + +// Layout of environment constant properties. +struct EnvironmentConstants +{ + float3 lightTop; + float _padding1; + float3 lightBottom; + float lightTexLuminanceIntegral; + float4x4 lightTransform; + float4x4 lightTransformInv; + float3 backgroundTop; + float _padding3; + float3 backgroundBottom; + float _padding4; + float4x4 backgroundTransform; + bool backgroundUseScreen; + bool hasLightTex; + bool hasBackgroundTex; +}; + +// Layout of an alias map entry. +// NOTE: Padding to 16 byte (float4) alignment is added for best performance. +struct AliasEntry +{ + uint alias; + float probability; + float pdf; + float _padding1; +}; + +// Environment properties. +struct Environment +{ + EnvironmentConstants constants; + Texture2D backgroundTexture; + SamplerState sampler; + Texture2D lightTexture; + +#if DIRECTX + StructuredBuffer aliasMap; +#endif +}; + +// Computes UV coordinates for an image from a direction. The image is assumed to have lat-long +// layout, with the center of the image in the -Z direction, and the top in the +Y direction. +float2 directionToLatLongUV(float3 direction) +{ + float2 uv; + uv.x = atan2(direction.x, -direction.z) * M_PI_INV * 0.5f + 0.5f; + uv.y = -asin(direction.y) * M_PI_INV + 0.5f; + + return uv; +} + +// Computes a direction from UV coordinates for an image. The image is assumed to have lat-long +// layout, with the center of the image in the -Z direction, and the top in the +Y direction. +float3 latLongUVToDirection(float2 uv) +{ + // Compute spherical coordinates. + float phi = (uv.x + 0.25f) * 2.0f * M_PI; // longitude + float theta = uv.y * M_PI; // latitude + + // Prepare values for computing the direction. + float sinPhi, cosPhi, sinTheta, cosTheta; + sincos(phi, sinPhi, cosPhi); + sincos(theta, sinTheta, cosTheta); + + // Compute the direction in Cartesian coordinates. + return float3(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta); +} + +// Compute UV from direction vector. +float2 computeEnvironmentUV(float4x4 transform, bool useScreen, float3 direction) +{ + // Compute the sampling coordinates, either using screen coordinates or lat-long layout. + float2 uv; + if (useScreen) + { + // Compute the texture coordinates from the dispatch index and dimensions. + float2 screenCoords = float2(DispatchRaysIndex().xy); + float2 screenSize = float2(DispatchRaysDimensions().xy); + uv = screenCoords / screenSize; + } + else + { + // Transform the direction vector, and compute UV coordinates from it. + direction = normalize(mul(transform, float4(direction, 0.0f)).xyz); + uv = directionToLatLongUV(direction); + } + + return uv; +} + +// Evaluate a procedural gradient environment with the specified properties, with the specified UV. +float3 evaluateEnvironmentGradient(float2 uv, float3 topColor, float3 bottomColor) +{ + // Sample the texture, or interpolate the gradient colors using the Y coordinate. + return lerp(topColor, bottomColor, uv.y); +} + +// Evaluate a texture environment with the specified properties, with the specified UV. +float3 evaluateEnvironmentTexture(float2 uv, Texture2D texture, SamplerState sampler) +{ + // Sample the texture, or interpolate the gradient colors using the Y coordinate. + return texture.SampleLevel(sampler, uv, 0.0f).rgb; +} + +// Evaluate the specified environment in the specified direction, optionally as a background. +float3 evaluateEnvironment(Environment environment, float3 direction, bool asBackground) +{ + + EnvironmentConstants constants = environment.constants; + + if (asBackground) + { + float2 uv = computeEnvironmentUV( + constants.backgroundTransform, constants.backgroundUseScreen, direction); + if (constants.hasBackgroundTex) + { + return evaluateEnvironmentTexture( + uv, environment.backgroundTexture, environment.sampler); + } + else + { + return evaluateEnvironmentGradient( + uv, constants.backgroundTop, constants.backgroundBottom); + } + } + else + { + float2 uv = computeEnvironmentUV(constants.lightTransform, false, direction); + + if (constants.hasLightTex) + { + return evaluateEnvironmentTexture( uv, + environment.lightTexture, environment.sampler); + } + else + { + return evaluateEnvironmentGradient(uv, constants.lightTop, constants.lightBottom); + } + } + +} + +// Samples the specified environment, returning a sample (light) direction and corresponding +// probability density function (PDF). +float3 sampleEnvironment(Environment environment, float2 random, out float3 L, out float pdf) +{ + // Sample the environment as either a texture or a gradient. + EnvironmentConstants constants = environment.constants; +#if DIRECTX + if (constants.hasLightTex) + { + // Get the textures dimensions and texel count. + uint width, height, count; + environment.lightTexture.GetDimensions(width, height); + count = width * height; + + // Select an alias map entry at random, corresponding to a texel in the environment texture. + // If the entry's probability does not exceed another random value, use the entry's alias + // instead, corresponding to a different texel. + uint index = min(count * random[0], count - 1); + AliasEntry entry = environment.aliasMap[index]; + if (entry.probability < random[1]) + { + index = entry.alias; + entry = environment.aliasMap[index]; + } + pdf = entry.pdf; + + // Compute texture coordinates in texel space, convert to a direction, and transform it. + // NOTE: The inverse light transform is used because the direction is computed here as part + // of sampling (an output), rather than being specified as for evaluation (an input). + int3 xy = int3(index % width, index / width, 0); + L = latLongUVToDirection(float2((xy.x + 0.5f) / width, (xy.y + 0.5f) / height)); + L = normalize(mul(constants.lightTransformInv, float4(L, 0.0f)).xyz); + + // Load from the texture using the texture coordinates and return as the radiance. + // NOTE: This does not perform texel interpolation, but that could be done with using + // SampleLevel() with a random location inside the lat-long patch represented by the texel. + return environment.lightTexture.Load(xy).rgb; + } + else +#endif + { + // Uniformly sample a direction. + L = sampleUniformDirection(random, pdf); + + // Transform the direction vector, and compute UV coordinates from it. + // NOTE: The inverse light transform is used because the direction is computed here as part + // of sampling (an output), rather than being specified as for evaluation (an input). + L = normalize(mul(constants.lightTransformInv, float4(L, 0.0f)).xyz); + float2 uv = directionToLatLongUV(L); + + // Interpolate the gradient colors using the Y coordinate and return as the radiance. + return lerp(constants.lightTop, constants.lightBottom, uv.y); + } +} + +#endif // __ENVIRONMENT_H__ diff --git a/Libraries/Aurora/Source/Shaders/Frame.slang b/Libraries/Aurora/Source/Shaders/Frame.slang new file mode 100644 index 0000000..11c476c --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Frame.slang @@ -0,0 +1,95 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __FRAME_H__ +#define __FRAME_H__ + +// Layout of sample parameters. +struct SampleData +{ + // The sample index (iteration) for the frame, for progressive rendering. + uint sampleIndex; + + // An offset to apply to the sample index for seeding a random number generator. + uint seedOffset; +}; + +// Layout of per-frame parameters. +struct FrameData +{ + // The view-projection matrix. + float4x4 cameraViewProj; + + // The inverse view matrix, also transposed. The *rows* must have the desired vectors: right, + // up, front, and eye position. HLSL array access with [] returns rows, not columns, hence the + // need for the matrix to be supplied transposed. + float4x4 cameraInvView; + + // The dimensions of the view (in world units) at a distance of 1.0 from the camera, which is + // useful to build ray directions. + float2 viewSize; + + // Whether the camera is using an orthographic projection. Otherwise a perspective projection is + // assumed. + bool isOrthoProjection; + + // The distance from the camera for sharpest focus, for depth of field. + float focalDistance; + + // The diameter of the lens for depth of field. If this is zero, there is no depth of field, + // i.e. pinhole camera. + float lensRadius; + + // The size of the scene, specifically the maximum distance between any two points in the scene. + float sceneSize; + + float2 _padding1; + + // The direction of the global light. + // NOTE: This is *toward* the light. + float3 lightDir; + + float _padding2; + + // The color of the light (RGB) and its intensity (A). + float4 lightColorAndIntensity; + + // The cosine of the light radius (as a disc). + float lightCosRadius; + + // Whether shadow evaluation should treat all objects as opaque, as a performance optimization. + bool isOpaqueShadowsEnabled; + + // Whether to write the NDC depth result to an output texture. + bool isDepthNDCEnabled; + + // Whether to render the diffuse material component only. + bool isDiffuseOnlyEnabled; + + // Whether to display shading errors as bright colored samples. + bool isDisplayErrorsEnabled; + + // Whether denoising is enabled, which affects how path tracing is performed. + bool isDenoisingEnabled; + + // Whether to write the AOV data required for denoising. + bool isDenoisingAOVsEnabled; + + // The maximum recursion level (or path length) when tracing rays. + int traceDepth; + + // The maximum luminance for path tracing samples, for simple firefly clamping. + float maxLuminance; +}; + +#endif // __FRAME_H__ diff --git a/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang b/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang new file mode 100644 index 0000000..5f6d175 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang @@ -0,0 +1,84 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __GLSL_TO_HLSL_H__ +#define __GLSL_TO_HLSL_H__ + +// Map the vector types +#define vec4 float4 +#define vec3 float3 +#define vec2 float2 +#define uvec4 int4 +#define uvec3 int3 +#define uvec2 int2 + +// Map mix to lerp. +#define mix lerp +// TODO: other built-ins. + +// 'texture' is reserved name, so rename in preprocessor. +#define texture sampleTextureStruct +// Assume float4 texture types. +#define DXTexture Texture2D + +// Map GLSL sampler2D to HLSL struct containing texture and sampler. +struct sampler2D +{ + DXTexture textureObject; + SamplerState sampler; +}; + +// Create sampler2D struct, called by HLSL wrapper code. +sampler2D createSampler2D(DXTexture textureObject, SamplerState sampler) +{ + sampler2D txt; + txt.textureObject = textureObject; + txt.sampler = sampler; + return txt; +} + +// Implement GLSL textureSize. +uint2 textureSize(sampler2D txt, int lod) +{ + float2 res = uint2(1, 1); + float numLevels = 0; + txt.textureObject.GetDimensions(lod, res.x, res.y, numLevels); + return res; +} + +// Implement GLSL texture function (renamed via define above). +float4 sampleTextureStruct(sampler2D txt, vec2 uv) +{ + return txt.textureObject.SampleLevel(txt.sampler, uv, 0.0f); +} + +// Implement dFdx +// TODO: This is horrible hacky solution needed by mx_compute_sample_size_uv, +// should use different solution that doesn't require this. +float2 dFdx(float2 val) +{ + // Return arbitrary small value. + return float2(1.0 / 256.0, 1.0 / 256.0); +} + +// Implement dFdx +// TODO: This is horrible hacky solution needed by mx_compute_sample_size_uv, +// should use different solution that doesn't require this. +float2 dFdy(float2 val) +{ + // Return arbitrary small value. + return float2(1.0 / 256.0, 1.0 / 256.0); +} + +#endif // __GLSL_TO_HLSL_H__ diff --git a/Libraries/Aurora/Source/Shaders/Geometry.slang b/Libraries/Aurora/Source/Shaders/Geometry.slang new file mode 100644 index 0000000..f05c29a --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Geometry.slang @@ -0,0 +1,151 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __GEOMETRY_H__ +#define __GEOMETRY_H__ + + +interface IGeometry +{ + uint3 getIndices(int bufferLocation); + float3 getPosition(int bufferLocation); + float3 getNormal(int bufferLocation); + float2 getTexCoord(int bufferLocation); + bool hasTexCoords(); + bool hasNormals(); +}; + + +// Shading data for an intersection point, with interpolated values from adjacent vertices. +struct ShadingData +{ + float3 position; // the shading position, which may be slightly above the surface + float3 geomPosition; // the geometric position, interpolated from adjancent vertices + float3 normal; // the shading normal, interpolated from adjancent vertices + float2 texCoord; // the shading texture coordinates, interpolated from adjancent vertices + float3 tangent; // the shading tangent (X), interpolated from adjacent vertices + float3 bitangent; // the shading bitangent (Y), interpolated from adjacent vertices +}; + +// Computes the barycentric coordinates from the specified triangle intersection. +float3 computeBarycentrics(BuiltInTriangleIntersectionAttributes attribs) +{ + float2 bary2 = attribs.barycentrics; + return float3(1.0f - bary2.x - bary2.y, bary2.x, bary2.y); +} + +// Projects the specified position (point) onto the plane with the specified origin and normal. +float3 projectOnPlane(float3 position, float3 origin, float3 normal) +{ + return position - dot(position - origin, normal) * normal; +} + +// Computes the shading position of the specified geometric position and vertex positions and +// normals. For a triangle with normals describing a convex surface, this point will be slightly +// above the surface. For a concave surface, the geometry position is used directly. +// NOTE: The difference between the shading position and geometry position is significant when +// casting shadow rays. If the geometric position is used, a triangle may fully shadow itself when +// it should be partly lit based on the shading normals; this is the "shadow terminator" problem. +float3 computeShadingPosition(float3 geomPosition, float3 shadingNormal, float3 positions[3], + float3 normals[3], float3 barycentrics) +{ + // Project the geometric position (inside the triangle) to the planes defined by the vertex + // positions and normals. + float3 p0 = projectOnPlane(geomPosition, positions[0], normals[0]); + float3 p1 = projectOnPlane(geomPosition, positions[1], normals[1]); + float3 p2 = projectOnPlane(geomPosition, positions[2], normals[2]); + + // Interpolate the projected positions using the barycentric coordinates, which gives the + // shading position. + float3 shadingPosition = p0 * barycentrics.x + p1 * barycentrics.y + p2 * barycentrics.z; + + // Return the shading position for a convex triangle, where the shading point is above the + // triangle based on the shading normal. Otherwise use the geometric position. + bool convex = dot(shadingPosition - geomPosition, shadingNormal) > 0.0f; + return convex ? shadingPosition : geomPosition; +} + +// Computes the shading data (e.g. interpolated vertex data) for the triangle with the specified +// (primitive) index, at the specified barycentric coordinates. +ShadingData computeShadingData( + GeometryType geometry, uint triangleIndex, float3 barycentrics, float3x4 objToWorld) +{ + // Initialize a result structure. + ShadingData shading; + shading.position = float3(0.0f, 0.0f, 0.0f); + shading.geomPosition = float3(0.0f, 0.0f, 0.0f); + shading.normal = float3(0.0f, 0.0f, 0.0f); + shading.texCoord = float2(0.0f, 0.0f); + shading.tangent = float3(1.0f, 0.0f, 0.0f); + + // Load the indices for the vertices of the triangle with the specified index. + uint3 indices = geometry.getIndices(triangleIndex); + + // Compute interpolated values for each of the data channels at the specified barycentric + // coordinates. As byte address buffers, the data arrays must be indexed by byte, and loaded + // as 32-bit unsigned ints, which are then read as floats. + float3 positions[3]; + float3 normals[3]; + for (uint i = 0; i < 3; i++) + { + positions[i] = (geometry.getPosition(indices[i])); + + // Accumulate the positions, weighted by the barycentric coordinates. + // NOTE: An alternative is to compute the position with ray intrinsics, as follows: + // WorldRayOrigin() + WorldRayDirection() * RayTCurrent(). However this will be less + // accurate due to floating-point precision; see "Ray Tracing Gems" chapter 6. + shading.geomPosition += positions[i] * barycentrics[i]; + + // Accumulate the optional normals, weighted by the barycentric coordinates. + if (geometry.hasNormals()) + { + normals[i] = (geometry.getNormal(indices[i])); + shading.normal += normals[i] * barycentrics[i]; + } + + // Accumulate the optional texture coordinates, weighted by the barycentric coordinates. + if (geometry.hasTexCoords()) + { + shading.texCoord += (geometry.getTexCoord(indices[i])) * barycentrics[i]; + } + } + + // Normalize the interpolated (shading) normal. If no normals are provided, compute a geometric + // normal using the vertex positions; this will yield flat shading. + // NOTE: If a provided normal has zero length, then the normal will have undefined components + // (likely NaN), leading to rendering artifacts (likely black pixels) along any path that + // acceseses the normal. + shading.normal = + normalize(geometry.hasNormals() ? shading.normal + : cross(positions[1] - positions[0], positions[2] - positions[0])); + + // Compute the shading position from the geometry position and vertex data. If no normals are + // provided, use the geometric position. + shading.position = geometry.hasNormals() ? computeShadingPosition(shading.geomPosition, shading.normal, + positions, normals, barycentrics) + : shading.geomPosition; + + // Transform the position, geometric position, and normal to world space. + shading.position = mul(objToWorld, float4(shading.position, 1.0f)); + shading.geomPosition = mul(objToWorld, float4(shading.geomPosition, 1.0f)); + shading.normal = normalize( + mul((float3x3)objToWorld, shading.normal)); // Re-normalize as world matrix can have scale. + + // Generate automatic tangent / bitangent vector from the shading normal. + // TODO: Use tangent vectors from the host instead of this. + buildBasis(shading.normal, shading.tangent, shading.bitangent); + + return shading; +} + +#endif // __GEOMETRY_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/Globals.slang b/Libraries/Aurora/Source/Shaders/Globals.slang new file mode 100644 index 0000000..c52f22d --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Globals.slang @@ -0,0 +1,77 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +// Math constants. +#define M_PI 3.1415926535897932384626433832795f +#define M_PI_INV 1.0 / 3.1415926535897932384626433832795 +#define M_FLOAT_EPS 0.000001f +#define M_RAY_TMIN 0.01f + +// Miss shader indices. +#if DIRECTX +#define kMissNull 0 // for a disabled miss shader (no effect) +#define kMissBackground 1 // for the background, visible by primary rays +#define kMissRadiance 2 // for lighting (radiance) +#define kMissShadow 3 // for shadow rays, forced to opaque +#else +// In HGI backend, backround and radiance miss shader are the same. +#define kMissBackground 0 // for the background, visible by primary rays +#define kMissRadiance 1 // for lighting (radiance) +#define kMissShadow 2 // for shadow rays, forced to opaque +#endif + +// Importance sampling modes. This is set with the IMPORTANCE_SAMPLING_MODE preprocessor symbol. +#define IMPORTANCE_SAMPLING_BSDF 0 // sample the BSDF alone +#define IMPORTANCE_SAMPLING_ENVIRONMENT 1 // sample the environment alone +#define IMPORTANCE_SAMPLING_MIS 2 // sample both the BSDF and environment +#if !defined(IMPORTANCE_SAMPLING_MODE) +#if DIRECTX +#define IMPORTANCE_SAMPLING_MODE IMPORTANCE_SAMPLING_MIS +#else +#define IMPORTANCE_SAMPLING_MODE IMPORTANCE_SAMPLING_BSDF +#endif + +#endif + +// Black and white constant values, which can also be used as a float3. +#define BLACK 0.0f +#define WHITE 1.0f + +// Neither INIFITIY or sizeof supported in Slang +#define INFINITY 10.0e+99 // Set to larger that largest float32, will get converted to infinity +#define WORD_SIZE 4 + +// Returns whether the specified color is black, i.e. all zero components. +bool isBlack(float3 color) +{ + return all(color == BLACK); +} + +// Square utility function. +float sqr(float x) +{ + return x * x; +} + +// Builds basis vectors (tangent and bitangent) from a normal vector. +void buildBasis(float3 normal, out float3 tangent, out float3 bitangent) +{ + bitangent = abs(normal.y) < 0.999f ? float3(0.0f, 1.0f, 0.0f) : float3(1.0f, 0.0f, 0.0f); + tangent = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); +} + +#endif // __GLOBALS_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/GroundPlane.slang b/Libraries/Aurora/Source/Shaders/GroundPlane.slang new file mode 100644 index 0000000..937cf24 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/GroundPlane.slang @@ -0,0 +1,169 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "BSDFCommon.slang" +#include "Colors.slang" +#include "Environment.slang" +#include "Globals.slang" +#include "Random.slang" +#include "RayTrace.slang" + +// Layout of ground plane properties. +struct GroundPlane +{ + bool enabled; + float3 position; + float3 normal; + float3 tangent; + float3 bitangent; + float shadowOpacity; + float3 shadowColor; + float reflectionOpacity; + float3 reflectionColor; + float reflectionRoughness; +}; + +// Shades a matte reflection effect with the specified properties. +float4 shadeMatteReflection(float opacity, float3 color, float roughness, + RaytracingAccelerationStructure scene, Environment environment, float3 rayDir, float3 normal, + float3 tangent, float3 bitangent, float3 position, uint maxDepth, inout Random rng) +{ + // Transform the ray to a view direction in tangent space. + float3 V = worldToTangent(-rayDir, tangent, bitangent, normal); + + // Sample a GGX-Smith BRDF with the f0 value set to the reflection color, to represent a metal + // material. This gives a BSDF result, which is scaled by the cosine term. + float3 L; + float pdf; + float3 f0 = color; + float2 random = random2D(rng); + BSDF_AND_COSINE bsdfAndCosine = sampleGGXSmithBRDF(V, f0, roughness, 0.0f, random, L, pdf); + bsdfAndCosine *= abs(L.z); + if (isBlack(bsdfAndCosine)) + { + return BLACK; + } + + // Trace a radiance ray with the sampled light direction. If the ray payload alpha is zero, + // treat this as a miss, i.e. there is no matte reflection of the environment. Otherwise, + // compute the outgoing radiance as the BSDF (with cosine term) multipled by the light radiance, + // divided by the PDF. + float3 Lw = tangentToWorld(L, tangent, bitangent, normal); + RadianceRayPayload rayPayload = + traceRadianceRay(scene, environment, position, Lw, M_RAY_TMIN, 1, maxDepth, false, rng); + if (rayPayload.alpha == 0.0f) + { + return BLACK; + } + float3 radiance = rayPayload.color * bsdfAndCosine / pdf; + + // Return the result as the radiance for color, and reflection opacity for alpha. + return float4(radiance, opacity); +} + +// Shades a matte shadow effect with the specified environment light and position. +// TODO: This is an approximation that avoids the use of extra buffers that are accumulated later, +// i.e. shadowed and unshadowed components. This also does not consider the directional light. Both +// of these issues should be addressed in the future. +float4 shadeMatteShadow(float opacity, float3 color, RaytracingAccelerationStructure scene, + Environment environment, float3 normal, float3 position, bool isOpaque, uint maxDepth, + inout Random rng) +{ + // Sample the environment to determine a sample (light) direction and the corresponding + // probability density function (PDF) value for that direction. + float3 L; + float pdf; + float2 random = random2D(rng); + float3 lightRadiance = sampleEnvironment(environment, random, L, pdf); + + // If the light direction is below the ground plane, the light does not contribute to shadowing. + if (dot(L, normal) < 0.0f) + { + return BLACK; + } + + // Use a shadow ray to compute the visibility of the light source at the hit position. + float3 lightVisibility = traceShadowRay(scene, position, L, M_RAY_TMIN, isOpaque, 1, maxDepth); + + // The result color is the shadow color, and the alpha (blend factor) is the luminance of the + // inverse light visibility (i.e. zero visibility means full shadow) scaled by the shadow + // opacity. + float4 result = BLACK; + result.rgb = color; + result.a = luminance(1.0f - lightVisibility) * opacity; + + return result; +} + +// Shades the specified ground plane for the specified ray. The result is intended to be blended +// with whatever is behind the ground plane. This returns black with zero alpha if the ground plane +// is not hit. +float4 shadeGroundPlane(GroundPlane groundPlane, RaytracingAccelerationStructure scene, + Environment environment, float3 rayOrigin, float3 rayDir, float maxDist, + bool isOpaqueShadowsEnabled, uint maxDepth, inout Random rng) +{ + // Collect ground plane properties. + float3 position = groundPlane.position; + float3 normal = groundPlane.normal; + float3 tangent = groundPlane.tangent; + float3 bitangent = groundPlane.bitangent; + float shadowOpacity = groundPlane.shadowOpacity; + float3 shadowColor = groundPlane.shadowColor; + float reflectionOpacity = groundPlane.reflectionOpacity; + float3 reflectionColor = groundPlane.reflectionColor; + float reflectionRoughness = groundPlane.reflectionRoughness; + + // Compute the dot product of the ray direction and plane normal. If this is greater than zero, + // the ray direction is below the plane and the plane is not visible. + float planeDot = dot(rayDir, normal); + if (planeDot >= 0.0) + { + return BLACK; + } + + // Compute the distance to the plane along the ray direction, i.e ray-plane intersection. If the + // distance is less than zero (behind the ray) or greater than the maximum distance, the plane + // is not visible. + float distance = dot(position - rayOrigin, normal) / planeDot; + if (distance <= 0.0f || distance >= maxDist) + { + return BLACK; + } + + // Compute the hit position. + float3 hitPosition = rayOrigin + rayDir * distance; + + // Shade with a matte reflection effect, if enabled. + float4 result = BLACK; + if (reflectionOpacity > 0.0f) + { + result = shadeMatteReflection(reflectionOpacity, reflectionColor, reflectionRoughness, + scene, environment, rayDir, normal, tangent, bitangent, hitPosition, maxDepth, rng); + } + + // Shade with a matte shadow effect, if enabled. + if (shadowOpacity > 0.0f) + { + float4 shadow = shadeMatteShadow(shadowOpacity, shadowColor, scene, environment, normal, + hitPosition, isOpaqueShadowsEnabled, maxDepth, rng); + + // Blend the shadow result with the prior result, using the shadow alpha. Also update the + // result alpha to the combined alpha of the shadow and the prior result, as if they are + // two overlapping layers with alpha as opacity. + result.rgb = lerp(result.rgb, shadow.rgb, shadow.a); + result.a = 1.0f - ((1.0f - shadow.a) * (1.0f - result.a)); + } + + return result; +} diff --git a/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang b/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang new file mode 100644 index 0000000..e86d86e --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang @@ -0,0 +1,23 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +Material initializeMaterial(ShadingData shading, + float3x4 objToWorld, + out float3 materialNormal, out bool isGeneratedNormal) +{ + return initializeDefaultMaterial( + shading, + objToWorld, materialNormal, isGeneratedNormal); +} + diff --git a/Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang b/Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang new file mode 100644 index 0000000..a6a7367 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang @@ -0,0 +1,147 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "PathTracingCommon.slang" +#include "InitializeMaterial.slang" +#include "ShadeFunctions.slang" + +[shader("miss")] void @MATERIAL_TYPE@LayerMissShader(inout RayPayload rayPayload) +{ + int depth = rayPayload.radianceRay.depth; + int maxDepth = gFrameData.traceDepth; + bool isOpaqueShadowsEnabled = gFrameData.isOpaqueShadowsEnabled; + + // Get the interpolated vertex data for the hit triangle, at the hit barycentric + // coordinates. + uint triangleIndex = rayPayload.primitiveIndex; + float3 barycentrics = computeBarycentrics(rayPayload.hit); + ShadingData shading = computeShadingData( + gGeometry, triangleIndex, barycentrics, rayPayload.objToWorld); + + // Initialize the view direction. + float3 V = rayPayload.viewDirection; + float3 materialNormal = shading.normal; + bool isGeneratedNormal; + + // Initialize the material values for repeated evaluations at the current shading point. + Material material = initializeMaterial(shading, rayPayload.objToWorld, materialNormal, isGeneratedNormal); + Environment environment = prepareEnvironmentValues(); + + // If a new normal has been generated, transform it to world space and build a corresponding + // basis from it. + if (isGeneratedNormal) + { + shading.normal = materialNormal; + buildBasis(shading.normal, shading.tangent, shading.bitangent); + } + float3 transparency = 1.0f - material.opacity; + float P = luminance(transparency); + + if (random2D(rayPayload.radianceRay.rng).x < P) + { + // Nothing more to do. + return; + } + + // Compute the NDC depth and view depth of the hit position: + // - NDC depth: Compute the clip space position using the supplied view-projection matrix, then + // divide the Z component by W, and remap from [-1.0, 1.0] to [0.0, 1.0]. + // - View Depth: For a primary ray, this is simply the T value of the ray. We don't use this + // value for non-primary rays; it is recorded here but not used. + float4 positionClip = mul(gFrameData.cameraViewProj, float4(shading.geomPosition, 1.0f)); + float depthNDC = (positionClip.z / positionClip.w + 1.0f) / 2.0f; + float depthView = RayTCurrent(); + + // Clamp roughness for the ray payload to a minimum, because the denoiser handles materials with + // low (near-zero) roughness poorly, leading to a noisy result. This addresses two separate but + // related issues: low-roughness metallic materials reflecting noisy surroundings, and low- + // roughness dielectric materials where the glossy lobe is sparsely sampled. + // NOTE: The roughness in the ray payload is used for denoising only. The material specular + // roughness (used in shading) is not modified here. + static const float kMinRoughness = 0.05f; + float clampedRoughness = max(material.specularRoughness, kMinRoughness); + + // Store initial data in the ray payload. + rayPayload.radianceRay.color = BLACK; + rayPayload.radianceRay.alpha = 1.0f; + rayPayload.radianceRay.direct = BLACK; + rayPayload.radianceRay.depthNDC = depthNDC; + rayPayload.radianceRay.depthView = depthView; + rayPayload.radianceRay.normal = shading.normal; + rayPayload.radianceRay.baseColor = material.baseColor; + rayPayload.radianceRay.roughness = clampedRoughness; + rayPayload.radianceRay.metalness = material.metalness; + rayPayload.radianceRay.indirect.clear(); + + // Shade with the global directional light. Skip this if the light intensity is zero. + if (gFrameData.lightColorAndIntensity.a > 0.0f) + { + rayPayload.radianceRay.direct = shadeDirectionalLight(gFrameData.lightDir, + gFrameData.lightColorAndIntensity, gFrameData.lightCosRadius, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + + // Add the direct lighting to the color result. + // NOTE: For denoising purposes, "direct" lighting only includes the directional light, and + // not the environment light computed below. + rayPayload.radianceRay.color += rayPayload.radianceRay.direct; + } + + // When denoising, primary rays for indirect lighting should not include the base color (also + // known as albedo, as used by diffuse lobes) in order to avoid blurring out color details. So + // the base color is set to white here and the color is added back after denoising. + if (gFrameData.isDenoisingEnabled && rayPayload.radianceRay.depth == 1) + { + material.baseColor = WHITE; + } + + // Shade with the environment light depending on the importance sampling type: + // - BSDF: The environment is treated as indirect light, and is evaluated in the miss shader. + // - Environment: Sample the environment light as direct lighting. + // - MIS: Use multiple importance sampling on both the material and the light. + // NOTE: For denoising purposes, "indirect" lighting includes the environment light. + if (IMPORTANCE_SAMPLING_MODE != IMPORTANCE_SAMPLING_BSDF) + { + float3 environmentRadiance = BLACK; + Environment environment = prepareEnvironmentValues(); + if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_ENVIRONMENT) + { + // TODO: This does not currently contribute to indirect lighting for denoising purposes. + // That will require having the material evaluation return separate diffuse and glossy + // components. + environmentRadiance = + shadeEnvironmentLightDirect(environment, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + } + else if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_MIS) + { + environmentRadiance = shadeEnvironmentLightMIS(environment, gScene, material, shading, V, isOpaqueShadowsEnabled, depth, + maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.indirect); + } + + // Add the radiance from the environment light to the color result. + rayPayload.radianceRay.color += environmentRadiance; + } + + // Shade with indirect light from the surrounding scene (i.e. path tracing). + rayPayload.radianceRay.color += shadeIndirectLight(gScene, environment, material, + shading, V, depth, maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.alpha, + rayPayload.radianceRay.indirect); + + // Scale the color components of the ray payload by opacity, and normalized by the probability + // of this segment being considered opaque. + // NOTE: The shading functions do not individually consider opacity, so that it can be handled + // in one place here. + rayPayload.radianceRay.scaleColor(material.opacity / (1.0f - P)); + + rayPayload.absorbedByLayer = true; +} diff --git a/Libraries/Aurora/Source/Shaders/Material.slang b/Libraries/Aurora/Source/Shaders/Material.slang new file mode 100644 index 0000000..beaf204 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Material.slang @@ -0,0 +1,314 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __MATERIAL_H__ +#define __MATERIAL_H__ + +#include "Geometry.slang" + +// Texture UV transform represent as scale, offset and rotation. +struct TextureTransform +{ + float2 pivot; + float2 scale; + float2 offset; + float rotation; +}; + +// Layout of material constants. +// NOTE: This must match the host (CPU) data structure, MaterialData, including the explicit padding +// variables (need due to Vulkan GLSL padding rules) +struct MaterialConstants +{ + float base; + float3 baseColor; + float diffuseRoughness; + float metalness; + float specular; + float _padding1; + float3 specularColor; + float specularRoughness; + float specularIOR; + float specularAnisotropy; + float specularRotation; + float transmission; + float3 transmissionColor; + float subsurface; + float _padding2; + float3 subsurfaceColor; + float3 subsurfaceRadius; + float subsurfaceScale; + float subsurfaceAnisotropy; + float sheen; + float2 _padding3; + float3 sheenColor; + float sheenRoughness; + float coat; + float3 coatColor; + float coatRoughness; + float coatAnisotropy; + float coatRotation; + float coatIOR; + float coatAffectColor; + float coatAffectRoughness; + float2 _padding4; + float3 opacity; + bool thinWalled; + bool hasBaseColorTex; + float3 _padding5; + TextureTransform baseColorTexTransform; + bool hasSpecularRoughnessTex; + TextureTransform specularRoughnessTexTransform; + bool hasOpacityTex; + TextureTransform opacityTexTransform; + bool hasNormalTex; + TextureTransform normalTexTransform; + bool isOpaque; +}; + +// The global sampler state, used by default for texture sampling. +[[vk::binding(6)]] SamplerState gDefaultSampler : register(s0); + +#if DIRECTX +// Material global variables, for the current acceleration structure instance only. +// NOTE: These must align with the variables declared in the main source file. +ConstantBuffer gMaterialConstants : register(b1, space1); +Texture2D gBaseColorTexture : register(t4, space1); +Texture2D gSpecularRoughnessTexture : register(t5, space1); +Texture2D gNormalTexture : register(t6, space1); +Texture2D gOpacityTexture : register(t7, space1); + +// Samplers for base color and opacity. +// TODO: Add for other textures. +SamplerState gBaseColorSampler : register(s1); +SamplerState gOpacitySampler : register(s2); + +float4 sampleBaseColorTexture(float2 uv, float level) +{ + return gBaseColorTexture.SampleLevel( + gBaseColorSampler, uv, level); // Use the base color sampler, not default sampler. +} +float4 sampleSpecularRoughnessTexture(float2 uv, float level) +{ + return gSpecularRoughnessTexture.SampleLevel( + gDefaultSampler, uv, level); // Use the default sampler. +} +float4 sampleOpacityTexture(float2 uv, float level) +{ + return gOpacityTexture.SampleLevel( + gOpacitySampler, uv, level); // Use the opacity sampler, not default sampler. +} +float4 sampleNormalTexture(float2 uv, float level) +{ + return gNormalTexture.SampleLevel(gDefaultSampler, uv, level); // Use the default sampler. +} + +#else +// Vulkan GLSL versions are forward declared and implemented in raw GLSL suffix file. +MaterialConstants getMaterial(); +float4 sampleBaseColorTexture(float2 uv, float level); +float4 sampleSpecularRoughnessTexture(float2 uv, float level); +float4 sampleOpacityTexture(float2 uv, float level); +float4 sampleNormalTexture(float2 uv, float level); +#endif + +// Material values used during material evaluation. +struct Material +{ + float base; + float3 baseColor; + float diffuseRoughness; + float metalness; + float3 metalColor; + float specular; + float3 specularColor; + + float specularRoughness; + float specularIOR; + float specularAnisotropy; + float specularRotation; + float transmission; + float3 transmissionColor; + float subsurface; + float3 subsurfaceColor; + float3 subsurfaceRadius; + float subsurfaceScale; + float subsurfaceAnisotropy; + float sheen; + float3 sheenColor; + float sheenRoughness; + float coat; + float3 coatColor; + float coatRoughness; + float coatAnisotropy; + float coatRotation; + float coatIOR; + float coatAffectColor; + float coatAffectRoughness; + float3 opacity; + bool thinWalled; + bool isOpaque; +}; + +// Normal map spaces definitions. +#define TANGENT_SPACE 0 +#define OBJECT_SPACE 1 + +// Rotate a 2D vector by given number of degrees. +// Based on MaterialX mx_rotate_vector2 functionn. +float2 rotateUV(float2 uv, float amountDegrees) +{ + float rotationRadians = radians(amountDegrees); + float sa = sin(rotationRadians); + float ca = cos(rotationRadians); + return float2(ca * uv.x + sa * uv.y, -sa * uv.x + ca * uv.y); +} + +// Rotate a 2D vector by given number of degrees. +// Based on MaterialX NG_place2d_vector2 function. +float2 applyUVTransform(float2 uv, float2 pivot, float2 scale, float rotate, float2 offset) +{ + float2 subpivot = uv - pivot; + float2 scaled = subpivot / scale; + float2 rotated = rotateUV(scaled, rotate); + float2 translated = rotated - offset; + float2 addpivot = translated + pivot; + return addpivot; +} + +// Calculate a per-pixel normal from a normal map texel value. +// NOTE: This is based on the MaterialX function mx_normalmap(). +float3 calculateNormalFromMap(float3 texelValue, int space, float scale, float3 N, float3 T) +{ + // Remap texel components from [0.0, 1.0] to [-1.0, 1.0]. + float3 v = texelValue * 2.0 - 1.0; + + // If the texel normal is in tangent space, transform it to the coordinate system defined by N + // and T. + if (space == TANGENT_SPACE) + { + float3 B = normalize(cross(N, T)); + return normalize(T * v.x * scale + B * v.y * scale + N * v.z); + } + + // Otherwise the texel normal is in object space, and is simply normalized. + else + { + return normalize(v); + } +} + +// Initializes the full set of property values for a material, for the specified shading data. +Material initializeDefaultMaterial( + ShadingData shading, float3x4 objToWorld, out float3 materialNormal, out bool isGeneratedNormal) +{ +#if DIRECTX + MaterialConstants materialConstants = gMaterialConstants; +#else + MaterialConstants materialConstants = getMaterial(); +#endif + + // Copy the constant values to the material from the constant buffer. + Material material; + material.base = materialConstants.base; + material.baseColor = materialConstants.baseColor; + material.diffuseRoughness = materialConstants.diffuseRoughness; + material.metalness = materialConstants.metalness; + material.specular = materialConstants.specular; + material.specularColor = materialConstants.specularColor; + material.specularRoughness = materialConstants.specularRoughness; + material.specularIOR = materialConstants.specularIOR; + material.specularAnisotropy = materialConstants.specularAnisotropy; + material.specularRotation = materialConstants.specularRotation; + material.transmission = materialConstants.transmission; + material.transmissionColor = materialConstants.transmissionColor; + material.subsurface = materialConstants.subsurface; + material.subsurfaceColor = materialConstants.subsurfaceColor; + material.subsurfaceRadius = materialConstants.subsurfaceRadius; + material.subsurfaceScale = materialConstants.subsurfaceScale; + material.subsurfaceAnisotropy = materialConstants.subsurfaceAnisotropy; + material.sheen = materialConstants.sheen; + material.sheenColor = materialConstants.sheenColor; + material.sheenRoughness = materialConstants.sheenRoughness; + material.coat = materialConstants.coat; + material.coatColor = materialConstants.coatColor; + material.coatRoughness = materialConstants.coatRoughness; + material.coatAnisotropy = materialConstants.coatAnisotropy; + material.coatRotation = materialConstants.coatRotation; + material.coatIOR = materialConstants.coatIOR; + material.coatAffectColor = materialConstants.coatAffectColor; + material.coatAffectRoughness = materialConstants.coatAffectRoughness; + material.opacity = materialConstants.opacity; + material.thinWalled = materialConstants.thinWalled; + material.isOpaque = materialConstants.isOpaque; + + // Sample base color from a texture if necessary. + float4 texCoord = float4(shading.texCoord, 0.0f, 1.0f); + if (materialConstants.hasBaseColorTex) + { + float2 uv = + applyUVTransform(shading.texCoord, materialConstants.baseColorTexTransform.pivot, + materialConstants.baseColorTexTransform.scale, + materialConstants.baseColorTexTransform.rotation, + materialConstants.baseColorTexTransform.offset); + material.baseColor = sampleBaseColorTexture(uv, 0.0f).rgb; + } + + // Sample specular roughness from a texture if necessary. + if (materialConstants.hasSpecularRoughnessTex) + { + float2 uv = applyUVTransform(shading.texCoord, + materialConstants.specularRoughnessTexTransform.pivot, + materialConstants.specularRoughnessTexTransform.scale, + materialConstants.specularRoughnessTexTransform.rotation, + materialConstants.specularRoughnessTexTransform.offset); + material.specularRoughness = sampleSpecularRoughnessTexture(uv, 0.0f).r; + } + + // Sample opacity from a texture if necessary. + if (materialConstants.hasOpacityTex) + { + float2 uv = applyUVTransform(shading.texCoord, materialConstants.opacityTexTransform.pivot, + materialConstants.opacityTexTransform.scale, + materialConstants.opacityTexTransform.rotation, + materialConstants.opacityTexTransform.offset); + material.opacity = sampleOpacityTexture(uv, 0.0f).rgb; + } + + // Set generated normal flag to false, as normal not modified by material. + isGeneratedNormal = false; + + // Sample a normal from the normal texture, convert it to an object-space normal, transform to + // world space, and store it in the output value. + if (materialConstants.hasNormalTex) + { + float2 uv = applyUVTransform(shading.texCoord, materialConstants.normalTexTransform.pivot, + materialConstants.normalTexTransform.scale, + materialConstants.normalTexTransform.rotation, + materialConstants.normalTexTransform.offset); + float3 normalTexel = sampleNormalTexture(uv, 0.0f).rgb; + float3 objectSpaceNormal = calculateNormalFromMap( + normalTexel, TANGENT_SPACE, 1.0, shading.normal, shading.tangent); + materialNormal = normalize(mul((float3x3)objToWorld, objectSpaceNormal)); + + // Set generated normal flag to true, as normal modified by normal map. + isGeneratedNormal = true; + } + + // Copy the base color to the (internal) metal color. + material.metalColor = material.baseColor; + + return material; +} + +#endif // __MATERIAL_H__ diff --git a/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang b/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang new file mode 100644 index 0000000..0820569 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang @@ -0,0 +1,20 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __MATERIALX_COMMON__ +#define __MATERIALX_COMMON__ + +static ShadingData vertexData; + +#endif // __MATERIALX_COMMON__ diff --git a/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang b/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang new file mode 100644 index 0000000..3b61630 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang @@ -0,0 +1,201 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Prefix containing the common code used by all material types. +// NOTE: Does not contain a hit shader. Hit shaders for the different material types must be +// appended to this file. + +// Define this symbol for NRD. +#define COMPILER_DXC + +#include "BSDFCommon.slang" +#include "Colors.slang" +#include "Environment.slang" +#include "Frame.slang" +#include "Geometry.slang" +#include "Globals.slang" +#include "GroundPlane.slang" +#include "Material.slang" +#include "Random.slang" +#include "RayTrace.slang" +#include "Sampling.slang" + +// ================================================================================================= +// Global Variables - For All Shaders +// ================================================================================================= + +// The top-level acceleration structure with the scene contents. +[[vk::binding(0)]] RaytracingAccelerationStructure gScene : register(t0); + +// Constant buffers of sample and per-frame values. +[[vk::binding(4)]] ConstantBuffer gSampleData : register(b0); +[[vk::binding(2)]] ConstantBuffer gFrameData : register(b1); + +// Environment data. +[[vk::binding(5)]] ConstantBuffer gEnvironmentConstants : register(b2); +StructuredBuffer gEnvironmentAliasMap : register(t1); +[[vk::binding(8)]] Texture2D gEnvironmentLightTexture : register(t2); +[[vk::binding(7)]] Texture2D gEnvironmentBackgroundTexture : register(t3); +ConstantBuffer gGroundPlane : register(b3); +RaytracingAccelerationStructure gNullScene : register(t4); + +// ================================================================================================= +// Ray Gen Shader Variables +// ================================================================================================= + +// The output texture. +[[vk::binding(1)]] RWTexture2D gDirect : register(u0); +RWTexture2D gDepthNDC : register(u1); +RWTexture2D gDepthView : register(u2); +RWTexture2D gNormalRoughness : register(u3); +RWTexture2D gBaseColorMetalness : register(u4); +RWTexture2D gDiffuse : register(u5); +RWTexture2D gGlossy : register(u6); + +// ================================================================================================= +// Radiance Hit Shader Variables +// ================================================================================================= + +// The maximum number of supported material layers, must match value in +// PTLayerIndexTable::kMaxMaterialLayers C++ code. +#define kMaxMaterialLayers 64 + +// The layer material shader IDs. +struct MaterialLayerShaderIDs +{ + // Shader indices (must be stored as floattors due to HLSL packing rules). + int4 shaderIDs[kMaxMaterialLayers / WORD_SIZE]; +}; + +#if DIRECTX +// Geometry data, for the current instance only. +ByteAddressBuffer gIndices : register(t0, space1); +ByteAddressBuffer gPositions : register(t1, space1); +ByteAddressBuffer gNormals : register(t2, space1); +ByteAddressBuffer gTexCoords : register(t3, space1); + +// NOTE: Material variables are inserted at register(b1, space1) between gGeometryMetadata and +// gMaterialLayerIDs by Material.slang. +cbuffer gGeometryMetadata : register(b0, space1) +{ + bool gHasNormals; // Are there normals? + bool gHasTexCoords; // Are there texture coordinates? + int gMaterialLayerCount; // Number of material layer miss shaders. +} + +// To hide DX-Vulkan differences, expose geometry access using functions. +uint3 getIndicesForTriangle(int triangleIndex) { + return gIndices.Load3((triangleIndex * 3) * 4); +} + +float3 getPositionForVertex(int vertexIndex) +{ + return asfloat(gPositions.Load3(vertexIndex * 3 * 4)); +} + +float3 getNormalForVertex(int vertexIndex) +{ + return asfloat(gNormals.Load3(vertexIndex * 3 * 4)); +} + +float2 getTexCoordForVertex(int vertexIndex) +{ + return asfloat(gTexCoords.Load2(vertexIndex * 2 * 4)); +} + +bool instanceHasNormals() +{ + return gHasNormals; +} +bool instanceHasTexCoords() +{ + return gHasTexCoords; +} + +#else + +// Forward declare these functions on Vulkan GLSL. As we need a platform-specific suffix file containing the implementation of these functions. +uint3 getIndicesForTriangle(int bufferLocation); +float3 getPositionForVertex(int bufferLocation); +float3 getNormalForVertex(int bufferLocation); +float2 getTexCoordForVertex(int bufferLocation); +bool instanceHasNormals(); +bool instanceHasTexCoords(); + +#endif + +// Slang interface implementation to handle geometry access in platform-independent way. +struct Geometry : IGeometry +{ + uint3 getIndices(int triangleIndex) { return getIndicesForTriangle(triangleIndex); } + float3 getPosition(int vertexIndex) { return getPositionForVertex(vertexIndex); } + float3 getNormal(int vertexIndex) { return normalize(getNormalForVertex(vertexIndex)); } + float2 getTexCoord(int vertexIndex) { return getTexCoordForVertex(vertexIndex); } + bool hasTexCoords() { return instanceHasTexCoords(); } + bool hasNormals() { return instanceHasNormals(); } +} gGeometry; + +// Constant buffer for layer material shader IDs. +ConstantBuffer gMaterialLayerIDs : register(b2, space1); + +// Get the layer material index for given layer. +int getMaterialLayerIndex(int layer) +{ + // TODO: Optimize the floattor->scalar look-up. + return gMaterialLayerIDs.shaderIDs[layer / 4][layer % 4]; +} + +// ================================================================================================= +// Utility Functions +// ================================================================================================= + +// Collects the full set of property values for an environment. +Environment prepareEnvironmentValues() +{ + Environment values; + values.constants = gEnvironmentConstants; + values.backgroundTexture = gEnvironmentBackgroundTexture; + values.sampler = gDefaultSampler; + values.lightTexture = gEnvironmentLightTexture; +#if DIRECTX + values.aliasMap = gEnvironmentAliasMap; +#endif + return values; +} + +// Adjusts the specified radiance, to clamp extreme values and detect errors. +void adjustRadiance(float maxLuminance, bool displayErrors, inout float3 radiance) +{ + // Clamp result colors above a certain luminance threshold, to minimize fireflies. + // NOTE: This biases the final result and should be used carefully. + float lum = luminance(radiance); + if (lum > maxLuminance) + { + radiance *= maxLuminance / lum; + } + + // Replace an invalid radiance sample with a diagnostic (infinite) value when displaying errors, + // or black otherwise. Shading errors are usually the result of bad geometry (e.g. zero-length + // normals), but may also be caused by internal shading errors that should be addressed. + static const float3 NAN_COLOR = float3(INFINITY, 0.0f, 0.0f); // red + static const float3 INF_COLOR = float3(0.0f, INFINITY, 0.0f); // green + if (any(isnan(radiance))) + { + radiance = displayErrors ? NAN_COLOR : BLACK; + } + else if (any(isinf(radiance))) + { + radiance = displayErrors ? INF_COLOR : BLACK; + } +} \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang b/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang new file mode 100644 index 0000000..34ea136 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang @@ -0,0 +1,39 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// The shadow miss shader. This simply sets full visibility on the ray payload, indicating that +// nothing was hit. +// The radiance miss shader, which evaluates the environment as an environment light. +#include "PathTracingCommon.slang" + +[shader("miss")] void RadianceMissShader(inout RayPayload rayPayload) { + // Initialize the radiance ray payload for a miss. + rayPayload.radianceRay.clear(); + + // For BSDF importance sampling, evaluate the environment as a light for the final segment of + // the current path. For any other importance sampling, the environment will be evaluated or + // sampled for shading at each hit (not here). + float3 color = BLACK; + if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_BSDF) + { + // Evaluate the environment, as a light. + Environment environment = prepareEnvironmentValues(); + color = evaluateEnvironment(environment, WorldRayDirection(), false); + } + + // Store the environment color. + // NOTE: The miss result is considered to be part of direct lighting, to allow for simpler logic + // during accumulation. + rayPayload.radianceRay.color = color; + rayPayload.radianceRay.direct = color; +} \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/Random.slang b/Libraries/Aurora/Source/Shaders/Random.slang new file mode 100644 index 0000000..db47af3 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Random.slang @@ -0,0 +1,148 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __RANDOM_H__ +#define __RANDOM_H__ + +// Choose a random number generator: +// - LCG: A pseudorandom LCG to generate independent random numbers. +// - PCG2D: A pseudorandom version of PCG to generate two distributed random numbers. +//#define RNG_LCG +#define RNG_PCG2D + +// Define initRand() and randomNext() functions based on the chosen random number generator. +#if defined(RNG_LCG) +#define initRand lcgInit +#define randomNext2D lcgNext2D +#endif +#if defined(RNG_PCG2D) +#define initRand pcg2DInit +#define randomNext2D pcg2DNext2D +#endif + +// State used for random number generation (RNG). +struct Random +{ + uint2 state; +}; + +// Generates a random 32-bit integer from two seed values, with the Tiny Encryption Algorithm (TEA). +// NOTE: Based on +// https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_jitter_cam +uint tea(uint val0, uint val1) +{ + uint v0 = val0; + uint v1 = val1; + uint s0 = 0; + + for (uint n = 0; n < 16; n++) + { + s0 += 0x9e3779b9; + v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4); + v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e); + } + + return v0; +} + +// Generates a random 32-bit integer from a seed value, using a linear congruential generator (LCG). +// NOTE: Based on "Hash Functions for GPU Rendering" @ http://www.jcgt.org/published/0009/03/02. +uint lcg(uint p) +{ + return p * 1664525u + 1013904223u; +} + +// Initializes a random number generator for LCG. +Random lcgInit(uint sampleIndex, uint2 screenSize, uint2 screenCoords) +{ + Random rng; + + // Compute a random integer as a seed for LCG, because LCG does not behave well with consecutive + // seeds. This is based on the pixel index and sample index, to prevent correlation across space + // and time. + uint pixelIndex = screenCoords.y * screenSize.x + screenCoords.x; + rng.state.x = tea(pixelIndex, sampleIndex); + + return rng; +} + +// Computes the next LCG value, updating the specified state value. +float lcgNext(inout uint state) +{ + // Use the lower 24 bits of an LCG result, with the RNG as a seed. + state = lcg(state) & 0x00FFFFFF; + + // Produce a floating-point number in the range [0.0, 1.0), from the 24-bit state. + return state / float(0x01000000); +} + +// Computes the next LCG value pair, updating the specified RNG. +float2 lcgNext2D(inout Random rng) +{ + return float2(lcgNext(rng.state.x), lcgNext(rng.state.x)); +} + +// Initializes a random number generator for PCG2D. +Random pcg2DInit(uint sampleIndex, uint2 screenSize, uint2 screenCoords) +{ + Random rng; + + // Initialize the state based on the pixel location and sample index, to prevent correlation + // across space and time. + // NOTE: Unlike LCG, PCG2D does behave well with consecutive seeds, so the seed does not need to + // be scrambled. See https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering. + rng.state.x = screenCoords.y * screenSize.x + screenCoords.x; + rng.state.y = sampleIndex; + + return rng; +} + +// Generates two random 32-bit integers from seed values, using a two-dimensional permuted +// congruential generator (PCG). +// NOTE: Based on "Hash Functions for GPU Rendering" @ http://www.jcgt.org/published/0009/03/02. +uint2 pcg2D(inout uint2 state) +{ + // Retain the current state, and advance to the next state with a simple LCG. + uint2 v = state; + state = state * 1664525u + 1013904223u; + + // Apply the PCG2D transformation to the current state to produce the result pair of values. + v.x += v.y * 1664525u; + v.y += v.x * 1664525u; + v = v ^ (v >> 16u); + v.x += v.y * 1664525u; + v.y += v.x * 1664525u; + v = v ^ (v >> 16u); + + return v; +} + +// Computes the next PCG2D value pair, updating the specified RNG. +float2 pcg2DNext2D(inout Random rng) +{ + rng.state = pcg2D(rng.state); + + // Produce floating-point numbers in the range [0.0, 1.0). + return float2(rng.state / float(0xFFFFFFFFu)); +} + +// Generates two uniformly distributed numbers in the range [0.0, 1.0), using the specified random +// number generator. +// NOTE: The two numbers may be correlated to provide an even distribution across the 2D range, +// which is why this is a single 2D function instead of two calls to a 1D function. +float2 random2D(inout Random rng) +{ + return randomNext2D(rng); +} + +#endif // __RANDOM_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/RayGenShader.slang b/Libraries/Aurora/Source/Shaders/RayGenShader.slang new file mode 100644 index 0000000..4cd7c71 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/RayGenShader.slang @@ -0,0 +1,108 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Prefix containing the common code used by all material types. +// NOTE: Does not contain a hit shader. Hit shaders for the different material types must be +// appended to this file. +#include"PathTracingCommon.slang" + +// The ray generation shader. +[shader("raygeneration")] void RayGenShader() { + int maxDepth = gFrameData.traceDepth; + + // Get the dispatch dimensions (screen size), dispatch index (screen coordinates), and sample + // index. + uint2 screenSize = DispatchRaysDimensions().xy; + uint2 screenCoords = DispatchRaysIndex().xy; + uint sampleIndex = gSampleData.sampleIndex; + + // Initialize a random number generator, so that each sample and pixel gets a unique seed. + Random rng = initRand(sampleIndex, screenSize, screenCoords); + + // Compute a camera ray (origin and direction) for the current screen coordinates. This applies + // a random offset to support antialiasing and depth of field. + float3 origin; + float3 dir; + computeCameraRay(screenCoords, screenSize, gFrameData.cameraInvView, gFrameData.viewSize, + gFrameData.isOrthoProjection, gFrameData.focalDistance, gFrameData.lensRadius, rng, origin, + dir); + + // Trace a radiance ray, and use the returned radiance (color) and alpha. + // NOTE: Use the background miss shader index for these primary (eye) rays. + Environment environment = prepareEnvironmentValues(); + RadianceRayPayload rayPayload = + traceRadianceRay(gScene, environment, origin, dir, 0.0f, 0, maxDepth, true, rng); + float4 result = float4(rayPayload.color, rayPayload.alpha); +#if DIRECTX + // Add the contribution of the ground plane, if enabled. + if (gGroundPlane.enabled) + { + // Shade the ground plane. + Environment environment = prepareEnvironmentValues(); + float4 groundPlaneResult = shadeGroundPlane(gGroundPlane, gScene, environment, origin, dir, + rayPayload.depthView, gFrameData.isOpaqueShadowsEnabled, maxDepth, rng); + + // Blend the ground plane result with the radiance and direct lighting. + // TODO: The latter ensures the ground plane result appears when denoising, but the result + // itself still needs to be denoised. + result.rgb = lerp(result.rgb, groundPlaneResult.rgb, groundPlaneResult.a); + rayPayload.direct = lerp(rayPayload.direct, groundPlaneResult.rgb, groundPlaneResult.a); + } +#endif + + // Adjust the radiance of the sample, e.g. to perform corrections. + adjustRadiance(gFrameData.maxLuminance, gFrameData.isDisplayErrorsEnabled, result.rgb); + + // Store the result in the output texture. The output texture is called "direct" as it contains + // direct lighting when denoising is enabled, or complete lighting otherwise. + result.rgb = gFrameData.isDenoisingEnabled ? rayPayload.direct : result.rgb; + gDirect[screenCoords] = result; + +#if DIRECTX + // Store the NDC depth value, if enabled. The value is only stored if the first sample was + // computed (i.e. there is no previously stored value), or it is less than the previously stored + // value. + float depthNDC = rayPayload.depthNDC; + if (gFrameData.isDepthNDCEnabled && (sampleIndex == 0 || depthNDC < gDepthNDC[screenCoords])) + { + gDepthNDC[screenCoords] = depthNDC; + } + + // Prepare and store the data for the denoising AOVs, if enabled. + // NOTE: Only the data for the last sample is stored, with the expectation that the sample count + // will be one when these AOVs are enabled. + if (gFrameData.isDenoisingAOVsEnabled) + { + // Remap the normal components from [-1.0, 1.0] to [0.0, 1.0] for unsigned normalized + // output. + float3 normalEncoded = (rayPayload.normal + 1.0f) * 0.5f; + + // Prepare the diffuse and glossy radiance and hit distances. The hit distances are + // normalized relative to the scene size, so they are always in [0.0, 1.0]. + IndirectOutput indirect = rayPayload.indirect; + float diffuseHitDist = saturate(indirect.diffuseHitDist / gFrameData.sceneSize); + float glossyHitDist = saturate(indirect.glossyHitDist / gFrameData.sceneSize); + float4 diffusePacked = float4(indirect.diffuse, diffuseHitDist); // TODO: use NRD packing + float4 glossyPacked = float4(indirect.glossy, glossyHitDist); // TODO: use NRD packing + + // Store the data for the denoising AOVs. + gDepthView[screenCoords] = rayPayload.depthView; + gNormalRoughness[screenCoords] = float4(normalEncoded, rayPayload.roughness); + gBaseColorMetalness[screenCoords] = float4(rayPayload.baseColor, rayPayload.metalness); + gDiffuse[screenCoords] = diffusePacked; + gGlossy[screenCoords] = glossyPacked; + } +#endif + +} diff --git a/Libraries/Aurora/Source/Shaders/RayTrace.slang b/Libraries/Aurora/Source/Shaders/RayTrace.slang new file mode 100644 index 0000000..c884970 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/RayTrace.slang @@ -0,0 +1,355 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __RAY_TRACE_H__ +#define __RAY_TRACE_H__ + +#include "Environment.slang" +#include "Globals.slang" +#include "Random.slang" +#include "Sampling.slang" + +// A structure for the results of indirect lighting. +struct IndirectOutput +{ + float3 diffuse; + float diffuseHitDist; + float3 glossy; + float glossyHitDist; + + // Clears the indirect output to values expected for a miss. +#if 1 //! DIRECTX + [mutating] +#endif + void + clear() + { + diffuse = BLACK; + diffuseHitDist = INFINITY; + glossy = BLACK; + glossyHitDist = INFINITY; + } +}; + +// The radiance ray payload structure. +// - depthView: The view depth is the distance from the eye to the hit position. This value is in +// world units. +// - depthNDC: The NDC depth is the Z component of NDC space, i.e. after dividing the clip space +// position by W. This is the value normally stored in the depth buffer for rasterization. The +// projection matrix must generate Z components in [-1.0, 1.0], which is remapped to [0.0, 1.0] +// as the NDC depth. +// +// NOTE: This is currently 28 floats in size. +struct RadianceRayPayload +{ + float3 color; + float alpha; + float3 direct; + float depthNDC; + float depthView; + float3 normal; + float3 baseColor; + float roughness; + float metalness; + IndirectOutput indirect; + int depth; + Random rng; + + // Clears the radiance ray payload to values expected for a miss. + // NOTE: The normal value (-1.0, -1.0, -1.0) will appear as black when remapped for output. + // NDC depth is typically in the range [-1.0, 1.0] or [0.0, 1.0] so a max value of 1.0 is used, + // while view depth is in world units so has a max value of INF. +#if 1//!DIRECTX + [mutating] +#endif + void + clear() + { + color = BLACK; + alpha = 0.0; + direct = BLACK; + depthNDC = 1.0f; + depthView = INFINITY; + normal = -1.0f; + baseColor = BLACK; + roughness = 0.1f; + metalness = 0.1f; + indirect.clear(); + }; + + // Scales the radiance ray payload color components by the specified values. +#if 1//!DIRECTX + [mutating] +#endif + void scaleColor(float3 scale) + { + color *= scale; + direct *= scale; + baseColor *= scale; + indirect.diffuse *= scale; + indirect.glossy *= scale; + } +}; + +// The shared ray payload that must be used by all TraceRay calls in the ray tree. +// NOTE: See: https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#ray-payload-structure +struct RayPayload +{ + // ================================================================================================= + // Radiance ray payload. + // Used by all shaders (radiance, shadow miss, and layer miss) + // ================================================================================================= + + // The incoming radiance ray data. + RadianceRayPayload radianceRay; + + // ================================================================================================= + // Hit and transform information from previous intersection. + // Used by ray miss shader. + // ================================================================================================= + + // The intersection data for radiance hit on base layer. + BuiltInTriangleIntersectionAttributes hit; + // The object-to-world matrix for base layer instance. + // TODO: This could be moved to constant buffer. + float3x4 objToWorld; + // The view direction for parent radiance ray. + float3 viewDirection; + // The primitive index for parent ray collision. + uint primitiveIndex; + // The current T for parent ray collision. + float currentT; + // Set to true if the layer absorbs the ray. + bool absorbedByLayer; +}; + +// Computes the origin and direction of a ray at the specified screen coordinates, using the +// specified view parameters (orientation, position, and FOV). +void computeCameraRay(float2 screenCoords, float2 screenSize, float4x4 invView, float2 viewSize, + bool isOrtho, float focalDistance, float lensRadius, inout Random rng, out float3 origin, + out float3 direction) +{ + // Apply a random offset to the screen coordinates, for antialiasing. Convert the screen + // coordinates to normalized device coordinates (NDC), i.e. the range [-1, 1] in X and Y. Also + // flip the Y component, so that +Y is up. + screenCoords += random2D(rng); + float2 ndc = (screenCoords / screenSize) * 2.0f - 1.0f; + ndc.y = -ndc.y; + + // Get the world-space orientation vectors from the inverse view matrix. + float3 right = invView[0].xyz; // right: row 0 + float3 up = invView[1].xyz; // up: row 1 + float3 front = -invView[2].xyz; // front: row 2, negated for RH coordinates + + // Build a world-space offset on the view plane, based on the view size and the right and up + // vectors. + float2 size = viewSize * 0.5f; + float3 offsetViewPlane = size.x * ndc.x * right + size.y * ndc.y * up; + + // Compute the ray origin and direction: + // - Direction: For orthographic projection, this is just the front direction (i.e. all rays are + // parallel). For perspective, it is the normalized combination of the front direction and the + // view plane offset. + // - Origin: For orthographic projection, this is the eye position (row 3 of the view matrix), + // translated by the view plane offset. For perspective, it is just the eye position. + // + // NOTE: It is common to "unproject" a NDC point using the view-projection matrix, and subtract + // that from the eye position to get a direction. However, this is numerically unstable when the + // eye position has very large coordinates and the projection matrix has small (nearby) clipping + // distances. Clipping is not relevant for ray tracing anyway. + if (isOrtho) + { + direction = front; + origin = invView[3].xyz + offsetViewPlane; + } + else + { + direction = normalize(front + offsetViewPlane); + origin = invView[3].xyz; + } + + // Adjust the ray origin and direction if depth of field is enabled. The ray must pass through + // the focal point (along the original direction, at the focal distance), with an origin that + // is offset on the lens, represented as a disk. + if (lensRadius > 0.0f) + { + float3 focalPoint = origin + direction * focalDistance; + float2 originOffset = sampleDisk(random2D(rng), lensRadius); + origin = origin + originOffset.x * right + originOffset.y * up; + direction = normalize(focalPoint - origin); + } +} + +// Traces a ray in the specified direction, returning the radiance from that direction. +RadianceRayPayload traceRadianceRay(RaytracingAccelerationStructure scene, Environment environment, + float3 origin, float3 dir, float tMin, int depth, int maxDepth, bool useBackgroundMiss, + inout Random rng) +{ + // If the maximum trace recursion depth has been reached, simply return black. + if (depth == maxDepth) + { + RadianceRayPayload rayPayload; + rayPayload.color = evaluateEnvironment(environment, dir, useBackgroundMiss); + + return rayPayload; + } + + // Set the force opaque ray flag to treat all objects as opaque, so that the any hit shader is + // not called. Also set the radiance or background miss shader. + uint rayFlags = RAY_FLAG_FORCE_OPAQUE; + uint missShaderIndex = useBackgroundMiss ? kMissBackground : kMissRadiance; + + // Prepare the ray. + RayDesc ray; + ray.Origin = origin; + ray.Direction = dir; + ray.TMin = tMin; + ray.TMax = INFINITY; + + // Prepare the ray payload. + RayPayload rayPayload; + rayPayload.radianceRay.depth = depth + 1; + rayPayload.radianceRay.rng = rng; + + // Trace the ray. + TraceRay(scene, // acceleration structure to be traced against + rayFlags, // flags to control ray behavior + 0xFF, // instance mask + 0, // ray contribution to hit group index + 0, // multiplier for geometry contribution to hit group index + missShaderIndex, // miss shader index + ray, // ray to be traced + rayPayload); // in/out data for shaders invoked during tracing + + // Update the random number generator. + rng = rayPayload.radianceRay.rng; + + return rayPayload.radianceRay; +} + +// The payload for shadow rays. +struct ShadowRayPayload +{ + float3 visibility; +}; + +// Traces a shadow ray for the specified sample position and light direction, returning the +// visibiliity of the light in that direction. +float3 traceShadowRay(RaytracingAccelerationStructure scene, float3 origin, float3 L, float tMin, + bool isOpaque, int depth, int maxDepth) +{ + // If the maximum trace recursion depth has been reached, treat the light as visible. This will + // mean there is more light than expected, but that works better than blocking the light, for + // typical scenes. Depth is not tracked in the shadow ray payload because there should not be + // any further ray tracing from shadow rays. Shadow rays are traced one level deeper than + // radiance rays, to avoid distracting shadow-less results. + if (depth == maxDepth + 1) + { + return WHITE; + } + + // Build a ray payload, starting with either: + // - Full visibility for normal shadow rays. This assumes a miss since the any hit shader + // will reduce visibility from there. + // - Zero visibility for shadow rays that treat all objects as opaque. if the miss shader is + // evaluated, it will set full visibility. + ShadowRayPayload rayPayload; + rayPayload.visibility = isOpaque ? BLACK : WHITE; + + // Prepare the shadow ray, with a small offset from the origin to avoid self-intersection. + RayDesc ray; + ray.Origin = origin; + ray.Direction = L; + ray.TMin = tMin; + ray.TMax = INFINITY; + + // Trace the shadow ray. This is different from standard tracing for performance and behavior, + // as follows: + // - Skip the closest hit shader, as none is needed for shadow rays. + // - For normal shadow rays, which support non-opaque objects, the any hit shader is used. So + // don't use a miss shader, as none is needed when the any hit shader is used. + // - For shadow rays that treat all objects as opaque (i.e. forced opaque): + // - Stop searching as soon as the first hit is found, which is not necessarily the closest. + // - Treat all intersections as opaque, so that the shadow any hit shader is never called. + // - Use the shadow miss shader, which simply sets full visibility on ray payload. + uint rayFlags = RAY_FLAG_SKIP_CLOSEST_HIT_SHADER; + rayFlags |= isOpaque ? RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_FORCE_OPAQUE : 0; +#if DIRECTX + uint missShaderIndex = isOpaque ? kMissShadow : kMissNull; +#else + uint missShaderIndex = kMissShadow; +#endif + + TraceRay(scene, rayFlags, 0xFF, 0, 0, missShaderIndex, ray, rayPayload); + + // Return the shadow ray visibility. + return rayPayload.visibility; +} + +// ================================================================================================= +// Layer material code +// ================================================================================================= + +// Shade the layer material for provided miss shader index. +// Returns true if absorbed by layer. +bool shadeMaterialLayer(RaytracingAccelerationStructure nullScene, int layerMissShaderIdx, + in out RadianceRayPayload rayPayload, ShadingData shading, + BuiltInTriangleIntersectionAttributes hit, int depth, int maxDepth) +{ + // If the maximum trace recursion depth has been reached, don't shade the layer. + if (depth == maxDepth) + { + return false; + } + + // Create a dummy ray, as we use a null scene this is never actuall intersected with anything. + RayDesc ray; + ray.Origin = float3(0, 0, 0); + ray.Direction = float3(0, 0, 1); + ray.TMin = 0; + ray.TMax = 0; + + // Create the layer data from the current intersection. + RayPayload layerData; + layerData.hit = hit; + layerData.radianceRay = rayPayload; + layerData.objToWorld = ObjectToWorld3x4(); + layerData.currentT = RayTCurrent(); + layerData.viewDirection = -WorldRayDirection(); + layerData.absorbedByLayer = false; + layerData.primitiveIndex = PrimitiveIndex(); + + // Increment ray depth. + layerData.radianceRay.depth = depth + 1; + + // "trace" the ray (this will go straight to the miss shader) + TraceRay(nullScene, // Null scene (so ray goes straight to miss) + 0, // flags to control ray behavior + 0xFF, // instance mask + 0, // ray contribution to hit group index + 0, // multiplier for geometry contribution to hit group index + layerMissShaderIdx, // miss shader index for layer material shader. + ray, // ray to be traced + layerData); // The layer data including details of current intersection. + + // If the ray is absorbed by the layer, then update the ray payload and return true. + if (layerData.absorbedByLayer) + { + rayPayload = layerData.radianceRay; + return true; + } + + // Return false (as not absorbed and ray payload unaffected) + return false; +} +#endif // __RAY_TRACE_H__ diff --git a/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang b/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang new file mode 100644 index 0000000..f485826 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang @@ -0,0 +1,372 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __REFERENCE_MATERIAL_H__ +#define __REFERENCE_MATERIAL_H__ + +// NOTE: This implements a reference material based on the glTF PBR material, with some +// modifications. See Appendix B here: +// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md + +// Evaluates the material BSDF with the specified view (toward the viewer) and light (toward the +// light) directions. +// NOTE: This includes the cosine term from the rendering equation. +BSDF_AND_COSINE evaluateMaterial( + Material material, ShadingData shading, float3 Vw, float3 Lw) +{ + // Transform the view and light directions to tangent space. + float3 N = shading.normal; + float3 X = shading.tangent; + float3 Y = shading.bitangent; + float3 V = worldToTangent(Vw, X, Y, N); + float3 L = worldToTangent(Lw, X, Y, N); + + // If the view and light directions are not in the same hemisphere, then there is no reflected + // radiance for an opaque material. + // NOTE: Vectors in opposite hemispheres should result in evaluating transmission (BTDF). + if (!sameHemisphere(V, L)) + { + return BLACK; + } + + // Collect relevant material properties. + float base = material.base; + float3 diffuseColor = material.baseColor; + float metalness = material.metalness; + float3 metalColor = material.metalColor; + float specular = material.specular; + float3 specularColor = material.specularColor; + float roughness = material.specularRoughness; + float anisotropy = material.specularAnisotropy; + float ior = material.specularIOR; + float transmission = material.transmission; + + // Compute the reflectance at normal incidence (f0). This a mix of dielctric and metal + // reflectance, based on the metalness. + float3 f0Dielectric = metalness < 1.0f ? iorToF0(ior) : 0.0f; + float3 f0Conductor = metalness > 0.0f ? metalColor : 0.0f; + float3 f0 = lerp(f0Dielectric, f0Conductor, metalness); + + // Compute the glossy BRDF. For dielectric (non-metal) materials, it is modulated by the + // specular factor and color; those are not used for metal materials. + float3 F; + float3 glossyBRDF = evaluateGGXSmithBRDF(V, L, f0, roughness, anisotropy, F); + glossyBRDF *= lerp(specular * specularColor, 1.0f, metalness); + + // Compute the diffuse BRDF, and modulate it by the base factor, metalness, and transmission. + float3 diffuseBRDF = evaluateLambertianBRDF(diffuseColor); + diffuseBRDF = base * diffuseBRDF * (1.0f - metalness) * (1.0f - transmission); + + // Sum the glossy and diffuse components, with the latter blended by the Fresnel term. This is + // multipled by the cosine term (NdotL or L.z) from the rendering equation. + return (glossyBRDF + diffuseBRDF * (1.0f - F)) * abs(L.z); +} + +// Samples the material (for a single randomly selected lobe) with the specified view +// direction (toward the viewer) and sampled light direction (toward the light), returning +// the evaluated BSDF, the corresponding PDF, and an integer lobe ID indicating the BSDF lobe +// that was sampled. +// NOTE: This includes the cosine term from the rendering equation. +BSDF_AND_COSINE evaluateMaterialAndPDF(Material material, ShadingData shading, float3 Vw, + float3 Lw, float2 random, out float pdf, out int lobeID) +{ + // Transform the view direction to tangent space. + float3 N = shading.normal; + float3 X = shading.tangent; + float3 Y = shading.bitangent; + float3 V = worldToTangent(Vw, X, Y, N); + float3 L = worldToTangent(Lw, X, Y, N); + float3 H = normalize(V + L); + + // If the view and light directions are not in the same hemisphere, then there is no reflected + // radiance for an opaque material. + // NOTE: Vectors in opposite hemispheres should result in evaluating transmission (BTDF). + if (!sameHemisphere(V, L)) + { + return BLACK; + } + + // Collect relevant material properties. + float base = material.base; + float3 diffuseColor = material.baseColor; + float metalness = material.metalness; + float3 metalColor = material.metalColor; + float specular = material.specular; + float3 specularColor = material.specularColor; + float roughness = material.specularRoughness; + float anisotropy = material.specularAnisotropy; + float ior = material.specularIOR; + float transmission = material.transmission; + bool thinWalled = material.thinWalled; + + // NOTE: The rest of this function selects a single lobe based on the material properties, using + // a random variable. That lobe is used to determine a sample direction and is also evaluated. + // The material lobes are based on Standard Surface and are as follows: + // + // - METAL- | ------- GLOSSY ------- + // | TRANSMISSION | DIFFUSE + // + // "P" below is a probability of a lobe being selected in isolation. In general, a lobe should + // have its contribution divided by the (accumulated) probability of having selected that lobe, + // but instead each contribution omits that same amount that would normally be applied as a + // scaling factor, e.g. the diffuse lobe isn't scaled by (1.0 - transmission) here as it is in + // the evaluation function. + + // METAL: Determine whether this lobe should be evaluated, using the metalness. + BSDF_AND_COSINE result = 0.0f; + float P = metalness; + if (random.x < P) + { + // Remap the random variable (pass case). + random.x = remapPass(random.x, P); + + // Convert roughness and anisotropy to alpha values. + float2 alpha = roughnessToAlpha(roughness, anisotropy); + + // Evaluate the GGX-Smith BRDF with view direction and sampled light direction. + float3 F; // output not used + result = evaluateGGXSmithBRDF(V, L, metalColor, roughness, anisotropy, F); + + // Compute the PDF, divided by a factor from using a reflected vector. + // NOTE: See sampleGGXSmithBRDF() for details. + float denom = 4.0f * dot(V, H); + pdf = computeGGXSmithPDF(V, H, alpha) / denom; + + // Set the lobe ID. + lobeID = METAL_LOBE; + } + + // GLOSSY (dielectric). + else + { + // Remap the random variable (fail case). + random.x = remapFail(random.x, P); + + // Compute the Fresnel reflectance term, using the IOR to determine an f0 value. This uses + // NdotV (V.z) as the cosine term, in the absence of a sampled light direction. + float3 f0 = iorToF0(ior); + float3 F = evaluateSchlickFresnel(f0, abs(V.z)); + + // Determine whether this lobe should be evaluated, using the luminance of the Fresnel term + // along with the specular factor and color. + P = luminance(F * specular * specularColor); + if (random.x < P) + { + // Remap the random variable (pass case). + random.x = remapPass(random.x, P); + + // Convert roughness and anisotropy to alpha values. + float2 alpha = roughnessToAlpha(roughness, anisotropy); + + // Sample and evaluate the GGX-Smith BRDF, for this dielectric glossy lobe. Scale the + // result with the specular factor and color. + result = evaluateGGXSmithBRDF(V, L, f0, roughness, anisotropy, F); + result *= specular * specularColor; + + // Normalize the result with the probability of having selected this lobe. This is + // needed for this lobe because the result is inherently scaled by a similar amount + // above, i.e. F * specular * specularColor. Otherwise the lobe contribution will be too + // small. + result /= P; + + // Compute the PDF, divided by a factor from using a reflected vector. + // NOTE: See sampleGGXSmithBRDF() for details. + float denom = 4.0f * dot(V, H); + pdf = computeGGXSmithPDF(V, H, alpha) / denom; + + // Set the lobe ID. + lobeID = GLOSSY_LOBE; + } + + // TODO: Fix the typos below. + + // TRANSMISSION (specular). + // NOTE: This is true specular (impulse) transmission, i.e. sharp refraction, and sharp + // total internal reflection. + else + { + // Remap the random variable (fail case). + random.x = remapFail(random.x, P); + + float3 N = float3(0.0f, 0.0f, V.z > 0.0f ? 1.0f : -1.0f); + pdf = dot(N, L) * M_PI_INV; + + // Sample and evaluate the Lambertian BRDF, for this dielectric diffuse lobe. Scale + // the result with the base factor. + result = evaluateLambertianBRDF(diffuseColor); + result *= base; + + // Set the lobe ID. + lobeID = DIFFUSE_LOBE; + } + } + + // Return the result multipled by the cosine term (NdotL or L.z) from the rendering equation. + return result * abs(L.z); +} + +// Samples the material with the specified view direction (toward the viewer), returning the +// evaluated BSDF, the sampled light direction (toward the light), the corresponding PDF, and (for +// debug purposes) an integer lobe ID indicating the BSDF lobe that was sampled. +// NOTE: This includes the cosine term from the rendering equation. +BSDF_AND_COSINE sampleMaterial(Material material, ShadingData shading, float3 Vw, + float2 random, out float3 Lw, out float pdf, out int lobeID) +{ + // Transform the view direction to tangent space. + float3 N = shading.normal; + float3 X = shading.tangent; + float3 Y = shading.bitangent; + float3 V = worldToTangent(Vw, X, Y, N); + + // Collect relevant material properties. + float base = material.base; + float3 diffuseColor = material.baseColor; + float metalness = material.metalness; + float3 metalColor = material.metalColor; + float specular = material.specular; + float3 specularColor = material.specularColor; + float roughness = material.specularRoughness; + float anisotropy = material.specularAnisotropy; + float ior = material.specularIOR; + float transmission = material.transmission; + bool thinWalled = material.thinWalled; + + // NOTE: The rest of this function selects a single lobe based on the material properties, using + // a random variable. That lobe is used to determine a sample direction and is also evaluated. + // The material lobes are based on Standard Surface and are as follows: + // + // - METAL- | ------- GLOSSY ------- + // | TRANSMISSION | DIFFUSE + // + // "P" below is a probability of a lobe being selected in isolation. In general, a lobe should + // have its contribution divided by the (accumulated) probability of having selected that lobe, + // but instead each contribution omits that same amount that would normally be applied as a + // scaling factor, e.g. the diffuse lobe isn't scaled by (1.0 - transmission) here, but that is + // done in the evaluation function. + + // METAL: Determine whether this lobe should be evaluated, using the metalness. + BSDF_AND_COSINE result = 0.0f; + float3 L; + float P = metalness; + if (random.x < P) + { + // Remap the random variable (pass case). + random.x = remapPass(random.x, P); + + // Sample and evaluate the GGX-Smith BRDF, for this metal lobe. + result = sampleGGXSmithBRDF(V, metalColor, roughness, anisotropy, random, L, pdf); + + // Set the lobe ID. + lobeID = METAL_LOBE; + } + + // GLOSSY (dielectric). + else + { + // Remap the random variable (fail case). + random.x = remapFail(random.x, P); + + // Compute the Fresnel reflectance term, using the IOR to determine an f0 value. This uses + // NdotV (V.z) as the cosine term, in the absence of a sampled light direction. + float3 f0 = iorToF0(ior); + float3 F = evaluateSchlickFresnel(f0, abs(V.z)); + + // Determine whether this lobe should be evaluated, using the luminance of the Fresnel term + // along with the specular factor and color. + P = luminance(F * specular * specularColor); + if (random.x < P) + { + // Remap the random variable (pass case). + random.x = remapPass(random.x, P); + + // Sample and evaluate the GGX-Smith BRDF, for this dielectric glossy lobe. Scale the + // result with the specular factor and color. + result = sampleGGXSmithBRDF(V, f0, roughness, anisotropy, random, L, pdf); + result *= specular * specularColor; + + // Normalize the result with the probability of having selected this lobe. This is + // needed for this lobe because the result is inherently scaled by a similar amount + // above, i.e. F * specular * specularColor. Otherwise the lobe contribution will be too + // small. + result /= P; + + // Set the lobe ID. + lobeID = GLOSSY_LOBE; + } + + // TRANSMISSION (specular). + // NOTE: This is true specular (impulse) transmission, i.e. sharp refraction, and sharp + // total internal reflection. + else + { + // Remap the random variable (fail case). + random.x = remapFail(random.x, P); + + // Determine whether this lobe should be evaluated, using the transmission factor. + P = transmission; + if (random.x < P) + { + // Remap the random variable (pass case). + random.x = remapPass(random.x, P); + + // Set the sampled light direction to the opposite of the view direction, which is + // appropriate for thin-walled materials. If this is not a thin-walled material, + // compute a refraction direction, or a reflection direction if total internal + // reflection occurs. + // NOTE: A ternary operator should not be used here as HLSL evaluates both results, + // and computeRefraction() has side-effects. + bool reflected = false; + L = -V; + if (!material.thinWalled) + { + L = computeRefraction(V, material.specularIOR, reflected); + } + + // The BSDF is simply the transmission color. Since this is a delta distribution + // (specular), it must be divided by the cosine term, and the PDF is simply 1.0. + // NOTE: See PBRT 8.2.2 for information on delta distributions. + result = material.transmissionColor; + result /= abs(L.z); + pdf = 1.0f; + + // Set the lobe ID. + // NOTE: Total internal reflection is not actually glossy, but a more specific state + // is not required here. + lobeID = reflected ? GLOSSY_LOBE : TRANSMISSION_LOBE; + } + + // DIFFUSE. + else + { + // Remap the random variable (fail case). + random.x = remapFail(random.x, P); + + // Sample and evaluate the Lambertian BRDF, for this dielectric diffuse lobe. Scale + // the result with the base factor. + result = sampleLambertianBRDF(V, diffuseColor, random, L, pdf); + result *= base; + + // Set the lobe ID. + lobeID = DIFFUSE_LOBE; + } + } + } + + // Transform the sampled light direction to world space. + Lw = tangentToWorld(L, X, Y, N); + + // Return the result multipled by the cosine term (NdotL or L.z) from the rendering equation. + return result * abs(L.z); +} + +#endif // __REFERENCE_MATERIAL_H__ diff --git a/Libraries/Aurora/Source/Shaders/Sampling.slang b/Libraries/Aurora/Source/Shaders/Sampling.slang new file mode 100644 index 0000000..c1accb4 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/Sampling.slang @@ -0,0 +1,109 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __SAMPLING_H__ +#define __SAMPLING_H__ + +#include "Globals.slang" + +// Generates a uniformly distributed random direction. +float3 sampleUniformDirection(float2 random, out float pdf) +{ + // Create a point on the unit sphere, from the uniform random variables. Since this is a unit + // sphere, this can also be used as a direction. + // NOTE: See "Ray Tracing Gems" section 16.5 for details. + float cosTheta = 1.0f - 2.0f * random[1]; + float sinTheta = sqrt(1.0f - cosTheta * cosTheta); + float phi = 2.0f * M_PI * random[0]; + float3 direction = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); + + // The PDF is uniform, at one over the area, i.e. the total solid angle of 4*Pi. + pdf = 0.25f * M_PI_INV; + + return direction; +} + +// Generates a random direction in the cosine-weighted hemisphere above the specified normal. This +// provides a PDF value ("probability density function") which is the *relative* probability that +// the returned direction will be chosen. +float3 sampleHemisphere(float2 random, float3 normal, out float pdf) +{ + // Uniformly sample a direction, i.e. over a sphere. + float3 direction = sampleUniformDirection(random, pdf); + + // To transform that into a sample from a cosine-weighted hemisphere over the normal, treat the + // sphere as tangent to the surface: add the normal to the direction and normalize it. The PDF + // is cos(theta) / PI, so use a dot product to compute cos(theta). + // NOTE: See "Ray Tracing in One Weekend" for details. + direction = normalize(normal + direction); + pdf = dot(normal, direction) * M_PI_INV; + + return direction; +} + +// Generates a random direction in the cosine-weighted hemisphere above the tangent-space normal +// (0.0, 0.0, 1.0). +float3 sampleHemisphere(float2 random, out float pdf) +{ + // Generate a uniform sample on the 2D disk, and project it up to the hemisphere. This results + // in a cosine-weighted hemisphere sample. + float r = sqrt(random[0]); + float phi = 2.0f * M_PI * random[1]; + float3 direction = float3(r * cos(phi), r * sin(phi), sqrt(1.0f - random[0])); + + // The PDF is the cosine of the angle (i.e. the Z component here) divided by Pi. + pdf = direction.z * M_PI_INV; + + return direction; +} + +// Generates a uniformly distributed random direction in a cone with the specified direction and +// angular radius. +float3 sampleCone(float2 random, float3 direction, float cosThetaMax) +{ + // Compute arbitrary basis vectors based on the cone direction. + float3 x_basis, y_basis; + buildBasis(direction, x_basis, y_basis); + + // Compute angles for the direction based on the random variables. + // NOTE: See "Ray Tracing Gems" section 16.5 for details. + float cosTheta = (1.0f - random[0]) + random[0] * cosThetaMax; + float sinTheta = sqrt(1.0f - cosTheta * cosTheta); + float phi = random[1] * 2 * M_PI; + + // Construct and return the direction vector. + return x_basis * cos(phi) * sinTheta + y_basis * sin(phi) * sinTheta + direction * cosTheta; +} + +// Generates a uniformly distributed random point on a disk with the specified radius. +// NOTE: See "Ray Tracing Gems" section 16.5 for details. +float2 sampleDisk(float2 random, float radius) +{ + float r = radius * sqrt(random[0]); + float phi = 2.0f * M_PI * random[1]; + + return r * float2(cos(phi), sin(phi)); +} + +// Computes the weight for scaling contributions for multiple importance sampling, which is used to +// reduce variance with MIS. +// NOTE: See PBRT 3E 13.10.1. This is specifically the "power heuristic." nf and ng are typically 1. +float computeMISWeight(int nf, float fPDF, int ng, float gPDF) +{ + float f = nf * fPDF; + float g = ng * gPDF; + + return (f * f) / (f * f + g * g); +} + +#endif // __SAMPLING_H__ diff --git a/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang b/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang new file mode 100644 index 0000000..9695b7d --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang @@ -0,0 +1,240 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "BSDF.slang" + +// Compute shading with a directional light. +float3 shadeDirectionalLight(float3 dir, float4 colorAndIntensity, float cosRadius, + RaytracingAccelerationStructure scene, Material material, ShadingData shading, float3 V, + bool isOpaqueShadowsEnabled, int depth, int maxDepth, inout Random rng) +{ + // Perform shading with the global directional light. Treat the light as having a disc area by + // sampling a random direction in a cone around the light direction. + float2 random = random2D(rng); + float3 L = sampleCone(random, dir, cosRadius); + + // Evaluate the BSDF of the material, using the view direction and light direction. + float3 bsdfAndCosine = evaluateMaterial(material, shading, V, L); + if (isBlack(bsdfAndCosine)) + { + return BLACK; + } + + // Compute the light radiance from the light properties, and use a shadow ray to compute the + // visibility of the light source at the hit position. If the view (ray) direction is on the + // back side of the geometry, use the geometric position to avoid self-intersection. + float3 lightRadiance = colorAndIntensity.a * colorAndIntensity.rgb; + float3 position = dot(V, shading.normal) < 0.0f ? shading.geomPosition : shading.position; + float3 lightVisibility = + traceShadowRay(scene, position, L, M_RAY_TMIN, isOpaqueShadowsEnabled, depth, maxDepth); + + // Compute the outgoing radiance as the BSDF (with cosine term) multipled by the light + // visibility and radiance. + return lightVisibility * lightRadiance * bsdfAndCosine; +} + +// Compute shading with an environment light as direct lighting. +float3 shadeEnvironmentLightDirect(Environment environment, + RaytracingAccelerationStructure scene, Material material, ShadingData shading, float3 V, + bool isOpaqueShadowsEnabled, int depth, int maxDepth, inout Random rng) +{ + // Sample the environment to determine a sample (light) direction and the corresponding + // probability density function (PDF) value for that direction. + float3 L; + float pdf; + float2 random = random2D(rng); + float3 lightRadiance = sampleEnvironment(environment, random, L, pdf); + + // Evaluate the BSDF of the material, using the view direction and light direction. + float3 bsdfAndCosine = evaluateMaterial(material, shading, V, L); + if (isBlack(bsdfAndCosine)) + { + return BLACK; + } + + // Use a shadow ray to compute the visibility of the environment at the hit position. If the + // view (ray) direction is on the back side of the geometry, use the geometric position to avoid + // self-intersection. + float3 position = dot(V, shading.normal) < 0.0f ? shading.geomPosition : shading.position; + float3 lightVisibility = + traceShadowRay(scene, position, L, M_RAY_TMIN, isOpaqueShadowsEnabled, depth, maxDepth); + + // Compute the outgoing radiance as the BSDF (with cosine term) multipled by the light + // visibility and radiance, divided by the PDF. + return lightVisibility * lightRadiance * bsdfAndCosine / pdf; +} + +// Compute shading with an environment light using multiple importance sampling. +float3 shadeEnvironmentLightMIS(Environment environment, RaytracingAccelerationStructure scene, Material material, + ShadingData shading, float3 V, bool isOpaqueShadowsEnabled, int depth, int maxDepth, + inout Random rng, inout IndirectOutput indirect) +{ + float3 result = BLACK; + + // Generate random numbers for sampling. + float2 random = random2D(rng); + + // If the view (ray) direction is on the back side of the geometry, use the geometric position + // for shadow rays to avoid self-intersection. Otherwise use the shading normal. + float3 position = dot(V, shading.normal) < 0.0f ? shading.geomPosition : shading.position; + + // Multiple importance sampling (MIS) proceeds as follows: + // 1) Sample the *material* BSDF-and-cosine to get a new light direction and calculate the PDF. + // 2) Evaluate the light in the light direction and calculate the PDF. + // 3) Trace a shadow ray in the light direction to compute visibility. + // 4) Weight the outgoing radiance with a balance heuristic and store that result. + // 5) Sample the *light* and calculate the PDF. + // 6) Evaluate the material BSDF-and-cosine in the light direction and calculate the PDF. + // 7) Trace a shadow ray in the light direction to compute visibility. + // 8) Weight the outgoing radiance with a balance heuristic and that to the result from step #4. + + // Step 1: Sample the BSDF of the material, using the view direction. In addition to the BSDF + // value, this computes a sample (light) direction and the corresponding PDF value for that + // direction. + float3 L; + float materialPDF; + int lobeID; + float3 bsdfAndCosine = + sampleMaterial(material, shading, V, random, L, materialPDF, lobeID); + + // If the sampled lobe is transmission, simply return black. Transmission is handled separately, + // and does not repond to lighting. + if (lobeID == TRANSMISSION_LOBE) + { + return BLACK; + } + + // Step 2: Evaluate the environment for the light direction. + float lightPDF; + float3 lightRadiance = evaluateEnvironment(environment, L, lobeID == TRANSMISSION_LOBE); + + // Skip the following steps if the light radiance or BSDF-and-cosine is black. + if (!isBlack(lightRadiance) && !isBlack(bsdfAndCosine)) + { + // Step 2 (PDF): Calculate the light PDF for the light direction, by dividing the luminance + // of the radiance by the integral of the luminance over the entire environment. If the + // environment is not from an image, use the PDF for uniform sampling over all directions. + lightPDF = environment.constants.hasLightTex + ? (luminance(lightRadiance) / environment.constants.lightTexLuminanceIntegral) + : (0.25f * M_PI_INV); + + // Step 3: Use a shadow ray to compute the visibility of the light at the hit position. + float3 lightVisibility = + traceShadowRay(scene, position, L, M_RAY_TMIN, isOpaqueShadowsEnabled, depth, maxDepth); + + // Step 4: Compute a weight based on the PDF of the material and the light, and apply that + // weight to the outgoing radiance: visibility * radiance * BSDF * cosine / material PDF. + // Add that to the prior weighted result. + float weight = computeMISWeight(1, materialPDF, 1, lightPDF); + result += weight * lightVisibility * lightRadiance * bsdfAndCosine / materialPDF; + } + + // Step 5: Sample the environment to determine a sample (light) direction and the corresponding + // probability density function (PDF) value for that direction. + lightRadiance = sampleEnvironment(environment, random, L, lightPDF); + + // Step 6: Evaluate the BSDF of the material, using the view direction and light direction, and + // calculate the material PDF for the light direction. + int evaluatedLobeID; + bsdfAndCosine = evaluateMaterialAndPDF(material, shading, V, L, random, materialPDF, evaluatedLobeID); + + // Skip the following steps if the light radiance or BSDF-and-cosine is black. + if (!isBlack(lightRadiance) && !isBlack(bsdfAndCosine)) + { + // Step 7: Use a shadow ray to compute the visibility of the light at the hit position. + float3 lightVisibility = + traceShadowRay(scene, position, L, M_RAY_TMIN, isOpaqueShadowsEnabled, depth, maxDepth); + + // Step 8: Compute a weight based on the PDF of the light and the material, and apply that + // weight to the outgoing radiance: visibility * radiance * BSDF * cosine / light PDF. + float weight = computeMISWeight(1, lightPDF, 1, materialPDF); + result += weight * lightVisibility * lightRadiance * bsdfAndCosine / lightPDF; + } + + // Update the indirect output values based on the sampled lobe at Step 1: diffuse for the + // diffuse lobe, and treat everything else as glossy. As this is an environment light, the hit + // distances are set to infinity. + // NOTE: This works because the material sampling (Step 1) and evaluation (Step 6) are using the + // same random numbers and therefore are expected to use the same lobe. An alternative is for + // Step 6 to perform a complete evaluation (rather than selecting a single lobe) and combine the + // PDFs of the lobes, with separate diffuse and glossy components returned from the evaluation. + if (lobeID == DIFFUSE_LOBE) + { + indirect.diffuse += result; + indirect.diffuseHitDist = INFINITY; + } + else + { + indirect.glossy += result; + indirect.glossyHitDist = INFINITY; + } + + return result; +} + +// Compute shading with indirect light, from a random direction. +float3 shadeIndirectLight(RaytracingAccelerationStructure scene, + Environment environment, Material material, ShadingData shading, float3 V, int depth, + int maxDepth, inout Random rng, out float alpha, inout IndirectOutput indirect) +{ + // Generate random numbers for sampling. + float2 random = random2D(rng); + + // Sample the BSDF of the material, using the view direction. In addition to the BSDF value, + // this computes a sample (light) direction and the corresponding probability density function + // (PDF) value for that direction. + float3 L; + float pdf; + int lobeID; + float3 bsdfAndCosine = + sampleMaterial(material, shading, V, random, L, pdf, lobeID); + if (isBlack(bsdfAndCosine)) + { + return BLACK; + } + + // Trace a radiance ray with the sampled light direction, i.e. the next step in tracing the + // current path. If the view (ray) direction is on the back side of the geometry, or the ray is + // from a transmission lobe, use the geometric position to avoid self-intersection. Also, if the + // ray is from a transmission lobe, use the background miss shader so that the background is + // sampled. + bool isTransmission = lobeID == TRANSMISSION_LOBE; + float3 position = + dot(V, shading.normal) < 0.0f || isTransmission ? shading.geomPosition : shading.position; + RadianceRayPayload rayPayload = traceRadianceRay( + scene, environment, position, L, M_RAY_TMIN, depth, maxDepth, isTransmission, rng); + + // Compute the outgoing radiance as the BSDF (with cosine term) multipled by the light radiance, + // divided by the PDF. + float3 radiance = rayPayload.color * bsdfAndCosine / pdf; + + // If the payload alpha is zero and this was a transmission lobe, pass along an alpha of zero. + // This indicates that all the path segments beyond this hit have been transparent or + // transmissive. Otherwise the alpha is assumed to be one (opaque). + alpha = (rayPayload.alpha == 0.0f && lobeID == TRANSMISSION_LOBE) ? 0.0f : 1.0f; + + // Update the indirect output values based on the sampled lobe: diffuse for the diffuse lobe, + // and treat everything else as glossy. + if (lobeID == DIFFUSE_LOBE) + { + indirect.diffuse += radiance; + indirect.diffuseHitDist = rayPayload.depthView; + } + else + { + indirect.glossy += radiance; + indirect.glossyHitDist = rayPayload.depthView; + } + + return radiance; +} diff --git a/Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang b/Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang new file mode 100644 index 0000000..c42d970 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang @@ -0,0 +1,80 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Entry point template that defines a hit shader for a material type. +// NOTE: This file is not valid HLSL as is. This template must be configured at runtime by +// replacing the tags surrounded by the @ character: +// -@MATERIAL_TYPE@ with the unique material type name for this shader. + +// Any hit shader for shadow rays. +// NOTE: It is possible for this to be called multiple times for the same intersection, which would +// lead to incorrect visibility determination. However, this has not been a problem in practice. +// If this does become an issue, the D3D12_RAYTRACING_FLAG_NO_DUPLICATE_ANYHIT_INVOCATION flag can +// be used, with a performance cost. +#include "PathTracingCommon.slang" +#include "InitializeMaterial.slang" +#include "ShadeFunctions.slang" + +[shader("anyhit")] void @MATERIAL_TYPE@ShadowAnyHitShader( + inout ShadowRayPayload rayPayload, in BuiltInTriangleIntersectionAttributes hit) +{ + // If the material is opaque, set the visibility to zero, accept the hit, and stop searching for + // hits, as the shadow ray is completely blocked. Doing this here is a performance optimization, + // as it avoids calling the full material initialization below, which can be expensive. + if (gMaterialConstants.isOpaque) + { + rayPayload.visibility = BLACK; + AcceptHitAndEndSearch(); + } + + // Get the interpolated vertex data for the hit triangle, at the hit barycentric coordinates. + uint triangleIndex = PrimitiveIndex(); + float3 barycentrics = computeBarycentrics(hit); + ShadingData shading = computeShadingData( + gGeometry, triangleIndex, barycentrics, ObjectToWorld3x4()); + + // Initialize the material values. + // NOTE: This evaluates all material properties, when only visibility (or opacity) is needed. It + // may be more efficient to generate a dedicated function for getting the visibility alone. + float3 materialNormal = shading.normal; + bool isGeneratedNormal = false; + Material material = initializeMaterial(shading, ObjectToWorld3x4(), materialNormal, isGeneratedNormal); + + // Compute the opacity at the current hit from the opacity, transmission, and transmission color + // properties. Then accumulate that (inverted) with the current visibility on the ray payload. + // NOTE: Visibility accumulation like this is order-independent, i.e. it does not matter what + // order intersections are processed. This is important because any hit shader intersections are + // processed in an arbitrary order. Also, while this does consider *transmission* to determine + // visibility, this technique does not support refraction (caustics), just simple straight + // shadow rays. + float3 opacity = + material.opacity * (WHITE - material.transmission * material.transmissionColor); + rayPayload.visibility *= 1.0f - opacity; + + // If the visibility is zero (opaque) at this point, accept this hit and stop searching for + // hits, as the shadow ray is now completely blocked. Otherwise, ignore the hit so that + // visibility can continue to be accumulated from other any hit intersections. + if (isBlack(rayPayload.visibility)) + { + AcceptHitAndEndSearch(); + } + else + { + IgnoreHit(); + } + + // NOTE: The default behavior of accepting the hit while continuing to search for hits is not + // useful for shadow rays. It does not happen here, because of the intrinsic function calls + // above. +} diff --git a/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang b/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang new file mode 100644 index 0000000..e939992 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang @@ -0,0 +1,21 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// The shadow miss shader. This simply sets full visibility on the ray payload, indicating that +// nothing was hit. +#include "Globals.slang" +#include "Geometry.slang" +#include "RayTrace.slang" + +[shader("miss")] void ShadowMissShader( + inout ShadowRayPayload rayPayload) { rayPayload.visibility = WHITE; } \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang b/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang new file mode 100644 index 0000000..a1e9bec --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang @@ -0,0 +1,1293 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __STANDARD_SURFACE_H__ +#define __STANDARD_SURFACE_H__ + +// NOTE: The following shader code is ported to HLSL from the MaterialX GLSL implementations. + +// Determines whether vector a and b are in the same hemisphere relative to normal n. +bool sameHemisphere(float3 a, float3 b, float3 n) +{ + return dot(a, n) * dot(b, n) > 0.0f; +} + +// Compute the 2D anisotropic roughness in tangent space. +// See +// http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf +// NOTE: From libraries/pbrlib/genglsl/mx_roughness_anisotropy.glsl in the MaterialX repo. +float2 mx_roughness_anisotropy(float roughness, float anisotropy) +{ + float2 result = 0.0f.xx; + + // Compute the clamped roughness squared. + float roughness_sqr = clamp(roughness * roughness, M_FLOAT_EPS, 1.0); + + // If anisotropy is non-zero then compute anisotropic roughness. + if (anisotropy > 0.0) + { + // Convert anisotropy to aspect ratio between 0 and 10.0. + float aspect = sqrt(1.0 - clamp(anisotropy, 0.0, 0.98)); + + // Compute 2D anisotropic roughness from aspect ratio. + result.x = min(roughness_sqr / aspect, 1.0); + result.y = roughness_sqr * aspect; + } + else + { + // If not, just return roughness squared. + result.x = roughness_sqr; + result.y = roughness_sqr; + } + + return result; +} + +float mx_square(float x) +{ + return x * x; +} + +float mx_pow5(float x) +{ + return mx_square(mx_square(x)) * x; +} + +// Mix two BSDFs based on weight +// NOTE: Based on mix functions in mx_mix_bsdf_transmission from +// libraries/pbrlib/genglsl/mx_mix_brdf.glsl in the MaterialX repo. +float3 mx_mix(BSDF bg, BSDF fg, float w) +{ + return lerp(bg, fg, clamp(w, 0.0f, 1.0f)); +} + +float mx_orennayar(float3 L, float3 V, float3 N, float NdotL, float roughness) +{ + float LdotV = dot(L, V); + float NdotV = abs(dot(N, V)); + + float t = LdotV - NdotL * NdotV; + t = t > 0.0 ? t / max(NdotL, NdotV) : 0.0; + + float sigma2 = mx_square(roughness * M_PI); + float A = 1.0 - 0.5 * (sigma2 / (sigma2 + 0.33)); + float B = 0.45f * sigma2 / (sigma2 + 0.09); + + return A + B * t; +} + +BSDF mx_diffuse_brdf_reflection( + float3 L, float3 V, float weight, float3 color, float roughness, float3 normal) +{ + // There is no contribution if the weight is close to zero, or if the light and view directions + // are not in the same hemisphere relative to the normal. + if (weight < M_FLOAT_EPS || !sameHemisphere(L, V, normal)) + { + return BLACK; + } + + BSDF result = 0.0f.xxx; + + float NdotL = max(dot(L, normal), 0.0); + + result = color * weight * NdotL * M_PI_INV; + if (roughness > 0.0) + { + result *= mx_orennayar(L, V, normal, NdotL, roughness); + } + + return result; +} + +// Compute the average of an anisotropic roughness pair +float mx_average_roughness(float2 roughness) +{ + return sqrt(roughness.x * roughness.y); +} + +// Convert a real-valued index of refraction to normal-incidence reflectivity. +float mx_ior_to_f0(float ior) +{ + return mx_square((ior - 1.0) / (ior + 1.0)); +} + +// https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf +// Appendix B.2 Equation 13 +float mx_ggx_NDF(float3 X, float3 Y, float3 H, float NdotH, float alphaX, float alphaY) +{ + float XdotH = dot(X, H); + float YdotH = dot(Y, H); + float denom = mx_square(XdotH / alphaX) + mx_square(YdotH / alphaY) + mx_square(NdotH); + return 1.0 / (M_PI * alphaX * alphaY * mx_square(denom)); +} + +// Standard Schlick Fresnel +float mx_fresnel_schlick(float cosTheta, float F0) +{ + float x = clamp(1.0 - cosTheta, 0.0, 1.0); + float x5 = mx_pow5(x); + return F0 + (1.0 - F0) * x5; +} + +// http://jcgt.org/published/0003/02/03/paper.pdf +// Equations 72 and 99 +float mx_ggx_smith_G(float NdotL, float NdotV, float alpha) +{ + float alpha2 = mx_square(alpha); + float lambdaL = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotL)); + float lambdaV = sqrt(alpha2 + (1.0 - alpha2) * mx_square(NdotV)); + return 2.0 / (lambdaL / NdotL + lambdaV / NdotV); +} + +// NOTE: The MaterialX implementation offers three options for computing directional albedo. Only +// the curve fit (below) is supported here. + +// https://www.unrealengine.com/blog/physically-based-shading-on-mobile +float3 mx_ggx_directional_albedo_curve_fit(float NdotV, float roughness, float3 F0, float3 F90) +{ + const float4 c0 = float4(-1, -0.0275, -0.572, 0.022); + const float4 c1 = float4(1, 0.0425, 1.04, -0.04); + float4 r = roughness * c0 + c1; + float a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y; + float2 AB = float2(-1.04, 1.04) * a004 + r.zw; + return F0 * AB.x + F90 * AB.y; +} + +float mx_ggx_directional_albedo(float NdotV, float roughness, float F0, float F90) +{ + return mx_ggx_directional_albedo_curve_fit(NdotV, roughness, F0.xxx, F90.xxx).x; +} + +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +// Equations 14 and 16 +float3 mx_ggx_energy_compensation(float NdotV, float roughness, float3 Fss) +{ + float Ess = mx_ggx_directional_albedo(NdotV, roughness, 1.0, 1.0); + return 1.0 + Fss * (1.0 - Ess) / Ess; +} + +float mx_ggx_energy_compensation(float NdotV, float roughness, float Fss) +{ + return mx_ggx_energy_compensation(NdotV, roughness, Fss.xxx).x; +} + +BSDF mx_dielectric_brdf_reflection(float3 L, float3 V, float weight, float3 tint, float ior, + float2 roughness, float3 N, float3 X, int distribution, BSDF base) +{ + BSDF result = 0.0f.xxx; + + if (weight < M_FLOAT_EPS) + { + return base; + } + + float NdotL = abs(dot(N, L)); + float NdotV = abs(dot(N, V)); + + float3 Y = normalize(cross(N, X)); + + float3 H = normalize(L + V); + float NdotH = dot(N, H); + float VdotH = dot(V, H); + + float avgRoughness = mx_average_roughness(roughness); + float F0 = mx_ior_to_f0(ior); + + float D = mx_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y); + float F = mx_fresnel_schlick(VdotH, F0); + float G = mx_ggx_smith_G(NdotL, NdotV, avgRoughness); + + float comp = mx_ggx_energy_compensation(NdotV, avgRoughness, F); + float dirAlbedo = mx_ggx_directional_albedo(NdotV, avgRoughness, F0, 1.0) * comp; + + // Note: NdotL is canceled out + result = abs(D * F * G * comp * tint * weight / (4 * NdotV)); // Top layer reflection + result += base *(1.0 - dirAlbedo * weight); // Base layer reflection attenuated by top layer + + return result; +} + +void mx_artistic_to_complex_ior( + float3 reflectivity, float3 edge_color, out float3 ior, out float3 extinction) +{ + float3 r = clamp(reflectivity, 0.0, 0.99); + float3 r_sqrt = sqrt(r); + float3 n_min = (1.0 - r) / (1.0 + r); + float3 n_max = (1.0 + r_sqrt) / (1.0 - r_sqrt); + ior = lerp(n_max, n_min, edge_color); + + float3 np1 = ior + 1.0; + float3 nm1 = ior - 1.0; + float3 k2 = (np1 * np1 * r - nm1 * nm1) / (1.0 - r); + k2 = max(k2, 0.0); + extinction = sqrt(k2); +} + +float3 mx_fresnel_conductor(float cosTheta, float3 n, float3 k) +{ + float c2 = cosTheta * cosTheta; + float3 n2_k2 = n * n + k * k; + float3 nc2 = 2.0 * n * cosTheta; + + float3 rs_a = n2_k2 + c2; + float3 rp_a = n2_k2 * c2 + 1.0; + float3 rs = (rs_a - nc2) / (rs_a + nc2); + float3 rp = (rp_a - nc2) / (rp_a + nc2); + + return 0.5 * (rs + rp); +} + +BSDF mx_conductor_brdf_reflection(float3 L, float3 V, float weight, float3 reflectivity, + float3 edge_color, float2 roughness, float3 N, float3 X, int distribution) +{ + BSDF result = 0.0f.xxx; + + if (weight < M_FLOAT_EPS || !sameHemisphere(L, V, N)) + { + return result; + } + + float NdotL = abs(dot(N, L)); + float NdotV = abs(dot(N, V)); + + float3 Y = normalize(cross(N, X)); + + float3 H = normalize(L + V); + float NdotH = dot(N, H); + float VdotH = dot(V, H); + + float3 ior_n, ior_k; + mx_artistic_to_complex_ior(reflectivity, edge_color, ior_n, ior_k); + + float avgRoughness = mx_average_roughness(roughness); + + float D = mx_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y); + float3 F = mx_fresnel_conductor(VdotH, ior_n, ior_k); + float G = mx_ggx_smith_G(NdotL, NdotV, avgRoughness); + + float3 comp = mx_ggx_energy_compensation(NdotV, avgRoughness, F); + + result = D * F * G * comp * weight / (4 * NdotV); + + return result; +} + +// Computes the normal distribution function used in sheen BRDF. +// Based on: +// http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf +// Equation 2 +// NOTE: From libraries/pbrlib/genglsl/mx_sheen_brdf.glsl in the MaterialX repo. +float mx_imageworks_sheen_NDF(float cosTheta, float roughness) +{ + // Given roughness is assumed to be clamped to [M_FLOAT_EPS, 1.0] + float invRoughness = 1.0 / roughness; + float cos2 = cosTheta * cosTheta; + float sin2 = 1.0 - cos2; + return (2.0 + invRoughness) * pow(sin2, invRoughness * 0.5) / (2.0 * M_PI); +} + +// LUT for sheen directional albedo. +// Pre-calculated using analytical integration as described in: +// https://www.researchgate.net/profile/Peter_Shirley/publication/220721563_A_microfacet-based_BRDF_generator/links/0046351ded39154fa9000000/A-microfacet-based-BRDF-generator.pdf +// A 2D table parameterized with 'cosTheta' (cosine of angle to normal) on x-axis and 'roughness' on +// y-axis. +// NOTE: From libraries/pbrlib/genglsl/mx_sheen_brdf.glsl in the MaterialX repo. +#define SHEEN_ALBEDO_TABLE_SIZE 16 +static const float u_sheenAlbedo[SHEEN_ALBEDO_TABLE_SIZE * SHEEN_ALBEDO_TABLE_SIZE] = { 1.6177, + 0.978927, 0.618938, 0.391714, 0.245177, 0.150234, 0.0893475, 0.0511377, 0.0280191, 0.0144204, + 0.00687674, 0.00295935, 0.00111049, 0.000336768, 7.07119e-05, 6.22646e-06, 1.1084, 0.813928, + 0.621389, 0.479304, 0.370299, 0.284835, 0.21724, 0.163558, 0.121254, 0.0878921, 0.0619052, + 0.0419894, 0.0270556, 0.0161443, 0.00848212, 0.00342323, 0.930468, 0.725652, 0.586532, 0.479542, + 0.393596, 0.322736, 0.26353, 0.213565, 0.171456, 0.135718, 0.105481, 0.0800472, 0.0588117, + 0.0412172, 0.0268329, 0.0152799, 0.833791, 0.671201, 0.558957, 0.471006, 0.398823, 0.337883, + 0.285615, 0.240206, 0.200696, 0.16597, 0.135422, 0.10859, 0.0850611, 0.0644477, 0.0464763, + 0.0308878, 0.771692, 0.633819, 0.537877, 0.461939, 0.398865, 0.344892, 0.297895, 0.256371, + 0.219562, 0.186548, 0.156842, 0.130095, 0.10598, 0.0841919, 0.0645311, 0.04679, 0.727979, + 0.606373, 0.52141, 0.453769, 0.397174, 0.348337, 0.305403, 0.267056, 0.232655, 0.201398, + 0.17286, 0.146756, 0.122808, 0.100751, 0.0804254, 0.0616485, 0.695353, 0.585281, 0.508227, + 0.44667, 0.394925, 0.350027, 0.310302, 0.274561, 0.242236, 0.212604, 0.185281, 0.16002, 0.13657, + 0.114693, 0.0942543, 0.0750799, 0.669981, 0.568519, 0.497442, 0.440542, 0.392567, 0.350786, + 0.313656, 0.280075, 0.249533, 0.221359, 0.195196, 0.170824, 0.148012, 0.126537, 0.106279, + 0.0870713, 0.649644, 0.554855, 0.488453, 0.435237, 0.390279, 0.351028, 0.316036, 0.284274, + 0.255266, 0.228387, 0.203297, 0.179796, 0.157665, 0.136695, 0.116774, 0.0977403, 0.632951, + 0.543489, 0.480849, 0.430619, 0.388132, 0.350974, 0.317777, 0.287562, 0.259885, 0.234153, + 0.210041, 0.187365, 0.165914, 0.145488, 0.125983, 0.10724, 0.61899, 0.533877, 0.47433, 0.426573, + 0.386145, 0.35075, 0.319078, 0.290197, 0.263681, 0.238971, 0.215746, 0.193838, 0.173043, + 0.153167, 0.134113, 0.115722, 0.607131, 0.52564, 0.468678, 0.423001, 0.38432, 0.35043, 0.320072, + 0.292349, 0.266856, 0.243055, 0.220636, 0.199438, 0.179264, 0.159926, 0.141332, 0.123323, + 0.596927, 0.518497, 0.463731, 0.419829, 0.382647, 0.350056, 0.320842, 0.294137, 0.269549, + 0.246564, 0.224875, 0.204331, 0.18474, 0.165919, 0.147778, 0.130162, 0.588052, 0.512241, + 0.459365, 0.416996, 0.381114, 0.349657, 0.321448, 0.295641, 0.271862, 0.24961, 0.228584, + 0.208643, 0.189596, 0.171266, 0.153566, 0.136341, 0.580257, 0.506717, 0.455481, 0.41445, + 0.379708, 0.34925, 0.321929, 0.296923, 0.273869, 0.252279, 0.231859, 0.212472, 0.193933, + 0.176066, 0.158788, 0.141945, 0.573355, 0.5018, 0.452005, 0.412151, 0.378416, 0.348844, + 0.322316, 0.298028, 0.275627, 0.254638, 0.234772, 0.215896, 0.197828, 0.180398, 0.163522, + 0.147049 }; + +// Looks up an albedo using the static LUT. +// NOTE: From libraries/pbrlib/genglsl/mx_sheen_brdf.glsl in the MaterialX repo. +float mx_imageworks_sheen_directional_albedo(float cosTheta, float roughness) +{ + // Compute x and y index within LUT from roughness and cosTheta. + float x = cosTheta * (SHEEN_ALBEDO_TABLE_SIZE - 1); + float y = roughness * (SHEEN_ALBEDO_TABLE_SIZE - 1); + int ix = int(x); + int iy = int(y); + int ix2 = clamp(ix + 1, 0, SHEEN_ALBEDO_TABLE_SIZE - 1); + int iy2 = clamp(iy + 1, 0, SHEEN_ALBEDO_TABLE_SIZE - 1); + float fx = x - ix; + float fy = y - iy; + + // Bi-linear interpolation of the LUT values + float v1 = lerp(u_sheenAlbedo[iy * SHEEN_ALBEDO_TABLE_SIZE + ix], + u_sheenAlbedo[iy * SHEEN_ALBEDO_TABLE_SIZE + ix2], fx); + float v2 = lerp(u_sheenAlbedo[iy2 * SHEEN_ALBEDO_TABLE_SIZE + ix], + u_sheenAlbedo[iy2 * SHEEN_ALBEDO_TABLE_SIZE + ix2], fx); + float albedo = lerp(v1, v2, fy); + + // Return the clamped value. + return clamp(albedo, 0.0, 1.0); +} + +// Compute the reflection component of the sheen BRDF. +// NOTE: From libraries/pbrlib/genglsl/mx_sheen_brdf.glsl in the MaterialX repo. +BSDF mx_sheen_brdf_reflection( + float3 L, float3 V, float weight, float3 color, float roughness, float3 N, BSDF base) +{ + // Ignore if the weight less than epsilon. + if (weight < M_FLOAT_EPS) + { + return base; + } + + BSDF result = 0.0f.xxx; + + // Make sure we are forward facing. + if (dot(N, V) < 0.0) + { + N = -N; + } + + // Compute the half angle. + float3 H = normalize(L + V); + + // Dot-products of normal with light, view and half-angle. + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + // Alpha is clamped roughness (so we can safely take the inverse without worrying about NaNs.) + float alpha = clamp(roughness, M_FLOAT_EPS, 1.0); + + // Compute microfacet normal distribution function for the surface. + float D = mx_imageworks_sheen_NDF(NdotH, alpha); + + // Geometry term is skipped and we use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + float3 fr = D * color / (4.0 * (NdotL + NdotV - NdotL * NdotV)); + + // Compute the albedo using LUT. + float dirAlbedo = mx_imageworks_sheen_directional_albedo(NdotV, alpha); + + // We need to include NdotL from the light integral here + // as in this case it's not canceled out by the BRDF denominator. + result = fr * NdotL * weight // Top layer reflection + + base * (1.0 - dirAlbedo * weight); // Base layer reflection attenuated by top layer + + return result; +} + +// Compute the specular component of the sheen BRDF. +// NOTE: From libraries/pbrlib/genglsl/mx_sheen_brdf.glsl in the MaterialX repo. +BSDF mx_sheen_brdf_specular_layer( + float3 L, float3 V, float weight, float3 color, float roughness, float3 N) +{ + // There is no contribution if the light and view directions are not in the same hemisphere + // relative to the normal. + if (!sameHemisphere(L, V, N)) + { + return BLACK; + } + + BSDF result = 0.0f.xxx; + + // Make sure we are forward facing. + if (dot(N, V) < 0.0) + { + N = -N; + } + + // Compute the half angle. + float3 H = normalize(L + V); + + // Dot-products of normal with light, view and half-angle. + float NdotL = clamp(dot(N, L), M_FLOAT_EPS, 1.0); + float NdotV = clamp(dot(N, V), M_FLOAT_EPS, 1.0); + float NdotH = clamp(dot(N, H), M_FLOAT_EPS, 1.0); + + // Alpha is clamped roughness (so we can safely take the inverse without worrying about NaNs.) + float alpha = clamp(roughness, M_FLOAT_EPS, 1.0); + + // Compute microfacet normal distribution function for the surface. + float D = mx_imageworks_sheen_NDF(NdotH, alpha); + + // Geometry term is skipped and we use a smoother denominator, as in: + // https://blog.selfshadow.com/publications/s2013-shading-course/rad/s2013_pbs_rad_notes.pdf + float3 fr = D * color / (4.0 * (NdotL + NdotV - NdotL * NdotV)); + + // Compute the albedo using LUT. + float dirAlbedo = mx_imageworks_sheen_directional_albedo(NdotV, alpha); + + // We need to include NdotL from the light integral here + // as in this case it's not canceled out by the BRDF denominator. + result = fr * NdotL * weight; + + return result; +} + +// Fake subsurface scattering with simple diffuse transmission. +// NOTE: From /libraries/pbrlib/genglsl/mx_subsurface_brdf.glsl in the MaterialX repo. +BSDF mx_subsurface_brdf_reflection(float3 L, float3 V, float weight, float3 color, float3 normal) +{ + // Invert normal since we're transmitting light from the other side + float NdotL = dot(L, -normal); + if (NdotL <= 0.0 || weight < M_FLOAT_EPS) + { + return BLACK; + } + + // Compute basic NdotL from back-facing normal + return color * weight * NdotL * M_PI_INV; +} + +// Create a rotation matrix from axis and angle in radians. +// NOTE: From libraries/stdlib/genglsl/mx_rotate_vector3.glsl in the MaterialX repo. +float4x4 mx_rotationMatrix(float3 axis, float angle) +{ + // Normalize axis. + axis = normalize(axis); + + // Compute sin and cos of angle. + float s = sin(angle); + float c = cos(angle); + float oc = 1.0 - c; + + // Build transform matrix.s + return float4x4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, + oc * axis.z * axis.x + axis.y * s, 0.0, oc * axis.x * axis.y + axis.z * s, + oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0, + oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, + oc * axis.z * axis.z + c, 0.0, 0.0, 0.0, 0.0, 1.0); +} + +// Rotate a vector around given axis by angle in degrees. +// NOTE: From libraries/stdlib/genglsl/mx_rotate_vector3.glsl in the MaterialX repo. +float3 mx_rotate_vector3(float3 vec, float amount, float3 axis) +{ + float rotationRadians = radians(amount); + float4x4 m = mx_rotationMatrix(axis, rotationRadians); + return mul(m, float4(vec, 1.0)).xyz; +} + +// Diffuse component of mx_dielectric_brdf_reflection used for sampling. +// Scales the base diffuse value by the GGX directional albedo. +BSDF mx_dielectric_brdf_diffuse_layer(float3 L, float3 V, float weight, float ior, float2 roughness, + float3 N, float3 X, int distribution, float3 albedo) +{ + // There is no contribution if the weight is close to zero, or if the light and view directions + // are not in the same hemisphere relative to the normal. + if (weight < M_FLOAT_EPS || !sameHemisphere(L, V, N)) + { + return BLACK; + } + + // Normal dot products + float NdotL = abs(dot(N, L)); + float NdotV = abs(dot(N, V)); + + // Half-vector dot products + float3 H = normalize(L + V); + float NdotH = dot(N, H); + float VdotH = dot(V, H); + + // Compute the Fresnel term + float avgRoughness = mx_average_roughness(roughness); + float F0 = mx_ior_to_f0(ior); + float F = mx_fresnel_schlick(VdotH, F0); + + // Compute the energy compensation term (compensates for scattering between micro-surfaces) + float comp = mx_ggx_energy_compensation(NdotV, avgRoughness, F); + // Compute the directional albedo term. + float dirAlbedo = mx_ggx_directional_albedo(NdotV, avgRoughness, F0, 1.0) * comp; + + // The diffuse term is one minus the directional albedo. + float3 result = 1.0 - dirAlbedo; + + // Return scaled result. + return result * albedo * weight * M_PI_INV; +} + +// Specular component of mx_dielectric_brdf_reflection used for sampling. +// NOTE: We may want to replace this with sampleGGXSmithBRDF() to match the sampleGGXSmithVNDF() +// already being used for sampling. +BSDF mx_dielectric_brdf_specular_layer(float3 L, float3 V, float3 tint, float ior, float2 roughness, + float3 N, float3 X, int distribution) +{ + // There is no contribution if the light and view directions are not in the same hemisphere + // relative to the normal. + if (!sameHemisphere(L, V, N)) + { + return BLACK; + } + + BSDF result = 0.0f.xxx; + + // Normal dot products + float NdotL = abs(dot(N, L)); + float NdotV = abs(dot(N, V)); + + // Compute bitangent + float3 Y = normalize(cross(N, X)); + + // Half-vector dot products + float3 H = normalize(L + V); + float NdotH = dot(N, H); + float VdotH = dot(V, H); + + // Compute average roughness. + float avgRoughness = mx_average_roughness(roughness); + float F0 = mx_ior_to_f0(ior); + + // Compute distribtion, fresnel, and geometry terms. + float D = mx_ggx_NDF(X, Y, H, NdotH, roughness.x, roughness.y); + float F = mx_fresnel_schlick(VdotH, F0); + float G = mx_ggx_smith_G(NdotL, NdotV, avgRoughness); + + // Compute the energy compensation term (compensates for scattering between micro-surfaces) + float comp = mx_ggx_energy_compensation(NdotV, avgRoughness, F); + + // Combine into final specular term. + // Note: NdotL is canceled out + return abs(D * F * G * comp * tint / (4 * NdotV * NdotL)); +} + +// Various flags to enable or disable material operations. +#define ENABLE_SUBSURFACE 1 // subsurface lobe +#define ENABLE_SHEEN 1 // sheen lobe +#define ENABLE_COAT 1 // coat lobe +#define ENABLE_TRANSMISSION 1 // transmission lobe + +// Debug flag, to enable simple hemisphere sampling. +#define SIMPLE_HEMISPHERE_SAMPLING 0 + +// Evaluates the material BSDF with the specified view (toward the viewer) and light (toward the +// light) directions. +// NOTE: This includes the cosine term from the rendering equation. +BSDF_AND_COSINE evaluateMaterial( + Material material, ShadingData shading, float3 Vw, float3 Lw) +{ + // Roughness influenced by coat. + float coat_affect_roughness_factor = + material.coatAffectRoughness * material.coat * material.coatRoughness; + float coat_affected_roughness = + lerp(material.specularRoughness, 1.0, coat_affect_roughness_factor); + + // Subsurface color influenced by coat. + float coat_clamped = clamp(material.coat, 0, 1); + float coat_gamma = coat_clamped * material.coatAffectColor + 1.0; + float3 coat_affected_subsurface_color = pow(material.subsurfaceColor, coat_gamma); + + // Apply the subsurface scale to the subsurface radius. + // NOTE: From node subsurface_radius_scaled in libraries/bxdf/standard_surface.mtlx in the + // MaterialX repo. + float3 subsurface_radius_scaled = material.subsurfaceRadius * material.subsurfaceScale; + + float2 main_roughness = + mx_roughness_anisotropy(coat_affected_roughness, material.specularAnisotropy); + + // Tangent rotation. + float3 main_tangent = + mx_rotate_vector3(shading.tangent, material.specularRotation * 360.0, shading.normal); + + // Coat tangent rotation. + float3 coat_tangent = + mx_rotate_vector3(shading.tangent, material.coatRotation * 360.0, shading.normal); + + // Colors influenced by coat ("coat gamma"). + float3 coat_affected_diffuse_color = pow(material.baseColor, coat_gamma); + + // Diffuse layer. + BSDF diffuse_bsdf = mx_diffuse_brdf_reflection(Lw, Vw, material.base, + coat_affected_diffuse_color, material.diffuseRoughness, shading.normal); + + // Thin film subsurface. + // TODO + + BSDF subsurface_mix = diffuse_bsdf; + +#if ENABLE_SUBSURFACE + if (material.subsurface > 0.0) + { + // Subsurface layer. + BSDF subsurface_bsdf = mx_subsurface_brdf_reflection( + Lw, Vw, 1.000000, coat_affected_subsurface_color, shading.normal); + + // Mix the diffuse BSDF and subsurface BSDF + // NOTE: From node subsurface_mix in libraries/bxdf/standard_surface.mtlx in the MaterialX + // repo. + subsurface_mix = mx_mix(diffuse_bsdf, subsurface_bsdf, material.subsurface); + } +#endif + + // Subsurface is the only lobe that needs back face lighting, so if the view and light + // directions are not in the same hemisphere, then return here. + if (dot(shading.normal, Vw) * dot(shading.normal, Lw) < 0.0f) + { + return subsurface_mix; + } + + // Sheen layer, which takes the subsurface and diffuse as an input + BSDF sheen_and_diffuse = subsurface_mix; +#if ENABLE_SHEEN + if (material.sheen > 0.0) + { + sheen_and_diffuse = mx_sheen_brdf_reflection(Lw, Vw, material.sheen, material.sheenColor, + material.sheenRoughness, shading.normal, subsurface_mix); + } +#endif + + // Factor in transmission layer. + // In the GLSL implementation this does a fake transmission step here. For pathtracing just + // scale by 1-transmission. + sheen_and_diffuse *= 1.0 - material.transmission; + + // Specular layer. + BSDF specular_bsdf = sheen_and_diffuse; + if (material.specular > 0.0) + { + specular_bsdf = mx_dielectric_brdf_reflection(Lw, Vw, material.specular, + material.specularColor, material.specularIOR, main_roughness, shading.normal, + main_tangent, 0, sheen_and_diffuse); + } + + // Metal layer. + BSDF metalness_mix = specular_bsdf; + if (material.metalness > 0.0) + { + float3 metal_reflectivity = material.metalColor * material.base; + float3 metal_edgecolor = material.specularColor * material.specular; + BSDF metal_bsdf = mx_conductor_brdf_reflection(Lw, Vw, 1.0f, metal_reflectivity, + metal_edgecolor, main_roughness, shading.normal, main_tangent, 0); + metalness_mix = mx_mix(specular_bsdf, metal_bsdf, material.metalness); + } + + // Coat layer. + BSDF final_color = metalness_mix; +#if ENABLE_COAT + if (material.coat > 0.0) + { + // Compute 2D anisotropic roughness for coat. + float2 coat_roughness = + mx_roughness_anisotropy(material.coatRoughness, material.coatAnisotropy); + + // Attenuate the output of the metal layer base. + float3 coat_attenuation = lerp(WHITE, material.coatColor, material.coat); + float3 metalness_mix_attenuated = coat_attenuation * metalness_mix; + + // The coat layer is a second dielectric specular layer, using attenuated output from metal + // layer as base. + final_color = mx_dielectric_brdf_reflection(Lw, Vw, material.coat, material.coatColor, + material.coatIOR, coat_roughness, shading.normal, coat_tangent, 0, + metalness_mix_attenuated); + } +#endif + + return final_color; +} + +// Sample direction on unit hemisphere and compute PDF. +float3 calculateHemisphereLightDirection( + float3 tangent, float3 bitangent, float3 normal, float3 V, float2 random, out float pdf) +{ + float3 Nh = float3(0.0f, 0.0f, V.z > 0.0f ? 1.0f : -1.0f); + float3 L = sampleHemisphere(random, Nh, pdf); + + return L; +} + +// Compute PDF on unit hemisphere. +float calculateHemispherePDF(float3 normal, float3 Lw) +{ + return dot(normal, Lw) * M_PI_INV; +} + +// Compute GGX PDF for given direction using VNDF. +float calculateGGXPDF(float3 V, float2 alpha, float2 random, float3 L) +{ + + float3 H = normalize(V + L); + + float denom = 4.0f * dot(V, H); + float pdf = evaluateGGXSmithVNDF(V, H, alpha) / denom; + + return pdf; +} + +// Compute GGX tangent-space light direction from random 2D value, along with half-vector, +// world-space light direction and GGX PDF. +// NOTE: This is an abbreviated version of sampleGGXSmithBRDF(). +float3 calculateGGXLightDirectionAndPDF(float3 tangent, float3 bitangent, float3 normal, float3 V, + float2 alpha, float2 random, out float3 Lw, out float pdf) +{ + // Sample the GGX-Smith VNDF to determine a microfacet normal (half-vector). If the view + // direction is in the lower hemisphere in tangent space, flip the view direction for sampling + // the VNDF, as the VNDF expects the view direction to be in the upper hemisphere (with the + // normal). Then flip the resulting microfacet normal. + bool flip = V.z <= 0.0f; + float3 H = sampleGGXSmithVNDF(flip ? -V : V, alpha, random); + H = flip ? -H : H; + + // Reflect the view direction across the microfacet normal to get the sample direction. + float3 L = reflect(-V, H); + + // Compute the PDF, divided by a factor from using a reflected vector. + float denom = 4.0f * dot(V, H); + pdf = computeGGXSmithPDF(V, H, alpha) / denom; + + // Compute world space light direction. + Lw = tangentToWorld(L, tangent, bitangent, normal); + + return L; +} + +// Samples the material using simple cosine-weighted hemisphere sampling. +// NOTE: This will provide suboptimal results, and is only intended for debugging. +BSDF_AND_COSINE sampleMaterialAllLobes(Material material, ShadingData shading, float3 Vw, + float2 random, out float3 Lw, out float pdf, out int lobeID) +{ + lobeID = 1; + // Sample a cosine-weighted direction from a hemisphere above the normal. If the view direction + // is not in the same hemisphere as the normal, flip the light direction to allow proper shading + // of the backface. + // TODO: Proper importance sampling of Standard Surface, similar to the reference material. + Lw = sampleHemisphere(random, shading.normal, pdf); + if (dot(shading.normal, Vw) < 0.0f) + { + Lw = -Lw; + } + + // Evaluate the material with the view direction and sampled light direction. + return evaluateMaterial(material, shading, Vw, Lw); +} + +// Samples the material (for a single randomly selected lobe) with the specified view +// direction (toward the viewer) and sampled light direction (toward the light), returning +// the evaluated BSDF, the corresponding PDF, and an integer lobe ID indicating the BSDF lobe +// that was sampled. +// NOTE: This includes the cosine term from the rendering equation. + +BSDF_AND_COSINE evaluateMaterialAndPDF(Material material, ShadingData shading, + float3 Vw, float3 Lw, float2 random, out float pdf, out int lobeID) +{ + // Transform the view direction to tangent space. + float3 N = shading.normal; + float3 X = shading.tangent; + float3 Y = shading.bitangent; + float3 V = worldToTangent(Vw, X, Y, N); + float3 L = worldToTangent(Lw, X, Y, N); + float NdotV = abs(V.z); + + // Default values (avoid compile errors if lobes disabled). + pdf = 0.0; + lobeID = -1; + + // Roughness influenced by coat. + float coat_affect_roughness_factor = + material.coatAffectRoughness * material.coat * material.coatRoughness; + float coat_affected_roughness = + lerp(material.specularRoughness, 1.0, coat_affect_roughness_factor); + + // Subsurface color influenced by coat. + float coat_clamped = clamp(material.coat, 0, 1); + float coat_gamma = coat_clamped * material.coatAffectColor + 1.0; + float3 coat_affected_subsurface_color = pow(material.subsurfaceColor, coat_gamma); + + // Apply the subsurface scale to the subsurface radius. + // NOTE: From node subsurface_radius_scaled in libraries/bxdf/standard_surface.mtlx in the + // MaterialX repo. + float3 subsurface_radius_scaled = material.subsurfaceRadius * material.subsurfaceScale; + + float2 main_roughness = + mx_roughness_anisotropy(coat_affected_roughness, material.specularAnisotropy); + + // Tangent rotation. + float3 main_tangent = + mx_rotate_vector3(shading.tangent, material.specularRotation * 360.0, shading.normal); + + // Coat tangent rotation. + float3 coat_tangent = + mx_rotate_vector3(shading.tangent, material.coatRotation * 360.0, shading.normal); + + // Colors influenced by coat ("coat gamma"). + float3 coat_affected_diffuse_color = pow(material.baseColor, coat_gamma); + + float3 result; + + // Sample coat lobe. + float coat = ENABLE_COAT ? material.coat : 0.0; + + // Evaluate the specular Fresnel component for coat BSDF. + float coatF0 = mx_ior_to_f0(material.coatIOR); + float3 coatF = mx_fresnel_schlick(NdotV, coatF0); + + // Compute luminance specular Fresnel component for coat BSDF. + float coatLuminance = luminance(coatF * coat * material.coatColor); + if (random.x < coatLuminance) + { +#if ENABLE_COAT + // Remap the random variable to 0-1 within coat lobe. + random.x = remapPass(random.x, coatLuminance); + + // Compute coat alpha from roughness and aniso. + float2 coatAlpha = mx_roughness_anisotropy(material.coatRoughness, material.coatAnisotropy); + + // Compte PDF from provided light direction.. + // TODO: Use rotated tangent. + pdf = calculateGGXPDF(V, coatAlpha, random, L); + + // Compute 2D anisotropic roughness for coat. + float2 coat_roughness = + mx_roughness_anisotropy(material.coatRoughness, material.coatAnisotropy); + + // Compute coat color. + result = mx_dielectric_brdf_specular_layer(Lw, Vw, material.coat * material.coatColor, + material.coatIOR, coat_roughness, N, coat_tangent, 0); + + // Assign lobe ID. + lobeID = COAT_LOBE; +#endif + } + else + { + // Remap the random variable to 0-1 outside coat lobe. + random.x = remapFail(random.x, coatLuminance); + + // Compute specular alpha from roughness and anisotropy. + float2 specAlpha = + mx_roughness_anisotropy(material.specularRoughness, material.specularAnisotropy); + + // Sample metal lobe. + if (random.x < material.metalness) + { + // Remap the random variable to 0-1 within metal lobe. + random.x = remapPass(random.x, material.metalness); + + // Compte PDF from provided light direction.. + // TODO: Use rotated tangent. + pdf = calculateGGXPDF(V, specAlpha, random, L); + + // Compute metallic color. + float3 metal_reflectivity = material.metalColor * material.base; + float3 metal_edgecolor = material.specularColor * material.specular; + result = mx_conductor_brdf_reflection(Lw, Vw, 1.0f, metal_reflectivity, metal_edgecolor, + main_roughness, N, main_tangent, 0); + + // Remove the N.L term when sampling (which comes from mx_ggx_smith_G), to avoid + // applying it twice. + result /= abs(L.z); + + // Set lobe ID. + lobeID = METAL_LOBE; + ; + } + else + { + // Remap the random variable to 0-1 outside metal lobe. + random.x = remapFail(random.x, material.metalness); + + // Evaluate the specular Fresnel component for BSDF. + float F0 = mx_ior_to_f0(material.specularIOR); + float3 F = mx_fresnel_schlick(NdotV, F0); + + // Compute luminance specular Fresnel component for BSDF. + float specLuminance = luminance(F * material.specular * material.specularColor); + + // Sample glossy lobe. + if (random.x < specLuminance) + { + // Remap the random variable to 0-1 within metal lobe. + random.x = remapPass(random.x, specLuminance); + + // Compte PDF from provided light direction.. + pdf = calculateGGXPDF(V, specAlpha, random, L); + + // Compute specular color. + result = mx_dielectric_brdf_specular_layer(Lw, Vw, + material.specularColor * material.specular, material.specularIOR, + main_roughness, shading.normal, main_tangent, 0); + + // Normalize the result with the probability of having selected this lobe. This is + // needed for this lobe because the result is inherently scaled by a similar amount + // above, i.e. F * specular * specularColor. Otherwise the lobe contribution will be + // too small. + result /= specLuminance; + + // Set lobe ID. + lobeID = GLOSSY_LOBE; + } + else + { + // Remap the random variable to 0-1 within metal lobe. + random.x = remapFail(random.x, specLuminance); + + // Alpha is just roughness clamped to epsilon-1.0 range (to avoid NaN) + float sheenAlpha = clamp(material.sheenRoughness, M_FLOAT_EPS, 1.0); + + // Sample sheen node. + float sheen = ENABLE_SHEEN ? material.sheen : 0.0; + if (random.x < material.sheen) + { +#if ENABLE_SHEEN + // Remap the random variable to 0-1 within sheen lobe. + random.x = remapPass(random.x, sheen); + + // Compte PDF from provided light direction.. + pdf = calculateHemispherePDF(N, Lw); + + // Compute half-vector. + float3 H = normalize(L + V); + + // Compute sheen color. + result = mx_sheen_brdf_specular_layer( + Lw, Vw, material.sheen, material.sheenColor, material.sheenRoughness, N); + + // Compute probability density function as sheen normal distribution function. + // Uses NDF directly without scaling by cosine values. + pdf = mx_imageworks_sheen_NDF(H.z, sheenAlpha); + + // Set lobe ID. + lobeID = SHEEN_LOBE; +#endif + } + else + { + // Remap the random variable to 0-1 within diffuse lobe. + random.x = remapFail(random.x, sheen); + + // Compte PDF from provided light direction.. + pdf = calculateHemispherePDF(N, Lw); + + // Compute diffuse color. + result = mx_dielectric_brdf_diffuse_layer(Lw, Vw, material.base, + material.specularIOR, main_roughness, N, main_tangent, 0, + coat_affected_diffuse_color); + + // Apply sheen direction albedo to diffuse color. + float dirAlbedo = mx_imageworks_sheen_directional_albedo(NdotV, sheenAlpha); + result *= (1.0 - + dirAlbedo * + material.sheen); // Base layer reflection attenuated by top layer + + // Set lobe ID. + lobeID = DIFFUSE_LOBE; + } + } + } + + // Apply coat attenuation factor to result. + float3 coat_attenuation = lerp(WHITE, material.coatColor, material.coat); + result *= coat_attenuation; + } + + // Multiply the N.L cosine term to final value. + float NdotL = abs(L.z); + + return result * NdotL; +} + +// Samples the material with the specified view direction (toward the viewer), returning the +// evaluated BSDF, the sampled light direction (toward the light) and the corresponding PDF. +// NOTE: This includes the cosine term from the rendering equation. +BSDF_AND_COSINE sampleMaterial(Material material, ShadingData shading, float3 Vw, + float2 random, out float3 Lw, out float pdf, out int lobeID) +{ + // Transform the view direction to tangent space. + float3 L; + float3 N = shading.normal; + float3 X = shading.tangent; + float3 Y = shading.bitangent; + float3 V = worldToTangent(Vw, X, Y, N); + float NdotV = abs(V.z); + + // If debug flag is set, just sample all the lobes over hemisphere. +#if SIMPLE_HEMISPHERE_SAMPLING + BSDF_AND_COSINE res = sampleMaterialAllLobes(material, shading, Vw, random, Lw, pdf, lobeID); + return res; +#endif + + // Default values (avoid compile errors if lobes disabled). + Lw = float3(1, 0, 0); + pdf = 0.0; + lobeID = -1; + + // Roughness influenced by coat. + float coat_affect_roughness_factor = + material.coatAffectRoughness * material.coat * material.coatRoughness; + float coat_affected_roughness = + lerp(material.specularRoughness, 1.0, coat_affect_roughness_factor); + + // Subsurface color influenced by coat. + float coat_clamped = clamp(material.coat, 0, 1); + float coat_gamma = coat_clamped * material.coatAffectColor + 1.0; + float3 coat_affected_subsurface_color = pow(material.subsurfaceColor, coat_gamma); + + // Apply the subsurface scale to the subsurface radius. + // NOTE: From node subsurface_radius_scaled in libraries/bxdf/standard_surface.mtlx in the + // MaterialX repo. + float3 subsurface_radius_scaled = material.subsurfaceRadius * material.subsurfaceScale; + + float2 main_roughness = + mx_roughness_anisotropy(coat_affected_roughness, material.specularAnisotropy); + + // Tangent rotation. + float3 main_tangent = + mx_rotate_vector3(shading.tangent, material.specularRotation * 360.0, shading.normal); + + // Coat tangent rotation. + float3 coat_tangent = + mx_rotate_vector3(shading.tangent, material.coatRotation * 360.0, shading.normal); + + // Colors influenced by coat ("coat gamma"). + float3 coat_affected_diffuse_color = pow(material.baseColor, coat_gamma); + + float3 result; + + // Sample coat lobe. + float coat = ENABLE_COAT ? material.coat : 0.0; + + // Evaluate the specular Fresnel component for coat BSDF. + float coatF0 = mx_ior_to_f0(material.coatIOR); + float3 coatF = mx_fresnel_schlick(NdotV, coatF0); + + // Compute luminance specular Fresnel component for coat BSDF. + float coatLuminance = luminance(coatF * coat * material.coatColor); + if (random.x < coatLuminance) + { +#if ENABLE_COAT + // Remap the random variable to 0-1 within coat lobe. + random.x = remapPass(random.x, coatLuminance); + + // Compute coat alpha from roughness and aniso. + float2 coatAlpha = mx_roughness_anisotropy(material.coatRoughness, material.coatAnisotropy); + + // Generate light direction and half vector. + // TODO: Use rotated tangent. + L = calculateGGXLightDirectionAndPDF(X, Y, N, V, coatAlpha, random, Lw, pdf); + + // Compute 2D anisotropic roughness for coat. + float2 coat_roughness = + mx_roughness_anisotropy(material.coatRoughness, material.coatAnisotropy); + + // Compute coat color. + result = mx_dielectric_brdf_specular_layer(Lw, Vw, coat * material.coatColor, + material.coatIOR, coat_roughness, N, coat_tangent, 0); + + // Normalize the result with the probability of having selected this lobe. This is + // needed for this lobe because the result is inherently scaled by a similar amount + // above, i.e. F * coat * coatColor. Otherwise the lobe contribution will be + // too small. + result /= coatLuminance; + + // Assign lobe ID. + lobeID = COAT_LOBE; +#endif + } + else + { + // Remap the random variable to 0-1 outside coat lobe. + random.x = remapFail(random.x, coatLuminance); + + // Compute specular alpha from roughness and anisotropy. + float2 specAlpha = + mx_roughness_anisotropy(material.specularRoughness, material.specularAnisotropy); + + // Sample metal lobe. + if (random.x < material.metalness) + { + // Remap the random variable to 0-1 within metal lobe. + random.x = remapPass(random.x, material.metalness); + + // Generate light direction and half vector. + // TODO: Use rotated tangent. + L = calculateGGXLightDirectionAndPDF(X, Y, N, V, specAlpha, random, Lw, pdf); + + // Compute metallic color. + float3 metal_reflectivity = material.metalColor * material.base; + float3 metal_edgecolor = material.specularColor * material.specular; + result = mx_conductor_brdf_reflection(Lw, Vw, 1.0f, metal_reflectivity, metal_edgecolor, + main_roughness, N, main_tangent, 0); + + // Remove the N.L term when sampling (which comes from mx_ggx_smith_G), to avoid + // applying it twice. + result /= abs(L.z); + + // Set lobe ID. + lobeID = METAL_LOBE; + } + else + { + // Remap the random variable to 0-1 outside metal lobe. + random.x = remapFail(random.x, material.metalness); + + // Evaluate the specular Fresnel component for BSDF. + float F0 = mx_ior_to_f0(material.specularIOR); + float3 F = mx_fresnel_schlick(NdotV, F0); + + // Compute luminance specular Fresnel component for BSDF. + float specLuminance = luminance(F * material.specular * material.specularColor); + + // Sample glossy lobe. + if (random.x < specLuminance) + { + // Remap the random variable to 0-1 within metal lobe. + random.x = remapPass(random.x, specLuminance); + + // Generate light direction and half vector. + L = calculateGGXLightDirectionAndPDF(X, Y, N, V, specAlpha, random, Lw, pdf); + + // Compute specular color. + result = mx_dielectric_brdf_specular_layer(Lw, Vw, + material.specularColor * material.specular, material.specularIOR, + main_roughness, shading.normal, main_tangent, 0); + + // Normalize the result with the probability of having selected this lobe. This is + // needed for this lobe because the result is inherently scaled by a similar amount + // above, i.e. F * specular * specularColor. Otherwise the lobe contribution will be + // too small. + result /= specLuminance; + + // Set lobe ID. + lobeID = GLOSSY_LOBE; + } + else + { + // Remap the random variable to 0-1 within metal lobe. + random.x = remapFail(random.x, specLuminance); + + // Alpha is just roughness clamped to epsilon-1.0 range (to avoid NaN) + float sheenAlpha = clamp(material.sheenRoughness, M_FLOAT_EPS, 1.0); + + // Sample sheen node. + float sheen = ENABLE_COAT ? material.sheen : 0.0; + if (random.x < material.sheen) + { +#if ENABLE_SHEEN + // Remap the random variable to 0-1 within sheen lobe. + random.x = remapPass(random.x, sheen); + + // Compute light direction within hemisphere. + L = calculateHemisphereLightDirection(X, Y, N, V, random, pdf); + Lw = tangentToWorld(L, X, Y, N); + + // Compute half-vector. + float3 H = normalize(L + V); + + // Compute sheen color. + result = mx_sheen_brdf_specular_layer( + Lw, Vw, material.sheen, material.sheenColor, material.sheenRoughness, N); + + // Compute probability density function as sheen normal distribution function. + // Uses NDF directly without scaling by cosine values. + pdf = mx_imageworks_sheen_NDF(H.z, sheenAlpha); + + // Set lobe ID. + lobeID = SHEEN_LOBE; +#endif + } + else + { + random.x = remapFail(random.x, sheen); + float transmission = ENABLE_TRANSMISSION ? material.transmission : 0.0; + if (random.x < transmission) + { +#if ENABLE_TRANSMISSION + // Remap the random variable (pass case). + random.x = remapPass(random.x, transmission); + + // Set the sampled light direction to the opposite of the view direction, + // which is appropriate for thin-walled materials. If this is not a + // thin-walled material, compute a refraction direction, or a reflection + // direction if total internal reflection occurs. + // NOTE: A ternary operator should not be used here as HLSL evaluates both + // results, and computeRefraction() has side-effects. + bool reflected = false; + L = -V; + if (!material.thinWalled) + { + L = computeRefraction(V, material.specularIOR, reflected); + } + Lw = tangentToWorld(L, X, Y, N); + + // The BSDF is simply the transmission color. Since this is a delta + // distribution (specular), it must be divided by the cosine term, and the + // PDF is simply 1.0. + // NOTE: See PBRT 8.2.2 for information on delta distributions. + result = material.transmissionColor; + result /= abs(L.z); + pdf = 1.0f; + + // Set the lobe ID. + // NOTE: Total internal reflection is not actually glossy, but a more + // specific state is not required here. + lobeID = reflected ? GLOSSY_LOBE : TRANSMISSION_LOBE; +#endif + } + else + { + // Remap the random variable to 0-1 outside transmission. + random.x = remapFail(random.x, transmission); + + // Compute light direction within hemisphere. + L = calculateHemisphereLightDirection(X, Y, N, V, random, pdf); + Lw = tangentToWorld(L, X, Y, N); + + // Compute diffuse color. + result = mx_dielectric_brdf_diffuse_layer(Lw, Vw, material.base, + material.specularIOR, main_roughness, N, main_tangent, 0, + coat_affected_diffuse_color); + + // Apply sheen direction albedo to diffuse color. + float dirAlbedo = mx_imageworks_sheen_directional_albedo(NdotV, sheenAlpha); + result *= (1.0 - + dirAlbedo * + material.sheen); // Base layer reflection attenuated by top layer + + // Set lobe ID. + lobeID = DIFFUSE_LOBE; + } + } + } + } + + // Apply coat attenuation factor to result. + float3 coat_attenuation = lerp(WHITE, material.coatColor, material.coat); + result *= coat_attenuation; + } + + // Multiply the N.L cosine term to final value. + float NdotL = abs(L.z); + + return result * NdotL; +} + +#endif // __STANDARD_SURFACE_H__ diff --git a/Libraries/Aurora/Source/Transpiler.cpp b/Libraries/Aurora/Source/Transpiler.cpp new file mode 100644 index 0000000..6343493 --- /dev/null +++ b/Libraries/Aurora/Source/Transpiler.cpp @@ -0,0 +1,238 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pch.h" + +#include "Transpiler.h" +#include + +BEGIN_AURORA + +// Slang UUID compare operator, needed by casting operations. +bool operator==(const SlangUUID& aIn, const SlangUUID& bIn) +{ + // Use the largest type the honors the alignment of Guid + typedef uint32_t CmpType; + union GuidCompare + { + SlangUUID guid; + CmpType data[sizeof(SlangUUID) / sizeof(CmpType)]; + }; + // Type pun - so compiler can 'see' the pun and not break aliasing rules + const CmpType* a = reinterpret_cast(aIn).data; + const CmpType* b = reinterpret_cast(bIn).data; + // Make the guid comparison a single branch, by not using short circuit + return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2]) | (a[3] ^ b[3])) == 0; +} + +// Slang string blob. Simple wrapper around std::string that can be used by Slang compiler. +struct StringSlangBlob : public ISlangBlob, ISlangCastable +{ + StringSlangBlob(const string& str) : _str(str) {} + virtual ~StringSlangBlob() = default; + + // Get a pointer to the string. + virtual SLANG_NO_THROW void const* SLANG_MCALL getBufferPointer() override + { + return _str.data(); + } + + // Get the length of the string. + virtual SLANG_NO_THROW size_t SLANG_MCALL getBufferSize() override { return _str.length(); } + + // Default queryInterface implementation for Slang type system. + virtual SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface( + SlangUUID const& uuid, void** outObject) SLANG_OVERRIDE + { + *outObject = getInterface(uuid); + + return *outObject ? SLANG_OK : SLANG_E_NOT_IMPLEMENTED; + } + + // Do not implement ref counting, just return 1. + virtual SLANG_NO_THROW uint32_t SLANG_MCALL addRef() SLANG_OVERRIDE { return 1; } + virtual SLANG_NO_THROW uint32_t SLANG_MCALL release() SLANG_OVERRIDE { return 1; } + + // Default castAs implementation for Slang type system. + void* castAs(const SlangUUID& guid) override { return getInterface(guid); } + + // Allow casting as Unknown and Blob, but nothing else. + void* getInterface(const SlangUUID& uuid) + { + if (uuid == ISlangUnknown::getTypeGuid() || uuid == ISlangBlob::getTypeGuid()) + { + return static_cast(this); + } + + return nullptr; + } + + // The actual string data. + const string& _str; +}; + +// Slang filesystem that reads from a simple lookup table of strings. +struct AuroraSlangFileSystem : public ISlangFileSystem +{ + AuroraSlangFileSystem(const std::map& fileText) : + _fileText(fileText) + { + } + virtual ~AuroraSlangFileSystem() = default; + + virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadFile( + char const* path, ISlangBlob** outBlob) override + { + // Is if we already have blob for this path, return that. + auto iter = _fileBlobs.find(path); + if (iter != _fileBlobs.end()) + { + *outBlob = iter->second.get(); + + return SLANG_OK; + } + + // Read from the text file map. + auto shaderIter = _fileText.find(path); + if (shaderIter != _fileText.end()) + { + // Create a blob from the text file string, add to the blob map, and return it. + _fileBlobs[path] = make_unique(shaderIter->second); + *outBlob = _fileBlobs[path].get(); + return SLANG_OK; + } + + return SLANG_FAIL; + } + + // Slang type interface not needed, just return null. + virtual SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface( + SlangUUID const& /* uuid*/, void** /* outObject*/) SLANG_OVERRIDE + { + return SLANG_E_NOT_IMPLEMENTED; + } + + // Do not implement ref counting, just return 1. + virtual SLANG_NO_THROW uint32_t SLANG_MCALL addRef() SLANG_OVERRIDE { return 1; } + virtual SLANG_NO_THROW uint32_t SLANG_MCALL release() SLANG_OVERRIDE { return 1; } + + // Slang type interface not needed, just return null. + void* castAs(const SlangUUID&) override { return nullptr; } + + // Set a string directly as blob in file blobs map. + void setSource(const string& name, const string& code) + { + _fileBlobs[name] = make_unique(code); + } + + // Map of string blobs. + map> _fileBlobs; + + // Map of file strings. + const std::map& _fileText; +}; + +Transpiler::Transpiler(const std::map& fileText) +{ + _pFileSystem = make_unique(fileText); + _pSession = spCreateSession(); +} + +Transpiler::~Transpiler() +{ + _pSession->release(); +} + +void Transpiler::setSource(const string& name, const string& code) +{ + _pFileSystem->setSource(name, code); +} + +bool Transpiler::transpileCode( + const string& shaderCode, string& codeOut, string& errorOut, Language target) +{ + // Dummy file name to use as container for shader code. + const string codeFileName = "__shaderCode"; + + // Set the shader code "file". + setSource(codeFileName, shaderCode); + + // Transpile the shader code. + bool res = transpile(codeFileName, codeOut, errorOut, target); + + // Clear the shader source to release memory. + setSource(codeFileName, ""); + + return res; +} + +bool Transpiler::transpile( + const string& shaderName, string& codeOut, string& errorOut, Language target) +{ + // Clear result. + errorOut.clear(); + codeOut.clear(); + + // Create a Slang compile request for transpilation. + slang::ICompileRequest* pRequest; + [[maybe_unused]] const int reqIndex = _pSession->createCompileRequest(&pRequest); + + // Set the file system and compile fiags. + pRequest->setFileSystem(_pFileSystem.get()); + pRequest->setCompileFlags(SLANG_COMPILE_FLAG_NO_MANGLING); + + // Create code gen target (with GLSL or HLSL language as required). + const int targetIndex = + pRequest->addCodeGenTarget(target == Language::GLSL ? SLANG_GLSL : SLANG_HLSL); + + // Set target flags to generate whole program. + pRequest->setTargetFlags(targetIndex, SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM); + // TODO: The buffer layout might be an issue, need to work out correct flags. + // pRequest->setTargetForceGLSLScalarBufferLayout(targetIndex, true); + + // At add translation unit from Slang to target language. + const int translationUnitIndex = + pRequest->addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, nullptr); + + // Use standard line directives (with filename) + pRequest->setTargetLineDirectiveMode(targetIndex, SLANG_LINE_DIRECTIVE_MODE_STANDARD); + // Use column major matrix format. + pRequest->setTargetMatrixLayoutMode(targetIndex, SLANG_MATRIX_LAYOUT_COLUMN_MAJOR); + + // Set shader name as source file name (file system will look up it up from file text map). + pRequest->addTranslationUnitSourceFile(translationUnitIndex, shaderName.c_str()); + + // Set DIRECTX preprocessor directive. + pRequest->addPreprocessorDefine("DIRECTX", target == Language::GLSL ? "0" : "1"); + + // Transpile the file. + const SlangResult compileRes = pRequest->compile(); + if (compileRes != SLANG_OK) + { + errorOut = pRequest->getDiagnosticOutput(); + return false; + } + + // Get blob for result. + ISlangBlob* pOutBlob = nullptr; + pRequest->getTargetCodeBlob(targetIndex, &pOutBlob); + codeOut = (const char*)pOutBlob->getBufferPointer(); + + // Release request. + pRequest->release(); + + return true; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/Transpiler.h b/Libraries/Aurora/Source/Transpiler.h new file mode 100644 index 0000000..d1db936 --- /dev/null +++ b/Libraries/Aurora/Source/Transpiler.h @@ -0,0 +1,54 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" +namespace slang +{ +struct IGlobalSession; +} +BEGIN_AURORA + +struct AuroraSlangFileSystem; + +class Transpiler +{ +public: + // Target language enum. + enum Language + { + HLSL, + GLSL + }; + + // Ctot take a map of file text strings. + Transpiler(const std::map& fileText); + ~Transpiler(); + + // Transpile a file with given name (looked up from map of file text strings) + bool transpile(const string& shaderName, string& codeOut, string& errorOut, Language target); + + // Transpile a string containing shader code. + bool transpileCode( + const string& shaderCode, string& codeOut, string& errorOut, Language target); + + // Set a source file in the file text string map. + void setSource(const string& name, const string& code); + +private: + unique_ptr _pFileSystem; + slang::IGlobalSession* _pSession; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/WindowsHeaders.h b/Libraries/Aurora/Source/WindowsHeaders.h new file mode 100644 index 0000000..78175e5 --- /dev/null +++ b/Libraries/Aurora/Source/WindowsHeaders.h @@ -0,0 +1,116 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// Windows headers. +#define WIN32_LEAN_AND_MEAN +#include + +// Include DirectX-specific headers and defines. +#if defined(AU_DIRECTX) + +// DirectX 12 headers. +#include +#include +#include +#include +#include +#include +#include + +// Microsoft Active Template library +#include + +// DirectX 12 smart pointers. +// NOTE: ComPtr is recommended. See https://github.com/Microsoft/DirectXTK/wiki/ComPtr. +#include +using Microsoft::WRL::ComPtr; +#define MAKE_DX_PTR(_x) using _x##Ptr = ComPtr<_x> +MAKE_DX_PTR(ID3D12CommandAllocator); +MAKE_DX_PTR(ID3D12CommandQueue); +MAKE_DX_PTR(ID3D12Debug); +MAKE_DX_PTR(ID3D12DescriptorHeap); +MAKE_DX_PTR(ID3D12Device5); +MAKE_DX_PTR(ID3D12Fence); +MAKE_DX_PTR(ID3D12GraphicsCommandList4); +MAKE_DX_PTR(ID3D12InfoQueue); +MAKE_DX_PTR(ID3D12PipelineState); +MAKE_DX_PTR(ID3D12Resource); +MAKE_DX_PTR(ID3D12RootSignature); +MAKE_DX_PTR(ID3D12StateObject); +MAKE_DX_PTR(ID3D12StateObjectProperties); +MAKE_DX_PTR(ID3DBlob); +MAKE_DX_PTR(IDXGIAdapter1); +MAKE_DX_PTR(IDXGIDebug1); +MAKE_DX_PTR(IDXGIFactory4); +MAKE_DX_PTR(IDXGIInfoQueue); +MAKE_DX_PTR(IDXGISwapChain1); +MAKE_DX_PTR(IDXGISwapChain3); + +// Short names for relevant DirectX 12 constants. +// NOTE: A shader record must be aligned on the byte boundary of the size specified here. Within the +// shader record, the arguments must likewise be aligned by their respective size, e.g. a root +// descriptor (or descriptor table handle) must be aligned on an 8-byte boundary. +static const size_t SHADER_ID_SIZE = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; +static const size_t SHADER_RECORD_ALIGNMENT = D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT; +static const size_t SHADER_RECORD_CONSTANT_SIZE = 4; // defined by DirectX 12 +static const size_t SHADER_RECORD_DESCRIPTOR_SIZE = 8; // defined by DirectX 12 + +#if defined(ENABLE_DENOISER) +// NVIDIA Real-time Denoisers (NRD) and NRI libraries. +#include +#include +#include + +// NVIDIA NRI helper interface. +// NOTE: Must be included after NRI, but before NRD integration. +#include +#include +#include + +// NVIDIA NRD integration utility. +// NOTE: NRI must be included before this. +#include +#endif + +#endif + +// Converts an HRESULT to a string for error reporting. +inline std::string hresultToString(HRESULT hr) +{ + char str[64] = {}; + ::sprintf_s(str, "HRESULT of 0x%08X", static_cast(hr)); + + return std::string(str); +} + +// An exception for failed HRESULT values. +class HRException : public std::runtime_error +{ +public: + HRException(HRESULT hr) : std::runtime_error(::hresultToString(hr)), _hr(hr) {} + HRESULT error() const { return _hr; } + +private: + const HRESULT _hr; +}; + +// Checks an HRESULT value and throws an exception if a failed HRESULT is provided. +inline void checkHR(HRESULT hr) +{ + if (FAILED(hr)) + { + throw HRException(hr); + } +} diff --git a/Libraries/Aurora/Source/pch.h b/Libraries/Aurora/Source/pch.h new file mode 100644 index 0000000..65e80a8 --- /dev/null +++ b/Libraries/Aurora/Source/pch.h @@ -0,0 +1,142 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// STL headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// GLM - OpenGL Mathematics. +// NOTE: This is a math library, and not specific to OpenGL. +#define GLM_FORCE_CTOR_INIT +#pragma warning(push) +#pragma warning(disable : 4201) // nameless struct/union +#include +#include +#include +#include +#pragma warning(pop) +using namespace glm; + +// Aurora headers. +#include +#include +#include +#include +using namespace Aurora; + +// Define the module export symbol, before including local declarations. +#if defined(WIN32) +#define AURORA_API __declspec(dllexport) +#else +#define AURORA_API __attribute__((visibility("default"))) +#endif + +#if !defined(WIN32) +using UINT = unsigned int; +#define CONST const +#define MAX_PATH 2048 +#endif + +// Namespace symbols. +#define BEGIN_AURORA \ + namespace Aurora \ + { +#define END_AURORA } + +// Utility macros. +// NOTE: In general STL smart pointers and arrays / vectors should be used instead of raw pointers +// and C arrays. +#define SAFE_DELETE(p) \ + { \ + if ((p) != nullptr) \ + { \ + delete (p); \ + (p) = nullptr; \ + } \ + } +#define SAFE_DELETE_ARRAY(p) \ + { \ + if ((p) != nullptr) \ + { \ + delete[](p); \ + (p) = nullptr; \ + } \ + } +#define ALIGNED_SIZE(size, alignment) ((size / alignment + 1) * alignment) +#define ALIGNED_OFFSET(offset, alignment) (alignment - offset % alignment) + +// Platform-specific headers. +#if defined(__APPLE__) +// macOS-specific headers. +// (none yet) +#elif defined(_WIN32) +// Windows-specific headers. +#include "WindowsHeaders.h" +#if DIRECTX_SUPPORT +#include "DirectX/DirectXHeaders.h" +#endif +#endif + +#if HGI_SUPPORT +#pragma warning(push) +#pragma warning(disable : 4244) // disabling type conversion warnings in USD +#pragma warning(disable : 4305) // disabling type truncation warnings in USD +#pragma warning(disable : 4127) // disabling const comparison warnings in USD +#pragma warning(disable : 4201) // disabling nameless struct warnings in USD +#pragma warning(disable : 4100) // disabling unreferenced parameter warnings in USD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +#endif + +using namespace std; + +// Include the entire Aurora library, a single header file. +// NOTE: This is included here for convenience. If the library is split into multiple header files, +// this should be removed and the relevant header files included where they are needed, e.g. +#include diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt new file mode 100644 index 0000000..8c75a4e --- /dev/null +++ b/Libraries/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(Foundation) +add_subdirectory(Aurora) +add_subdirectory(HdAurora) diff --git a/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h b/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h new file mode 100644 index 0000000..07cf827 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h @@ -0,0 +1,148 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// NOTE: This file requires the use of glm, the vector math library. + +#include +#include + +namespace Aurora +{ +namespace Foundation +{ + +// A class template representing an axis-aligned bounding box (AABB) with a min point and max point. +template +class BoundingBoxT +{ +public: + //*** Lifetime Management *** + + /// Constructor. + BoundingBoxT() = default; + + /// Constructor with min / max points. + BoundingBoxT(const VEC3& min, const VEC3& max) : _min(min), _max(max) {} + + //*** Functions *** + + /// Gets the minimum point of the bounding box. + const VEC3& min() const { return _min; } + + /// Gets the maximum point of the bounding box. + const VEC3& max() const { return _max; } + + /// Gets whether the bounding box is valid. + bool isValid() const { return _min.x <= _max.x && _min.y <= _max.y && _min.z <= _max.z; } + + /// Gets whether the bounding box is a proper 3D volume.. + bool isVolume() const { return _min.x < _max.x && _min.y < _max.y && _min.z < _max.z; } + + /// Resets the bounding box to its initial state. + void reset() + { + _min = VEC3(INFINITY); + _max = VEC3(-INFINITY); + } + + /// Adds the specified bounds to the bounding box. + void add(const BoundingBoxT& bounds) + { + add(bounds.min()); + add(bounds.max()); + } + + /// Adds the specified pointer to the bounding box, expanding it if needed. + void add(const VEC3& vec) { add(vec.x, vec.y, vec.z); } + + /// Adds a point with the specified coordinates to the bounding box, expanding it if needed. + void add(T x, T y, T z) + { + _min.x = glm::min(x, _min.x); + _min.y = glm::min(y, _min.y); + _min.z = glm::min(z, _min.z); + _max.x = glm::max(x, _max.x); + _max.y = glm::max(y, _max.y); + _max.z = glm::max(z, _max.z); + } + + /// Adds an array of positions (XYZ) to the bounding box, expanding it if needed. + void add(const T* pPositions, uint32_t positionCount = 1) + { + AU_ASSERT(pPositions && positionCount > 0, "Invalid arguments for BoundingBox::Add()."); + + for (uint32_t i = 0; i < positionCount; i++) + { + add(pPositions[0], pPositions[1], pPositions[2]); + pPositions += 3; + } + } + + /// Gets the center point of the bounding box. + VEC3 center() const { return _min + dimensions() * T(0.5); } + + /// Gets the dimensions of the bounding box. + VEC3 dimensions() const { return _max - _min; } + + /// Gets the spherical radius. + T radius() const + { + auto d = dimensions() * T(0.5); + auto emax = std::max(std::max(d.x, d.y), d.z); + return length(VEC3(emax, emax, emax)); + } + + /// Gets a bounding box corner with the specified index (0 - 7). + /// + /// \note Index 0 is the min point, and index 7 is the max point. + VEC3 getCorner(uint32_t index) const + { + VEC3 result; + result.x = (index & 1) == 0 ? _min.x : _max.x; + result.y = (index & 2) == 0 ? _min.y : _max.y; + result.z = (index & 4) == 0 ? _min.z : _max.z; + + return result; + } + + /// Transforms the corners of the bounding box with the specified matrix and adds them to a new + /// bounding box. + BoundingBoxT transform(const MATRIX& transform, bool pdiv = false) const + { + BoundingBoxT result; + for (int i = 0; i < 8; i++) + { + VEC4 hp = transform * VEC4(getCorner(i), 1.0); + result.add(pdiv ? (VEC3(hp) / hp.w) : VEC3(hp)); + } + + return result; + } + +private: + /*** Private Variables ***/ + + VEC3 _min = VEC3(INFINITY); + VEC3 _max = VEC3(-INFINITY); +}; + +/// A class representing a single precision bounding box. +using BoundingBox = BoundingBoxT; + +/// A class representing a double precision bounding box. +using BoundingBoxDbl = BoundingBoxT; + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/API/Aurora/Foundation/Frustum.h b/Libraries/Foundation/API/Aurora/Foundation/Frustum.h new file mode 100644 index 0000000..3bb5be6 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/Frustum.h @@ -0,0 +1,141 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// NOTE: This file requires the use of glm, the vector math library. + +#include +#include +#include +#include +#include + +//#define USE_SPHERICAL_BOUNDS 1 + +namespace Aurora +{ +namespace Foundation +{ + +// A class representing a view frustum. +template +class FrustumT +{ +public: + //*** Lifetime Management *** + + enum Boundary + { + Left, + Right, + Bottom, + Top, + Near, + Far + }; + + /// Constructors. + FrustumT() = default; + + /// Initialize frustum planes from a matrix. + FrustumT(const MATRIX& mat) { setFrom(mat); } + + /// Extract frustum planes from a model-view-projection matrix using the Gribb/Hartmann method. + /// The planes are defined in the source coordinate system of the matrix. + /// Assumes column-major matrix layout. + /// http://www8.cs.umu.se/kurser/5DV051/HT12/lab/plane_extraction.pdf + void setFrom(const MATRIX& mat) + { + _planes[Left] = row(mat, 3) + row(mat, 0); + _planes[Right] = row(mat, 3) - row(mat, 0); + _planes[Bottom] = row(mat, 3) + row(mat, 1); + _planes[Top] = row(mat, 3) - row(mat, 1); + _planes[Near] = row(mat, 2); // Z is in the range of [0..1]. + _planes[Far] = row(mat, 3) - row(mat, 2); + + for (auto& plane : _planes) + plane.normalize(); + } + + /// Returns true if the point is inside the view frustum. + bool contains(const VEC3& point, bool farClip = false) const + { + size_t plnCount = farClip ? _planes.size() : _planes.size() - 1; + for (size_t i = 0; i < plnCount; i++) + if (_planes[i].classify(point) == Halfspace::Negative) + return false; + + // The point is inside the frustum. + return true; + } + + /// Returns true if the bounding box intersects the view frustum. + bool intersects(const BBOX& box, bool farClip = false) const + { + size_t plnCount = farClip ? _planes.size() : _planes.size() - 1; + for (size_t i = 0; i < plnCount; i++) + { +#if defined(USE_SPHERICAL_BOUNDS) + if (_planes[i].distanceTo(box.center()) < -box.radius()) + ; + return false; +#else + if (_planes[i].inLower(box)) + return false; +#endif + } + + // The box intersects the frustum. + return true; + } + + /// Returns true if the bounding box is completely inside the view frustum. + bool contains(const BBOX& box, bool farClip = false) const + { + size_t plnCount = farClip ? _planes.size() : _planes.size() - 1; + for (int i = 0; i < plnCount; i++) + { +#if defined(USE_SPHERICAL_BOUNDS) + double d = _planes[i].distanceTo(box.center()); + if (d < 0.0 || d < box.radius()) + return false; +#else + if (!_planes[i].inUpper(box)) + return false; +#endif + } + + // The box is inside the frustum. + return true; + } + +private: + // Row element access. + VEC4 row(const MATRIX& m, unsigned int index) const + { + return VEC4(m[0][index], m[1][index], m[2][index], m[3][index]); + } + + /*** Private Variables ***/ + std::array _planes; +}; + +/// A class representing a single precision view frustum. +using Frustum = FrustumT; + +/// A class representing a double precision view frustum. +using FrustumDbl = FrustumT; + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/API/Aurora/Foundation/Log.h b/Libraries/Foundation/API/Aurora/Foundation/Log.h new file mode 100644 index 0000000..ba80784 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/Log.h @@ -0,0 +1,331 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include +#include + +// undefine snprintf (which is defined to _snprintf on some systems) +#ifdef snprintf +#undef snprintf +#endif + +// Event logging is support by a set of macros defined here, all of which have the "AU_" prefix. +// The Log class and associated singleton object can be used to configure logging behavior. The log +// is not for providing messages to end users; any messages are intended only for other developers. +// The log macros and their intended use are described below and in the coding guidelines: +// +// AU_INFO: Use this to provide diagnostic information to developers. For example, progress reports +// or the results of successful file operations. +// +// AU_WARN: Use this to indicate to developers that a requested operation succeeded, but that some +// undesirable result may have occurred. For example, a resource exceeding a recommended size in +// memory. +// +// AU_ERROR: Use this to indicate to developers that a requested operation could not be completed +// as requested. For example, a requested data entry or file was not found and therefore could not +// be processed. This is usually followed by some form of error handling. +// +// AU_FAIL: In addition to reporting a message, this also aborts the application. Use this to +// indicate a catastrophic failure, due to an unhandled programming error. For example, a switch +// statement reaches an unexpected case. User action should not intentionally result in a failure. +// +// AU_ASSERT: This accepts a test condition that is expected to be true. If the condition evaluates +// to false, the failure is reported and the application is immediately aborted as with AU_FAIL. By +// convention, the test condition must not produce side effects. Use this throughout the code to +// ensure expected conditions are met, with any failures being the result of unhandled programming +// errors. +// +// AU_ASSERT_DEBUG: This is identical to AU_ASSERT except that it only executes in debug +// builds. Use this in performance-critical sections of the code, where it would be undesirable to +// test conditions in release builds. This should be used rarely, preferring AU_ASSERT. +// +// AU_DEBUG_BREAK: Use this to break into the attached debugger, if any. This only works in debug +// builds. + +// Set the ADD_FILE_AND_LINE_TO_LOGS symbol to 0 to remove file and line information from log +// output. +#if !defined(ADD_FILE_AND_LINE_TO_LOGS) +#define ADD_FILE_AND_LINE_TO_LOGS 1 +#endif + +// Only include file and line information in log output if ADD_FILE_AND_LINE_TO_LOGS is set +// to 1. +#if ADD_FILE_AND_LINE_TO_LOGS +#define AU_FILE __FILE__ +#define AU_LINE __LINE__ +#else +#define AU_FILE "" +#define AU_LINE 0 +#endif + +/// Triggers an information event, which logs a message along with the current file and line. +#define AU_INFO(_msg, ...) Aurora::Foundation::Log::info(AU_FILE, AU_LINE, _msg, ##__VA_ARGS__) + +/// Triggers a warning event, which logs a message along with the current file and line. +#define AU_WARN(_msg, ...) Aurora::Foundation::Log::warn(AU_FILE, AU_LINE, _msg, ##__VA_ARGS__) + +/// Triggers an error event, which logs a message along with the current file and line. +#define AU_ERROR(_msg, ...) Aurora::Foundation::Log::error(AU_FILE, AU_LINE, _msg, ##__VA_ARGS__) + +/// Triggers a failure event, which logs a message along with the current file and line. +/// +/// This is considered a catastrophic failure and will abort the application. +#define AU_FAIL(_msg, ...) Aurora::Foundation::Log::fail(AU_FILE, AU_LINE, _msg, ##__VA_ARGS__) + +/// Tests the _check condition and triggers a failure event if it is false. +#define AU_ASSERT(_check, _msg, ...) \ + do \ + { \ + if (!(_check)) \ + { \ + Aurora::Foundation::Log::fail(AU_FILE, AU_LINE, \ + "AU_ASSERT test failed:\nEXPRESSION: %s\nDETAILS: " _msg, #_check, ##__VA_ARGS__); \ + } \ + } while (false) + +/// Tests the _check condition and triggers a failure event if it is false. +/// +/// This is completely removed in non-debug builds, so this is intended for checks in +/// performance sensitive situations. +#if defined NDEBUG +#define AU_ASSERT_DEBUG(_check, _msg, ...) \ + do \ + { \ + } while (false) +#else +#define AU_ASSERT_DEBUG(_check, _msg, ...) AU_ASSERT(_check, _msg, ##__VA_ARGS__) +#endif + +/// Breaks into the attached debugger, if any. +/// +/// This does nothing in non-debug builds. +#define AU_DEBUG_BREAK() Aurora::Foundation::Log::debugBreak(); + +namespace Aurora +{ +namespace Foundation +{ + +/// A class which manages event logging. +class Log +{ +public: + /*** Types ***/ + + /// An enumeration of event levels. + /// + /// Setting a specific level means all higher levels are included, e.g. setting kError + /// means error and failure (kFail) events are logged. + enum class Level + { + kInfo = 0, // information events + kWarn, // warning events + kError, // error events + kFail, // failure events, including assertion failures + kNone // disable logging + }; + + /// A callback function type for custom logging functionality. + /// + /// The function accepts the file and line number where the event occurred, as well as + /// the event level and message. + using CBFunction = + std::function; + + /*** Functions ***/ + + /// Sets a custom logging callback function. + void setLogFunction(CBFunction cb) { _logCB = cb; } + + /// Sets the minimum log level. + // + /// Events with this level or higher will be logged. + void setLogLevel(Level level) { _logLevel = level; } + + /// Enables displaying a dialog box on failure events. + void enableFailureDialog(bool enabled) { _failureDialogEnabled = enabled; } + + /// Performs a catastrophic abort, with an optional failure dialog to cancel the abort. + template + void abort(const std::string& file, int line, const std::string& format, Args... args) + { + // Abort the application if _failureMessageBoxEnabled is false or displayFailureDialog() + // returns true. + if (!_failureDialogEnabled || + displayFailureDialog(file, line, stringFormat(format, args...))) + { + ::abort(); + } + } + + /// Logs event information, as used by the other log functions. + template + bool log(Level messageLevel, std::ostream& stream, const std::string& file, int line, + const std::string& format, Args... args) + { + // Do nothing if the current log level is higher than level for this message. + if (_logLevel > messageLevel) + { + return false; + } + + // Format the message with the variable arguments. + std::string formattedMsg = stringFormat(format, args...); + + // Do nothing if the log callback returns false. + if (_logCB && !_logCB(file, line, messageLevel, formattedMsg)) + { + return false; + } + + // Get the file and line prefix for non-zero lines and the expanded format string. + std::string prefix = line > 0 ? (file + " (" + std::to_string(line) + "):\t") : ""; + std::string msg = prefix + formattedMsg; + + // Output to the debug console and the specified output stream. + writeToConsole(msg); + stream << msg; + + return true; + } + + /*** Static Functions ***/ + + /// Gets the singleton logger for the application. + static Log& logger(); + + /// Logs information event output from the application. + /// + /// \note Most code should use the AU_INFO macro, instead of calling this directly. + template + static bool info(const std::string& file, int line, const std::string& format, Args... args) + { + return logger().log(Level::kInfo, std::cout, file, line, format, args...); + } + + /// Logs warning event output from the application. + /// + /// \note Most code should use the AU_WARN macro, instead of calling this directly. + template + static bool warn(const std::string& file, int line, const std::string& format, Args... args) + { + return logger().log(Level::kWarn, std::cerr, file, line, format, args...); + } + + /// Logs error event output from the application. + /// + /// \note Most code should use the AU_ERROR macro, instead of calling this directly. + template + static bool error(const std::string& file, int line, const std::string& format, Args... args) + { + return logger().log(Level::kError, std::cerr, file, line, format, args...); + } + + /// Logs failure event output and terminates the application. + /// + /// \note Most code should use the AU_FAIL macro, instead of calling this directly. + template + static bool fail(const std::string& file, int line, const std::string& format, Args... args) + { + if (logger().log(Level::kFail, std::cerr, file, line, format, args...)) + { + logger().abort(file, line, format, args...); + + return true; + } + + return false; + } + + /// Breaks into the attached debugger, if any. + /// + /// This does nothing in non-debug builds. + static void debugBreak(); + + // Writes a string to the console. + static void writeToConsole(const std::string& msg); + +private: + /*** Private Functions ***/ + + /// Creates a string from variable arguments. + template + std::string stringFormat(const std::string& format, Args... args) + { + // Disable the Clang security warning. + // TODO: Implement a better workaround for this warning. +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" +#endif + // Calculate the size of the expanded format string. + const char* formatStr = format.c_str(); + int size = std::snprintf(nullptr, 0, formatStr, args...) + 2; + if (size <= 0) + { + // Report an error if formating fails. + std::cerr << __FUNCTION__ << " Error during formatting." << std::endl; + + return ""; + } + + // Allocate a buffer for the formatted string and place the expanded string in it. + std::vector buf(size); + std::snprintf(buf.data(), size, formatStr, args...); +#if __clang__ +#pragma clang diagnostic pop +#endif + + // Add a newline if there is not already one at the end, and terminate the string. + size_t offset = static_cast(size); + if (buf[offset - 3] != '\n') + { + buf[offset - 2] = '\n'; + buf[offset - 1] = 0; + } + else + { + buf[offset - 2] = 0; + } + + // Return a new string object from the buffer. + // TODO: Avoid multiple allocations. + return std::string(buf.data(), buf.data() + size - 1); + }; + + // Displays a failure dialog box. + bool displayFailureDialog(const std::string& file, int line, const std::string& msg); + + /*** Private Variables ***/ + + // The current log callback. + CBFunction _logCB; + + // The current log level. + Level _logLevel = Level::kInfo; + + // Whether to display a dialog box on failure events. + // NOTE: Defaults to true on debug builds, false otherwise. +#if defined NDEBUG + bool _failureDialogEnabled = false; +#else + bool _failureDialogEnabled = true; +#endif +}; + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/API/Aurora/Foundation/Plane.h b/Libraries/Foundation/API/Aurora/Foundation/Plane.h new file mode 100644 index 0000000..02f1fd7 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/Plane.h @@ -0,0 +1,120 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +// NOTE: This file requires the use of glm, the vector math library. + +#include +#include +#include + +namespace Aurora +{ +namespace Foundation +{ + +enum class Halfspace : int +{ + Negative = -1, + OnPlane = 0, + Positive = 1 +}; + +// A class template representing a plane. +template +class PlaneT +{ +public: + //*** Lifetime Management *** + + /// Constructors. + PlaneT() = default; + + /// Define plane from equation coefficients. + PlaneT(const VEC4& coeff) : _planeq(coeff) { normalize(); } + + /// Define plane from three points. + PlaneT(const VEC3& A, const VEC3& B, const VEC3& C) + { + // clang-format off + auto a = (B.y - A.y)*(C.z - A.z) - (C.y - A.y)*(B.z - A.z); + auto b = (B.z - A.z)*(C.x - A.x) - (C.z - A.z)*(B.x - A.x); + auto c = (B.x - A.x)*(C.y - A.y) - (C.x - A.x)*(B.y - A.y); + auto d = -(a * A.x + b * A.y + c * A.z); + // clang-format on + _planeq = VEC4(a, b, c, d); + normalize(); + } + + T& operator[](unsigned int index) { return _planeq[index]; } + + /// Return the signed distance to the point. + T distanceTo(const VEC3& point) const { return glm::dot(VEC3(_planeq), point) + _planeq.w; } + + /// Classify the point in the halfspace. + Halfspace classify(const VEC3& point) const + { + T d = distanceTo(point); + if (d < 0) + return Halfspace::Negative; + if (d > 0) + return Halfspace::Positive; + return Halfspace::OnPlane; + } + + // Returns true if the bounding box is entirely in the negative (lower) half-space. + bool inLower(const BBOX& box) const + { + for (int corner = 0; corner < 8; corner++) + { + if (classify(box.getCorner(corner)) != Halfspace::Negative) + return false; + } + + // All corners are in the negative (lower) half-space. + return true; + } + + // Returns true if the bounding box is entirely in the positive (upper) half-space. + bool inUpper(const BBOX& box) const + { + for (int corner = 0; corner < 8; corner++) + { + if (classify(box.getCorner(corner)) == Halfspace::Negative) + return false; + } + + // All corners are in the positive (upper) half-space. + return true; + } + + void normalize() + { + T mag = sqrt(glm::dot(VEC3(_planeq), VEC3(_planeq))); + _planeq /= mag; + } + +private: + /*** Private Variables ***/ + VEC4 _planeq; +}; + +/// A class representing a single precision plane. +using Plane = PlaneT; + +/// A class representing a double precision plane. +using PlaneDbl = PlaneT; + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/API/Aurora/Foundation/Timer.h b/Libraries/Foundation/API/Aurora/Foundation/Timer.h new file mode 100644 index 0000000..64849d5 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/Timer.h @@ -0,0 +1,289 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include +#include +#include + +namespace Aurora +{ +namespace Foundation +{ + +/// A simple timer to measure time spent on the CPU. +class CPUTimer +{ +public: + using clock = std::chrono::high_resolution_clock; + + /// Constructor. + CPUTimer() { reset(); }; + + /// Resets the CPU timer. + /// + /// /param shouldSuspend Whether to reset the timer in a suspended state. + void reset(bool shouldSuspend = false) + { + _startTime = clock::now(); + _isSuspended = false; + if (shouldSuspend) + { + suspend(); + } + } + + /// Suspends the timer, so that any time spent suspended is not included in the elapsed time. + void suspend() + { + if (!_isSuspended) + { + _suspendTime = clock::now(); + _isSuspended = true; + } + } + + /// Resumes the timer from a suspended state. + void resume() + { + if (_isSuspended) + { + _startTime += clock::now() - _suspendTime; + _isSuspended = false; + } + } + + /// Gets the time since the CPU timer was reset, in milliseconds. + /// + /// This does not include the time spent while the timer is suspended. + float elapsed() const + { + auto duration = (_isSuspended ? _suspendTime : clock::now()) - _startTime; + auto durationMs = std::chrono::duration_cast(duration); + + return static_cast(durationMs.count()); + } + +private: + clock::time_point _startTime; + clock::time_point _suspendTime; + bool _isSuspended; +}; + +/// A regulator for progressive rendering. +/// +/// This maintains a running average of time spent rendering previous samples to estimate the number +/// of samples (iterations) that can be performed in a target time. +/// +/// There are two target times: "active" and "idle" which correspond to the counter being restarted +/// recently or not, respectively. If the counter is being restarted frequently, this usually +/// corresponds to user activity (e.g. moving the camera) and a lower target time is desirable. When +/// the counter (and user activity) is idle, more time can be allocated for additional samples, +/// which reduces the *total* rendering time due to fewer per-frame updates; such updates have +/// overhead. +class SampleCounter +{ +public: + /// Constructor. + /// + /// /param activeFrameTime The desired amount of time to render, in milliseconds, when the + /// counter is actively being restarted. + /// /param idleFrameTime The desired amount of time to render, in milliseconds, when the + /// counter hasn't been restarted recently. + /// /param maxSamples The maximum number of samples, at which point rendering is complete. + SampleCounter( + float activeFrameTime = 33.3f, float idleFrameTime = 200.0f, uint32_t maxSamples = 1000) : + _activeFrameTime(activeFrameTime), _idleFrameTime(idleFrameTime), _maxSamples(maxSamples) + { + assert(activeFrameTime > 0.0f && idleFrameTime > 0.0f && maxSamples > 0); + + // Prepare the record array and reset the counter. + _records.resize(TIME_COUNT); + reset(); + } + + /// Resets the counter. + /// + /// This is useful after significant changes to the data being rendered, e.g. loading a new + /// scene with possibly different complexity. + void reset() + { + _timer.reset(); + _idleTimer.reset(); + _records[0].samples = 1; + _isFull = false; + _currentTimeIndex = 0; + _sampleStart = 0; + _storedSamples = 0; + _storedTime = 0; + } + + /// Gets the current number of samples that have been accumulated, up to the maximum number + /// of samples. + uint32_t currentSamples() const { return _sampleStart; } + + /// Gets whether rendering is complete, i.e. the maximum number of samples has been reached. + bool isComplete() const { return _sampleStart == _maxSamples; } + + /// Sets the maximum number of samples, at which point rendering is complete. + /// + /// This will not reset the counter, as the client may want rendering to continue starting from + /// the current accumulated result, using additional samples. + void setMaxSamples(uint32_t maxSamples) + { + assert(maxSamples > 0); + + _maxSamples = maxSamples; + } + + /// Computes and returns the target number of samples for the next frame, based on previously + /// collected frame render times and sample counts. + /// + /// This returns zero if rendering is complete. This also updates the current number of samples. + /// + /// /param sampleStart The sample index to use for rendering; the returned sample count refers + /// to the following samples. + /// /param restart When set to true, restarts sample tracking, so that sampleStart is zero. This + /// should be set when the scene is changed, e.g. moving the camera. + uint32_t update(uint32_t& sampleStart, bool restart = false) + { + uint32_t samples = 1; + + // Record whether rendering is already complete, as that state may change below. + bool isDone = isComplete(); + + // Handle a request to restart the counter. + if (restart) + { + // Set the start sample to zero and reset the idle timer. + _sampleStart = 0; + _idleTimer.reset(); + + // If rendering was previously complete, reset the timer and return a sample count to + // fill the active frame time. This is necessary because a long idle time has likely + // occurred since the last update, after rendering was completed. + if (isDone) + { + _timer.reset(); + samples = samples = computeSampleCount(_activeFrameTime); + sampleStart = 0; + _sampleStart += samples; + + return samples; + } + } + + // If rendering is already complete, return zero samples (nothing to do). + if (isDone) + { + sampleStart = _maxSamples; + + return 0; + } + + // Get the elapsed time since the last update and reset the timer. This usually corresponds + // to the amount of time required to render the previous samples. + float elapsedTime = _timer.elapsed(); + _timer.reset(); + + // Store the elapsed time in the last record, and increment the total time. If the record + // array is already full (primed), also subtract the previous time from the running total. + Record& prevRecord = _records[_currentTimeIndex]; + if (_isFull) + { + _storedTime -= prevRecord.time; + } + prevRecord.time = elapsedTime; + _storedTime += elapsedTime; + + // If the record array is full (primed), compute the target number of samples. If the record + // array is not full yet, just use one sample. + if (_isFull) + { + // Determine the target frame time: using the idle frame time if the idle duration has + // passed since the counter was reset, and otherwise use the active frame time. + const float kIdleDuration = 1000.0f; + float targetFrameTime = + _idleTimer.elapsed() > kIdleDuration ? _idleFrameTime : _activeFrameTime; + + // Determine the number of samples for the target frame time. + samples = computeSampleCount(targetFrameTime); + } + + // Advance to the next record. Since the records are stored in an array, loop back to the + // beginning if needed. When this (first) happens, it also means the array is full, i.e. + // primed with a complete set of data. + _currentTimeIndex++; + if (_currentTimeIndex == TIME_COUNT) + { + _isFull = true; + _currentTimeIndex = 0; + } + + // Store the sample count in the next record, and increment the running total. If the record + // array is already full (primed), also subtract the previous sample count from the total. + Record& nextRecord = _records[_currentTimeIndex]; + if (_isFull) + { + _storedSamples -= nextRecord.samples; + } + nextRecord.samples = samples; + _storedSamples += samples; + + // Set the output sample start, and increment the member value. + sampleStart = _sampleStart; + _sampleStart += samples; + + return samples; + } + +private: + static const uint32_t TIME_COUNT = 20; + + struct Record + { + float time = 0; + uint32_t samples = 0; + }; + + uint32_t computeSampleCount(float targetFrameTime) + { + // Compute the average time per sample (iteration), and then the number of samples that + // could be rendered in the target frame time, with at least one sample. If rendering would + // be complete with these samples (i.e. up to the maximum number of samples), use that + // smaller remaining number. + float averageTime = _storedTime / _storedSamples; + uint32_t sampleCount = std::max(static_cast(targetFrameTime / averageTime), 1u); + sampleCount = std::min(_maxSamples - _sampleStart, sampleCount); + + return sampleCount; + } + + CPUTimer _timer; + CPUTimer _idleTimer; + std::vector _records; + float _activeFrameTime = 33.3f; + float _idleFrameTime = 250.0f; + uint32_t _maxSamples = 0; + bool _isFull = false; + size_t _currentTimeIndex = 0; + uint32_t _sampleStart = 0; + uint32_t _storedSamples = 0; + float _storedTime = 0; +}; + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/API/Aurora/Foundation/Utilities.h b/Libraries/Foundation/API/Aurora/Foundation/Utilities.h new file mode 100644 index 0000000..aa9e902 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/Utilities.h @@ -0,0 +1,144 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#undef snprintf + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Aurora +{ +namespace Foundation +{ + +/// Combine a hash with a seed value. +void hashCombine(size_t& seed, size_t otherHash); + +/// Compute a hash from an array of ints. +std::size_t hashInts(const uint32_t* pFirst, size_t count); + +/// Converts a UTF-16 string to UTF-8 (variable-width encoding). +/// +/// \note This will throw a range_error exception if the input is invalid. +inline std::string w2s(const std::wstring& utf16Source) +{ +// TODO: Work out how to avoid deprecated warnings, as there is nothing to replace this in the +// standard. +#pragma warning(push) +#pragma warning(disable : 4996) + std::wstring_convert> converter; + + return converter.to_bytes(utf16Source); +#pragma warning(pop) +} + +/// Converts a UTF-8 string to UTF-16 (variable-width encoding). +/// +/// \note This will throw a range_error exception if the input is invalid. +inline std::wstring s2w(const std::string& utf8Source) +{ +// TODO: Work out how to avoid deprecated warnings, as there is nothing to replace this in the +// standard. +#pragma warning(push) +#pragma warning(disable : 4996) + std::wstring_convert> converter; + + return converter.from_bytes(utf8Source); +#pragma warning(pop) +} + +/// Converts the provided string to lowercase, in place. +inline void sLower(std::string& data) +{ + std::transform(data.begin(), data.end(), data.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); +} + +// Convert 64-bit hash to hex string. +inline const std::string sHash(size_t hash) +{ + // Create a string stream and ensure locale does not add extra characters. + std::stringstream sstream; + sstream.imbue(std::locale::classic()); + + // Convert to hex string. + // See unit test TestUtilities in Tests\Foundation\API\TestUtilities.cpp for example. + sstream << std::setfill('0') << std::setw(2) << std::hex << hash; + return sstream.str(); +} + +/// Creates a string from variable arguments. +template +std::string sFormat(const std::string& format, Args... args) +{ + // Disable the Clang security warning. + // TODO: Implement a better workaround for this warning. +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" +#endif + // Calculate the size of the expanded format string. + const char* formatStr = format.c_str(); + int size = std::snprintf(nullptr, 0, formatStr, args...) + 1; // extra space for the \0 + if (size <= 0) + { + // Report an error if formating fails. + std::cerr << __FUNCTION__ << " Error during formatting." << std::endl; + + return ""; + } + + // Allocate a buffer for the formatted string and place the expanded string in it. + std::vector buf(size); + std::snprintf(buf.data(), size, formatStr, args...); +#if __clang__ +#pragma clang diagnostic pop +#endif + + // Return a new string object from the buffer. + // TODO: Avoid multiple allocations. + return std::string(buf.data(), buf.data() + size - 1); // don't include the \0 +}; + +/// Get the absolute path for the currently executing module. +std::string getModulePath(); + +/// Wrap a positive or negative integer x in the range 0 to y-1. +inline int iwrap(int x, int y) +{ + // If x is positive return x mod y. + if (x > 0) + return x % y; + + // If x is negative compute modulo, then add y-1. + if (x < 0) + return (x + 1) % y + y - 1; + + return 0; +} + +/// Return a string where all occurences of searchTerm in src are replaced with replaceTerm. +std::string replace( + const std::string& str, const std::string& searchTerm, const std::string& replaceTerm); + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/CMakeLists.txt b/Libraries/Foundation/CMakeLists.txt new file mode 100644 index 0000000..4e84a6b --- /dev/null +++ b/Libraries/Foundation/CMakeLists.txt @@ -0,0 +1,27 @@ +project(Foundation) + +add_library(${PROJECT_NAME} STATIC + "API/Aurora/Foundation/BoundingBox.h" + "API/Aurora/Foundation/Frustum.h" + "API/Aurora/Foundation/Log.h" + "API/Aurora/Foundation/Plane.h" + "API/Aurora/Foundation/Timer.h" + "API/Aurora/Foundation/Utilities.h" + "Source/Utilities.cpp" + "Source/Log.cpp" +) + +# Set custom ouput properties. +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER "Libraries" + # TODO: set a custom output name for this target: OUTPUT_NAME "aurora_foundation" + RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" +) + +# Add the Aurora folder a public include +target_include_directories(${PROJECT_NAME} PUBLIC "API") + +target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFAULT_COMPILE_DEFINITIONS}) \ No newline at end of file diff --git a/Libraries/Foundation/Source/Log.cpp b/Libraries/Foundation/Source/Log.cpp new file mode 100644 index 0000000..0dc81c0 --- /dev/null +++ b/Libraries/Foundation/Source/Log.cpp @@ -0,0 +1,74 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Aurora/Foundation/Log.h" + +#include + +#if _WIN32 +#include +#endif + +namespace Aurora +{ +namespace Foundation +{ + +// The singleton logger instance. +Log theLogger; + +// Get the singleton logger instance. +Log& Log::logger() +{ + return theLogger; +} + +void Log::debugBreak() +{ +#if !defined(NDEBUG) +#if defined(_WIN32) + __debugbreak(); +#endif +#endif +} + +bool Log::displayFailureDialog([[maybe_unused]] const std::string& file, [[maybe_unused]] int line, + [[maybe_unused]] const std::string& msg) +{ +#if defined(_WIN32) + std::string dialogMsg = msg + "File:" + file + "\nLine:" + std::to_string(line) + "\n"; + + // On windows debug builds display message as error message box + int msgboxID = MessageBox(NULL, s2w(dialogMsg).c_str(), + (LPCWSTR)L"Aurora Failure (click Cancel to ignore)", MB_ICONERROR | MB_OKCANCEL); + + // If message box is not canceled, then return true to trigger abort. + return (msgboxID != IDCANCEL); +#else + // On non-windows platforms just return true to trigger abort. + return true; +#endif +} + +void Log::writeToConsole([[maybe_unused]] const std::string& msg) +{ +#if defined(_WIN32) + // Output to windows debug console. + std::wstring widestr = std::wstring(msg.begin(), msg.end()); + OutputDebugString(widestr.c_str()); +#endif +} + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/Foundation/Source/Utilities.cpp b/Libraries/Foundation/Source/Utilities.cpp new file mode 100644 index 0000000..a897eb5 --- /dev/null +++ b/Libraries/Foundation/Source/Utilities.cpp @@ -0,0 +1,106 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Aurora/Foundation/Utilities.h" +#include "Aurora/Foundation/Log.h" + +// For UNIX equivalent of GetModuleFileName to get the location of shared library +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif + +namespace Aurora +{ +namespace Foundation +{ + +void hashCombine(size_t& seed, size_t otherHash) +{ + seed ^= otherHash + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +size_t hashInts(const uint32_t* pFirst, size_t count) +{ + size_t seed = std::hash()(count); + for (size_t i = 0; i < count; i++) + { + hashCombine(seed, std::hash()(pFirst[i])); + } + return seed; +} + +std::string replace( + const std::string& str, const std::string& searchTerm, const std::string& replaceTerm) +{ + std::string res = str; + for (size_t index = res.find(searchTerm, 0); index != std::string::npos && searchTerm.length(); + index = res.find(searchTerm, index + replaceTerm.length())) + res.replace(index, searchTerm.length(), replaceTerm); + return res; +} + +std::string getModulePath() +{ +#if defined(WIN32) + char path[MAX_PATH + 1]; + + DWORD pathSize = _MAX_PATH; + if (path == nullptr) + return std::string(); + + // Get a handle to the module that this static function lives in. + HMODULE hModule = nullptr; + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)getModulePath, &hModule); + + DWORD size = GetModuleFileNameA(hModule, path, pathSize); + if (size == 0 || size == pathSize) + { + AU_FAIL("Failed to get module path."); + } + std::string tempBuf(path); + +#else + Dl_info info; + std::string tempBuf("/"); + if (dladdr((void*)getModulePath, &info)) + { + tempBuf += info.dli_fname; + } + else + { + AU_FAIL("Failed to get module path."); + } +#endif + + size_t charOffset = tempBuf.find('/'); + while (charOffset != std::string::npos) + { + tempBuf.replace(charOffset, 1, "\\"); + charOffset = tempBuf.find('/'); + } + + // runTimeDir contains path up to executable name + // Remove the executable name. + charOffset = tempBuf.rfind(L'\\'); + if (charOffset != std::string::npos) + tempBuf.erase(charOffset + 1, (tempBuf.length() - charOffset) - 1); + return tempBuf; +} + +} // namespace Foundation +} // namespace Aurora diff --git a/Libraries/HdAurora/CMakeLists.txt b/Libraries/HdAurora/CMakeLists.txt new file mode 100644 index 0000000..5c19220 --- /dev/null +++ b/Libraries/HdAurora/CMakeLists.txt @@ -0,0 +1,67 @@ +# Specify the library (for dependents), project (IDE), and output (binary file) names. +project(hdAurora) + +find_package(glm REQUIRED) # Find the GLM vector maths package. +find_package(OpenGL REQUIRED) +find_package(Vulkan REQUIRED) +find_package(GLEW REQUIRED) + +find_package(pxr REQUIRED) # Find the Universal Scene Description (USD) library +# Add namespace to the imported usd targets +add_library(pxr::usd ALIAS usd) +add_library(pxr::hf ALIAS hf) +add_library(pxr::hd ALIAS hd) +add_library(pxr::hdx ALIAS hdx) + +# Add shard library with all source files. +add_library(${PROJECT_NAME} SHARED + "DLL.cpp" + "HdAuroraImageCache.cpp" + "HdAuroraImageCache.h" + "HdAuroraCamera.cpp" + "HdAuroraCamera.h" + "HdAuroraInstancer.cpp" + "HdAuroraInstancer.h" + "HdAuroraLight.cpp" + "HdAuroraLight.h" + "HdAuroraMaterial.cpp" + "HdAuroraMaterial.h" + "HdAuroraMesh.cpp" + "HdAuroraMesh.h" + "HdAuroraPlugin.cpp" + "HdAuroraPlugin.h" + "HdAuroraRenderBuffer.cpp" + "HdAuroraRenderBuffer.h" + "HdAuroraRenderDelegate.cpp" + "HdAuroraRenderDelegate.h" + "HdAuroraRenderPass.cpp" + "HdAuroraRenderPass.h" + "HdAuroraTokens.h" + "pch.h" +) + +# Set custom ouput properties. +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER "Libraries" + RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" +) + +# Add dependencies. +target_link_libraries(${PROJECT_NAME} +PRIVATE + glm::glm + OpenGL::GL + GLEW::glew + pxr::usd + pxr::hf + pxr::hd + pxr::hdx + Foundation + Aurora +) + +# Add default compile definitions (set in root CMakefile) +target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFAULT_COMPILE_DEFINITIONS}) \ No newline at end of file diff --git a/Libraries/HdAurora/DLL.cpp b/Libraries/HdAurora/DLL.cpp new file mode 100644 index 0000000..da3a01b --- /dev/null +++ b/Libraries/HdAurora/DLL.cpp @@ -0,0 +1,52 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#if defined(WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "HdAuroraPlugin.h" + +#if defined WIN32 +// The module entry point +BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + HdRendererPluginRegistry::Define(); // register HdAurora plugin with + // USD + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#else +extern "C" +{ + __attribute__((constructor)) static void Initializer( + int /*argc*/, char** /*argv*/, char** /*envp*/) + { + HdRendererPluginRegistry::Define(); // register HdAurora plugin with + // USD + } + + __attribute__((destructor)) static void Finalizer() {} +} +#endif \ No newline at end of file diff --git a/Libraries/HdAurora/HdAuroraCamera.cpp b/Libraries/HdAurora/HdAuroraCamera.cpp new file mode 100644 index 0000000..3cfe72b --- /dev/null +++ b/Libraries/HdAurora/HdAuroraCamera.cpp @@ -0,0 +1,108 @@ +#include "pch.h" + +#include "HdAuroraCamera.h" +#include "HdAuroraRenderDelegate.h" +#include + +#include + +#pragma warning(disable : 4506) // inline function warning (from USD but appears in this file) + +HdAuroraCamera::HdAuroraCamera(SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : + HdCamera(rprimId), _owner(renderDelegate) +{ +} + +HdAuroraCamera::~HdAuroraCamera() +{ + // Ensure progressive rendering is reset. + _owner->SetSampleRestartNeeded(true); +} + +HdDirtyBits HdAuroraCamera::GetInitialDirtyBitsMask() const +{ + return HdCamera::DirtyParams; +} + +void HdAuroraCamera::Sync( + HdSceneDelegate* delegate, HdRenderParam* /* renderParam */, HdDirtyBits* dirtyBits) +{ + + const auto& id = GetId(); + // Laking of viewmatrix and projection matrix in HDCameraTokens + // Camera' viewMatrix transmit directly by transform cache + GfMatrix4f viewMat = GfMatrix4f(delegate->GetTransform(id).GetInverse()); + GfMatrix4f projMat = GfMatrix4f(0.0f); + + // Set Camera properties to aurora by GfCamera physical propeties + float focalLength = + (delegate->GetCameraParamValue(id, HdCameraTokens->focalLength)).Get() / + float(GfCamera::FOCAL_LENGTH_UNIT); + float horizontalAperture = + (delegate->GetCameraParamValue(id, HdCameraTokens->horizontalAperture)).Get() / + float(GfCamera::APERTURE_UNIT); + float verticalAperture = + (delegate->GetCameraParamValue(id, HdCameraTokens->verticalAperture)).Get() / + float(GfCamera::APERTURE_UNIT); + float horizontalApertureOffset = + (delegate->GetCameraParamValue(id, HdCameraTokens->horizontalApertureOffset)).Get() / + float(GfCamera::APERTURE_UNIT); + float verticalApertureOffset = + (delegate->GetCameraParamValue(id, HdCameraTokens->verticalApertureOffset)).Get() / + float(GfCamera::APERTURE_UNIT); + GfRange1f clippingRange = + (delegate->GetCameraParamValue(id, HdCameraTokens->clippingRange)).Get(); + HdCamera::Projection projectionType = + (delegate->GetCameraParamValue(id, HdCameraTokens->projection)).Get(); + + // Set projection matrix based on projection type. + if (HdCamera::Projection::Perspective == projectionType) + { + projMat[0][0] = 2 * focalLength / horizontalAperture; + projMat[1][1] = 2 * focalLength / verticalAperture; + projMat[2][0] = 2 * horizontalApertureOffset / horizontalAperture; + projMat[2][1] = 2 * verticalApertureOffset / verticalAperture; + projMat[3][2] = 2 * clippingRange.GetMin() * clippingRange.GetMax() / + (clippingRange.GetMin() - clippingRange.GetMax()); + projMat[2][2] = (clippingRange.GetMin() + clippingRange.GetMax()) / + (clippingRange.GetMin() - clippingRange.GetMax()); + } + else if (HdCamera::Projection::Orthographic == projectionType) + { + projMat.SetIdentity(); + projMat[0][0] = (2.0f / float(GfCamera::APERTURE_UNIT)) / horizontalAperture; + projMat[1][1] = (2.0f / float(GfCamera::APERTURE_UNIT)) / verticalAperture; + projMat[3][0] = + static_cast(horizontalApertureOffset / (-0.5 * (horizontalAperture))); + projMat[3][1] = static_cast(verticalApertureOffset / (-0.5 * (verticalAperture))); + projMat[2][2] = static_cast(2 / (clippingRange.GetMin() + clippingRange.GetMax())); + projMat[3][2] = (clippingRange.GetMin() + clippingRange.GetMax()) / + (clippingRange.GetMin() - clippingRange.GetMax()); + } + // Check to see if we need to flip the output image + static TfToken tokenFlipYOutput("HdAuroraFlipYOutput"); + bool flipY = true; + auto flipYValue = this->_owner->GetRenderSetting(tokenFlipYOutput); + if (!flipYValue.IsEmpty() && flipYValue.IsHolding()) + flipY = flipYValue.Get(); + if (flipY) + { + // Invert the second row of the projection matrix to flip the rendered image, + // because OpenGL expects the first pixel to be at the *bottom* corner. + projMat[1][0] = -projMat[1][0]; + projMat[1][1] = -projMat[1][1]; + projMat[1][2] = -projMat[1][2]; + projMat[1][3] = -projMat[1][3]; + } + // check for camera movements + static GfMatrix4f viewMatOld; + static GfMatrix4f projMatOld; + if (viewMatOld != viewMat || projMatOld != projMat) + { + _owner->SetSampleRestartNeeded(true); + viewMatOld = viewMat; + projMatOld = projMat; + } + _owner->GetRenderer()->setCamera((float*)&viewMat, (float*)&projMat); + *dirtyBits = Clean; +} \ No newline at end of file diff --git a/Libraries/HdAurora/HdAuroraCamera.h b/Libraries/HdAurora/HdAuroraCamera.h new file mode 100644 index 0000000..7af8bed --- /dev/null +++ b/Libraries/HdAurora/HdAuroraCamera.h @@ -0,0 +1,31 @@ +//**************************************************************************/ +// Copyright (c) 2022 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc., and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//**************************************************************************/ +// DESCRIPTION: Camera for HdAurora. +// AUTHOR: Autodesk Inc. +//**************************************************************************/ + +#pragma once + +class HdAuroraRenderDelegate; + +class HdAuroraCamera : public HdCamera +{ +public: + HdAuroraCamera(pxr::SdfPath const& sprimId, HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraCamera() override; + + HdDirtyBits GetInitialDirtyBitsMask() const override; + void Sync( + HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override; + +private: + HdAuroraRenderDelegate* _owner; +}; diff --git a/Libraries/HdAurora/HdAuroraImageCache.cpp b/Libraries/HdAurora/HdAuroraImageCache.cpp new file mode 100644 index 0000000..81e2aab --- /dev/null +++ b/Libraries/HdAurora/HdAuroraImageCache.cpp @@ -0,0 +1,133 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraImageCache.h" + +Aurora::Path HdAuroraImageCache::acquireImage( + const string& sFilePath, bool isEnvironmentImage, bool forceLinear) +{ + // Calculate an Aurora image path from file path (should be different for environment images.) + Aurora::Path auroraImagePath = + isEnvironmentImage ? "HdAuroraEnvImage:" + sFilePath : "HdAuroraImage:" + sFilePath; + if (forceLinear) + auroraImagePath += "-linear"; + + auto iter = _cache.find(auroraImagePath); + if (iter != _cache.end()) + return auroraImagePath; + + _cache[auroraImagePath] = HdAuroraImageCacheEntry(); + HdAuroraImageCacheEntry& newEntry = _cache[auroraImagePath]; + newEntry.imageDesc.getPixelData = [this, auroraImagePath]( + Aurora::PixelData& dataOut, glm::ivec2, glm::ivec2) { + const HdAuroraImageCacheEntry& entry = _cache[auroraImagePath]; + dataOut.address = entry.pPixelData.get(); + dataOut.size = entry.sizeInBytes; + return true; + }; + newEntry.imageDesc.isEnvironment = isEnvironmentImage; + newEntry.imageDesc.linearize = !forceLinear; + + pxr::HioImageSharedPtr const image = pxr::HioImage::OpenForReading(sFilePath); + if (image) + { + pxr::HioImage::StorageSpec imageData; + auto hioFormat = image->GetFormat(); + imageData.width = image->GetWidth(); + imageData.height = image->GetHeight(); + imageData.depth = 1; + imageData.flipped = true; + imageData.format = image->GetFormat(); + bool paddingRequired = + hioFormat == HioFormatUNorm8Vec3srgb || hioFormat == HioFormatUNorm8Vec3; + unique_ptr pTempPixels; + if (paddingRequired) + { + hioFormat = hioFormat == HioFormatUNorm8Vec3srgb ? HioFormatUNorm8Vec4srgb + : HioFormatUNorm8Vec4; + newEntry.sizeInBytes = image->GetWidth() * image->GetHeight() * 4; + pTempPixels.reset( + new uint8_t[image->GetWidth() * image->GetHeight() * image->GetBytesPerPixel()]); + newEntry.pPixelData.reset(new uint8_t[newEntry.sizeInBytes]); + imageData.data = pTempPixels.get(); + } + else + { + newEntry.sizeInBytes = + image->GetWidth() * image->GetHeight() * image->GetBytesPerPixel(); + newEntry.pPixelData.reset(new uint8_t[newEntry.sizeInBytes]); + imageData.data = newEntry.pPixelData.get(); + } + + bool res = image->Read(imageData); + if (res) + { + newEntry.imageDesc.width = image->GetWidth(); + newEntry.imageDesc.height = image->GetHeight(); + if (paddingRequired) + { + for (size_t idx = 0; idx < newEntry.imageDesc.width * newEntry.imageDesc.height; + idx++) + { + newEntry.pPixelData[idx * 4 + 0] = pTempPixels[idx * 3 + 0]; + newEntry.pPixelData[idx * 4 + 1] = pTempPixels[idx * 3 + 1]; + newEntry.pPixelData[idx * 4 + 2] = pTempPixels[idx * 3 + 2]; + newEntry.pPixelData[idx * 4 + 3] = 0xFF; + } + } + + switch (hioFormat) + { + case HioFormatUNorm8Vec4srgb: + // Set linearize flag, unless force linear is true. + newEntry.imageDesc.linearize = !forceLinear; + // Fall through... + case HioFormatUNorm8Vec4: + newEntry.imageDesc.format = Aurora::ImageFormat::Integer_RGBA; + break; + case HioFormatFloat32Vec3: + newEntry.imageDesc.format = Aurora::ImageFormat::Float_RGB; + break; + case HioFormatFloat32Vec4: + newEntry.imageDesc.format = Aurora::ImageFormat::Float_RGBA; + break; + case HioFormatUNorm8: + newEntry.imageDesc.format = Aurora::ImageFormat::Byte_R; + break; + default: + AU_ERROR("%s: Unsupported image format:%x", sFilePath.c_str(), image->GetFormat()); + newEntry.pPixelData.reset(); + break; + } + } + else + newEntry.pPixelData.reset(); + } + if (!newEntry.pPixelData) + { + AU_ERROR("Failed to load image image :%s, using placeholder", sFilePath.c_str()); + newEntry.sizeInBytes = 8; + newEntry.pPixelData.reset(new uint8_t[8]); + memset(newEntry.pPixelData.get(), 0xff, newEntry.sizeInBytes); + newEntry.pPixelData[0] = 0xff; + newEntry.imageDesc.width = 2; + newEntry.imageDesc.height = 1; + newEntry.imageDesc.format = Aurora::ImageFormat::Integer_RGBA; + } + + _pAuroraScene->setImageDescriptor(auroraImagePath, newEntry.imageDesc); + + return auroraImagePath; +} diff --git a/Libraries/HdAurora/HdAuroraImageCache.h b/Libraries/HdAurora/HdAuroraImageCache.h new file mode 100644 index 0000000..05d92e4 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraImageCache.h @@ -0,0 +1,43 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +// Cache entry, use in cache map lookup. +struct HdAuroraImageCacheEntry +{ + // The pixels for the image. + std::unique_ptr pPixelData; + // Total bytes of pixel data. + size_t sizeInBytes; + // The Aurora descriptor describing the image. + Aurora::ImageDescriptor imageDesc; +}; + +// Image cache used by materials and environments to load Aurora images. +class HdAuroraImageCache +{ +public: + HdAuroraImageCache(Aurora::IScenePtr pAuroraScene) : _pAuroraScene(pAuroraScene) {} + ~HdAuroraImageCache() {} + + // Acquire an image from the cache, loading if necessary. + // Returns the Aurora path for the image (will be different for environment ismages.) + Aurora::Path acquireImage( + const string& sFilePath, bool isEnvironmentImage = false, bool linearize = false); + +private: + map _cache; + Aurora::IScenePtr _pAuroraScene; +}; diff --git a/Libraries/HdAurora/HdAuroraInstancer.cpp b/Libraries/HdAurora/HdAuroraInstancer.cpp new file mode 100644 index 0000000..0d3b6a1 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraInstancer.cpp @@ -0,0 +1,188 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraInstancer.h" + +HdAuroraInstancer::HdAuroraInstancer(HdSceneDelegate* delegate, SdfPath const& id) : + HdInstancer(delegate, id) +{ +} + +HdAuroraInstancer::~HdAuroraInstancer() +{ + TF_FOR_ALL(it, _primvarMap) { delete it->second; } + _primvarMap.clear(); +} + +void HdAuroraInstancer::Sync( + HdSceneDelegate* delegate, HdRenderParam* /*renderParam*/, HdDirtyBits* dirtyBits) +{ + _UpdateInstancer(delegate, dirtyBits); + + if (HdChangeTracker::IsAnyPrimvarDirty(*dirtyBits, GetId())) + { + _SyncPrimvars(delegate, *dirtyBits); + } +} + +void HdAuroraInstancer::_SyncPrimvars(HdSceneDelegate* delegate, HdDirtyBits dirtyBits) +{ + SdfPath const& id = GetId(); + + HdPrimvarDescriptorVector primvars = + delegate->GetPrimvarDescriptors(id, HdInterpolationInstance); + + for (HdPrimvarDescriptor const& pv : primvars) + { + if (HdChangeTracker::IsPrimvarDirty(dirtyBits, id, pv.name)) + { + VtValue value = delegate->Get(id, pv.name); + if (!value.IsEmpty()) + { + if (_primvarMap.count(pv.name) > 0) + { + delete _primvarMap[pv.name]; + } + _primvarMap[pv.name] = new HdVtBufferSource(pv.name, value); + } + } + } +} + +bool SampleBuffer(HdVtBufferSource const& buffer, int index, void* value, HdTupleType dataType) +{ + if (buffer.GetNumElements() <= (size_t)index || buffer.GetTupleType() != dataType) + { + return false; + } + + size_t elemSize = HdDataSizeOfTupleType(dataType); + size_t offset = elemSize * index; + + std::memcpy(value, static_cast(buffer.GetData()) + offset, elemSize); + + return true; +} + +VtMatrix4dArray HdAuroraInstancer::ComputeInstanceTransforms(SdfPath const& prototypeId) +{ + // The transforms for this level of instancer are computed by: + // foreach(index : indices) { + // instancerTransform * translate(index) * rotate(index) * + // scale(index) * instanceTransform(index) + // } + // If any transform isn't provided, it's assumed to be the identity. + + GfMatrix4d instancerTransform = GetDelegate()->GetInstancerTransform(GetId()); + VtIntArray instanceIndices = GetDelegate()->GetInstanceIndices(GetId(), prototypeId); + + VtMatrix4dArray transforms(instanceIndices.size()); + for (size_t i = 0; i < instanceIndices.size(); ++i) + { + transforms[i] = instancerTransform; + } + + // "translate" holds a translation vector for each index. + if (_primvarMap.count(HdInstancerTokens->translate) > 0) + { + const auto* pBuffer = _primvarMap[HdInstancerTokens->translate]; + for (size_t i = 0; i < instanceIndices.size(); ++i) + { + GfVec3f translate; + if (SampleBuffer(*pBuffer, instanceIndices[i], &translate, { HdTypeFloatVec3, 1 })) + { + GfMatrix4d translateMat(1); + translateMat.SetTranslate(GfVec3d(translate)); + transforms[i] = translateMat * transforms[i]; + } + } + } + + // "rotate" holds a quaternion in format for each index. + if (_primvarMap.count(HdInstancerTokens->rotate) > 0) + { + const auto* pBuffer = _primvarMap[HdInstancerTokens->rotate]; + for (size_t i = 0; i < instanceIndices.size(); ++i) + { + GfVec4f quat; + if (SampleBuffer(*pBuffer, instanceIndices[i], &quat, { HdTypeFloatVec4, 1 })) + { + GfMatrix4d rotateMat(1); + rotateMat.SetRotate(GfQuatd(quat[0], quat[1], quat[2], quat[3])); + transforms[i] = rotateMat * transforms[i]; + } + } + } + + // "scale" holds an axis-aligned scale vector for each index. + if (_primvarMap.count(HdInstancerTokens->scale) > 0) + { + const auto* pBuffer = _primvarMap[HdInstancerTokens->scale]; + for (size_t i = 0; i < instanceIndices.size(); ++i) + { + GfVec3f scale; + if (SampleBuffer(*pBuffer, instanceIndices[i], &scale, { HdTypeFloatVec3, 1 })) + { + GfMatrix4d scaleMat(1); + scaleMat.SetScale(GfVec3d(scale)); + transforms[i] = scaleMat * transforms[i]; + } + } + } + + // "instanceTransform" holds a 4x4 transform matrix for each index. + if (_primvarMap.count(HdInstancerTokens->instanceTransform) > 0) + { + const auto* pBuffer = _primvarMap[HdInstancerTokens->instanceTransform]; + for (size_t i = 0; i < instanceIndices.size(); ++i) + { + GfMatrix4d instanceTransform; + if (SampleBuffer( + *pBuffer, instanceIndices[i], &instanceTransform, { HdTypeDoubleMat4, 1 })) + { + transforms[i] = instanceTransform * transforms[i]; + } + } + } + + if (GetParentId().IsEmpty()) + { + return transforms; + } + + HdInstancer* parentInstancer = GetDelegate()->GetRenderIndex().GetInstancer(GetParentId()); + if (!TF_VERIFY(parentInstancer)) + { + return transforms; + } + + // The transforms taking nesting into account are computed by: + // parentTransforms = parentInstancer->ComputeInstanceTransforms(GetId()) + // foreach (parentXf : parentTransforms, xf : transforms) { + // parentXf * xf + // } + VtMatrix4dArray parentTransforms = + static_cast(parentInstancer)->ComputeInstanceTransforms(GetId()); + + VtMatrix4dArray final(parentTransforms.size() * transforms.size()); + for (size_t i = 0; i < parentTransforms.size(); ++i) + { + for (size_t j = 0; j < transforms.size(); ++j) + { + final[i * transforms.size() + j] = transforms[j] * parentTransforms[i]; + } + } + return final; +} diff --git a/Libraries/HdAurora/HdAuroraInstancer.h b/Libraries/HdAurora/HdAuroraInstancer.h new file mode 100644 index 0000000..d50009b --- /dev/null +++ b/Libraries/HdAurora/HdAuroraInstancer.h @@ -0,0 +1,32 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +class HdAuroraInstancer : public HdInstancer +{ +public: + HdAuroraInstancer(HdSceneDelegate* delegate, SdfPath const& id); + ~HdAuroraInstancer(); + + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) override; + + VtMatrix4dArray ComputeInstanceTransforms(SdfPath const& prototypeId); + +private: + void _SyncPrimvars(HdSceneDelegate* delegate, HdDirtyBits dirtyBits); + + TfHashMap _primvarMap; +}; diff --git a/Libraries/HdAurora/HdAuroraLight.cpp b/Libraries/HdAurora/HdAuroraLight.cpp new file mode 100644 index 0000000..a8a5be4 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraLight.cpp @@ -0,0 +1,157 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraImageCache.h" +#include "HdAuroraLight.h" +#include "HdAuroraRenderDelegate.h" + +#include + +#pragma warning(disable : 4506) // inline function warning (from USD but appears in this file) + +HdAuroraDomeLight::HdAuroraDomeLight( + SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : + HdLight(rprimId), _owner(renderDelegate) +{ +} + +HdAuroraDomeLight::~HdAuroraDomeLight() +{ + _owner->setAuroraEnvironmentLightImagePath(""); +} + +HdDirtyBits HdAuroraDomeLight::GetInitialDirtyBitsMask() const +{ + return HdLight::DirtyParams; +} + +void HdAuroraDomeLight::Sync( + HdSceneDelegate* delegate, HdRenderParam* /* renderParam */, HdDirtyBits* dirtyBits) +{ + const auto& id = GetId(); + + // Get the environment image file path, if any. + VtValue envFilePathVal = delegate->GetLightParamValue(id, pxr::HdLightTokens->textureFile); + auto envFilePath = envFilePathVal.Get().GetAssetPath(); + + // If the environment image file path has changed, apply the new value. + if (_environmentImageFilePath != envFilePath) + { + // Store the new file path, and reset the history on the renderer for the next frame, as we + // don't want the prior environment image visible through temporal accumulation. + _environmentImageFilePath = envFilePath; + + // Attempt to load the environment image, if any. + if (!_environmentImageFilePath.empty()) + { + _auroraImagePath = _owner->imageCache().acquireImage(_environmentImageFilePath, true); + } + else + { + _auroraImagePath.clear(); + } + + // Set this flag to update Aurora background. + _owner->setAuroraEnvironmentLightImagePath(_auroraImagePath); + } + + // Set environment and background transform in Aurora environment. + GfMatrix4d envTransformDbl = delegate->GetTransform(id); + GfMatrix4f envTransform(envTransformDbl); + glm::mat4 envMat4 = glm::make_mat4(envTransform.GetTranspose().data()); + if (_envTransform != envMat4) + { + _owner->setAuroraEnvironmentLightTransform(envMat4); + _envTransform = envMat4; + } + + *dirtyBits &= ~HdLight::AllDirty; +} + +HdAuroraDistantLight::HdAuroraDistantLight( + SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : + HdLight(rprimId), _owner(renderDelegate) +{ + GfVec3f lightDirection = { 0.1f, -1.0f, 0.1f }; + GfVec3f lightColor = { 1.0f, 1.0f, 1.0f }; + float lighIntensity = 0.0f; + float lightAngularDiameter = 0.1f; + _owner->GetScene()->setLight( + lighIntensity, lightColor.data(), lightDirection.data(), lightAngularDiameter); +} + +HdAuroraDistantLight::~HdAuroraDistantLight() +{ + // Ensure sampler counter is reset. + _owner->SetSampleRestartNeeded(true); + + /// Reset the light intensity to zero. + GfVec3f lightDirection = { 0.1f, -1.0f, 0.1f }; + GfVec3f lightColor = { 1.0f, 1.0f, 1.0f }; + float lighIntensity = 0.0f; + float lightAngularDiameter = 0.1f; + _owner->GetScene()->setLight( + lighIntensity, lightColor.data(), lightDirection.data(), lightAngularDiameter); +} + +HdDirtyBits HdAuroraDistantLight::GetInitialDirtyBitsMask() const +{ + return HdLight::DirtyParams; +} + +void HdAuroraDistantLight::Sync( + HdSceneDelegate* delegate, HdRenderParam* /* renderParam */, HdDirtyBits* dirtyBits) +{ + const auto& id = GetId(); + + // Ensure sampler counter is reset. + _owner->SetSampleRestartNeeded(true); + + //// Get the light intensity. + float intensity = (delegate->GetLightParamValue(id, HdLightTokens->intensity)).Get(); + + // Apply exposure. + float exposure = (delegate->GetLightParamValue(id, HdLightTokens->exposure)).Get(); + intensity *= powf(2.0f, GfClamp(exposure, -50.0f, 50.0f)); + + // Normalize the intensity. + VtValue normalizeVal = delegate->GetLightParamValue(id, HdLightTokens->normalize); + VtValue angleDegVal = delegate->GetLightParamValue(id, HdLightTokens->angle); + if (!normalizeVal.GetWithDefault(false)) + { + float area = 1.0f; + if (angleDegVal.IsHolding()) + { + // Calculate solid angle area to normalize intensity. + float angleRadians = angleDegVal.Get() / 180.0f * static_cast(M_PI); + float solidAngleSteradians = + 2.0f * static_cast(M_PI) * (1.0f - cos(angleRadians / 2.0f)); + area = solidAngleSteradians; + } + intensity *= area; + } + + // Get the light color from Hydra. + GfVec3f lightColor = + (delegate->GetLightParamValue(id, HdLightTokens->color)).Get(); + + // Compute light direction from transform matrix. + GfMatrix4f transformMatrix(delegate->GetTransform(id)); + GfVec3f lightDirection = { -transformMatrix[2][0], -transformMatrix[2][1], + -transformMatrix[2][2] }; + _owner->GetScene()->setLight(intensity, lightColor.data(), lightDirection.data()); + + *dirtyBits = Clean; +} diff --git a/Libraries/HdAurora/HdAuroraLight.h b/Libraries/HdAurora/HdAuroraLight.h new file mode 100644 index 0000000..3efd788 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraLight.h @@ -0,0 +1,50 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +class HdAuroraRenderDelegate; + +class HdAuroraDomeLight : public HdLight +{ +public: + HdAuroraDomeLight(SdfPath const& sprimId, HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraDomeLight() override; + + HdDirtyBits GetInitialDirtyBitsMask() const override; + void Sync( + HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override; + +private: + HdAuroraRenderDelegate* _owner; + std::string _environmentImageFilePath; + Aurora::Path _auroraImagePath; + glm::vec3 _bottomColor = glm::vec3(0, 0, 0); + glm::vec3 _topColor = glm::vec3(1, 1, 1); + glm::mat4 _envTransform; +}; + +class HdAuroraDistantLight : public HdLight +{ +public: + HdAuroraDistantLight(SdfPath const& sprimId, HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraDistantLight() override; + + HdDirtyBits GetInitialDirtyBitsMask() const override; + void Sync( + HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override; + +private: + HdAuroraRenderDelegate* _owner; +}; \ No newline at end of file diff --git a/Libraries/HdAurora/HdAuroraMaterial.cpp b/Libraries/HdAurora/HdAuroraMaterial.cpp new file mode 100644 index 0000000..98cb796 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraMaterial.cpp @@ -0,0 +1,661 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraMaterial.h" + +#include + +#include "HdAuroraImageCache.h" +#include "HdAuroraMesh.h" +#include "HdAuroraRenderDelegate.h" +#include "HdAuroraTokens.h" + +#include +#include + +#pragma warning(disable : 4506) // inline function warning (from USD but appears in this file) + +// Processed Hydra material network. +// TODO: These should be cached to avoid recalculating each time. +struct ProcessedMaterialNetwork +{ + // Map of material nodes by name. + std::map nodeMap; + // Name of surface node within material. + std::string surfaceName; + // Surface node. + HdMaterialNode* surfaceNode; + // Map of network relationships by output node name. + std::map relationships; +}; + +static const string paramBindingTemplate = R"( + )"; + +static const string nodeGraphBindingTemplate = R"( + )"; + +static const string nodeGraphImageTemplate = R"( + + + + + + + + )"; + +static const string nodeGraphNormalMapTemplate = R"( + + + + + + + + + + + )"; + +static const string materialDefinitionTemplate = R"( + + %s + %s + + + + + + + +)"; + +HdAuroraMaterial::HdAuroraMaterial(SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : + HdMaterial(rprimId), _owner(renderDelegate) +{ +} + +HdAuroraMaterial::~HdAuroraMaterial() {} + +HdDirtyBits HdAuroraMaterial::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::AllSceneDirtyBits; +} + +bool HdAuroraMaterial::SetupAuroraMaterial( + const string& materialType, const string& materialDocument) +{ + // Calculate hash of document. + std::size_t docHash = std::hash {}(materialDocument); + auto pRenderer = _owner->GetRenderer(); + auto pScene = _owner->GetScene(); + + // If we already have this material type and document just return without doing anything. + if (_auroraMaterialType.compare(materialType) == 0 && _auroraMaterialDocumentHash == docHash) + { + // This comparison should only be true if we already have a material. + AU_ASSERT(!_auroraMaterialPath.empty(), "Failed to create Aurora material"); + + return false; + } + + // Calculate Aurora path from Hydra ID. + _auroraMaterialPath = GetAuroraMaterialPath(GetId()); + + // Set the material type for material (this will create it if needed.) + pScene->setMaterialType(_auroraMaterialPath, materialType, materialDocument); + + // Set the material type and document hash. + _auroraMaterialDocumentHash = docHash; + _auroraMaterialType = materialType; + + return true; +} + +void HdAuroraMaterial::ProcessHDMaterial(HdSceneDelegate* delegate) +{ + const auto& id = GetId(); + VtValue hdMatVal = delegate->GetMaterialResource(id); + auto pRenderer = _owner->GetRenderer(); + + string hdMaterialXType; + string hdMaterialXDocument; + if (GetHDMaterialXDocument(delegate, hdMaterialXType, hdMaterialXDocument)) + { + // If we have Hydra materialX document or filename, just use that. + SetupAuroraMaterial(hdMaterialXType, hdMaterialXDocument); + } + else if (!ProcessHDMaterialNetwork(hdMatVal)) + { + Aurora::Properties materialProperties = { { "base_color_image", nullptr }, + { "base_color", nullptr } }; + + // If we don't have a material network, create preview surface using default built-in + // shader. + SetupAuroraMaterial(Aurora::Names::MaterialTypes::kBuiltIn, "Default"); + + // If no material network read from the Hydra material parameters. + // These parameters are set as overrides over what is specified in the + // MaterialX document. + std::vector> knownMapParameters = { + { "base_color_map", "base_color_image" }, + { "roughness_map", "specular_roughness_image" } + //,{ "coat_map", "coat_color_image" } + //,{ "coat_rough_map", "coat_roughness_image" } + , + { "bump_map", "normal_image" } + //,{ "displacement_map", "displacement_image" } + }; + for (auto inputName : knownMapParameters) + { + VtValue texFilenameVal = delegate->Get(id, pxr::TfToken(inputName.first)); + auto texFilenamePath = texFilenameVal.Get(); + string texFilename = texFilenamePath.GetAssetPath(); + bool forceLinear = inputName.second.compare("normal_image") == 0; + + if (texFilename.size() > 0) + { + Aurora::Path auroraImagePath = + _owner->imageCache().acquireImage(texFilename, false, forceLinear); + + materialProperties[inputName.second] = auroraImagePath; + } + } + + std::vector> knownFloatParameters = { + { "roughness", "specular_roughness" }, { "base_weight", "base" }, + { "reflectivity", "specular" }, { "metalness", "metalness" }, + { "anisotrophy", "specular_anisotropy" }, { "anisoangle", "specular_rotation" }, + { "transparency", "transmission" }, { "trans_depth", "transmission_depth" }, + { "emission", "emission" }, { "diff_roughness", "diffuse_roughness" }, + { "coating", "coat" }, { "coat_roughness", "coat_roughness" }, + { "coat_ior", "coat_IOR" } + }; + for (auto inputName : knownFloatParameters) + { + pxr::VtValue val = delegate->Get(id, pxr::TfToken(inputName.first)); + if (val.IsHolding()) + { + float fVal = val.Get(); + materialProperties[inputName.second] = fVal; + } + } + + std::vector> knownFloat4Parameters = { + { "base_color", "base_color" }, { "refl_color", "specular_color" }, + { "trans_color", "transmission_color" }, { "emit_color", "emission_color" }, + { "coat_color", "coat_color" } + }; + for (auto inputName : knownFloat4Parameters) + { + pxr::VtValue val = delegate->Get(id, pxr::TfToken(inputName.first)); + if (val.IsHolding()) + { + GfVec4f color = val.Get(); + materialProperties[inputName.second] = GfVec4ToGLMVec3(&color); + } + } + + _owner->GetScene()->setMaterialProperties(_auroraMaterialPath, materialProperties); + } +} + +bool HdAuroraMaterial::GetHDMaterialXDocument( + HdSceneDelegate* pDelegate, string& materialTypeOut, string& documentOut) +{ + SdfPath id = GetId(); + + // Return value indicating if materialX is used to create the material. + // This will be set to true if a materialX document is loaded from file or from string. + bool isMaterialX = false; + + // Default Aurora material type is default built-in material. + std::string materialType = Aurora::Names::MaterialTypes::kBuiltIn; + std::string materialDocument = "Default"; + + VtValue materialXFilePath = pDelegate->Get(id, HdAuroraTokens::kMaterialXFilePath); + + // First we look to see if there is a document by file name. + if (!materialXFilePath.IsEmpty()) + { + // Set Aurora material type and document for materialX path. + materialDocument = materialXFilePath.Get().GetAssetPath(); + materialType = Aurora::Names::MaterialTypes::kMaterialXPath; + + isMaterialX = true; + } + else + { + // If we don't find a filename property we look for a document string property. + VtValue materialXDocument = pDelegate->Get(id, HdAuroraTokens::kMaterialXDocument); + if (!materialXDocument.IsEmpty() && materialXDocument.IsHolding()) + { + // Set Aurora material type and document for materialX path. + materialDocument = materialXDocument.UncheckedGet(); + materialType = Aurora::Names::MaterialTypes::kMaterialX; + isMaterialX = true; + } + } + + materialTypeOut = materialType; + documentOut = materialDocument; + + return isMaterialX; +} + +void HdAuroraMaterial::Sync( + HdSceneDelegate* delegate, HdRenderParam* /* renderParam */, HdDirtyBits* dirtyBits) +{ + + if (_auroraMaterialPath.empty() || (*dirtyBits & HdMaterial::DirtyResource) || + (*dirtyBits & HdMaterial::DirtyParams)) + { + // Note: We reprocess the network even for parameter changes. + // A new Aurora Material is only generated if the hash of the material changes, otherwise + // only values are updated. In the future this could be more optimal by caching the network + // for less processing when parameters change only. + ProcessHDMaterial(delegate); + + _owner->SetSampleRestartNeeded(true); + } + + *dirtyBits &= ~HdMaterial::AllDirty; +} + +void HdAuroraMaterial::initialize(HdSceneDelegate* delegate) +{ + if (_auroraMaterialPath.empty()) + { + ProcessHDMaterial(delegate); + } +} + +bool HdAuroraMaterial::BuildMaterialXDocumentFromHDNetwork( + const ProcessedMaterialNetwork& network, string& materialXDocumentOut) +{ + // MaterialX node graph XML string. + string nodeGraphsStr; + // MaterialX parameter binding XML string. + string parameterBindingStr; + + // Node graph index. + int ngIndex = 1; + + // Float3 setter function to convert Hydra GfVec3f values to Aurora values + auto setF3Value = [](string& parameterBindingStr, const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + // Add the input binding for input to the dynamically built materialX document. + // Note: Values are applied seperately and not built into the document. + parameterBindingStr += + Aurora::Foundation::sFormat(paramBindingTemplate, auroraName.c_str(), "color3"); + } + }; + + // Float setter function to convert Hydra float values to Aurora values + static auto setF1Value = [](string& parameterBindingStr, const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + // Add the input binding for input to the dynamically built materialX document. + // Note: Values are applied seperately and not built into the document. + parameterBindingStr += + Aurora::Foundation::sFormat(paramBindingTemplate, auroraName.c_str(), "float"); + } + }; + + // Parameter information struct used by translation callback. + struct ParameterInfo + { + TfToken token; + std::string auroraName; + std::function converter; + }; + + // A generic lambda function to translate properties from hydra to Aurora + // token is the name of the Hydra value to get + // auroraName is the name of the Aurora parameter to set + // setValueFn is the function that converts the type specific VtValue + // to an appropriate Aurora function call. + auto translate = [&network, &nodeGraphsStr, ¶meterBindingStr, &ngIndex, this]( + const ParameterInfo& parameterInfo) { + if (network.surfaceNode->parameters.find(parameterInfo.token) != + network.surfaceNode->parameters.end()) + { + // Set HdAurora's material property using the property converter + VtValue& colorVal = network.surfaceNode->parameters[parameterInfo.token]; + if (parameterInfo.converter) + parameterInfo.converter(parameterBindingStr, colorVal, parameterInfo.auroraName); + } + else + { + static const TfToken fileTok("file"); + + // If no parameter look for a output connected to the input for this node. + std::string paramOutputName = + network.surfaceName + ":" + parameterInfo.token.GetString(); + auto relIter = network.relationships.find(paramOutputName); + if (relIter != network.relationships.end()) + { + const HdMaterialRelationship* paramRelationship = relIter->second; + // Get the file node connected to parameter being translated/ + auto nodeIter = network.nodeMap.find(paramRelationship->inputId.GetString()); + if (nodeIter != network.nodeMap.end()) + { + auto fileNode = nodeIter->second; + // Try and get the file parameter + auto paramIter = fileNode->parameters.find(fileTok); + if (paramIter != fileNode->parameters.end() && + paramIter->second.IsHolding()) + { + // Get the texture path from the node. + SdfAssetPath texturePath = paramIter->second.Get(); + string filename = texturePath.GetResolvedPath(); + + // Keep track of unique materials and node graphs. + string indexStr = std::to_string(ngIndex++); + + // Set the Aurora image property + string paramName = parameterInfo.auroraName + "_image"; + if (_supportedimages.find(paramName) != _supportedimages.end()) + { + + // Choose type based on outputName + string txtType = "color3"; + auto outputName = paramRelationship->outputName.GetString(); + + if (outputName.compare("normal") == 0) + { + txtType = "vector3"; + // Add the texture nodegraph and the input binding for each textured + // input to the dynamically built materialX document. + nodeGraphsStr += Aurora::Foundation::sFormat( + nodeGraphNormalMapTemplate, indexStr.c_str(), paramName.c_str(), + paramName.c_str(), paramName.c_str(), paramName.c_str()); + } + else + { + if (outputName.compare("diffuseColor") == 0) + txtType = "color3"; + else + txtType = "float"; + + // Add the texture nodegraph and the input binding for each textured + // input to the dynamically built materialX document. + nodeGraphsStr += Aurora::Foundation::sFormat(nodeGraphImageTemplate, + indexStr.c_str(), paramName.c_str(), txtType.c_str(), + txtType.c_str(), paramName.c_str()); + } + parameterBindingStr += Aurora::Foundation::sFormat( + nodeGraphBindingTemplate, parameterInfo.auroraName.c_str(), + txtType.c_str(), indexStr.c_str()); + } + } + } + } + } + }; + + // Specify the named pair of parameters to translate from Hydra to Aurora + // and the translation function to use for the parameter. + // { Hydra token, Aurora name (standard_surface), translation function } + // clang-format off + static const std::vector parameterMap = + { + { TfToken("diffuseColor"), "base_color", setF3Value }, + { TfToken("specularColor"), "specular_color", setF3Value }, + { TfToken("roughness"), "specular_roughness", setF1Value }, + { TfToken("ior"), "specular_IOR", setF1Value }, + { TfToken("emissiveColor"), "emission_color", setF3Value }, + { TfToken("opacity"), "transmission", setF1Value },// Map UsdPreviewSurface opacity to standard surface transmission. + { TfToken("clearcoat"), "coat", setF1Value }, + { TfToken("clearcoatColor"), "coat_color", setF3Value }, + { TfToken("clearcoatRoughness"), "coat_roughness", setF1Value }, + { TfToken("metallic"), "metalness", setF1Value }, + { TfToken("normal"), "normal", nullptr } // No converter funciton, can only be a texture. + }; + // clang-format on + + // translate all the supported parameters in the map declared above + for (auto paramInfo : parameterMap) + translate(paramInfo); + + // Build the MaterialX document by string. + materialXDocumentOut = Aurora::Foundation::sFormat( + materialDefinitionTemplate, nodeGraphsStr.c_str(), parameterBindingStr.c_str()); + + return true; +} + +bool HdAuroraMaterial::ApplyHDNetwork(const ProcessedMaterialNetwork& network) +{ + // Float3 setter function to convert Hydra GfVec3f values to Aurora values + auto setF3Value = [](Aurora::Properties& materialProperties, const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + GfVec3f val = vtVal.UncheckedGet(); + materialProperties[auroraName] = GfVec3ToGLM(&val); + } + }; + + // Setter to apply float1 to float3 Aurora value. + [[maybe_unused]] auto setF1ToF3Value = [](Aurora::Properties& materialProperties, + const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + float val = vtVal.UncheckedGet(); + materialProperties[auroraName] = glm::vec3(val, val, val); + } + }; + + // Setter to apply UsdPreviewSurface opacity to statndard surface transmission. + auto setTransmissionFromOpacity = [](Aurora::Properties& materialProperties, + const VtValue& vtVal, const std::string& auroraName) { + if (vtVal.IsHolding()) + { + float val = vtVal.UncheckedGet(); + materialProperties[auroraName] = 1.0f - val; // Transmission is one minux opacity. + } + }; + + // Custom setter function to convert Hydra Preview Surface specular color to SS. + static auto setSpecularColor = [](Aurora::Properties& materialProperties, const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + GfVec3f val = vtVal.UncheckedGet(); + val[0] = min(std::sqrt(std::sqrt(val[0])) * 2.0f, 1.0f); + val[1] = min(std::sqrt(std::sqrt(val[1])) * 2.0f, 1.0f); + val[2] = min(std::sqrt(std::sqrt(val[2])) * 2.0f, 1.0f); + materialProperties[auroraName] = GfVec3ToGLM(&val); + } + }; + + // Float setter function to convert Hydra float values to Aurora values + static auto setF1Value = [](Aurora::Properties& materialProperties, const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + float val = vtVal.UncheckedGet(); + materialProperties[auroraName] = val; + } + }; + + // Parameter information struct used by apply callback. + struct ParameterInfo + { + TfToken token; + std::string auroraName; + std::function converter; + }; + + Aurora::Properties materialProperties; + + // A generic lambda function to translate properties from Hydra to Aurora + // token is the name of the Hydra value to get + // auroraName is the name of the Aurora parameter to set + // setValueFn is the function that converts the type specific VtValue + // to an appropriate Aurora function call. + auto apply = [&network, &materialProperties, this](const ParameterInfo& parameterInfo) { + if (network.surfaceNode->parameters.find(parameterInfo.token) != + network.surfaceNode->parameters.end()) + { + // Set HdAurora's material property using the property converter + VtValue& colorVal = network.surfaceNode->parameters[parameterInfo.token]; + if (parameterInfo.converter) + parameterInfo.converter(materialProperties, colorVal, parameterInfo.auroraName); + } + else + { + static const TfToken fileTok("file"); + + // If no parameter look for a output connected to the input for this node. + std::string paramOutputName = + network.surfaceName + ":" + parameterInfo.token.GetString(); + auto relIter = network.relationships.find(paramOutputName); + if (relIter != network.relationships.end()) + { + const HdMaterialRelationship* paramRelationship = relIter->second; + // Get the file node connected to parameter being translated/ + auto nodeIter = network.nodeMap.find(paramRelationship->inputId.GetString()); + if (nodeIter != network.nodeMap.end()) + { + auto fileNode = nodeIter->second; + // Try and get the file parameter + auto paramIter = fileNode->parameters.find(fileTok); + if (paramIter != fileNode->parameters.end() && + paramIter->second.IsHolding()) + { + // Get the texture path from the node. + SdfAssetPath texturePath = paramIter->second.Get(); + string filename = texturePath.GetResolvedPath(); + + // Set the Aurora image property + string paramName = parameterInfo.auroraName + "_image"; + if (_supportedimages.find(paramName) != _supportedimages.end()) + { + bool forceLinear = parameterInfo.auroraName.compare("normal") == 0; + materialProperties[paramName] = + _owner->imageCache().acquireImage(filename, false, forceLinear); + } + } + } + } + } + }; + + // Specify the named pair of parameters to translate from Hydra to Aurora + // and the translation function to use for the parameter. + // { Hydra token, Aurora name (standard_surface), translation function } + // clang-format off + static const std::vector parameterMap = + { + { TfToken("diffuseColor"), "base_color", setF3Value }, + { TfToken("specularColor"), "specular_color", setSpecularColor }, + { TfToken("roughness"), "specular_roughness", setF1Value }, + { TfToken("ior"), "specular_IOR", setF1Value }, + { TfToken("emissiveColor"), "emission_color", setF3Value }, + { TfToken("opacity"), "transmission", setTransmissionFromOpacity },// Map UsdPreviewSurface opacity to standard surface transmission. + { TfToken("clearcoat"), "coat", setF1Value }, + { TfToken("clearcoatColor"), "coat_color", setF3Value }, + { TfToken("clearcoatRoughness"), "coat_roughness", setF1Value }, + { TfToken("metallic"), "metalness", setF1Value }, + { TfToken("normal"), "normal", nullptr } + }; + // clang-format on + + // translate all the supported parameters in the map declared above + for (auto paramInfo : parameterMap) + apply(paramInfo); + + _owner->GetScene()->setMaterialProperties(_auroraMaterialPath, materialProperties); + + return true; +} + +bool HdAuroraMaterial::ProcessHDMaterialNetwork(VtValue& materialValue) +{ + // Get the network map associated with this Hydra material. + if (!materialValue.IsHolding()) + return false; + auto networkMap = materialValue.UncheckedGet(); + + // Process the Hydra material network. + ProcessedMaterialNetwork processedNetwork; + + // Get terminals for the network. + if (networkMap.terminals.size() == 0) + { + TF_WARN("No terminals in material network map for material " + GetId().GetString()); + return false; + } + + // The first terminal is assumed to be the name of the surface node in the network + auto outputTerminal = networkMap.terminals[0]; + processedNetwork.surfaceName = outputTerminal.GetString(); + + // Get the network for surface (not to be confused with the surface node) + auto networkIter = networkMap.map.find(pxr::HdMaterialTerminalTokens->surface); + if (networkIter == networkMap.map.end()) + { + TF_WARN("No surface network in materal network map for material " + GetId().GetString()); + return false; + } + auto network = networkIter->second; + + // GAM TODO: We should be able to cache a lot of this stuff so we aren't rebuilding all these + // structures every time you set a color Build lookup table of nodes in network + for (size_t n = 0; n < network.nodes.size(); n++) + { + HdMaterialNode& node = network.nodes[n]; + processedNetwork.nodeMap[node.path.GetString()] = &network.nodes[n]; + } + // Build lookup table of relationships in network + for (size_t i = 0; i < network.relationships.size(); i++) + { + auto rel = network.relationships[i]; + // Combine output ID and name to create key + processedNetwork + .relationships[rel.outputId.GetString() + ":" + rel.outputName.GetString()] = + &network.relationships[i]; + } + + // Get the node for surface itself + processedNetwork.surfaceNode = processedNetwork.nodeMap[outputTerminal.GetString()]; + if (!processedNetwork.surfaceNode) + { + TF_WARN( + "No surface node for output terminal in network for material " + GetId().GetString()); + return false; + } + + // Build a materialX document string from the processed network. + string mtlXDocument; + BuildMaterialXDocumentFromHDNetwork(processedNetwork, mtlXDocument); + + // Setup the material from the materialX document. This will recreate the material if there are + // any changes. + SetupAuroraMaterial(Aurora::Names::MaterialTypes::kMaterialX, mtlXDocument); + + // Setup the material from the materialX document, setting the parameters on the Aurora + // material. + ApplyHDNetwork(processedNetwork); + + return true; +} diff --git a/Libraries/HdAurora/HdAuroraMaterial.h b/Libraries/HdAurora/HdAuroraMaterial.h new file mode 100644 index 0000000..a3a8633 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraMaterial.h @@ -0,0 +1,80 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +class HdAuroraRenderDelegate; +struct ProcessedMaterialNetwork; + +// Macros to convert between USD GF vector/matrix types and GLM. +#define GfVec3ToGLM(_gfVecPtr) (glm::make_vec3(reinterpret_cast(_gfVecPtr))) +#define GfVec4ToGLM(_gfVecPtr) (glm::make_vec4(reinterpret_cast(_gfVecPtr))) +#define GfVec4ToGLMVec3(_gfVecPtr) (glm::make_vec3(reinterpret_cast(_gfVecPtr))) +#define GfMatrix4fToGLM(_gfMtxPtr) (glm::make_mat4(reinterpret_cast(_gfMtxPtr))) +#define GLMMat4ToGF(_glmMtxPtr) (*reinterpret_cast(_glmMtxPtr)) + +class HdAuroraMaterial : public HdMaterial +{ +public: + HdAuroraMaterial(SdfPath const& sprimId, HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraMaterial() override; + void initialize(HdSceneDelegate* delegate); + + HdDirtyBits GetInitialDirtyBitsMask() const override; + void Sync( + HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override; + + // Comvert the provide Hydra Material ID to Aurora Path. + static const Aurora::Path GetAuroraMaterialPath(const pxr::SdfPath& mtlId) + { + return "HdAuroraMaterial/" + mtlId.GetAsString(); + } + +private: + friend class HdAuroraMesh; + + // Process the Hydra material for this material object + void ProcessHDMaterial(HdSceneDelegate* delegate); + // Process the Hydra material network for this material object + bool ProcessHDMaterialNetwork(VtValue& materialValue); + // Build a MaterialX document string from a Hydra material network. + bool BuildMaterialXDocumentFromHDNetwork( + const ProcessedMaterialNetwork& network, string& materialXDocumentOut); + // Apply the material from Hydra material network to current Aurora material. + bool ApplyHDNetwork(const ProcessedMaterialNetwork& network); + // Get the MaterialX document or path associated with the material in Hydra. + bool GetHDMaterialXDocument( + HdSceneDelegate* pDelegate, string& materialTypeOut, string& documentOut); + // Create a new aurora material with this material type and document, if required. + // Does nothing if current material type and document match the ones provided. + bool SetupAuroraMaterial(const string& materialType, const string& materialDocument); + + Aurora::Path _auroraMaterialPath; + HdAuroraRenderDelegate* _owner; + + // Current Aurora material type. + std::string _auroraMaterialType = ""; + // Hash current Aurora material document (store only hash to avoid keeping multiple copies of + // large documents). + size_t _auroraMaterialDocumentHash = 0; + + const set _supportedimages = { + "base_color_image", + "opacity_image", + "specular_roughness_image", + "normal_image", + }; +}; diff --git a/Libraries/HdAurora/HdAuroraMesh.cpp b/Libraries/HdAurora/HdAuroraMesh.cpp new file mode 100644 index 0000000..2510bf0 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraMesh.cpp @@ -0,0 +1,804 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraInstancer.h" +#include "HdAuroraMaterial.h" +#include "HdAuroraMesh.h" +#include "HdAuroraRenderDelegate.h" +#include "HdAuroraRenderPass.h" +#include "HdAuroraTokens.h" + +// If true extra validation done on the HDMesh geometry upon loading. +#define VALIDATE_HDMESH 1 + +// Vertex data for mesh, passed to Aurora getVertexData callback. Deleted once vertex update is +// complete. +struct HdAuroraMeshVertexData +{ + VtVec3fArray points; + VtVec3fArray normals; + VtVec2fArray uvs; + VtVec3iArray triangulatedIndices; + VtVec3fArray flattenedPoints; + VtVec3fArray flattenedNormals; + VtVec2fArray flattenedUVs; + const VtArray* st; + bool hasNormals; + bool hasTexCoords; +}; + +bool HdAuroraMesh::validateIndices(const VtVec3iArray& indices, int maxAttrCount) +{ + uint32_t maxVert = static_cast(maxAttrCount); + for (size_t j = 0; j < indices.size(); ++j) + { + auto& tri = indices[j]; + // If any of the indices overrun the attribute array completely then skip mesh + if (static_cast(tri[0]) >= maxVert || (uint32_t)tri[1] >= maxVert || + (uint32_t)tri[2] >= maxVert) + { + TF_RUNTIME_ERROR( + "Invalid index for triangle " + std::to_string(j) + " for " + GetId().GetString()); + return false; + } + } + return true; +} + +HdAuroraMesh::HdAuroraMesh(SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : + HdMesh(rprimId), _owner(renderDelegate) +{ +} + +HdAuroraMesh::~HdAuroraMesh() +{ + _owner->SetSampleRestartNeeded(true); + ClearAuroraInstances(); +} + +void HdAuroraMesh::ClearAuroraInstances() +{ + _owner->GetScene()->removeInstances(_auroraInstances); + + _auroraInstances.clear(); +} + +HdDirtyBits HdAuroraMesh::GetInitialDirtyBitsMask() const +{ + return HdChangeTracker::AllSceneDirtyBits; +} + +void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) +{ + SdfPath id = GetId(); + + // Skip adding an Aurora instance if the object is hidden, or there are no indices for it. + if (!delegate->GetVisible(GetId())) + { + if (_instanceData.size()) + { + // Lock the renderer mutex before accessing Aurora renderer. + std::lock_guard rendererLock(_owner->rendererMutex()); + // Remove any existing instances. + ClearAuroraInstances(); + } + + return; + } + + // Create vertex data object, that will exist until the renderer has read the vertex data. + _pVertexData = make_unique(); + _pVertexData->points = delegate->Get(id, HdTokens->points).Get(); + _pVertexData->normals = delegate->Get(id, HdTokens->normals).Get(); + _pVertexData->uvs = delegate->Get(id, pxr::TfToken("map1")).Get(); + + // Sample code for extracting extra uv set + // The name for the base uv set is st, other uv sets (st0, st1, etc.) + HdPrimvarDescriptorVector pvs = + delegate->GetPrimvarDescriptors(id, HdInterpolation::HdInterpolationVertex); + + // Compute the triangulated indices from the mesh topology. + HdMeshTopology topology = delegate->GetMeshTopology(id); + HdMeshUtil meshUtil(&topology, id); + VtIntArray trianglePrimitiveParams; + meshUtil.ComputeTriangleIndices(&_pVertexData->triangulatedIndices, &trianglePrimitiveParams); + + bool hasFaceVaryingNormals = false; + bool hasFaceVaryingSTs = false; + bool hasFaceVaryings = false; + + // If we don't have per-vertex UVs look for STs in primvars + VtValue stVal; + if (_pVertexData->uvs.size() != _pVertexData->points.size()) + { + if (readSTs(&stVal, delegate, meshUtil)) + { + if (stVal.GetArraySize() == _pVertexData->triangulatedIndices.size() * 3) + { + hasFaceVaryings = true; + hasFaceVaryingSTs = true; + _pVertexData->st = &stVal.Get>(); + if (!_pVertexData->st) + TF_WARN("Failed to get STs for " + GetId().GetString()); + } + else + TF_WARN("Triangulated ST array length does not match index count for " + + GetId().GetString()); + } + // If failed to get face varyings STs just remove the UVs completely. + if (!hasFaceVaryingSTs) + _pVertexData->uvs.clear(); + } + + // Does the mesh have normals? + _pVertexData->hasNormals = !_pVertexData->normals.empty() && + // This is what is received from USD for geometry that has no normals + !(_pVertexData->normals.size() == 1 && _pVertexData->normals[0][0] == 0.0f && + _pVertexData->normals[0][1] == 0.0f && _pVertexData->normals[0][2] == 0.0f); + + // Does the mesh have texture coordinates (UVs or STs)? + _pVertexData->hasTexCoords = _pVertexData->uvs.size() || _pVertexData->st; + + int minAttrCount = static_cast(_pVertexData->points.size()); + int maxAttrCount = minAttrCount; + + // If there are valid normals but normal and position attribute arrays are not the same + // clamp indices to smallest value + VtValue pvNormals; + if (_pVertexData->hasNormals && _pVertexData->normals.size() != _pVertexData->points.size()) + { + HdPrimvarDescriptorVector fpvs = + delegate->GetPrimvarDescriptors(id, HdInterpolation::HdInterpolationFaceVarying); + + // We should be looking at the material network to see which primvar is use as texcoord, but + // for now just look for a primvar called "st" + size_t stIdx; + for (stIdx = 0; stIdx < fpvs.size(); stIdx++) + { + if (fpvs[stIdx].name == HdTokens->normals) + break; + } + + if (stIdx != fpvs.size()) + { + + HdPrimvarDescriptor pv = fpvs[stIdx]; + + // Get primvar for STs + auto triNormals = GetPrimvar(delegate, pv.name); + HdVtBufferSource buffer(pv.name, triNormals); + int count = (int)buffer.GetNumElements(); + // Triangulate the STs (should produce an ST for each index) + meshUtil.ComputeTriangulatedFaceVaryingPrimvar( + buffer.GetData(), count, buffer.GetTupleType().type, &pvNormals); + + if (pvNormals.GetArraySize() == _pVertexData->triangulatedIndices.size() * 3) + { + _pVertexData->flattenedNormals = pvNormals.Get(); + hasFaceVaryingNormals = true; + hasFaceVaryings = true; + } + else + TF_WARN("Triangulated normal array length does not match index count for " + + GetId().GetString()); + } + if (!hasFaceVaryingNormals) + { + if (_pVertexData->normals.size() < _pVertexData->points.size()) + { + TF_WARN("Normal array smaller than points array: " + GetId().GetString()); + while (_pVertexData->normals.size() < _pVertexData->points.size()) + { + GfVec3f lv = _pVertexData->normals[_pVertexData->normals.size() - 1]; + _pVertexData->normals.push_back(lv); + } + } + else + { + TF_WARN("Normal array smaller than points array: " + GetId().GetString()); + while (_pVertexData->normals.size() > _pVertexData->points.size()) + { + GfVec3f pv = _pVertexData->points[_pVertexData->points.size() - 1]; + _pVertexData->points.push_back(pv); + } + } + } + } + + if (hasFaceVaryings) + { + size_t numFlattenedVerts = _pVertexData->triangulatedIndices.size() * 3; + _pVertexData->flattenedPoints.resize(numFlattenedVerts); + if (_pVertexData->hasNormals && !hasFaceVaryingNormals) + _pVertexData->flattenedNormals.resize(numFlattenedVerts); + if (_pVertexData->uvs.size() && !hasFaceVaryingSTs) + _pVertexData->flattenedUVs.resize(numFlattenedVerts); + + // STs are flattened so we must flatten all the attribute data + // GAM TODO: Could do a lookup here to find matching vertex data and create a new index + // array (but it will be expensive) + for (size_t j = 0; j < _pVertexData->triangulatedIndices.size(); ++j) + { + auto& tri = _pVertexData->triangulatedIndices[j]; + // If any of the indices overrun the attribute array completely then skip mesh + if (tri[0] >= maxAttrCount || tri[1] >= maxAttrCount || tri[2] >= maxAttrCount) + { + TF_RUNTIME_ERROR("Invalid index for triangle " + std::to_string(j) + " for " + + GetId().GetString()); + } + + // Clamp to the minimum attribute count (so something will render without crashing + // if there is mis-match between point and normal array length) + int t0 = std::min(minAttrCount - 1, tri[0]); + int t1 = std::min(minAttrCount - 1, tri[1]); + int t2 = std::min(minAttrCount - 1, tri[2]); + + _pVertexData->flattenedPoints[j * 3 + 0] = _pVertexData->points[t0]; + _pVertexData->flattenedPoints[j * 3 + 1] = _pVertexData->points[t1]; + _pVertexData->flattenedPoints[j * 3 + 2] = _pVertexData->points[t2]; + if (_pVertexData->hasNormals && !hasFaceVaryingNormals) + { + _pVertexData->flattenedNormals[j * 3 + 0] = _pVertexData->normals[t0]; + _pVertexData->flattenedNormals[j * 3 + 1] = _pVertexData->normals[t1]; + _pVertexData->flattenedNormals[j * 3 + 2] = _pVertexData->normals[t2]; + }; + if (_pVertexData->uvs.size() && !hasFaceVaryingSTs) + { + _pVertexData->flattenedUVs[j * 3 + 0] = _pVertexData->uvs[t0]; + _pVertexData->flattenedUVs[j * 3 + 1] = _pVertexData->uvs[t1]; + _pVertexData->flattenedUVs[j * 3 + 2] = _pVertexData->uvs[t2]; + } + } + } + else + { + // If VALIDATE_HDMESH is set do extra validation on the geometry. + if (VALIDATE_HDMESH && !validateIndices(_pVertexData->triangulatedIndices, maxAttrCount)) + return; + + // Can't handle this case unless we are flattening, so ignore this geometry (will have + // warned further up) + if (maxAttrCount != minAttrCount) + return; + + // Can't handle this case since index is int type. + if (_pVertexData->points.size() > INT_MAX) + { + TF_RUNTIME_ERROR("Invalid vertex count " + std::to_string(_pVertexData->points.size()) + + " for " + GetId().GetString()); + return; + } + } + + // Create a geometry descriptor for mesh's geometry. + Aurora::GeometryDescriptor geomDesc; + + // Fill default vertex attributes. + geomDesc.vertexDesc.attributes = { + { Aurora::Names::VertexAttributes::kPosition, Aurora::AttributeFormat::Float3 }, + }; + + // Set normals attibute type, if the mesh has them. + if (_pVertexData->hasNormals) + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kNormal] = + Aurora::AttributeFormat::Float3; + + // Set texcoord attibute type, if the mesh has them. + if (_pVertexData->hasTexCoords) + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTexCoord0] = + Aurora::AttributeFormat::Float2; + + // Set up index and vertex count. + int numVertices = static_cast(_pVertexData->points.size()); + int numIndices = static_cast(_pVertexData->triangulatedIndices.size() * 3); + geomDesc.indexCount = _pVertexData->st ? 0ul : numIndices; // No indices if we have STs. + geomDesc.vertexDesc.count = + hasFaceVaryings ? numIndices : numVertices; // Vertices are flattened if we have STs. + + // Setup vertex attribute callback to read vertex and index data. + // This will be called when the geometry is added to scene via instance. + geomDesc.getAttributeData = [this, hasFaceVaryings, hasFaceVaryingSTs]( + Aurora::AttributeDataMap& dataOut, size_t firstVertex, + size_t vertexCount, size_t firstIndex, size_t indexCount) { + // Sanity check, ensure we are not doing a partial update (not actually implemented yet) + AU_ASSERT(firstVertex == 0, "Partial update not supported"); + // Ensure we still have vertex data, if not then this goemetry has been activated, + // deactivated and then reactivated. + // TODO: Support this case. + AU_ASSERT(_pVertexData, "No vertex data for mesh %s, reactivation not supported.", + GetId().GetString().c_str()); + + // We have very different geometry layout if we have STs. + if (hasFaceVaryings) + { + // Sanity check, ensure we are not doing a partial update (not actually implemented yet) + AU_ASSERT(vertexCount == _pVertexData->flattenedPoints.size(), + "Partial update not supported"); + + // Use the flattened positions and normals. + dataOut[Aurora::Names::VertexAttributes::kPosition].address = + &_pVertexData->flattenedPoints[0]; + dataOut[Aurora::Names::VertexAttributes::kPosition].stride = sizeof(GfVec3f); + if (_pVertexData->hasNormals) + { + dataOut[Aurora::Names::VertexAttributes::kNormal].address = + &_pVertexData->flattenedNormals[0]; + dataOut[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(GfVec3f); + } + + // Use the STs. + if (hasFaceVaryingSTs) + { + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].address = + _pVertexData->st->data(); + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].stride = sizeof(GfVec2f); + } + else if (_pVertexData->flattenedUVs.size()) + { + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].address = + &_pVertexData->flattenedUVs[0]; + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].stride = sizeof(GfVec2f); + } + + // No indices for ST case. + } + else + { + // Ensure we are not doing a partial update (not actually implemented yet) + AU_ASSERT(vertexCount == _pVertexData->points.size(), "Partial update not supported"); + + // Get position, texcoords, and normals directly from Hydra mesh. + dataOut[Aurora::Names::VertexAttributes::kPosition].address = &_pVertexData->points[0]; + dataOut[Aurora::Names::VertexAttributes::kPosition].stride = sizeof(GfVec3f); + if (_pVertexData->hasNormals) + { + dataOut[Aurora::Names::VertexAttributes::kNormal].address = + &_pVertexData->normals[0]; + dataOut[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(GfVec3f); + } + if (_pVertexData->uvs.size()) + { + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].address = + &_pVertexData->uvs[0]; + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].stride = sizeof(GfVec2f); + } + + // Use the triangulated indices computed above. + AU_ASSERT(firstIndex == 0, "Partial update not supported"); + AU_ASSERT(indexCount == _pVertexData->triangulatedIndices.size() * 3, + "Partial update not supported"); + dataOut[Aurora::Names::VertexAttributes::kIndices].address = + &_pVertexData->triangulatedIndices[0]; + dataOut[Aurora::Names::VertexAttributes::kIndices].stride = sizeof(uint32_t); + } + + return true; + }; + + // Setup completion callback to release data when vertex data has been read. + // This will be called when the render is done acccesing vertex attributes. + geomDesc.attributeUpdateComplete = [this](const Aurora::AttributeDataMap&, size_t, size_t, + size_t, size_t) { _pVertexData.reset(); }; + + // Actually create the instances in the renderer. + { + // Lock the renderer mutex before accessing Aurora renderer. + std::lock_guard rendererLock(_owner->rendererMutex()); + + // Process material layers. + // TODO: Some of this can be done outside mutex lock. + auto materialLayersVal = delegate->Get(GetId(), HdAuroraTokens::kMeshMaterialLayers); + vector materialLayerPaths; + vector geometryLayerPaths; + if (!materialLayersVal.IsEmpty()) + { + auto geomtryLayerUVsVal = delegate->Get(GetId(), HdAuroraTokens::kMeshGeometryLayerUVs); + // Get the Hydra ID for material layer. + auto layerArray = materialLayersVal.Get(); + _layerUVData = vector(layerArray.size()); + for (size_t layerIdx = 0; layerIdx < layerArray.size(); layerIdx++) + { + // Convert Hydra material ID to Aurora path, + auto& auroraMtlPath = HdAuroraMaterial::GetAuroraMaterialPath(layerArray[layerIdx]); + // Add path to vector. + materialLayerPaths.push_back(auroraMtlPath); + + // Get the geometry layer UVs. + Aurora::Path geomLayerPath = ""; + + // Value passed to Hydra is primvar name. + auto uvPrimVarArray = geomtryLayerUVsVal.Get(); + if (uvPrimVarArray.size() > layerIdx) + { + // UV primvar name. + pxr::TfToken primvarName = uvPrimVarArray[layerIdx]; + + // Get the primvar given by name. + auto layerUVsVal = delegate->Get(id, primvarName); + + if (layerUVsVal.IsEmpty()) + { + AU_ERROR("Invalid layer UV%d primvar %s: Primvar not found.", layerIdx, + primvarName.GetString().c_str()); + } + else + { + auto layerUVs = layerUVsVal.Get(); + // Create an Aurora geometry object for layer UVs, if any. + if (layerUVs.size() != geomDesc.vertexDesc.count) + { + AU_ERROR( + "Invalid layer UV%d primvar %s: Array length %d does not match " + "base mesh vertex count of %d", + layerIdx, primvarName.GetString().c_str(), layerUVs.size(), + geomDesc.vertexDesc.count); + } + else + { + // Create path for layer geometry. + geomLayerPath = + GetId().GetAsString() + "/GeometryLayer" + to_string(layerIdx); + _layerUVData[layerIdx] = layerUVs; + + // Create descriptor for layer. + Aurora::GeometryDescriptor layerGeomDesc; + + // Geometry is incomplete, only containing UVs. + layerGeomDesc.vertexDesc.attributes = { + { Aurora::Names::VertexAttributes::kTexCoord0, + Aurora::AttributeFormat::Float2 }, + }; + layerGeomDesc.indexCount = 0ul; // No indices. + layerGeomDesc.vertexDesc.count = layerUVs.size(); + + // Setup vertex attribute callback to read vertex data. + // This will be called when the geometry is added to scene via + // instance. + layerGeomDesc.getAttributeData = + [this, layerIdx](Aurora::AttributeDataMap& dataOut, + size_t /* firstVertex*/, size_t /* vertexCount*/, + size_t /* firstIndex*/, size_t /* indexCount*/) { + // Set the UVs for layer only. + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].address = + &(_layerUVData[layerIdx][0]); + dataOut[Aurora::Names::VertexAttributes::kTexCoord0].stride = + sizeof(GfVec2f); + + return true; + }; + + // Setup completion callback to release data when UV data has been + // read. This will be called when the render is done acccesing + // vertex attributes. + layerGeomDesc.attributeUpdateComplete = + [this, layerIdx](const Aurora::AttributeDataMap&, size_t, size_t, + size_t, size_t) { _layerUVData[layerIdx] = {}; }; + + _owner->GetScene()->setGeometryDescriptor(geomLayerPath, layerGeomDesc); + } + } + } + + geometryLayerPaths.push_back(geomLayerPath); + } + } + // Update bounds with this mesh. + UpdateAuroraSceneBounds(_pVertexData->points); + + // Remove any existing instances. + ClearAuroraInstances(); + + // Update the Aurora material path for the mesh's material. + UpdateAuroraMaterialPath(); + + // Set the material and object ID for all the instances. + for (auto& instData : _instanceData) + { + instData.properties[Aurora::Names::InstanceProperties::kObjectID] = GetPrimId(); + instData.properties[Aurora::Names::InstanceProperties::kMaterial] = _auroraMaterialPath; + instData.properties[Aurora::Names::InstanceProperties::kMaterialLayers] = + materialLayerPaths; + instData.properties[Aurora::Names::InstanceProperties::kGeometryLayers] = + geometryLayerPaths; + } + + // Set the geometry descriptor for the instances. Will create it if it doesn't exist. + Aurora::Path geomPath = id.GetString() + "_Geometry"; + _owner->GetScene()->setGeometryDescriptor(geomPath, geomDesc); + + // Create the instances (this will invoke attribute callbacks). + _auroraInstances = _owner->GetScene()->addInstances(geomPath, _instanceData); + } +} + +bool isMeshDirty(const HdDirtyBits* dirtyBits, const SdfPath& id) +{ + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->points) || + HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->normals) || + HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, pxr::TfToken("st")) || + HdChangeTracker::IsTopologyDirty(*dirtyBits, id)) + { + return true; + } + + if (HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->displayColor) || + (*dirtyBits & pxr::HdChangeTracker::DirtyMaterialId) || + HdChangeTracker::IsVisibilityDirty(*dirtyBits, id)) + { + return true; + } + + return false; +} + +void HdAuroraMesh::UpdateAuroraMaterialPath() +{ + + if (!GetMaterialId().IsEmpty()) + { + // Get material path the Hydra material if it exists. + _auroraMaterialPath = HdAuroraMaterial::GetAuroraMaterialPath(GetMaterialId()); + if (_owner->GetScene()->getResourceType(_auroraMaterialPath) == + Aurora::ResourceType::Invalid) + { + // Create a default material if there is no material for this mesh at the . + _owner->GetScene()->setMaterialType( + _auroraMaterialPath, Aurora::Names::MaterialTypes::kBuiltIn); + TF_WARN("No material %s found in scene for mesh %s", _auroraMaterialPath.c_str(), + GetId().GetString().c_str()); + } + } + else + { + // Create default Aurora material if we don't have one. + if (_auroraDefaultMaterialPath.empty()) + { + // Set base color property on new default material (this will create it as it doesn't + // yet exist.) + _auroraDefaultMaterialPath = GetId().GetAsString() + "/DefaultMaterial"; + _owner->GetScene()->setMaterialProperties( + _auroraDefaultMaterialPath, { { "base_color", _displayColor } }); + } + + // Use default material for this mesh if there is no Hydra material. + _auroraMaterialPath = _auroraDefaultMaterialPath; + } +} + +bool HdAuroraMesh::readSTs(VtValue* stOut, HdSceneDelegate* delegate, HdMeshUtil& meshUtil) +{ + + SdfPath id = GetId(); + + HdPrimvarDescriptorVector pvs = + delegate->GetPrimvarDescriptors(id, HdInterpolation::HdInterpolationFaceVarying); + + // We should be looking at the material network to see which primvar is use as texcoord, but for + // now just look for a primvar called "st" + size_t stIdx; + for (stIdx = 0; stIdx < pvs.size(); stIdx++) + { + string primVarName = pvs[stIdx].name.GetString(); + if (primVarName.compare("st") == 0 || primVarName.substr(0, 3).compare("map") == 0) + break; + } + + if (stIdx == pvs.size()) + return false; + + HdPrimvarDescriptor pv = pvs[stIdx]; + + // Get primvar for STs + auto st = GetPrimvar(delegate, pv.name); + HdVtBufferSource buffer(pv.name, st); + + // Triangulate the STs (should produce an ST for each index) + return meshUtil.ComputeTriangulatedFaceVaryingPrimvar( + buffer.GetData(), (int)buffer.GetNumElements(), buffer.GetTupleType().type, stOut); +} + +void HdAuroraMesh::Sync(HdSceneDelegate* delegate, HdRenderParam* /* renderParam */, + HdDirtyBits* dirtyBits, TfToken const& /* reprToken */) +{ + const auto& id = GetId(); + _owner->SetSampleRestartNeeded(true); + + // Synchronize instancer. + _UpdateInstancer(delegate, dirtyBits); + HdInstancer::_SyncInstancerAndParents(delegate->GetRenderIndex(), GetInstancerId()); + + // Set the Hydra Mesh object's material ID if it does not match the value from the render + // delegate. + bool materialIDChanged = false; + auto newMatID = delegate->GetMaterialId(id); + if (newMatID != GetMaterialId()) + { + SetMaterialId(newMatID); + materialIDChanged = true; + } + + // Calculate dirty flags. + const bool meshDirty = isMeshDirty(dirtyBits, id); + const bool instancesDirty = HdChangeTracker::IsTransformDirty(*dirtyBits, id) || + HdChangeTracker::IsInstancerDirty(*dirtyBits, id); + const bool materialDirty = (*dirtyBits) & HdChangeTracker::RprimDirtyBits::DirtyMaterialId; + + if (_owner->GetRenderer() && (meshDirty || instancesDirty)) + { + // get mesh transform + GfMatrix4f meshTM = GfMatrix4f(delegate->GetTransform(id)); + + // get instance transforms + if (!GetInstancerId().IsEmpty()) + { + // retrieve instance transforms from the instancer. + VtMatrix4dArray transforms; + HdInstancer* instancer = delegate->GetRenderIndex().GetInstancer(GetInstancerId()); + transforms = + static_cast(instancer)->ComputeInstanceTransforms(GetId()); + + // apply instance transforms on top of mesh transforms + _instanceData.resize(transforms.size()); + for (size_t j = 0; j < transforms.size(); ++j) + { + GfMatrix4f xform = meshTM * GfMatrix4f(transforms[j]); + _instanceData[j].path = GetId().GetString() + "_Instance" + std::to_string(j); + _instanceData[j].properties[Aurora::Names::InstanceProperties::kTransform] = + GfMatrix4fToGLM(&xform); + } + } + else + { + // If there's no instancer, add a single instance with mesh transform. + _instanceData.resize(1); + _instanceData[0].path = GetId().GetString() + "_Instance"; + _instanceData[0].properties[Aurora::Names::InstanceProperties::kTransform] = + GfMatrix4fToGLM(&meshTM); + } + + // see if we need to add new instances + const bool addInstances = _auroraInstances.size() > _instanceData.size(); + + if (meshDirty || addInstances) + { + // Get mesh color (used by default material if no Hydra material for this mesh) + auto displayColorAttr = delegate->Get(id, HdTokens->displayColor); + if (displayColorAttr.IsArrayValued()) + { + VtVec3fArray dispColorArr = displayColorAttr.Get(); + _displayColor = GfVec3ToGLM(&dispColorArr[0]); + } + + // Rebuild the Aurora instances and geometry for this mesh. + RebuildAuroraInstances(delegate); + } + else if (instancesDirty) + { + // Lock the renderer mutex before accessing Aurora renderer. + std::lock_guard rendererLock(_owner->rendererMutex()); + + // size down (if necessary). + if (_instanceData.size() < _auroraInstances.size()) + { + Aurora::Paths staleInstances( + _auroraInstances.begin() + _instanceData.size(), _auroraInstances.end()); + _owner->GetScene()->removeInstances(staleInstances); + } + _auroraInstances.resize(_instanceData.size()); + + // update transforms only (as other properties have not changed) + for (size_t i = 0; i < _instanceData.size(); ++i) + { + _owner->GetScene()->setInstanceProperties(_auroraInstances[i], + { { Aurora::Names::InstanceProperties::kTransform, + _instanceData[i].properties.at( + Aurora::Names::InstanceProperties::kTransform) } }); + } + + // Update bounds with this mesh. + UpdateAuroraSceneBounds(delegate->Get(id, HdTokens->points).Get()); + } + else if (materialDirty || materialIDChanged) + { + // Lock the renderer mutex before accessing Aurora renderer. + std::lock_guard rendererLock(_owner->rendererMutex()); + + // Update the aurora material paths. + UpdateAuroraMaterialPath(); + + // Set the material for all the instances. + _owner->GetScene()->setInstanceProperties(_auroraInstances, + { { Aurora::Names::InstanceProperties::kMaterial, _auroraMaterialPath } }); + } + } + *dirtyBits &= ~HdChangeTracker::AllSceneDirtyBits; +} + +void HdAuroraMesh::UpdateAuroraSceneBounds(const VtVec3fArray& points) +{ + // Calculate mesh bounds in local space. + GfVec3f minBounds(+FLT_MAX, +FLT_MAX, +FLT_MAX); + GfVec3f maxBounds(-FLT_MAX, -FLT_MAX, -FLT_MAX); + for (size_t i = 0; i < points.size(); i++) + { + const GfVec3f& pnt = points[i]; + for (int j = 0; j < 3; j++) + { + minBounds[j] = std::min(minBounds[j], pnt[j]); + maxBounds[j] = std::max(maxBounds[j], pnt[j]); + } + } + + // Construct array of 8 bound vertices + GfVec3f boundPoints[8] = { { minBounds[0], minBounds[1], minBounds[2] }, + { minBounds[0], maxBounds[1], minBounds[2] }, { minBounds[0], minBounds[1], maxBounds[2] }, + { minBounds[0], maxBounds[1], maxBounds[2] }, { maxBounds[0], minBounds[1], minBounds[2] }, + { maxBounds[0], maxBounds[1], minBounds[2] }, { maxBounds[0], minBounds[1], maxBounds[2] }, + { maxBounds[0], maxBounds[1], maxBounds[2] } }; + + // Merge in world space bounds of every instance + minBounds = GfVec3f(+FLT_MAX, +FLT_MAX, +FLT_MAX); + maxBounds = GfVec3f(-FLT_MAX, -FLT_MAX, -FLT_MAX); + for (const auto& instanceData : _instanceData) + { + // Get the instance's transform. + glm::mat4 transformGlm = + instanceData.properties.at(Aurora::Names::InstanceProperties::kTransform).asMatrix4(); + + // Convert to GF matrix. + GfMatrix4f transform = GLMMat4ToGF(&transformGlm); + + // Transform corners of AABB + for (int i = 0; i < 8; i++) + { + const GfVec3f& pnt = transform.Transform(boundPoints[i]); + for (int j = 0; j < 3; j++) + { + minBounds[j] = std::min(minBounds[j], pnt[j]); + maxBounds[j] = std::max(maxBounds[j], pnt[j]); + } + } + } + + // Ensure bounds are valid. + for (int j = 0; j < 3; j++) + { + float diff = maxBounds[j] - minBounds[j]; + if (diff == 0.0f) + { + maxBounds[j] = minBounds[j] + FLT_EPSILON; + } + } + + // Update owner bounds + _owner->UpdateBounds(minBounds, maxBounds); +} + +HdDirtyBits HdAuroraMesh::_PropagateDirtyBits(HdDirtyBits bits) const +{ + return bits; +} + +void HdAuroraMesh::_InitRepr(TfToken const& reprToken, HdDirtyBits*) +{ + _ReprVector::iterator it = + std::find_if(_reprs.begin(), _reprs.end(), _ReprComparator(reprToken)); + if (it == _reprs.end()) + { + _reprs.emplace_back(reprToken, HdReprSharedPtr()); + } +} + +void HdAuroraMesh::Finalize(HdRenderParam* /* renderParam */) {} diff --git a/Libraries/HdAurora/HdAuroraMesh.h b/Libraries/HdAurora/HdAuroraMesh.h new file mode 100644 index 0000000..bd6d1a4 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraMesh.h @@ -0,0 +1,61 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +class HdAuroraMaterial; +class HdAuroraRenderDelegate; +struct HdAuroraMeshVertexData; +class HdAuroraMesh : public HdMesh +{ +public: + HdAuroraMesh(SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraMesh() override; + + HdDirtyBits GetInitialDirtyBitsMask() const override; + void Sync(HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits, + TfToken const& reprToken) override; + void Finalize(HdRenderParam* renderParam) override; + + void UpdateMaterialResources(HdAuroraMaterial* pMaterial, HdSceneDelegate* pDelegate); + +protected: + HdDirtyBits _PropagateDirtyBits(HdDirtyBits bits) const override; + void _InitRepr(TfToken const& reprToken, HdDirtyBits* dirtyBits) override; + +private: + void UpdateAuroraMaterialPath(); + + bool readSTs(VtValue* stOut, HdSceneDelegate* delegate, HdMeshUtil& meshUtil); + + void RebuildAuroraInstances(HdSceneDelegate* delegate); + + bool validateIndices(const VtVec3iArray& indices, int maxAttrCount); + + void ClearAuroraInstances(); + void UpdateAuroraSceneBounds(const VtVec3fArray& points); + + unique_ptr _pVertexData; + vector _layerUVData; + + HdAuroraRenderDelegate* _owner; + Aurora::IMaterialPtr _pAuroraMaterial; + + Aurora::Path _auroraMaterialPath; + Aurora::Path _auroraDefaultMaterialPath; + std::vector _auroraMaterialLayerPaths; + Aurora::Paths _auroraInstances; + Aurora::InstanceDefinitions _instanceData; + glm::vec3 _displayColor = { 1.f, 1.f, 1.f }; +}; diff --git a/Libraries/HdAurora/HdAuroraPlugin.cpp b/Libraries/HdAurora/HdAuroraPlugin.cpp new file mode 100644 index 0000000..2993721 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraPlugin.cpp @@ -0,0 +1,82 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraPlugin.h" +#include "HdAuroraRenderDelegate.h" + +// Always dump logs to VS debug console. +// TODO: Turned on for now for development purposes, we should turn off and use ASDK product tools +// for internal logs. +#define AU_DEV_ALWAYS_DUMP_LOGS 1 + +void setLogFunction() +{ + auto logCB = [](const std::string& file, int line, Aurora::Foundation::Log::Level level, + const std::string& msg) { + std::string fullMsg = file.empty() ? "" : (file + ", " + std::to_string(line) + ": "); + fullMsg += msg; + + if (AU_DEV_ALWAYS_DUMP_LOGS) + Aurora::Foundation::Log::writeToConsole(fullMsg); + + switch (level) + { + case Aurora::Foundation::Log::Level::kInfo: + TF_STATUS(fullMsg); + break; + case Aurora::Foundation::Log::Level::kWarn: + TF_WARN(fullMsg); + break; + case Aurora::Foundation::Log::Level::kError: + TF_RUNTIME_ERROR(fullMsg); + break; + case Aurora::Foundation::Log::Level::kFail: + TF_FATAL_ERROR(fullMsg); + break; + case Aurora::Foundation::Log::Level::kNone: + break; + } + + return false; + }; + + Aurora::logger().setLogFunction(logCB); + Aurora::Foundation::Log::logger().setLogFunction(logCB); +} + +HdRenderDelegate* HdAuroraRendererPlugin::CreateRenderDelegate() +{ + setLogFunction(); + + return new HdAuroraRenderDelegate(); +} + +HdRenderDelegate* HdAuroraRendererPlugin::CreateRenderDelegate( + HdRenderSettingsMap const& settingsMap) +{ + setLogFunction(); + + return new HdAuroraRenderDelegate(settingsMap); +} + +void HdAuroraRendererPlugin::DeleteRenderDelegate(HdRenderDelegate* renderDelegate) +{ + delete renderDelegate; +} + +bool HdAuroraRendererPlugin::IsSupported() const +{ + return true; +} diff --git a/Libraries/HdAurora/HdAuroraPlugin.h b/Libraries/HdAurora/HdAuroraPlugin.h new file mode 100644 index 0000000..388e328 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraPlugin.h @@ -0,0 +1,33 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +class HdAuroraRendererPlugin : public HdRendererPlugin +{ +public: + HdAuroraRendererPlugin() = default; + virtual ~HdAuroraRendererPlugin() = default; + + HdRenderDelegate* CreateRenderDelegate() override; + HdRenderDelegate* CreateRenderDelegate(HdRenderSettingsMap const& settingsMap) override; + + void DeleteRenderDelegate(HdRenderDelegate* renderDelegate) override; + + bool IsSupported() const override; + +private: + HdAuroraRendererPlugin(const HdAuroraRendererPlugin&) = delete; + HdAuroraRendererPlugin& operator=(const HdAuroraRendererPlugin&) = delete; +}; diff --git a/Libraries/HdAurora/HdAuroraRenderBuffer.cpp b/Libraries/HdAurora/HdAuroraRenderBuffer.cpp new file mode 100644 index 0000000..a808109 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraRenderBuffer.cpp @@ -0,0 +1,514 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HdAuroraRenderBuffer.h" +#include "HdAuroraRenderDelegate.h" + +#include +#include +#include +#include +#include +#include + +#include +static std::atomic gUniqueRenderBufferIdCounter(1); + +void HdAuroraPostPendingGLErrors() +{ + GLenum error; + // Protect from doing infinite looping when glGetError + // is called from an invalid context. + int watchDogCount = 0; + while ((watchDogCount++ < 256) && ((error = glGetError()) != GL_NO_ERROR)) + { + std::ostringstream errorMessage; + errorMessage << "GL error code: 0x" << std::hex << error << std::dec; + TF_RUNTIME_ERROR(errorMessage.str()); + } +} + +// An OpenGL GPU texture resource. +class HgiHdAuroraTextureGL final : public HgiTexture +{ +public: + HgiHdAuroraTextureGL(HgiTextureDesc const& desc, void* sharedHandle); + + ~HgiHdAuroraTextureGL() override; + + size_t GetByteSizeOfResource() const override + { + GfVec3i const& s = _descriptor.dimensions; + return HgiGetDataSizeOfFormat(_descriptor.format) * s[0] * s[1] * std::max(s[2], 1); + } + + uint64_t GetRawResource() const override { return (uint64_t)_textureId; } + + /// Returns the OpenGL id / name of the texture. + uint32_t GetTextureId() const { return _textureId; } + +private: + HgiHdAuroraTextureGL() = delete; + HgiHdAuroraTextureGL& operator=(const HgiHdAuroraTextureGL&) = delete; + HgiHdAuroraTextureGL(const HgiHdAuroraTextureGL&) = delete; + + uint32_t _textureId; +}; + +HgiHdAuroraTextureGL::HgiHdAuroraTextureGL( + HgiTextureDesc const& desc, [[maybe_unused]] void* sharedHandle) : + HgiTexture(desc), _textureId(0) +{ + if (desc.layerCount > 1) + { + // XXX Further below we are missing support for layered textures. + TF_CODING_ERROR("XXX Missing implementation for texture arrays"); + } + + GLenum glInternalFormat = 0; + + if (desc.usage & HgiTextureUsageBitsDepthTarget) + { + TF_VERIFY(desc.format == HgiFormatFloat32 || desc.format == HgiFormatFloat32UInt8); + + if (desc.format == HgiFormatFloat32UInt8) + { + glInternalFormat = GL_DEPTH32F_STENCIL8; + } + else + { + glInternalFormat = GL_DEPTH_COMPONENT32F; + } + } + else + { + glInternalFormat = GL_RGBA16F; + } + + if (desc.sampleCount == HgiSampleCount1) + { + glCreateTextures(GL_TEXTURE_2D, 1, &_textureId); + } + else + { + if (desc.type != HgiTextureType2D) + { + TF_CODING_ERROR("Only 2d multisample textures are supported"); + } + glCreateTextures(GL_TEXTURE_2D_MULTISAMPLE, 1, &_textureId); + } + + if (!_descriptor.debugName.empty()) + { + glObjectLabel(GL_TEXTURE, _textureId, -1, _descriptor.debugName.c_str()); + } + + if (desc.sampleCount == HgiSampleCount1) + { + // XXX sampler state etc should all be set via tex descriptor. + // (probably pass in HgiSamplerHandle in tex descriptor) + glTextureParameteri(_textureId, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(_textureId, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(_textureId, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + const uint16_t mips = desc.mipLevels; + GLint minFilter = mips > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; + glTextureParameteri(_textureId, GL_TEXTURE_MIN_FILTER, minFilter); + glTextureParameteri(_textureId, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + float aniso = 2.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &aniso); + glTextureParameterf(_textureId, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso); + glTextureParameteri(_textureId, GL_TEXTURE_BASE_LEVEL, /*low-mip*/ 0); + glTextureParameteri(_textureId, GL_TEXTURE_MAX_LEVEL, /*hi-mip*/ mips - 1); + + // gl needs memory to be aligned at pow2 row sizes. + int nextPow2 = 1; + while (nextPow2 < desc.dimensions[0]) + nextPow2 *= 2; + size_t bytesPerPixel = HgiGetDataSizeOfFormat(_descriptor.format); + + // Create an OpenGL memory object and sync the address of the shared GPU pointer from + // Aurora to this gl memory address. (DX12 and GL will use the same render buffer) + GLuint glData = 0; + glCreateMemoryObjectsEXT(1, &glData); + +#if defined(WIN32) + // The correct enum here appears to be GL_HANDLE_TYPE_D3D12_RESOURCE_EXT but on AMD cards + // the glImportMemoryWin32HandleEXT call reports an Invalid Enum so we use + // GL_HANDLE_TYPE_OPAQUE_WIN32_EXT on AMD cards instead. + GLenum handleType = GL_HANDLE_TYPE_D3D12_RESOURCE_EXT; + std::string vendorString((char*)glGetString(GL_VENDOR)); + + // GL_VENDOR "ATI Technologies Inc." + if (vendorString.length() >= 3 && vendorString.substr(0, 3) == "ATI") + handleType = GL_HANDLE_TYPE_OPAQUE_WIN32_EXT; + glImportMemoryWin32HandleEXT( + glData, nextPow2 * bytesPerPixel * desc.dimensions[1], handleType, sharedHandle); +#else + // TODO: Implement for other platforms to support Vulkan/Metal + (void)glData; + (void)bytesPerPixel; + (void)nextPow2; + AU_FAIL("HgiHdAuroraTexture not supported on this platform."); +#endif + glTextureStorageMem2DEXT( + _textureId, 1, glInternalFormat, desc.dimensions[0], desc.dimensions[1], glData, 0); + + // Check for errors after making OpenGL calls. + HdAuroraPostPendingGLErrors(); + } + else + { + // Note: Setting sampler state values on multi-sample texture is invalid + glTextureStorage2DMultisample(_textureId, desc.sampleCount, glInternalFormat, + desc.dimensions[0], desc.dimensions[1], GL_TRUE); + } + + HdAuroraPostPendingGLErrors(); +} + +HgiHdAuroraTextureGL::~HgiHdAuroraTextureGL() +{ + if (_textureId > 0) + { + glDeleteTextures(1, &_textureId); + _textureId = 0; + } + + HdAuroraPostPendingGLErrors(); +} + +// An DirectX GPU texture resource, using a shared handled. +class HgiHdAuroraTextureDX final : public HgiTexture +{ +public: + HgiHdAuroraTextureDX(HgiTextureDesc const& desc, void* sharedDXHandle) : + HgiTexture(desc), _sharedDXHandle(sharedDXHandle) + { + } + + ~HgiHdAuroraTextureDX() override {} + + size_t GetByteSizeOfResource() const override + { + GfVec3i const& s = _descriptor.dimensions; + return HgiGetDataSizeOfFormat(_descriptor.format) * s[0] * s[1] * std::max(s[2], 1); + } + + uint64_t GetRawResource() const override { return reinterpret_cast(_sharedDXHandle); } + +private: + HgiHdAuroraTextureDX() = delete; + HgiHdAuroraTextureDX& operator=(const HgiHdAuroraTextureDX&) = delete; + HgiHdAuroraTextureDX(const HgiHdAuroraTextureDX&) = delete; + + void* _sharedDXHandle; +}; + +// 1x1 pixel of invalid data +static const uint8_t INVALID_DATA[] = { 0xFF, 0xFF, 0xFF, 0xFF }; + +// Obtain appropriate usage bits according to the format and purpose of the texture. +static HgiTextureUsage _getTextureUsage(HdFormat format, TfToken const& name) +{ + if (HdAovHasDepthSemantic(name)) + { + if (format == HdFormatFloat32UInt8) + { + return HgiTextureUsageBitsDepthTarget | HgiTextureUsageBitsStencilTarget; + } + return HgiTextureUsageBitsDepthTarget; + } + + return HgiTextureUsageBitsColorTarget; +} + +HdAuroraRenderBuffer::HdAuroraRenderBuffer( + SdfPath const& id, HdAuroraRenderDelegate* renderDelegate) : + HdRenderBuffer(id), + _owner(renderDelegate), + _pRenderBuffer(nullptr), + _dimensions(0, 0, 1), + _stride(0), + _format(HdFormatInvalid), + _converged(false), + _valid(true) // Uninitialized render buffers are considered valid +{ +} + +HdAuroraRenderBuffer::~HdAuroraRenderBuffer() +{ + _pRenderBuffer = nullptr; +} + +void HdAuroraRenderBuffer::Sync( + HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) +{ + HdRenderBuffer::Sync(sceneDelegate, renderParam, dirtyBits); +} + +void HdAuroraRenderBuffer::Finalize(HdRenderParam* /* renderParam */) +{ + _Deallocate(); +} + +void HdAuroraRenderBuffer::_Deallocate() +{ + _pRenderBuffer.reset(); + _dimensions = GfVec3i(0, 0, 1); + _stride = 0; + _format = HdFormatUNorm8Vec4; + _converged = false; + _valid = true; +} + +void* HdAuroraRenderBuffer::Map() +{ + + _mapped = true; + + // Return a single pixel of invalid data if the buffer is not valid (in this case dimensions + // will always 1x1x1) Hydra will create a bunch of invalid buffers for currently unsupported AOV + // types (so we need to return *something* in this case) + if (!_valid) + return (void*)INVALID_DATA; + + if (!_pRenderBuffer) + return nullptr; + return (void*)_pRenderBuffer->data(_stride, true); +} + +bool HdAuroraRenderBuffer::IsConverged() const +{ + // Invalid render buffers are considered always converged + return !_valid || _converged; +} + +bool HdAuroraRenderBuffer::Allocate( + GfVec3i const& dimensions, HdFormat format, bool /* multiSampled */) +{ + // TODO We should not be creating render buffers with invalid formats. + // Happens with depth and other AOVs. + if (format == HdFormatInvalid) + { + _valid = false; + _format = HdFormatUNorm8Vec4; + _dimensions = GfVec3i(1, 1, 1); + return false; + } + + _valid = true; + _dimensions = dimensions; + + Aurora::ImageFormat imageFormat; + switch (format) + { + case HdFormatUNorm8Vec4: + imageFormat = Aurora::ImageFormat::Integer_RGBA; + break; + case HdFormatInt32: + imageFormat = Aurora::ImageFormat::Integer_RG; + break; + case HdFormatFloat32: + imageFormat = Aurora::ImageFormat::Float_R; + break; + default: + imageFormat = Aurora::ImageFormat::Half_RGBA; + break; + } + + _pRenderBuffer = + _owner->GetRenderer()->createRenderBuffer(dimensions[0], dimensions[1], imageFormat); + + // Get the stride TODO: not an ideal way to get the stride, but I don't think we need it anyway. + _pRenderBuffer->asReadable(_stride); + _format = format; + _converged = false; + return true; +} + +void HdAuroraRenderBuffer::Resolve() +{ + if (!_valid) + return; + + if (!_pRenderBuffer) + return; + + // Create and populate the HGI texture description + pxr::HgiTextureDesc texDesc; + texDesc.debugName = "HdAurora AovInput Texture"; + texDesc.dimensions = pxr::GfVec3i(GetWidth(), GetHeight(), 1); + texDesc.format = pxr::HdxHgiConversions::GetHgiFormat(GetFormat()); + texDesc.initialData = nullptr; + texDesc.layerCount = 1; + texDesc.mipLevels = 1; + texDesc.pixelsByteSize = 0; + texDesc.sampleCount = pxr::HgiSampleCount1; + texDesc.usage = + _getTextureUsage(GetFormat(), GetId().GetNameToken()) | pxr::HgiTextureUsageBitsShaderRead; + + // Shared buffers are only supported on DX12 (and therefore Windows) for now. +#if defined(WIN32) + bool sharedBuffersSupported = _owner->GetRenderBufferSharingType() != + HdAuroraRenderDelegate::RenderBufferSharingType::NONE; +#else + bool sharedBuffersSupported = false; +#endif + + // Try and get a shared buffer + if (sharedBuffersSupported) + { + Aurora::IRenderBuffer::IBufferPtr sharedBuffer = _pRenderBuffer->asShared(); + if (sharedBuffer) + { + // If we have a shareable buffer then we create a special HdAurora supplied version of + // an HgiTexture that handles the syncing of DX12 and GL memory. + if (!hasTextureResource(texDesc)) + { + auto* sharedHandle = const_cast(sharedBuffer->handle()); + if (_owner->GetRenderBufferSharingType() == + HdAuroraRenderDelegate::RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE) + { + // Create a HgiTextureHandle that wraps a DirectX shareable texture handle + _texture = HgiTextureHandle(new HgiHdAuroraTextureDX(texDesc, sharedHandle), + gUniqueRenderBufferIdCounter.fetch_add(1)); + } + else + { + // Create a HgiTextureHandle that creates and wraps an OpenGL texture id + _texture = HgiTextureHandle(new HgiHdAuroraTextureGL(texDesc, sharedHandle), + gUniqueRenderBufferIdCounter.fetch_add(1)); + } + } + + // We need to wait for Aurora to finish copying its result into the shared buffer before + // we continue. + _owner->GetRenderer()->waitForTask(); + + // No more work to do if they are shared. Buffers are synced automatically. + return; + } + } + + // If we don't have a shareable buffer then fallback to using HGI blit + size_t stride; + auto pixelBuffer = _pRenderBuffer->asReadable(stride); + if (!pixelBuffer) + return; + + const void* pixelData = pixelBuffer->data(); + if (!pixelData) + return; + + size_t bytesPerPixel = 16; + switch (GetFormat()) + { + case pxr::HdFormatFloat32: + case pxr::HdFormatUNorm8Vec4: + bytesPerPixel = 4; + break; + case pxr::HdFormatFloat16Vec4: + bytesPerPixel = 8; + break; + case pxr::HdFormatUNorm8: + case pxr::HdFormatUNorm8Vec2: + case pxr::HdFormatUNorm8Vec3: + case pxr::HdFormatSNorm8: + case pxr::HdFormatSNorm8Vec2: + case pxr::HdFormatSNorm8Vec3: + case pxr::HdFormatSNorm8Vec4: + case pxr::HdFormatFloat16: + case pxr::HdFormatFloat16Vec2: + case pxr::HdFormatFloat16Vec3: + case pxr::HdFormatFloat32Vec2: + case pxr::HdFormatFloat32Vec3: + case pxr::HdFormatFloat32Vec4: + case pxr::HdFormatUInt16: + case pxr::HdFormatUInt16Vec2: + case pxr::HdFormatUInt16Vec3: + case pxr::HdFormatUInt16Vec4: + case pxr::HdFormatInt16: + case pxr::HdFormatInt16Vec2: + case pxr::HdFormatInt16Vec3: + case pxr::HdFormatInt16Vec4: + case pxr::HdFormatInt32: + case pxr::HdFormatInt32Vec2: + case pxr::HdFormatInt32Vec3: + case pxr::HdFormatInt32Vec4: + case pxr::HdFormatFloat32UInt8: + case pxr::HdFormatInvalid: + case pxr::HdFormatCount: + break; + } + + int actualWidth = static_cast(stride / bytesPerPixel); + + pxr::GfVec3i dim(actualWidth, static_cast(GetHeight()), 1); + + // populate with data from render buffer + texDesc.dimensions = dim; + texDesc.initialData = pixelData; + texDesc.pixelsByteSize = stride * GetHeight(); + + bool hasTexture = static_cast(_texture); + if (hasTexture && texDesc != _texture->GetDescriptor()) + { + _owner->GetHgi()->DestroyTexture(&_texture); + hasTexture = false; + } + if (!hasTexture) + { + _texture = _owner->GetHgi()->CreateTexture(texDesc); + } + else + { + // Update the GL texture in place. + HgiBlitCmdsUniquePtr blitCmds = _owner->GetHgi()->CreateBlitCmds(); + HgiTextureCpuToGpuOp blitOp; + blitOp.gpuDestinationTexture = _texture; + blitOp.mipLevel = 0; + blitOp.destinationTexelOffset = GfVec3i(0, 0, 0); + blitOp.cpuSourceBuffer = pixelData; + blitCmds->CopyTextureCpuToGpu(blitOp); + _owner->GetHgi()->SubmitCmds(blitCmds.get()); + } +} + +VtValue HdAuroraRenderBuffer::GetResource(bool /*multiSampled*/) const +{ + if (!_valid) + return VtValue(); + + if (!_pRenderBuffer || !_texture) + return VtValue(); + + return VtValue(_texture); +} + +bool HdAuroraRenderBuffer::hasTextureResource(const pxr::HgiTextureDesc& texDesc) +{ + // if we have a texture, but the required description had changed (aka changed size), then we + // destroy the existing texture and recreate a new one. + bool hasTexture = static_cast(_texture); + if (hasTexture && texDesc != _texture->GetDescriptor()) + { + _owner->GetHgi()->DestroyTexture(&_texture); + hasTexture = false; + } + + return hasTexture; +} diff --git a/Libraries/HdAurora/HdAuroraRenderBuffer.h b/Libraries/HdAurora/HdAuroraRenderBuffer.h new file mode 100644 index 0000000..8e26358 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraRenderBuffer.h @@ -0,0 +1,82 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +class HdAuroraRenderDelegate; + +#include +class HdAuroraRenderBuffer : public HdRenderBuffer +{ +public: + HdAuroraRenderBuffer(SdfPath const& id, HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraRenderBuffer(); + + void Sync(HdSceneDelegate* sceneDelegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits) override; + + void Finalize(HdRenderParam* renderParam) override; + + bool Allocate(GfVec3i const& dimensions, HdFormat format, bool multiSampled) override; + + unsigned int GetWidth() const override { return _dimensions[0]; } + + unsigned int GetHeight() const override { return _dimensions[1]; } + + unsigned int GetDepth() const override { return _dimensions[2]; } + + HdFormat GetFormat() const override { return _format; } + + bool IsMultiSampled() const override { return false; } + + void* Map() override; + + void Unmap() override { _mapped = false; } + + bool IsMapped() const override { return _mapped; } + + bool IsConverged() const override; + + void Resolve() override; + + VtValue GetResource(bool multiSampled) const override; + + // Has the underlying Aurora object been allocated? + bool IsAllocated() const { return _pRenderBuffer != nullptr; } + + // Get the Aurora object for this render buffer + Aurora::IRenderBufferPtr GetAuroraBuffer() { return _pRenderBuffer; } + + // Set the converged flag on render buffer (called from render pass) + void SetConverged(bool val) { _converged = val; } + + // Get the stride of the render buffer + size_t GetStride() { return _stride; } + +private: + virtual void _Deallocate() override; + + bool hasTextureResource(const pxr::HgiTextureDesc& texDesc); + + HdAuroraRenderDelegate* _owner; + + Aurora::IRenderBufferPtr _pRenderBuffer; + pxr::HgiTextureHandle _texture; + GfVec3i _dimensions; + size_t _stride; + bool _mapped; + HdFormat _format; + bool _converged; + bool _valid; +}; diff --git a/Libraries/HdAurora/HdAuroraRenderDelegate.cpp b/Libraries/HdAurora/HdAuroraRenderDelegate.cpp new file mode 100644 index 0000000..fe97773 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraRenderDelegate.cpp @@ -0,0 +1,559 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#pragma warning(push) +#pragma warning(disable : 4003) +#include +#pragma warning(pop) + +#include "HdAuroraCamera.h" +#include "HdAuroraImageCache.h" +#include "HdAuroraInstancer.h" +#include "HdAuroraLight.h" +#include "HdAuroraMaterial.h" +#include "HdAuroraMesh.h" +#include "HdAuroraRenderBuffer.h" +#include "HdAuroraRenderDelegate.h" +#include "HdAuroraRenderPass.h" +#include "HdAuroraTokens.h" + +const TfTokenVector SUPPORTED_RPRIM_TYPES = { + HdPrimTypeTokens->mesh, +}; + +const TfTokenVector SUPPORTED_SPRIM_TYPES = { + HdPrimTypeTokens->camera, + HdPrimTypeTokens->material, + HdPrimTypeTokens->domeLight, + HdPrimTypeTokens->distantLight, +}; + +const TfTokenVector SUPPORTED_BPRIM_TYPES = { + HdPrimTypeTokens + ->renderBuffer // Supporting renderBuffer BPrim tells Hydra to create AOVs for us +}; + +const TfTokenVector& HdAuroraRenderDelegate::GetSupportedRprimTypes() const +{ + return SUPPORTED_RPRIM_TYPES; +} + +const TfTokenVector& HdAuroraRenderDelegate::GetSupportedSprimTypes() const +{ + return SUPPORTED_SPRIM_TYPES; +} + +const TfTokenVector& HdAuroraRenderDelegate::GetSupportedBprimTypes() const +{ + return SUPPORTED_BPRIM_TYPES; +} + +HdAovDescriptor HdAuroraRenderDelegate::GetDefaultAovDescriptor(TfToken const& name) const +{ + if (name == HdAovTokens->color) + { + return HdAovDescriptor(HdFormatFloat16Vec4, false, VtValue(GfVec4f(0.0f))); + } + else if (name == HdAovTokens->depth) + { + return HdAovDescriptor(HdFormatFloat32, false, VtValue(1.0f)); + } + + return HdAovDescriptor(HdFormatInvalid, false, VtValue()); +} + +static std::mutex mutexResourceRegistry; +static int counterResourceRegistry = 0; +static HdResourceRegistrySharedPtr resourceRegistry; + +HdAuroraRenderDelegate::HdAuroraRenderDelegate(HdRenderSettingsMap const& settings) : + HdRenderDelegate(settings), + _auroraRenderer(Aurora::createRenderer()), + _sampleCounter(33, 250, 50), + _hgi(nullptr) +{ + if (_auroraRenderer) + { + // TODO: For long-term, we need an API to set material unit information from client + // side. + // Background: "1 ASM unit = 1 cm" in Inventor! Unit section of ASM tutorial mentions + // the words we want confirm: "ASM is unit-less so it's up to the calling application to + // decide what one unit of ASM represents. Inventor has chosen: 1 ASM unit = 1 cm. + // Inventor allows the user to select mm or inches but all Inventor/ASM transaction is + // done in cm." That is, for Inventor Alpha release, it seems easier to use centimeter + // for Aurora for now. + _auroraRenderer->options().setString("units", "centimeter"); + + // Turn gamma correction off. Leave tonemapping and color space correction to Hydra or + // the application. + _auroraRenderer->options().setBoolean("isGammaCorrectionEnabled", false); + + // These render settings are the same as the default values. + // Convenient to have them here to understand the capabilities and options of the + // renderer. + float intensity[3] = { 1.0f, 1.0f, 1.0f }; + _auroraRenderer->options().setFloat3("brightness", intensity); + _auroraRenderer->options().setBoolean("isToneMappingEnabled", false); + _auroraRenderer->options().setFloat("maxLuminance", 1000.0f); + _auroraRenderer->options().setBoolean("isDiffuseOnlyEnabled", false); + _auroraRenderer->options().setInt("traceDepth", 5); + _auroraRenderer->options().setBoolean("isDenoisingEnabled", false); + _auroraRenderer->options().setBoolean("alphaEnabled", false); + _sampleCounter.setMaxSamples(1000); + _sampleCounter.reset(); + + // create a new scene for this renderer. + _auroraScene = _auroraRenderer->createScene(); + _pImageCache = std::make_unique(_auroraScene); + + // Set default directional light off + float color[3] = { 0.0f, 0.0f, 0.0f }; + float dir[3] = { 0.0f, 0.0f, 1.0f }; + float lightIntensity = 0.0f; + _auroraScene->setLight(lightIntensity, color, dir); + + // Create a ground plane object, which is assigned to the scene. + _pGroundPlane = make_unique(_auroraRenderer.get(), _auroraScene.get()); + + // add the scene to the renderer. + _auroraRenderer->setScene(_auroraScene); + + // Create an aurora path based on Hydra id. + _auroraEnvironmentPath = "HdAuroraEnvironment"; + + // Create an environment for path by setting empty properties.s + _auroraScene->setEnvironmentProperties(_auroraEnvironmentPath, {}); + + // Set the scene's environment. + _auroraScene->setEnvironment(_auroraEnvironmentPath); + + // Set up the setting functions, that are called when render settings change. + _settingFunctions[HdAuroraTokens::kTraceDepth] = [this](VtValue const& value) { + _auroraRenderer->options().setInt("traceDepth", value.Get()); + return true; + }; + _settingFunctions[HdAuroraTokens::kMaxSamples] = [this](VtValue const& value) { + int currentSamples = static_cast(_sampleCounter.currentSamples()); + // Newly max sample value is smaller than current sample, stop render and keep the + // current number, otherwise set to the new value. + _sampleCounter.setMaxSamples(std::max(currentSamples, value.Get())); + return false; + }; + _settingFunctions[HdAuroraTokens::kIsDenoisingEnabled] = [this](VtValue const& value) { + _auroraRenderer->options().setBoolean("isDenoisingEnabled", value.Get()); + return true; + }; + _settingFunctions[HdAuroraTokens::kIsAlphaEnabled] = [this](VtValue const& value) { + _auroraRenderer->options().setBoolean("alphaEnabled", value.Get()); + _bEnvironmentIsDirty = true; + return false; + }; + _settingFunctions[HdAuroraTokens::kUseEnvironmentImageAsBackground] = + [this](VtValue const& value) { + _useEnvironmentLightAsBackground = value.Get(); + _bEnvironmentIsDirty = true; + return false; + }; + _settingFunctions[HdAuroraTokens::kBackgroundImage] = [this](VtValue const& value) { + _backgroundImageFilePath = value.Get().GetAssetPath(); + _bEnvironmentIsDirty = true; + return false; + }; + _settingFunctions[HdAuroraTokens::kBackgroundColors] = [this](VtValue const& value) { + const auto& colors = value.Get(); + if (colors.size() == 2 && + (_backgroundTopColor != colors[0] || _backgroundBottomColor != colors[1])) + { + _backgroundTopColor = colors[0]; + _backgroundBottomColor = colors[1]; + _bEnvironmentIsDirty = true; + } + return false; + }; + _settingFunctions[HdAuroraTokens::kGroundPlaneSettings] = [this](VtValue const& value) { + // Update the ground plane object, which return true if the data has changed. + return _pGroundPlane->update(value.Get()); + }; + _settingFunctions[HdAuroraTokens::kIsSharedHandleEnabled] = [this](VtValue const& value) { + // Aurora provides shared DX or GL texture handles + if (value.Get()) + { + _renderBufferSharingType = RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE; + } + else + _renderBufferSharingType = RenderBufferSharingType::NONE; + + // Set render settings that can be queried by the scene delegate + switch (_renderBufferSharingType) + { + case RenderBufferSharingType::NONE: + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleDirectX, VtValue(false)); + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(false)); + break; + case RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE: + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleDirectX, VtValue(true)); + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(false)); + break; + case RenderBufferSharingType::OPENGL_TEXTURE_ID: + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleDirectX, VtValue(false)); + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(true)); + break; + } + return true; + }; + } + else + { + TF_FATAL_ERROR("HdAurora fails to create renderer!"); + } +} + +void HdAuroraRenderDelegate::SetDrivers(HdDriverVector const& drivers) +{ + if (_hgi) + { + TF_CODING_ERROR("Cannot set HdDriver twice for a render delegate."); + return; + } + + // For HdAurora we want to use the Hgi driver, so extract it. + for (HdDriver* hdDriver : drivers) + { + if (hdDriver->name == pxr::HgiTokens->renderDriver && + hdDriver->driver.IsHolding()) + { + _hgi = hdDriver->driver.UncheckedGet(); + break; + } + } + + std::lock_guard guard(mutexResourceRegistry); + + if (counterResourceRegistry++ == 0) + { + resourceRegistry.reset(new HdResourceRegistry()); + } + + TF_VERIFY(_hgi, "HdAurora requires a Hgi HdDriver."); +} + +HdAuroraRenderDelegate::~HdAuroraRenderDelegate() +{ + std::lock_guard guard(mutexResourceRegistry); + if (counterResourceRegistry-- == 1) + { + resourceRegistry.reset(); + } +} + +HdResourceRegistrySharedPtr HdAuroraRenderDelegate::GetResourceRegistry() const +{ + return resourceRegistry; +} + +void HdAuroraRenderDelegate::CommitResources(HdChangeTracker* /* tracker */) +{ + resourceRegistry->Commit(); +} + +void HdAuroraRenderDelegate::SetRenderSetting(TfToken const& key, VtValue const& value) +{ + // Get settings version before applying new value. + unsigned int prevVersion = _settingsVersion; + + // first, call the parent method to actually set the value + HdRenderDelegate::SetRenderSetting(key, value); + + // Is a sample restart needed based on settings change? + bool restartNeeded = false; + + // kIsRestartEnabled is a special key, it is used to restart the sampling. + // It triggers a restart even if the value is alread set to true. + if (HdAuroraTokens::kIsRestartEnabled == key) + { + if (value.Get()) + { + restartNeeded = true; + } + } + + // If the value changed call the setting function for this key. + if (prevVersion != _settingsVersion) + { + auto iter = _settingFunctions.find(key); + if (iter != _settingFunctions.end()) + { + // Get the function from map (if it exists for this key.) + std::function func = iter->second; + + // Call function and set restart flag if it returns true. + if (func(value)) + restartNeeded = true; + } + } + + // Set the restart needed flag if any functions returned true. + _sampleRestartNeeded |= restartNeeded; +} + +VtValue HdAuroraRenderDelegate::GetRenderSetting(TfToken const& key) const +{ + if (HdAuroraTokens::kCurrentSamples == key) + { + return VtValue(static_cast(_sampleCounter.currentSamples())); + } + else if (HdAuroraTokens::kIsAlphaEnabled == key) + { + return VtValue(_alphaEnabled); + } + + return HdRenderDelegate::GetRenderSetting(key); +} + +HdRprim* HdAuroraRenderDelegate::CreateRprim(TfToken const& typeId, SdfPath const& rprimId) +{ + if (typeId == HdPrimTypeTokens->mesh) + { + return new HdAuroraMesh(rprimId, this); + } + + return nullptr; +} + +void HdAuroraRenderDelegate::DestroyRprim(HdRprim* rPrim) +{ + delete rPrim; +} + +HdSprim* HdAuroraRenderDelegate::CreateSprim(TfToken const& typeId, SdfPath const& sprimId) +{ + if (typeId == HdPrimTypeTokens->camera) + { + return new HdAuroraCamera(sprimId, this); + } + else if (typeId == HdPrimTypeTokens->material) + { + return new HdAuroraMaterial(sprimId, this); + } + else if (typeId == HdPrimTypeTokens->domeLight) + { + return new HdAuroraDomeLight(sprimId, this); + } + else if (typeId == HdPrimTypeTokens->distantLight) + { + return new HdAuroraDistantLight(sprimId, this); + } + return nullptr; +} + +void HdAuroraRenderDelegate::DestroySprim(HdSprim* sprim) +{ + delete sprim; +} + +HdBprim* HdAuroraRenderDelegate::CreateBprim(TfToken const& typeId, SdfPath const& bprimId) +{ + if (typeId == HdPrimTypeTokens->renderBuffer) + { + return new HdAuroraRenderBuffer(bprimId, this); + } + + return nullptr; +} + +void HdAuroraRenderDelegate::DestroyBprim(HdBprim* /* bprim */) {} + +HdSprim* HdAuroraRenderDelegate::CreateFallbackSprim(TfToken const& typeId) +{ + if (typeId == HdPrimTypeTokens->camera) + { + return new HdCamera(SdfPath::EmptyPath()); + } + else if (typeId == HdPrimTypeTokens->material) + { + return new HdAuroraMaterial(SdfPath::EmptyPath(), this); + } + + return nullptr; +} + +HdBprim* HdAuroraRenderDelegate::CreateFallbackBprim(TfToken const& /* typeId */) +{ + return nullptr; +} + +HdInstancer* HdAuroraRenderDelegate::CreateInstancer(HdSceneDelegate* delegate, SdfPath const& id) +{ + return new HdAuroraInstancer(delegate, id); +} + +void HdAuroraRenderDelegate::DestroyInstancer(HdInstancer* instancer) +{ + delete instancer; +} + +HdRenderParam* HdAuroraRenderDelegate::GetRenderParam() const +{ + return nullptr; +} + +HdRenderPassSharedPtr HdAuroraRenderDelegate::CreateRenderPass( + HdRenderIndex* index, HdRprimCollection const& collection) +{ + return HdRenderPassSharedPtr(new HdAuroraRenderPass(index, collection, this)); +} + +void HdAuroraRenderDelegate::ActivateRenderPass( + HdAuroraRenderPass* pRenderPass, const std::map& renderBuffers) +{ + // Collect supplied render buffers into target assignments for Aurora. + Aurora::TargetAssignments targetAssignments; + auto colorTargetIt = renderBuffers.find(HdAovTokens->color); + if (colorTargetIt != renderBuffers.end()) + targetAssignments[Aurora::AOV::kFinal] = colorTargetIt->second->GetAuroraBuffer(); + auto depthTargetIt = renderBuffers.find(HdAovTokens->depth); + if (depthTargetIt != renderBuffers.end()) + targetAssignments[Aurora::AOV::kDepthNDC] = depthTargetIt->second->GetAuroraBuffer(); + + // Set the Aurora target assignments. + _auroraRenderer->setTargets(targetAssignments); + + // Keep track of which render pass is currently active in the Aurora renderer + _activeRenderPass = pRenderPass; +} + +void HdAuroraRenderDelegate::RenderPassDestroyed(HdAuroraRenderPass* pRenderPass) +{ + // Unset the active render pass if needed + if (_activeRenderPass == pRenderPass) + { + _activeRenderPass = nullptr; + } +} + +void HdAuroraRenderDelegate::ResetBounds() +{ + // Reset to min/max floating point values, so next call to up date will replace them. + _boundsMin = GfVec3f(+FLT_MAX, +FLT_MAX, +FLT_MAX); + _boundsMax = GfVec3f(-FLT_MAX, -FLT_MAX, -FLT_MAX); + _boundsValid = false; +} + +void HdAuroraRenderDelegate::UpdateBounds(const pxr::GfVec3f& min, const GfVec3f& max) +{ + // Update scene bounds for each coordinate. + for (int j = 0; j < 3; j++) + { + _boundsMin[j] = std::min(_boundsMin[j], min[j]); + _boundsMax[j] = std::max(_boundsMax[j], max[j]); + } + + // Update the Aurora bounds with new value. + // TODO: This can happen frequently, but the setBounds() function is known to be simple, so + // it should be fine in practice. Perhaps use a dirty flag to only update this right before + // rendering. + _auroraScene->setBounds((float*)&_boundsMin, (float*)&_boundsMax); + + _boundsValid = true; +} + +void HdAuroraRenderDelegate::UpdateAuroraEnvironment() +{ + if (!_bEnvironmentIsDirty) + return; + SetSampleRestartNeeded(true); + + if (!_backgroundImageFilePath.empty()) + { + _auroraEnvironmentBackgroundImagePath = + imageCache().acquireImage(_backgroundImageFilePath, false, false); + } + else + { + _auroraEnvironmentBackgroundImagePath.clear(); + } + + // Use the background or light image as background, based on flag. + const auto& backgroundImage = _useEnvironmentLightAsBackground + ? _auroraEnvironmentLightImagePath + : _auroraEnvironmentBackgroundImagePath; + + // Set the image on the environment for background. + Aurora::Properties props = { + { Aurora::Names::EnvironmentProperties::kBackgroundTop, + glm::make_vec3(_backgroundTopColor.data()) }, + { Aurora::Names::EnvironmentProperties::kBackgroundBottom, + glm::make_vec3(_backgroundBottomColor.data()) }, + { Aurora::Names::EnvironmentProperties::kBackgroundUseScreen, + !_useEnvironmentLightAsBackground }, + { Aurora::Names::EnvironmentProperties::kBackgroundImage, backgroundImage }, + { Aurora::Names::EnvironmentProperties::kLightImage, _auroraEnvironmentLightImagePath }, + }; + + // Issue in the properties code means these need to set seperately. + props.insert({ Aurora::Names::EnvironmentProperties::kLightTransform, + _auroraEnvironmentLightTransform }); + props.insert({ Aurora::Names::EnvironmentProperties::kBackgroundTransform, + _auroraEnvironmentLightTransform }); + + GetScene()->setEnvironmentProperties(_auroraEnvironmentPath, props); + + _bEnvironmentIsDirty = false; +} + +GroundPlane::GroundPlane(Aurora::IRenderer* pRenderer, Aurora::IScene* pScene) +{ + // Create a ground plane object, set it disabled, and assign it to the scene. + _pGroundPlane = pRenderer->createGroundPlanePointer(); + _pGroundPlane->values().setBoolean("enabled", false); + pScene->setGroundPlanePointer(_pGroundPlane); + + // NOTE: _value is initialized with data that may not fully match the (new) Aurora ground + // plane, but it will be fully initialized on the next call to update(), since the default + // data won't match any sensible ground plane data. +} + +bool GroundPlane::update(const ValueType& value) +{ + // Return false if the value is unchanged. + if (value == _value) + { + return false; + } + + // Get the ground plane settings from the value. + auto [isEnabled, position, normal, shadowOpacity, shadowColor, reflectionOpacity, + reflectionColor, reflectionRoughness] = value; + _value = value; + + // Apply the settings to the Aurora ground plane object. + Aurora::IValues& values = _pGroundPlane->values(); + values.setBoolean("enabled", isEnabled); + values.setFloat3("position", position.data()); + values.setFloat3("normal", normal.data()); + values.setFloat("shadow_opacity", shadowOpacity); + values.setFloat3("shadow_color", shadowColor.data()); + values.setFloat("reflection_opacity", reflectionOpacity); + values.setFloat3("reflection_color", reflectionColor.data()); + values.setFloat("reflection_roughness", reflectionRoughness); + + return true; +} diff --git a/Libraries/HdAurora/HdAuroraRenderDelegate.h b/Libraries/HdAurora/HdAuroraRenderDelegate.h new file mode 100644 index 0000000..268ed80 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraRenderDelegate.h @@ -0,0 +1,164 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include + +class GroundPlane; +class HdAuroraRenderPass; +class HdAuroraRenderBuffer; +class HdAuroraImageCache; + +// Functoin used to update a Hydra render setting. +using UpdateRenderSettingFunction = std::function; + +class HdAuroraRenderDelegate final : public HdRenderDelegate +{ +public: + HdAuroraRenderDelegate(HdRenderSettingsMap const& settings = {}); + ~HdAuroraRenderDelegate() override; + + void SetDrivers(pxr::HdDriverVector const& drivers) override; + + const pxr::TfTokenVector& GetSupportedRprimTypes() const override; + const pxr::TfTokenVector& GetSupportedSprimTypes() const override; + const pxr::TfTokenVector& GetSupportedBprimTypes() const override; + + pxr::HdRprim* CreateRprim(pxr::TfToken const& typeId, pxr::SdfPath const& rprimId) override; + pxr::HdSprim* CreateSprim(pxr::TfToken const& typeId, pxr::SdfPath const& sprimId) override; + pxr::HdBprim* CreateBprim(pxr::TfToken const& typeId, pxr::SdfPath const& bprimId) override; + + void DestroyRprim(pxr::HdRprim* rPrim) override; + void DestroySprim(pxr::HdSprim* sprim) override; + void DestroyBprim(pxr::HdBprim* bprim) override; + + pxr::HdSprim* CreateFallbackSprim(pxr::TfToken const& typeId) override; + pxr::HdBprim* CreateFallbackBprim(pxr::TfToken const& typeId) override; + + pxr::HdInstancer* CreateInstancer( + pxr::HdSceneDelegate* delegate, pxr::SdfPath const& id) override; + void DestroyInstancer(pxr::HdInstancer* instancer) override; + + pxr::HdRenderParam* GetRenderParam() const override; + + HdAovDescriptor GetDefaultAovDescriptor(TfToken const& name) const override; + + pxr::HdRenderPassSharedPtr CreateRenderPass( + pxr::HdRenderIndex* index, pxr::HdRprimCollection const& collection) override; + + pxr::HdResourceRegistrySharedPtr GetResourceRegistry() const override; + void CommitResources(pxr::HdChangeTracker* tracker) override; + void SetRenderSetting(TfToken const& key, VtValue const& value) override; + VtValue GetRenderSetting(TfToken const& key) const override; + + Aurora::IRendererPtr GetRenderer() { return _auroraRenderer; } + Aurora::IScenePtr GetScene() { return _auroraScene; } + + // Update the scene bounds with the AABB for a single mesh. + void UpdateBounds(const pxr::GfVec3f& min, const GfVec3f& max); + // Reset the scene bounds. + // This will reset them to invalid values, so the next call to UpdateBounds will set the bounds. + void ResetBounds(); + // Are the bounds currently valid? + bool BoundsValid() { return _boundsValid; } + + Aurora::Foundation::SampleCounter& GetSampleCounter() { return _sampleCounter; } + bool SampleRestartNeeded() const { return _sampleRestartNeeded; } + void SetSampleRestartNeeded(bool needed) { _sampleRestartNeeded = needed; } + + void ActivateRenderPass(HdAuroraRenderPass* pRenderPass, + const std::map& pRenderBuffers); + void RenderPassDestroyed(HdAuroraRenderPass* pRenderPass); + + // Returns Hydra graphics interface + pxr::Hgi* GetHgi() { return _hgi; } + + // Settings for shared buffers. + // NOTE: This determines whether the GetRawResource() function of the HgiTexture object created + // in HdAuroraRenderBuffer::Resolve() returns a handle to shared GPU-resident object. + enum class RenderBufferSharingType + { + NONE, + DIRECTX_TEXTURE_HANDLE, + OPENGL_TEXTURE_ID + }; + + RenderBufferSharingType GetRenderBufferSharingType() { return _renderBufferSharingType; } + std::mutex& rendererMutex() { return _rendererMutex; } + // Get the primIndex mutex, required to access prim. index via GetSprimSubtree as this is not + // thread safe. + std::mutex& primIndexMutex() { return _primIndexMutex; } + HdAuroraImageCache& imageCache() { return *_pImageCache; } + + void setAuroraEnvironmentLightImagePath(const Aurora::Path& path) + { + _auroraEnvironmentLightImagePath = path; + _bEnvironmentIsDirty = true; + } + + void setAuroraEnvironmentLightTransform(const glm::mat4& xform) + { + _auroraEnvironmentLightTransform = xform; + _bEnvironmentIsDirty = true; + } + + void UpdateAuroraEnvironment(); + +private: + std::mutex _rendererMutex; + std::mutex _primIndexMutex; + + map _settingFunctions; + Aurora::IRendererPtr _auroraRenderer; + Aurora::IScenePtr _auroraScene; + unique_ptr _pGroundPlane; + unique_ptr _pImageCache; + + HdAuroraRenderPass* _activeRenderPass = nullptr; + + bool _sampleRestartNeeded = true; + Aurora::Foundation::SampleCounter _sampleCounter; + + GfVec3f _boundsMin = GfVec3f(+FLT_MAX, +FLT_MAX, +FLT_MAX); + GfVec3f _boundsMax = GfVec3f(-FLT_MAX, -FLT_MAX, -FLT_MAX); + bool _boundsValid = false; + + // Settings for background. + bool _bEnvironmentIsDirty = true; + bool _alphaEnabled = false; + bool _useEnvironmentLightAsBackground = true; + GfVec3f _backgroundTopColor = GfVec3f(1.0f, 1.0f, 1.0f); + GfVec3f _backgroundBottomColor = GfVec3f(1.0f, 1.0f, 1.0f); + glm::mat4 _auroraEnvironmentLightTransform; + std::string _backgroundImageFilePath; + Aurora::Path _auroraEnvironmentBackgroundImagePath; + Aurora::Path _auroraEnvironmentLightImagePath; + Aurora::Path _auroraEnvironmentPath; + + RenderBufferSharingType _renderBufferSharingType = RenderBufferSharingType::NONE; + pxr::Hgi* _hgi; +}; + +// A class that manages ground plane data, including an Aurora ground plane object. +class GroundPlane +{ +public: + using ValueType = tuple; + GroundPlane(Aurora::IRenderer* pRenderer, Aurora::IScene* pScene); + bool update(const ValueType& value); + +private: + Aurora::IGroundPlanePtr _pGroundPlane; + ValueType _value; +}; diff --git a/Libraries/HdAurora/HdAuroraRenderPass.cpp b/Libraries/HdAurora/HdAuroraRenderPass.cpp new file mode 100644 index 0000000..e6ed577 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraRenderPass.cpp @@ -0,0 +1,177 @@ +#include "pch.h" + +#include "HdAuroraRenderBuffer.h" +#include "HdAuroraRenderDelegate.h" +#include "HdAuroraRenderPass.h" +#include + +HdAuroraRenderPass::HdAuroraRenderPass(HdRenderIndex* index, HdRprimCollection const& collection, + HdAuroraRenderDelegate* renderDelegate) : + HdRenderPass(index, collection), + _owner(renderDelegate), + _frameBufferTexture(0), + _frameBufferObject(0), + _ownedRenderBuffer(nullptr) +{ + _renderBuffers[HdAovTokens->color] = nullptr; +} + +HdAuroraRenderPass::~HdAuroraRenderPass() +{ + _owner->RenderPassDestroyed(this); +} + +bool HdAuroraRenderPass::IsConverged() const +{ + return _owner->GetSampleCounter().isComplete(); +} + +void HdAuroraRenderPass::_Execute( + HdRenderPassStateSharedPtr const& renderPassState, TfTokenVector const& /* renderTags */) +{ + // GetViewport has been deprecated. Only use when camera framing is not valid. + const CameraUtilFraming& framing = renderPassState->GetFraming(); + int viewportWidth = framing.IsValid() ? framing.dataWindow.GetWidth() + : static_cast(renderPassState->GetViewport()[2]); + int viewportHeight = framing.IsValid() ? framing.dataWindow.GetHeight() + : static_cast(renderPassState->GetViewport()[3]); + + HdFormat format = _owner->GetDefaultAovDescriptor(HdAovTokens->color).format; + + if (_viewportSize.first != viewportWidth || _viewportSize.second != viewportHeight) + { + _renderBuffers[HdAovTokens->color] = nullptr; + } + + // Set up AOV bindings based on bindings in renderPassState passed in from Hydra + HdRenderPassAovBindingVector aovBindings = renderPassState->GetAovBindings(); + if (aovBindings.size() == 0) + { + format = HdFormatUNorm8Vec4; + + // If AOV Bindings in the Hydra render pass state are empty + // it is up to the plugin to own the frame buffer + if (!_ownedRenderBuffer) + { + // Create a render buffer to render into + _ownedRenderBuffer = + std::make_shared(SdfPath::EmptyPath(), _owner); + + // create openGL frame buffer texture + char imageData[4 * 4 * 4]; + glGenTextures(1, &_frameBufferTexture); + glBindTexture(GL_TEXTURE_2D, _frameBufferTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); + + // create openGL frame buffer object + glGenFramebuffers(1, &_frameBufferObject); + glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBufferObject); + glFramebufferTexture2D( + GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _frameBufferTexture, 0); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + // Create a set of AOV bindings from our render buffer + HdRenderPassAovBinding colorAov; + colorAov.aovName = HdAovTokens->color; + colorAov.renderBuffer = _ownedRenderBuffer.get(); + // Set the default clear color (this is currently ignored we should set our clear color + // based on value in AOV bindings) + colorAov.clearValue = VtValue(GfVec4f(0.05f, 0.05f, 0.09f, 1.0f)); + _aovBindings.clear(); + _aovBindings.push_back(colorAov); + } + _renderBuffers[HdAovTokens->color] = _ownedRenderBuffer.get(); + } + else if (!_renderBuffers[HdAovTokens->color] || _aovBindings != aovBindings) + { + // If we are given AOV bindings we should use them (currently we only care about color + // AOV) + + // Clear the current buffers + _renderBuffers[HdAovTokens->color] = nullptr; + _ownedRenderBuffer.reset(); + + // Find the color buffer in the AOV bindings + for (size_t i = 0; i < aovBindings.size(); i++) + { + if (aovBindings[i].aovName == HdAovTokens->color) + { + // Color buffers are requested as 32bit float buffers when using AOVs + _renderBuffers[HdAovTokens->color] = + static_cast(aovBindings[i].renderBuffer); + format = HdFormatFloat16Vec4; + } + else if (aovBindings[i].aovName == HdAovTokens->depth) + { + _renderBuffers[HdAovTokens->depth] = + static_cast(aovBindings[i].renderBuffer); + } + } + if (!_renderBuffers[HdAovTokens->color]) + std::cerr << "Failed to find color buffer in AOV bindings" << std::endl; + _aovBindings = aovBindings; + } + + // resize Aurora render buffer + if (!_renderBuffers[HdAovTokens->color]->IsAllocated() || + _viewportSize.first != viewportWidth || _viewportSize.second != viewportHeight) + { + _renderBuffers[HdAovTokens->color]->Allocate( + GfVec3i(viewportWidth, viewportHeight, 1), format, false); + + auto depthAovIt = _renderBuffers.find(HdAovTokens->depth); + // do we have a valid depth AOV? + if (depthAovIt != _renderBuffers.end() && depthAovIt->second != nullptr) + { + _renderBuffers[HdAovTokens->depth]->Allocate(GfVec3i(viewportWidth, viewportHeight, 1), + _owner->GetDefaultAovDescriptor(HdAovTokens->depth).format, false); + } + + _owner->ActivateRenderPass(this, _renderBuffers); + + _viewportSize.first = viewportWidth; + _viewportSize.second = viewportHeight; + _owner->SetSampleRestartNeeded(true); + } + + // Update the background. + _owner->UpdateAuroraEnvironment(); + + // Render the scene. + uint32_t sampleStart = 0; + uint32_t sampleCount = + _owner->GetSampleCounter().update(sampleStart, _owner->SampleRestartNeeded()); + if (sampleCount > 0) + { + _owner->GetRenderer()->render(sampleStart, sampleCount); + } + + // Clear the restart flag. + _owner->SetSampleRestartNeeded(false); + + // Use HdAuroraRenderPass::IsConverged() here as it supports Rasterization and Path Tracing + _renderBuffers[HdAovTokens->color]->SetConverged(IsConverged()); + + // If render pass owns the render buffer, we are responsible for doing the BLIT to the GL FBO + if (_ownedRenderBuffer) + { + // copy data to openGL texture + const void* pData = _renderBuffers[HdAovTokens->color]->Map(); + size_t stride = _renderBuffers[HdAovTokens->color]->GetStride(); + glBindTexture(GL_TEXTURE_2D, _frameBufferTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)stride / 4, viewportHeight, 0, GL_RGBA, + GL_UNSIGNED_BYTE, pData); + _renderBuffers[HdAovTokens->color]->Unmap(); + + // copy frame buffer texture to the screen + glBindFramebuffer(GL_READ_FRAMEBUFFER, _frameBufferObject); + glBlitFramebuffer(0, 0, _viewportSize.first, _viewportSize.second, 0, 0, + _viewportSize.first, _viewportSize.second, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // Restore GL state + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + } +} diff --git a/Libraries/HdAurora/HdAuroraRenderPass.h b/Libraries/HdAurora/HdAuroraRenderPass.h new file mode 100644 index 0000000..0783172 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraRenderPass.h @@ -0,0 +1,45 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +class HdAuroraRenderBuffer; +class HdAuroraRenderDelegate; + +class HdAuroraRenderPass final : public HdRenderPass +{ +public: + HdAuroraRenderPass(HdRenderIndex* index, HdRprimCollection const& collection, + HdAuroraRenderDelegate* renderDelegate); + ~HdAuroraRenderPass(); + + bool IsConverged() const override; + +protected: + void _Execute(HdRenderPassStateSharedPtr const& renderPassState, + TfTokenVector const& renderTags) override; + + void _MarkCollectionDirty() override {} + +private: + HdAuroraRenderDelegate* _owner; + + HdRenderPassAovBindingVector _aovBindings; + std::pair _viewportSize { 0, 0 }; + unsigned int _frameBufferTexture = 0; + unsigned int _frameBufferObject = 0; + + std::shared_ptr _ownedRenderBuffer; + std::map _renderBuffers; +}; \ No newline at end of file diff --git a/Libraries/HdAurora/HdAuroraTokens.h b/Libraries/HdAurora/HdAuroraTokens.h new file mode 100644 index 0000000..ba93631 --- /dev/null +++ b/Libraries/HdAurora/HdAuroraTokens.h @@ -0,0 +1,92 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +namespace HdAuroraTokens +{ + +//**************************************************************************/ +// Render Setting Tokens +//**************************************************************************/ + +/// The maximum path tracing trace depth. +static const TfToken kTraceDepth("aurora:trace_depth"); + +/// The maximum number of path tracing samples per-pixel. +static const TfToken kMaxSamples("aurora:max_samples"); + +/// The current number of per-pixel samples rendered, as an output value. +static const TfToken kCurrentSamples("aurora:current_samples"); + +/// Whether to restart rendering, from the first sample. +static const TfToken kIsRestartEnabled("aurora:is_restart_enabled"); + +/// Whether to enable denoising. +static const TfToken kIsDenoisingEnabled("aurora:is_denoising_enabled"); + +/// Whether the alpha channel is enabled for output. +static const TfToken kIsAlphaEnabled("aurora:is_alpha_enabled"); + +/// The path to the background image file. +static const TfToken kBackgroundImage("aurora:background_image"); + +/// The top and bottom colors of the background, for a vertical gradient. +static const TfToken kBackgroundColors("aurora:background_colors"); + +/// Whether to use the environment image (from the dome light) as the background image. +static const TfToken kUseEnvironmentImageAsBackground("aurora:use_environment_image_as_background"); + +/// Whether to perform inverse tone mapping on the background image. +static const TfToken kIsInverseToneMappingEnabled("aurora:is_inverse_tone_mapping_enabled"); + +/// The exposure to use for inverse tone mapping on the background image. +static const TfToken kInverseToneMappingExposure("aurora:inverse_tone_mapping_exposure"); + +/// Whether to use a shared handle for renderer output. +static const TfToken kIsSharedHandleEnabled("aurora:is_shared_handle_enabled"); + +/// Whether the shared handle for renderer output is from DirectX, as an output value. +static const TfToken kIsSharedHandleDirectX("aurora:is_shared_handle_directx"); + +/// Whether the shared handle for renderer output is from OpenGL, as an output value. +static const TfToken kIsSharedHandleOpenGL("aurora:is_shared_handle_opengl"); + +/// The settings for the global ground plane, as defined in the Aurora API. +static const TfToken kGroundPlaneSettings("aurora:ground_plane_settings"); + +//**************************************************************************/ +// Material Tokens +//**************************************************************************/ + +/// The path to a MaterialX document file for a material. +static const TfToken kMaterialXFilePath("aurora:materialx_file_path"); + +/// The MaterialX document for a material, as XML in memory. +static const TfToken kMaterialXDocument("aurora:materialx_document"); + +//**************************************************************************/ +// Mesh Tokens +//**************************************************************************/ + +/// A vector of SdfPath objects providing material IDs of material layers for a mesh. +/// +/// Layers begin at the inner-most layer (closest to the base layer) and proceed outward. +static const TfToken kMeshMaterialLayers("aurora:mesh_material_layers"); + +/// A vector of VtToken objects providing primvar names of geometry layer UVs for a mesh. +/// +/// Layers begin at the inner-most layer (closest to the base layer) and proceed outward. +static const TfToken kMeshGeometryLayerUVs("aurora:mesh_geometry_layer_uvs"); + +}; // namespace HdAuroraTokens diff --git a/Libraries/HdAurora/pch.h b/Libraries/HdAurora/pch.h new file mode 100644 index 0000000..3cdb959 --- /dev/null +++ b/Libraries/HdAurora/pch.h @@ -0,0 +1,87 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +using namespace std; + +#pragma warning(push) +#pragma warning(disable : 4244) // disabling type conversion warnings in USD +#pragma warning(disable : 4305) // disabling type truncation warnings in USD +#pragma warning(disable : 4127) // disabling const comparison warnings in USD +#pragma warning(disable : 4201) // disabling nameless struct warnings in USD +#pragma warning(disable : 4100) // disabling unreferenced parameter warnings in USD +#pragma warning(disable : 4275) // disabling non dll-interface class used as base for dll-interface + // class in USD + +// disable clang warnings about deprecated declarations in USD headers +#if defined(__APPLE__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +// warning: order of some includes here is important for successful compilation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#pragma clang diagnostic pop +#endif +#pragma warning(pop) + +// GLM - OpenGL Mathematics. +// NOTE: This is a math library, and not specific to OpenGL. +#define GLM_FORCE_CTOR_INIT +#pragma warning(push) +#pragma warning(disable : 4201) // nameless struct/union +#include "glm/glm.hpp" +#include "glm/gtc/matrix_access.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "glm/gtc/type_ptr.hpp" +#pragma warning(pop) + +// Aurora. +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE diff --git a/Libraries/HdAurora/resources/plugInfo.json b/Libraries/HdAurora/resources/plugInfo.json new file mode 100644 index 0000000..7e49278 --- /dev/null +++ b/Libraries/HdAurora/resources/plugInfo.json @@ -0,0 +1,22 @@ +{ + "Plugins": [ + { + "Info": { + "Types": { + "HdAuroraRendererPlugin": { + "bases": [ + "HdRendererPlugin" + ], + "displayName": "Aurora", + "priority": 10 + } + } + }, + "LibraryPath": "../hdAurora.dll", + "Name": "hdAurora", + "ResourcePath": "resources", + "Root": "..", + "Type": "library" + } + ] +} diff --git a/README.md b/README.md index b722abf..188da39 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,209 @@ # Aurora -**Aurora** is a real-time path tracing renderer that leverage GPU hardware ray tracing. As a real-time renderer, it is intended to support rapid design iteration in a real-time viewport, which differs from "final frame" production rendering from a render like Autodesk Arnold. Aurora has a USD Hydra render delegate called HdAurora, which allows it to be used from a USD Hydra scene delegate. It can also be used directly through its own API. +**Aurora** is a real-time path tracing renderer that leverages GPU hardware ray tracing. As a real-time renderer, it is intended to support rapid design iteration in a real-time viewport, which differs from a "final frame" production renderer like [Autodesk Arnold](https://www.arnoldrenderer.com). Aurora has a USD Hydra render delegate called [HdAurora](Doc/HdAurora.md), which allows it to be used from a USD Hydra scene delegate. It can also be used directly through its own API, which is demonstrated with a standalone sample application called [Plasma](Doc/Plasma.md). -Aurora is developed and maintained by Autodesk. It is fully open source under the Apache license, with feature requests and contributions welcome! +Aurora is developed and maintained by Autodesk. The software and this documentation are a work-in-progress and under active development. The contents of this repository are fully open source under [the Apache license](LICENSE.md), with [feature requests and code contributions](Doc/Contributing.md) welcome! -Sample screenshot +Below you can learn about features, system requirements, how to build Aurora, how to run it, and access additional documentation. -

A screenshot of a sample model rendered in Autodesk Inventor 2023, using the HdAurora render delegate.

+Sample screenshot + +

Screenshots of the Autodesk Telescope model rendered with Aurora. Model courtesy of Roberto Ziche.

+ +## Features + +- Path tracing and the global effects that come with it: soft shadows, reflections, refractions, bounced light, and more. +- Interactive performance for complex scenes, using hardware ray tracing in modern GPUs. +- [Autodesk Standard Surface](https://autodesk.github.io/standard-surface) materials defined with [MaterialX](https://materialx.org) documents, which can represent a wide variety of real-world materials with physically-based shading. Also, independent layers of materials are supported, which can be used to implement decals. +- Environment lighting with a wrap-around lat-long image. +- Triangle geometry with object instancing. +- A USD Hydra render delegate (HdAurora) and standalone sample application (Plasma). + +... with new features and enhancements to performance and quality planned. This will include denoising with [NVIDIA Real-Time Denoisers](https://developer.nvidia.com/rtx/ray-tracing/rt-denoisers), support for alternative material models, discrete light sources, and more. + +## System Requirements + +### Operating System + +Aurora is officially supported on **Windows 10** or **Ubuntu 20.04**. Windows 11 and other Linux distributions may work, but are not yet supported. + +To run Aurora, the latest GPU drivers from [NVIDIA](https://www.nvidia.com/download/index.aspx), [AMD](https://www.amd.com/en/support), or [Intel](https://www.intel.com/content/www/us/en/download-center/home.html) are recommended as ray tracing API support is being actively improved. No other software is required to run Aurora. + +### Build Software + +The following software is required to build Aurora: + +- **C++ Compiler:** [Microsoft Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads) or [Clang 11](https://releases.llvm.org). Newer versions may work, but are not yet supported. +- **Git** (any recent version) from [the Git web site](https://git-scm.com/downloads). +- **CMake** version 3.21 or later, from [the CMake web site](https://cmake.org/download). +- **Python** version 3.7 or later, from [the Python web site](https://www.python.org/downloads). +- **The Vulkan SDK**, which can be downloaded from [LunarG's Vulkan SDK web site](https://vulkan.lunarg.com/sdk/home). + +#### Windows + +The following software is also required for building on Windows: + +- **Windows SDK** version 10.0.22000.194 or later, for Windows builds. This can be installed using the Visual Studio Installer, or with a package from the [Microsoft web site](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk). This version is needed for a more recent build of the DirectX Shader Compiler (DXC). Note that the SDK is called "Windows SDK for Windows 11" but it supports Windows 10 as well. +- **Netwide Assembler (NASM)**, from [the NASM web site](https://www.nasm.us). This is needed to build the JPEG library required by USD. + +#### Linux (Ubuntu 20.04) + +Clang 11 can be installed with the following commands: +``` +sudo apt-get update +sudo apt-get install -y build-essential clang-11 clang-format-11 clang-tidy-11 +``` + +The default compiler is gcc, so you will need to configure clang-11 as the default compiler using the `update-alternatives` command. + +Certain libraries must be installed before building on Linux. These can be installed with the following command: + +``` +sudo apt-get -y install zlib1g-dev libjpeg-turbo8-dev libtiff-dev libpng-dev libglm-dev libglew-dev libglfw3-dev libgtest-dev libgmock-dev +``` + +Note that on Windows, these same libraries are built as part of the build instructions below. + +### GPU + +Aurora requires a GPU with hardware ray tracing support, either through **DirectX Raytracing** (DXR) on Windows, or **Vulkan Ray Tracing** on Windows or Linux. These include, but are not limited to: + +- **NVIDIA GPUs with native ray tracing support** include any GPU with "RTX" in the brand name, including mobile GPUs. This includes: + - The GeForce RTX series, such as the GeForce RTX 2060. + - The Quadro RTX series, such as the Quadro RTX 4000. + - The RTX A series, such as the RTX A2000. +- **NVIDIA GPUs with compute-based support** include any GPU with the "Pascal" microarchitecture and at least 6 GB of RAM. Note that these GPUs will perform substantially slower with GPU ray tracing due to the lack of native ray tracing support. This includes: + - The GeForce 10 series, such as the GeForce GTX 1080. + - The Quadro P series, such as the Quadro P4000. +- **AMD GPUs with native ray tracing support** include any GPU with the "RDNA 2" microarchitecture. This includes: + - The RX 6000 and RX 7000 series, including the mobile RX 6000M series. + - The Radeon PRO W6000 series, such as the Radeon PRO W6800. + - The Ryzen 7 6000 series of mobile processors, which have 600M series integrated GPUs. +- **Intel GPUs with native ray tracing support** include any GPU with “Xe” architecture and DX12 support. This includes: + - The Intel™ Arc® Pro A-series for workstations, such as the Intel Arc Pro A40 and Intel Arc Pro A50. + - The Intel™ Arc® A-series, such as the Intel Arc A380 and Intel Arc A770. + +## Graphics API Support + +As noted in the system requirements above, Aurora can use either the [DirectX Raytracing](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html) API (Windows only) or the [Vulkan Ray Tracing](https://www.khronos.org/blog/ray-tracing-in-vulkan) API (on Windows or Linux). These are referred to as "backends" in the build process. + +On Windows, you can set a flag in the CMake configuration to enable the desired backend(s): +- `-D ENABLE_DIRECTX_BACKEND=[ON/OFF]` for DirectX (default is ON). +- `-D ENABLE_HGI_BACKEND=[ON/OFF]` for Vulkan (default is OFF). + +On Linux, `ENABLE_HGI_BACKEND` is `ON` and `ENABLE_DIRECTX_BACKEND` is `OFF` and cannot be changed. + +Vulkan support is provided through USD Hydra's "HGI" interface, using a prototype extension for Vulkan ray tracing available in [this branch of the Autodesk fork of USD](https://github.com/autodesk-forks/USD/tree/adsk/feature/hgiraytracing). For this reason, USD is required when compiling Aurora with the Vulkan backend on Windows or Linux. USD is built as part of the build process described below, to support both the HdAurora render delegate and Vulkan. + +NOTE: At this time Vulkan is supported on NVIDIA GPUs only. + +## Quick Start + +Follow these steps to build Aurora and its dependencies and run the sample application. + +#### Windows + +Run the following on a command prompt with compiler tools, such as "x64 Native Tools Command Prompt for VS 2019". + +``` +python Scripts\installExternals.py ..\AuroraExternals +cmake -S . -B Build -D EXTERNALS_DIR=..\AuroraExternals +cmake --build Build --config Release +cd Build\bin\Release +Plasma.exe +``` +#### Linux (Ubuntu 20.04) +``` +python Scripts/installExternals.py ../AuroraExternals +cmake -S . -B Build -D EXTERNALS_DIR=../AuroraExternals/Release +cmake --build Build +cd Build/bin/Release +./Plasma --output {OUTPUT_IMAGE_FILE.png} --scene {INPUT_SCENE_FILE.obj} --renderer hgi +``` +## Building Aurora + +Aurora includes a script that retrieves and builds dependencies ("externals") from source. This script is based on the [USD build script](https://github.com/PixarAnimationStudios/USD/tree/release/build_scripts). CMake is used to build Aurora directly, or to create an IDE project which can then be used to build or debug Aurora. + +1. **Download or clone** the contents of this repository to a location of your choice. Cloning with Git is not strictly necessary as Aurora does not currently use submodules. We refer to this location as "AURORA_DIR". + +2. **Start a command line** with access to your C++ compiler tools. When using Visual Studio, the "x64 Native Tools Command Prompt for VS 2019" shortcut will provide the proper environment. The CMake and Python executables must also be available, through the PATH environment variable. + +3. **Installing externals:** Run *[Scripts/installExternals.py](Scripts/installExternals.py)* with Python in AURORA_DIR to build and install externals. + + - You can run the install script with the desired location for storing and compiling externals as the only argument. We will refer to this location as "EXTERNALS_DIR". + ``` + python Scripts/installExternals.py {EXTERNALS_DIR} + ``` + + - The script will retrieve the source code for all dependencies using available release packages, or clone with Git as needed. The script will also compile all of the dependencies. If you want more feedback on the script execution, you can run the script with the `-v` option for verbose output. + + - The entire process takes about 30 minutes to run, and consumes about 10 GB of disk space in EXTERNALS_DIR. While USD is being compiled, the script will use most of the CPU cores, and your computer may be sluggish for a few minutes. + + - Use the `--build-variant` option to choose the build configuration of externals. It can be `Debug`, `Release` (default), or `All`. On Windows, this option needs to match the CONFIGURATION value used in the next step. + + - Use the `-h` option with the script to see available options. + +4. **Generating projects:** Run CMake in AURORA_DIR to generate build projects, e.g. a Visual Studio solution. + + - You must specify the externals installation directory (EXTERNALS_DIR, above) as a CMake path variable called `EXTERNALS_DIR`. If you are using cmake-gui, you should specify this variable before generating. + + - You must specify a build directory, and we refer to this location as "AURORA_BUILD_DIR". The recommended build directory is {AURORA_DIR}/Build, which is filtered by [.gitignore](.gitignore) so it won't appear as local changes for Git. + + - You can use CMake on the command line or the GUI (cmake-gui). The CMake command to generate projects is as follows: + + ``` + cmake -S . -B {AURORA_BUILD_DIR} -D CMAKE_BUILD_TYPE={CONFIGURATION} -D EXTERNALS_DIR={EXTERNALS_DIR} + ``` + + - The CONFIGURATION value can be one of `Debug` or `Release` (default). + + - You can optionally specify the desired graphics API backend as described above, e.g. `-D ENABLE_HGI_BACKEND=ON`. + + - On Windows, you may need to specify the toolchain and architecture with `-G "Visual Studio 16 2019" -A x64`. + +5. **Building:** You can load the *Aurora.sln* Visual Studio solution file from the Aurora build directory, and build Aurora using the build configuration used with the *installExternals.py* script (see below), or use CMake. + + - The CMake command to build Aurora is as follows: + + ``` + cmake --build {AURORA_BUILD_DIR} --config {CONFIGURATION} -v + ``` + + - The build for a single build configuration (e.g. Debug or Release) takes about a minute and consumes about 500 MB of disk space. + + +### Changing Configurations + +By default, Aurora will be built with the *Release* build configuration, i.e. for application deployment and best performance. To change to another configuration, see the information below. + +- **For the externals** installed with the *installExternals.py* script, you can specify the desired configuration with the `--build-variant` option, and specify `Debug`, `Release` (default), or `All`. Note that you can have multiple configurations built. +- **For Aurora itself**, you can specify the desired configuration with the `CMAKE_BUILD_TYPE` variable when running CMake project generation, with the same values as above. +- On Windows, it is necessary for Aurora *and* the externals be built with the same configuration. Since Visual Studio allows multiple configurations in a project, you must select the appropriate configuration in the Visual Studio interface, or you will get linker errors. +- On Linux, due to a CMake error in the debug configuration of OpenImageIO, `--build-variant` is disabled and only the release configuration of externals is installed. However, you can build either the debug or release configuration of Aurora with the release configuration of externals. To do this, specify the configuration *subdirectory* with the `EXTERNALS_DIR` variable, e.g. {EXTERNALS_DIR}/Release. + +## Running Aurora + +Aurora can be exercised in three ways: + +- Using the [Plasma](Doc/Plasma.md) sample application, either interactively or on the command line. +- Using the [HdAurora](Doc/HdAurora.md) render delegate, through a compliant USD Hydra-based application like [Usdview](https://graphics.pixar.com/usd/release/toolset.html) or certain design applications. +- Using the Aurora unit tests, which use the [Google Test](https://github.com/google/googletest) framework: [Foundation](Tests/Foundation), [AuroraInternals](Tests/AuroraInternals), and [Aurora](Tests/Aurora). + +All of these are built with Aurora, and binaries can be found in the build output directory after following the instructions above. + +## Sample Data + +The Autodesk Telescope model shown above was developed by [Roberto Ziche](https://robertoziche.com), and was inspired by Celestron products. It is made available for use with Aurora or any another application under [the CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0). You can [download a package](https://drive.google.com/file/d/1RM09qDOGcRinLJTbXCsiRfQrHmKA-1aN/view?usp=share_link) containing an OBJ version for use with Plasma, or a USD version for use with Usdview or other applications. + +Other recommended sources of data include the [McGuire Computer Graphics Archive](https://casual-effects.com/data) and the [ASWF USD Working Group](https://wiki.aswf.io/display/WGUSD/Sample+Assets). + +## Documentation + +Available documentation can by found in the [Doc](Doc) directory. This includes the following: + +- [Plasma](Doc/Plasma.md): instructions for using the sample application. +- [HdAurora](Doc/HdAurora.md): instructions for using the sample application. +- [Coding standards](Doc/CodingStandards.md). + +More information about contributions and licensing can be found here: +- [How to contribute to the project](CONTRIBUTING.md). +- [The software license](LICENSE.md), which is the Apache License 2.0. diff --git a/Scripts/cmake/externals.cmake b/Scripts/cmake/externals.cmake new file mode 100644 index 0000000..22c1154 --- /dev/null +++ b/Scripts/cmake/externals.cmake @@ -0,0 +1,85 @@ +# Configure the search paths of find_package() + +# All externals will be properly found by find_package() with the following setup. +if(DEFINED EXTERNALS_DIR) + # We need to use get_filename_component to get rid of the trailing separator + get_filename_component(EXTERNALS_DIR ${EXTERNALS_DIR} ABSOLUTE) + cmake_path(GET EXTERNALS_DIR FILENAME BUILD_TYPE_SUBFOLDER) + + # On Linux, the build type of externals is not required to match the build type + # of Aurora. We want to allow users to explicitly choose the build type of + # externals. + if(BUILD_TYPE_SUBFOLDER STREQUAL "Debug" OR BUILD_TYPE_SUBFOLDER STREQUAL "Release") + message(STATUS "Build type of EXTERNALS_DIR is specified: ${BUILD_TYPE_SUBFOLDER}") + message(STATUS "Using externals from ${EXTERNALS_DIR}") + else() + message(STATUS "Build type of EXTERNALS_DIR is not specified.") + message(STATUS "Using externals from ${EXTERNALS_DIR}/${CMAKE_BUILD_TYPE} as CMAKE_BUILD_TYPE is ${CMAKE_BUILD_TYPE}") + + # If the user does not specify the build type of externals, we will append the + # the build type with the build type of Aurora + cmake_path(APPEND EXTERNALS_DIR ${CMAKE_BUILD_TYPE}) + endif() + + if(NOT IS_DIRECTORY "${EXTERNALS_DIR}") + message(FATAL_ERROR "EXTERNALS_DIR does not exist: ${EXTERNALS_DIR}") + endif() + + list(APPEND CMAKE_PREFIX_PATH "${EXTERNALS_DIR}") + + if(NOT DEFINED NRI_ROOT) + set(NRI_ROOT "${EXTERNALS_DIR}/NRI") + endif() + + if(NOT DEFINED NRD_ROOT) + set(NRD_ROOT "${EXTERNALS_DIR}/NRD") + endif() + + if(NOT DEFINED Slang_ROOT) + set(Slang_ROOT "${EXTERNALS_DIR}/Slang") + endif() + + # GLM cmake config file is not coded properly, we have to set GLM_DIR pointed + # to the dir that contains glmConfig.cmake + if(NOT DEFINED glm_ROOT AND NOT DEFINED glm_DIR) + set(glm_DIR "${EXTERNALS_DIR}/cmake/glm") + endif() +endif() + +# If you want to use you own build of certain external library, simply set _ROOT or _DIR +# to guide find_package() to locate your own build. External libraries used by Aurora are: +# pxr +# MaterialX +# NRI +# NRD +# OpenImageIO +# glm +# Slang +# stb +# PNG +# ZLIB +# TinyGLTF +# tinyobjloader +# glew +# glfw3 +# cxxopts +# GTest + +# Debug finding the external libraries +# find_package_verbose(D3D12 REQUIRED) +# find_package_verbose(pxr REQUIRED) +# find_package_verbose(MaterialX REQUIRED) +# find_package_verbose(NRI REQUIRED) +# find_package_verbose(NRD REQUIRED) +# find_package_verbose(OpenImageIO REQUIRED) +# find_package_verbose(Slang REQUIRED) +# find_package_verbose(glm REQUIRED) +# find_package_verbose(stb REQUIRED) +# find_package_verbose(PNG REQUIRED) +# find_package_verbose(ZLIB REQUIRED) +# find_package_verbose(TinyGLTF REQUIRED) +# find_package_verbose(tinyobjloader REQUIRED) +# find_package_verbose(GLEW REQUIRED) +# find_package_verbose(glfw3 REQUIRED) +# find_package_verbose(cxxopts REQUIRED) +# find_package_verbose(GTest REQUIRED) diff --git a/Scripts/cmake/modules/FindD3D12.cmake b/Scripts/cmake/modules/FindD3D12.cmake new file mode 100644 index 0000000..865ae24 --- /dev/null +++ b/Scripts/cmake/modules/FindD3D12.cmake @@ -0,0 +1,107 @@ +# Work out Windows 10 SDK path and version +# NOTE: Based on https://github.com/microsoft/DirectXShaderCompiler/blob/master/cmake/modules/FindD3D12.cmake +if("$ENV{WINDOWS_SDK_PATH}$ENV{WINDOWS_SDK_VERSION}" STREQUAL "" ) + get_filename_component(WINDOWS_SDK_PATH "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]" ABSOLUTE CACHE) + set (WINDOWS_SDK_VERSION ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}) +else() + set (WINDOWS_SDK_PATH $ENV{WINDOWS_SDK_PATH}) + set (WINDOWS_SDK_VERSION $ENV{WINDOWS_SDK_VERSION}) +endif() + +# WINDOWS_SDK_PATH will be something like C:\Program Files (x86)\Windows Kits\10 +# WINDOWS_SDK_VERSION will be something like 10.0.14393 or 10.0.14393.0; we need the +# one that matches the directory name. +if(IS_DIRECTORY "${WINDOWS_SDK_PATH}/Include/${WINDOWS_SDK_VERSION}.0") + set(WINDOWS_SDK_VERSION "${WINDOWS_SDK_VERSION}.0") +endif() + +# Find the d3d12 and dxgi include path, it will typically look something like this. +# C:\Program Files (x86)\Windows Kits\10\Include\10.0.10586.0\um\d3d12.h +# C:\Program Files (x86)\Windows Kits\10\Include\10.0.10586.0\shared\dxgi1_6.h +find_path(D3D12_INCLUDE_DIR # Set variable D3D12_INCLUDE_DIR + d3d12.h # Find a path with d3d12.h + HINTS "${WINDOWS_SDK_PATH}/Include/${WINDOWS_SDK_VERSION}/um" + DOC "path to WINDOWS SDK header files" +) + +find_path(DXGI_INCLUDE_DIR # Set variable DXGI_INCLUDE_DIR + dxgi1_6.h # Find a path with dxgi1_6.h + HINTS "${WINDOWS_SDK_PATH}/Include/${WINDOWS_SDK_VERSION}/shared" + DOC "path to WINDOWS SDK header files" +) + +find_library(D3D12_LIBRARY + NAMES d3d12.lib + HINTS ${WINDOWS_SDK_PATH}/Lib/${WINDOWS_SDK_VERSION}/um/x64 +) + +find_library(DXGI_LIBRARY + NAMES dxgi.lib + HINTS ${WINDOWS_SDK_PATH}/Lib/${WINDOWS_SDK_VERSION}/um/x64 +) + +find_library(D3DCOMPILER_LIBRARY + NAMES d3dcompiler + HINTS ${WINDOWS_SDK_PATH}/Lib/${WINDOWS_SDK_VERSION}/um/x64 +) +find_library(DXGUID_LIBRARY + NAMES dxguid + HINTS ${WINDOWS_SDK_PATH}/Lib/${WINDOWS_SDK_VERSION}/um/x64 +) +find_library(DXCOMPILER_LIBRARY + NAMES dxcompiler + HINTS ${WINDOWS_SDK_PATH}/Lib/${WINDOWS_SDK_VERSION}/um/x64 +) +find_file(DXCOMPILER_DLL + NAMES dxcompiler.dll + HINTS ${WINDOWS_SDK_PATH}/bin/${WINDOWS_SDK_VERSION}/x64 +) +find_file(DXIL_DLL + NAMES dxil.dll + HINTS ${WINDOWS_SDK_PATH}/bin/${WINDOWS_SDK_VERSION}/x64 +) + +get_filename_component(D3D12_LIBRARY_DIR ${D3D12_LIBRARY} DIRECTORY) + +set(D3D12_LIBRARIES ${D3D12_LIBRARY} ${DXGI_LIBRARY}) +set(D3D12_COMPILER_LIBRARIES ${D3DCOMPILER_LIBRARY} ${DXGUID_LIBRARY} ${DXCOMPILER_LIBRARY}) +set(D3D12_INCLUDE_DIRS ${D3D12_INCLUDE_DIR} ${DXGI_INCLUDE_DIR}) + +set(DXCOMPILER_DLLS ${DXCOMPILER_DLL} ${DXIL_DLL}) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set D3D12_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(D3D12 + DEFAULT_MSG + D3D12_INCLUDE_DIRS + D3D12_LIBRARIES +) + +if(D3D12_FOUND) + add_library(D3D12::D3D12 UNKNOWN IMPORTED) + set_target_properties(D3D12::D3D12 PROPERTIES + IMPORTED_LOCATION ${D3D12_LIBRARY} + IMPORTED_LINK_INTERFACE_LIBRARIES "${D3D12_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${D3D12_INCLUDE_DIRS}" + ) + add_library(D3D12::compiler UNKNOWN IMPORTED) + set_target_properties(D3D12::compiler PROPERTIES + IMPORTED_LOCATION ${D3DCOMPILER_LIBRARY} + IMPORTED_LINK_INTERFACE_LIBRARIES "${D3D12_COMPILER_LIBRARIES}" + ) +endif() + +mark_as_advanced( + D3D12_INCLUDE_DIRS + D3D12_LIBRARIES + D3D12_INCLUDE_DIR + D3D12_LIBRARY + D3D12_LIBRARY_DIR + DXGI_INCLUDE_DIR + DXGI_LIBRARY + DXCOMPILER_DLLS + D3D12_COMPILER_LIBRARIES + WINDOWS_SDK_PATH + WINDOWS_SDK_VERSION +) diff --git a/Scripts/cmake/modules/FindNRD.cmake b/Scripts/cmake/modules/FindNRD.cmake new file mode 100644 index 0000000..61d6c62 --- /dev/null +++ b/Scripts/cmake/modules/FindNRD.cmake @@ -0,0 +1,94 @@ +# Find the NRD include path +if (DEFINED NRD_ROOT) + # Prioritize the NVIDIA Real-time Denoisers installed at ${NRD_ROOT} + find_path(NRD_INCLUDE_DIR # Set variable NRD_INCLUDE_DIR + NRD.h # Find a path with NRD.h + NO_DEFAULT_PATH + PATHS "${NRD_ROOT}/Include" + DOC "path to NVIDIA Real-time Denoisers SDK header files" + ) + find_path(NRD_INTEGRATION_INCLUDE_DIR # Set variable NRD_INTEGRATION_INCLUDE_DIR + NRDIntegration.h # Find a path with NRDIntegration.h + NO_DEFAULT_PATH + PATHS "${NRD_ROOT}/Integration" + DOC "path to NVIDIA Real-time Denoisers SDK header files" + ) + find_library(NRD_LIBRARY # Set variable NRD_LIBRARY + NRD # Find library path with libNRD.so, NRD.dll, or NRD.lib + NO_DEFAULT_PATH + PATHS "${NRD_ROOT}/Lib" + PATH_SUFFIXES Release RelWithDebInfo Debug + DOC "path to NVIDIA Real-time Denoisers SDK library files" + ) + find_path(NRD_SHADERS_INCLUDE_DIR + NRD.hlsli + NO_DEFAULT_PATH + PATHS "${NRD_ROOT}/Shaders/Include" + DOC "path to NVIDIA Real-time Denoisers SDK shader header files" + ) +endif() +# Once the prioritized find_path succeeds the result variable will be set and stored in the cache +# so that no call will search again. +find_path(NRD_INCLUDE_DIR # Set variable NRD_INCLUDE_DIR + NRD.h # Find a path with NRD.h + DOC "path to NVIDIA Real-time Denoisers SDK header files" +) +find_path(NRD_INTEGRATION_INCLUDE_DIR # Set variable NRD_INTEGRATION_INCLUDE_DIR + NRDIntegration.h # Find a path with NRDIntegration.h + DOC "path to NVIDIA Real-time Denoisers SDK header files" +) +find_library(NRD_LIBRARY # Set variable NRD_INCLUDE_DIR + NRD # Find library path with libNRD.so, NRD.dll, or NRD.lib + DOC "path to NVIDIA Real-time Denoisers SDK library files" +) +find_path(NRD_SHADERS_INCLUDE_DIR + NRD.hlsli + DOC "path to NVIDIA Real-time Denoisers SDK shader header files" +) + +if(NRD_SHADERS_INCLUDE_DIR) + cmake_path(GET NRD_SHADERS_INCLUDE_DIR PARENT_PATH NRD_SHADERS_DIR) + set(NRD_SHADERS_SOURCE_DIR ${NRD_SHADERS_DIR}/Source) + set(NRD_SHADERS_RESOURCES_DIR ${NRD_SHADERS_DIR}/Resources) +endif() + +set(NRD_LIBRARIES ${NRD_LIBRARY}) +set(NRD_INCLUDE_DIRS ${NRD_INCLUDE_DIR} ${NRD_INTEGRATION_INCLUDE_DIR}) + +get_filename_component(NRD_LIBRARY_DIR ${NRD_LIBRARY} DIRECTORY) +if(WIN32) + set(NRD_LIBRARY_DLL ${NRD_LIBRARY_DIR}/NRD.dll) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set NRD_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(NRD + DEFAULT_MSG + NRD_INCLUDE_DIRS + NRD_INTEGRATION_INCLUDE_DIR + NRD_LIBRARIES +) + +if(NRD_FOUND) + add_library(NRD::NRD UNKNOWN IMPORTED) + set_target_properties(NRD::NRD PROPERTIES + IMPORTED_LOCATION ${NRD_LIBRARY} + IMPORTED_LINK_INTERFACE_LIBRARIES "${NRD_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${NRD_INCLUDE_DIRS}" + ) +endif() + +mark_as_advanced( + NRD_INCLUDE_DIRS + NRD_INCLUDE_DIR + NRD_INTEGRATION_INCLUDE_DIR + NRD_LIBRARIES + NRD_LIBRARY + NRD_LIBRARY_DIR + NRD_LIBRARY_DLL + NRD_SHADERS_DIR + NRD_SHADERS_INCLUDE_DIR + NRD_SHADERS_SOURCE_DIR + NRD_SHADERS_RESOURCES_DIR +) diff --git a/Scripts/cmake/modules/FindNRI.cmake b/Scripts/cmake/modules/FindNRI.cmake new file mode 100644 index 0000000..e382553 --- /dev/null +++ b/Scripts/cmake/modules/FindNRI.cmake @@ -0,0 +1,62 @@ +# Find the NRI include path +if (DEFINED NRI_ROOT) + # Prioritize the NRI installed at ${NRI_ROOT} + find_path(NRI_INCLUDE_DIR # Set variable NRI_INCLUDE_DIR + NRI.h # Find a path with NRI.h + NO_DEFAULT_PATH + PATHS "${NRI_ROOT}/Include" + DOC "path to NVIDIA NRI SDK header files" + ) + find_library(NRI_LIBRARY # Set variable NRI_LIBRARY + NRI # Find library path with libNRI.so, NRI.dll or NRI.lib + NO_DEFAULT_PATH + PATHS "${NRI_ROOT}/Lib" + PATH_SUFFIXES Release RelWithDebInfo Debug + DOC "path to NVIDIA NRI SDK library files" + ) +endif() +# Once the privous find_path succeeds the result variable will be set and stored in the cache +# so that no call will search again. +find_path(NRI_INCLUDE_DIR # Set variable NRI_INCLUDE_DIR + NRI.h # Find a path with NRI.h + DOC "path to NVIDIA NRI SDK header files" +) +find_library(NRI_LIBRARY # Set variable NRI_INCLUDE_DIR + NRI # Find library path with libNRI.so, NRI.dll or NRI.lib + DOC "path to NVIDIA NRI SDK library files" +) + +set(NRI_LIBRARIES ${NRI_LIBRARY}) +set(NRI_INCLUDE_DIRS ${NRI_INCLUDE_DIR}) + +get_filename_component(NRI_LIBRARY_DIR ${NRI_LIBRARY} DIRECTORY) +if(WIN32) + set(NRI_LIBRARY_DLL ${NRI_LIBRARY_DIR}/NRI.dll) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set NRI_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(NRI + DEFAULT_MSG + NRI_INCLUDE_DIRS + NRI_LIBRARIES +) + +if(NRI_FOUND) + add_library(NRI::NRI UNKNOWN IMPORTED) + set_target_properties(NRI::NRI PROPERTIES + IMPORTED_LOCATION ${NRI_LIBRARY} + IMPORTED_LINK_INTERFACE_LIBRARIES "${NRI_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${NRI_INCLUDE_DIRS}" + ) +endif() + +mark_as_advanced( + NRI_INCLUDE_DIRS + NRI_INCLUDE_DIR + NRI_LIBRARIES + NRI_LIBRARY + NRI_LIBRARY_DIR + NRI_LIBRARY_DLL +) diff --git a/Scripts/cmake/modules/FindSlang.cmake b/Scripts/cmake/modules/FindSlang.cmake new file mode 100644 index 0000000..400e15d --- /dev/null +++ b/Scripts/cmake/modules/FindSlang.cmake @@ -0,0 +1,68 @@ +if(WIN32) + set(Slang_BUILD_CONFIG "windows-x64") +elseif(UNIX) + set(Slang_BUILD_CONFIG "linux-x64") +endif() + +# Find the Slang include path +if (DEFINED Slang_ROOT) + # Prioritize the Slang installed at ${Slang_ROOT} + find_path(Slang_INCLUDE_DIR # Set variable Slang_INCLUDE_DIR + slang.h # Find a path with Slang.h + NO_DEFAULT_PATH + PATHS "${Slang_ROOT}" + DOC "path to Slang header files" + ) + find_library(Slang_LIBRARY # Set variable Slang_LIBRARY + slang # Find library path with libslang.so, slang.dll, or slang.lib + NO_DEFAULT_PATH + PATHS "${Slang_ROOT}/bin/${Slang_BUILD_CONFIG}" + PATH_SUFFIXES release debug + DOC "path to slang library files" + ) +endif() +# Once the prioritized find_path succeeds the result variable will be set and stored in the cache +# so that no call will search again. +find_path(Slang_INCLUDE_DIR # Set variable Slang_INCLUDE_DIR + slang.h # Find a path with Slang.h + DOC "path to Slang header files" +) +find_library(Slang_LIBRARY # Set variable Slang_LIBRARY + slang # Find library path with libslang.so, slang.dll, or slang.lib + DOC "path to slang library files" +) + +set(Slang_LIBRARIES ${Slang_LIBRARY}) +set(Slang_INCLUDE_DIRS ${Slang_INCLUDE_DIR}) + +get_filename_component(Slang_LIBRARY_DIR ${Slang_LIBRARY} DIRECTORY) +if(WIN32) + file(GLOB Slang_LIBRARY_DLL ${Slang_LIBRARY_DIR}/*.dll) +endif() + +include(FindPackageHandleStandardArgs) +# Handle the QUIETLY and REQUIRED arguments and set Slang_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(Slang + DEFAULT_MSG + Slang_INCLUDE_DIRS + Slang_LIBRARIES +) + +if(Slang_FOUND) + add_library(Slang::Slang UNKNOWN IMPORTED) + set_target_properties(Slang::Slang PROPERTIES + IMPORTED_LOCATION ${Slang_LIBRARY} + IMPORTED_LINK_INTERFACE_LIBRARIES "${Slang_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${Slang_INCLUDE_DIRS}" + ) +endif() + +mark_as_advanced( + Slang_INCLUDE_DIRS + Slang_INCLUDE_DIR + Slang_LIBRARIES + Slang_LIBRARY + Slang_LIBRARY_DIR + Slang_LIBRARY_DLL +) diff --git a/Scripts/cmake/modules/Findstb.cmake b/Scripts/cmake/modules/Findstb.cmake new file mode 100644 index 0000000..76d3289 --- /dev/null +++ b/Scripts/cmake/modules/Findstb.cmake @@ -0,0 +1,36 @@ +# Find the stb include path +if (DEFINED stb_ROOT) + # Prioritize the stb installed at ${stb_ROOT} + find_path(stb_INCLUDE_DIR # Set variable stb_INCLUDE_DIR + stb_image.h # Find a path with stb_image.h + NO_DEFAULT_PATH + PATHS "${NRI_ROOT}" + ) +endif() +# Once the privous find_path succeeds the result variable will be set and stored in the cache +# so that no call will search again. +find_path(stb_INCLUDE_DIR # Set variable stb_INCLUDE_DIR + stb_image.h # Find a path with stb_image.h +) + +set(stb_INCLUDE_DIRS ${stb_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set NRI_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(stb + DEFAULT_MSG + stb_INCLUDE_DIRS +) + +if(stb_FOUND) + add_library(stb::stb INTERFACE IMPORTED) + set_target_properties(stb::stb PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${stb_INCLUDE_DIRS} + ) +endif() + +mark_as_advanced( + stb_INCLUDE_DIRS + stb_INCLUDE_DIR +) diff --git a/Scripts/cmake/modules/Findtinyobjloader.cmake b/Scripts/cmake/modules/Findtinyobjloader.cmake new file mode 100644 index 0000000..eb67f54 --- /dev/null +++ b/Scripts/cmake/modules/Findtinyobjloader.cmake @@ -0,0 +1,55 @@ +# Find the tinyobjloader include path +if (DEFINED tinyobjloader_ROOT) + # Prioritize the tinyobjloader installed at ${tinyobjloader_ROOT} + find_path(tinyobjloader_INCLUDE_DIR # Set variable tinyobjloader_INCLUDE_DIR + tiny_obj_loader.h # Find a path with tiny_obj_loader.h + NO_DEFAULT_PATH + PATHS "${tinyobjloader_ROOT}/Include" + DOC "path to tinyobjloader header files" + ) + find_library(tinyobjloader_LIBRARY # Set variable tinyobjloader_LIBRARY + tinyobjloader # Find library path with libtinyobjloader.so, tinyobjloader.dll or tinyobjloader.lib + NO_DEFAULT_PATH + PATHS "${tinyobjloader_ROOT}/Lib" + PATH_SUFFIXES Release RelWithDebInfo Debug + DOC "path to tinyobjloader library file" + ) +endif() +# Once the privous find_path succeeds the result variable will be set and stored in the cache +# so that no call will search again. +find_path(tinyobjloader_INCLUDE_DIR # Set variable tinyobjloader_INCLUDE_DIR + tiny_obj_loader.h # Find a path with tiny_obj_loader.h + DOC "path to tinyobjloader header files" +) +find_library(tinyobjloader_LIBRARY # Set variable tinyobjloader_INCLUDE_DIR + tinyobjloader # Find library path with libtinyobjloader.so, tinyobjloader.dll or tinyobjloader.lib + DOC "path to tinyobjloader library file" +) + +set(tinyobjloader_LIBRARIES ${tinyobjloader_LIBRARY}) +set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set tinyobjloader_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(tinyobjloader + DEFAULT_MSG + tinyobjloader_INCLUDE_DIRS + tinyobjloader_LIBRARIES +) + +if(tinyobjloader_FOUND) + add_library(tinyobjloader::tinyobjloader UNKNOWN IMPORTED) + set_target_properties(tinyobjloader::tinyobjloader PROPERTIES + IMPORTED_LOCATION "${tinyobjloader_LIBRARY}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${tinyobjloader_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${tinyobjloader_INCLUDE_DIRS}" + ) +endif() + +mark_as_advanced( + tinyobjloader_INCLUDE_DIRS + tinyobjloader_INCLUDE_DIR + tinyobjloader_LIBRARIES + tinyobjloader_LIBRARY +) diff --git a/Scripts/cmake/toolbox.cmake b/Scripts/cmake/toolbox.cmake new file mode 100644 index 0000000..e2831ea --- /dev/null +++ b/Scripts/cmake/toolbox.cmake @@ -0,0 +1,43 @@ +include(CMakePrintHelpers) + +# Debug helper to print the variables and imported targets of a package. +function(find_package_verbose PKG_NAME) + get_directory_property(_varsBefore VARIABLES) + get_directory_property(_targetsBefore IMPORTED_TARGETS) + + find_package(${PKG_NAME} ${ARGN}) + + get_directory_property(_vars VARIABLES) + list(REMOVE_ITEM _vars _varsBefore ${_varsBefore} _targetsBefore ) + message(STATUS "${PKG_NAME}:") + message(STATUS " VARIABLES:") + foreach(_var IN LISTS _vars) + message(STATUS " ${_var} = ${${_var}}") + endforeach() + + get_directory_property(_targets IMPORTED_TARGETS) + list(REMOVE_ITEM _targets _targetsBefore ${_targetsBefore}) + message(STATUS " IMPORTED_TARGETS:") + foreach(_target IN LISTS _targets) + message(STATUS " ${_target}") + endforeach() +endfunction() + + +find_package(Python3 REQUIRED) # Required to minify shaders +# Macro to setup custom command and custom build tool that minifies all of them if any has changed. +# This will remove unneeded characters and pack the shaders into a single C++ header file. +macro(minify_shaders header shader_folder shaders) + # Set the tool override for the shaders to "NONE", this will mean VS does not attempt to compile them (though they will appear in project.) + foreach(shader ${shaders}) + set_property(SOURCE ${shader} PROPERTY VS_TOOL_OVERRIDE "NONE") + endforeach() + + # Add a custom command to create minified shader. + add_custom_command( + OUTPUT ${header} + COMMAND ${Python3_EXECUTABLE} ${SCRIPTS_DIR}/minifyShadersFolder.py ${shader_folder} ${header} + COMMENT "Minifying path tracing shaders to ${header}" + DEPENDS ${shaders} + ) +endmacro() diff --git a/Scripts/deployHdAurora.py b/Scripts/deployHdAurora.py new file mode 100644 index 0000000..9963af6 --- /dev/null +++ b/Scripts/deployHdAurora.py @@ -0,0 +1,126 @@ +# Copyright 2022 Autodesk, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License.' +import os +import shutil +import sys +import pathlib +import glob +import argparse +import pathlib +import subprocess + +programDescription = """ +Deploy HdAurora to the provided USD folder (will build a new USD install if build argument provided) +""" + +parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=programDescription) + +parser.add_argument("usd_root", type=str, + help="The USD root to deploy hdAurora into.") +parser.add_argument("-a", "--aurora_root", type=str, dest="aurora_root", action="store", default=".", + help="The root of the Aurora Git repo.") +parser.add_argument("-c", "--config", type=str,dest="config", action="store", default="Release", + help="The Aurora configuration to deploy.") +parser.add_argument("-f", "--build_folder", dest="aurora_cmake_build_folder", action="store", default="Build", + help="The Aurora CMake build folder to deploy from.") +parser.add_argument("-e", "--externals_folder", dest="externals_folder", action="store", default="~/ADSK/AuroraExternals", + help="Externals folder used to build Aurora and USD (if --build specified).") +parser.add_argument("-b", "--build", dest="build", action="store_true", default=0, + help="Build Aurora and USD before deploying.") + +args = parser.parse_args() + + +# Get the USD target folders. +usd_bin_folder = os.path.join(args.usd_root,"bin") +usd_lib_folder = os.path.join(args.usd_root,"lib") +usd_plugin_folder = os.path.join(args.usd_root,"plugin","usd") +usd_hdaurora_resource_folder = os.path.join(usd_plugin_folder,"hdAurora","resources") +usd_mtlx_folder = os.path.join(usd_lib_folder,"MaterialX") + +# Get the Aurora source folders. +aurora_build_folder = os.path.join(args.aurora_root, args.aurora_cmake_build_folder) +aurora_build_bin_folder = os.path.join(aurora_build_folder ,"bin",args.config) +aurora_resource_folder = os.path.join(args.aurora_root, "Libraries", "hdAurora", "resources") +aurora_mtlx_folder = os.path.join(aurora_build_bin_folder, "MaterialX") + +# If the build arg is set, then build Aurora and USD before deploying. +if(args.build): + externals_path = pathlib.Path(args.externals_folder) + externals_folder = str(externals_path.expanduser()) + usd_repo_root = os.path.join(externals_folder,"src","USD") + print("Build flag is set, building Aurora in "+aurora_build_folder+" and building+installing USD from "+usd_repo_root+" into "+args.usd_root+" (With externals from "+externals_folder+")") + print("- Installing externals.") + if(subprocess.run(["python","Scripts/installExternals.py",externals_folder],cwd=args.aurora_root).returncode!=0): + sys.exit("Failed to install externals") + print("- Running Cmake.") + if(subprocess.run(["cmake","-S",args.aurora_root,"-B",aurora_build_folder,"-D","EXTERNALS_DIR="+externals_folder,"-D","CMAKE_BUILD_TYPE="+args.config],cwd=args.aurora_root).returncode!=0): + sys.exit("Failed to run Cmake") + print("- Building Aurora.") + if(subprocess.run(["cmake","--build",aurora_build_folder,"--config",args.config],cwd=args.aurora_root).returncode!=0): + sys.exit("Failed to build Aurora") + print("- Building USD.") + if(subprocess.run(["python","build_scripts/build_usd.py","--python",args.usd_root],cwd=usd_repo_root).returncode!=0): + sys.exit("Failed to build USD") + +# Copy a file if it has changed. +def copy_if_changed(src, dst): + if(not os.path.exists(src)): + print("File not found "+src) + return + if (not os.path.exists(dst)) or (os.stat(src).st_mtime != os.stat(dst).st_mtime): + print("Copying "+src+" to "+dst) + shutil.copy2 (src, dst) + +# Copy a file from src_folder to dst_folder if it has changed. +def copy_file(src_folder,dst_folder,name): + copy_if_changed(os.path.join(src_folder,name), os.path.join(dst_folder,name)) + +# Create target folders if they don't exist. +os.makedirs(usd_bin_folder, exist_ok=True) +os.makedirs(usd_lib_folder, exist_ok=True) +os.makedirs(usd_plugin_folder, exist_ok=True) +os.makedirs(usd_hdaurora_resource_folder, exist_ok=True) + +# Copy plugin JSON. +copy_file(aurora_resource_folder,usd_hdaurora_resource_folder,"plugInfo.json") + +# Copy hdAurora and PDB (if it exists) +copy_file(aurora_build_bin_folder,usd_plugin_folder,"hdAurora.dll") +copy_file(aurora_build_bin_folder,usd_plugin_folder,"hdAurora.pdb") + +# Copy aurora and PDB (if it exists) +copy_file(aurora_build_bin_folder,usd_lib_folder,"Aurora.dll") +copy_file(aurora_build_bin_folder,usd_lib_folder,"Aurora.pdb") + +# Copy compiler DLLs. +copy_file(aurora_build_bin_folder,usd_lib_folder,"d3dcompiler_47.dll") +copy_file(aurora_build_bin_folder,usd_lib_folder,"dxcompiler.dll") +copy_file(aurora_build_bin_folder,usd_lib_folder,"dxil.dll") +copy_file(aurora_build_bin_folder,usd_lib_folder,"slang.dll") +copy_file(aurora_build_bin_folder,usd_lib_folder,"msvcp140.dll") +copy_file(aurora_build_bin_folder,usd_lib_folder,"glew32.dll") + +# Copy MtlX library folder. +if(not os.path.exists(usd_mtlx_folder)): + os.makedirs(aurora_mtlx_folder, exist_ok=True) + mtlx_files = glob.glob(os.path.join(aurora_mtlx_folder, "**"), recursive=True) + for mtlx_file in mtlx_files: + usd_mtlx_file = mtlx_file.replace(aurora_mtlx_folder,usd_mtlx_folder) + if(os.path.isfile(mtlx_file)): + copy_if_changed(mtlx_file,usd_mtlx_file) + else: + os.makedirs(usd_mtlx_file, exist_ok=True) diff --git a/Scripts/installExternals.py b/Scripts/installExternals.py new file mode 100644 index 0000000..031b5e8 --- /dev/null +++ b/Scripts/installExternals.py @@ -0,0 +1,1479 @@ +# +# Copyright 2017 Pixar +# Copyright 2022 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +from __future__ import print_function +import argparse +import codecs +import contextlib +import datetime +import fnmatch +import glob +import locale +import multiprocessing +import os +import platform +import re +import shlex +import shutil +import subprocess +import sys +import sysconfig +import tarfile +import zipfile + +from urllib.request import urlopen +from shutil import which + +if sys.version_info.major < 3: + raise Exception("Python 3 or a more recent version is required.") + +# Helpers for printing output +verbosity = 1 + +def Print(msg): + if verbosity > 0: + print(msg) + +def PrintWarning(warning): + if verbosity > 0: + print("WARNING:", warning) + +def PrintStatus(status): + if verbosity >= 1: + print("STATUS:", status) + +def PrintInfo(info): + if verbosity >= 2: + print("INFO:", info) + +def PrintCommandOutput(output): + if verbosity >= 3: + sys.stdout.write(output) + +def PrintError(error): + if verbosity >= 3 and sys.exc_info()[1] is not None: + import traceback + traceback.print_exc() + print ("ERROR:", error) + +# Helpers for determining platform +def Windows(): + return platform.system() == "Windows" +def Linux(): + return platform.system() == "Linux" + +def GetLocale(): + return sys.stdout.encoding or locale.getdefaultlocale()[1] or "UTF-8" + +def GetCommandOutput(command): + """Executes the specified command and returns output or None.""" + try: + return subprocess.check_output(shlex.split(command), + stderr=subprocess.STDOUT).decode(GetLocale(), 'replace').strip() + except subprocess.CalledProcessError: + pass + return None + +def GetVisualStudioCompilerAndVersion(): + """ + Returns a tuple containing the path to the Visual Studio compiler + and a tuple for its version, e.g. (14, 0). If the compiler is not found + or version number cannot be determined, returns None. + """ + if not Windows(): + return None + + msvcCompiler = which('cl') + if msvcCompiler: + # VisualStudioVersion environment variable should be set by the + # Visual Studio Command Prompt. + match = re.search(r"(\d+)\.(\d+)", + os.environ.get("VisualStudioVersion", "")) + if match: + return (msvcCompiler, tuple(int(v) for v in match.groups())) + return None + +def IsVisualStudioVersionOrGreater(desiredVersion): + if not Windows(): + return False + + msvcCompilerAndVersion = GetVisualStudioCompilerAndVersion() + if msvcCompilerAndVersion: + _, version = msvcCompilerAndVersion + return version >= desiredVersion + return False + +def IsVisualStudio2019OrGreater(): + VISUAL_STUDIO_2019_VERSION = (16, 0) + return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2019_VERSION) + +def GetCPUCount(): + try: + return multiprocessing.cpu_count() + except NotImplementedError: + return 1 + +def Run(cmd, logCommandOutput = True): + """ + Run the specified command in a subprocess. + """ + PrintInfo('Running "{cmd}"'.format(cmd=cmd)) + + with codecs.open("log.txt", "a", "utf-8") as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M")) + logfile.write("\n") + logfile.write(cmd) + logfile.write("\n") + + # Let exceptions escape from subprocess calls -- higher level + # code will handle them. + if logCommandOutput: + p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + while True: + l = p.stdout.readline().decode(GetLocale(), 'replace') + if l: + logfile.write(l) + PrintCommandOutput(l) + elif p.poll() is not None: + break + else: + p = subprocess.Popen(shlex.split(cmd)) + p.wait() + + if p.returncode != 0: + # If verbosity >= 3, we'll have already been printing out command output + # so no reason to print the log file again. + if verbosity < 3: + with open("log.txt", "r") as logfile: + Print(logfile.read()) + raise RuntimeError("Failed to run '{cmd}'\nSee {log} for more details." + .format(cmd=cmd, log=os.path.abspath("log.txt"))) + +@contextlib.contextmanager +def CurrentWorkingDirectory(dir): + """ + Context manager that sets the current working directory to the given + directory and resets it to the original directory when closed. + """ + curdir = os.getcwd() + os.chdir(dir) + try: yield + finally: os.chdir(curdir) + +def CopyFiles(context, src, dest, destPrefix): + """ + Copy files like shutil.copy, but src may be a glob pattern. + """ + filesToCopy = glob.glob(src) + if not filesToCopy: + raise RuntimeError("File(s) to copy {src} not found".format(src=src)) + + instDestDir = os.path.join(context.externalsInstDir, destPrefix, dest) + if not os.path.isdir(instDestDir): + os.makedirs(instDestDir) + + for f in filesToCopy: + PrintCommandOutput("Copying {file} to {destDir}\n" + .format(file=f, destDir=instDestDir)) + shutil.copy(f, instDestDir) + +def CopyDirectory(context, srcDir, destDir, destPrefix): + """ + Copy directory like shutil.copytree. + """ + instDestDir = os.path.join(context.externalsInstDir, destPrefix, destDir) + if os.path.isdir(instDestDir): + shutil.rmtree(instDestDir) + + PrintCommandOutput("Copying {srcDir} to {destDir}\n" + .format(srcDir=srcDir, destDir=instDestDir)) + shutil.copytree(srcDir, instDestDir) + +def FormatMultiProcs(numJobs, generator): + tag = "-j" + if generator: + if "Visual Studio" in generator: + tag = "/M:" # This will build multiple projects at once. + + return "{tag}{procs}".format(tag=tag, procs=numJobs) + +def BuildConfigs(context): + configs = [] + if context.buildDebug: + configs.append("Debug") + if context.buildRelease: + configs.append("Release") + if context.buildRelWithDebInfo : + configs.append("RelWithDebInfo") + return configs + +def RunCMake(context, force, extraArgs = None, configExtraArgs = None, install = True): + """ + Invoke CMake to configure, build, and install a library whose + source code is located in the current working directory. + """ + # Create a directory for out-of-source builds in the build directory + # using the name of the current working directory. + srcDir = os.getcwd() + generator = context.cmakeGenerator + + if generator is not None: + generator = '-G "{gen}"'.format(gen=generator) + elif IsVisualStudio2019OrGreater(): + generator = '-G "Visual Studio 16 2019" -A x64' + + toolset = context.cmakeToolset + if toolset is not None: + toolset = '-T "{toolset}"'.format(toolset=toolset) + + + for config in BuildConfigs(context): + buildDir = os.path.join(context.buildDir, os.path.split(srcDir)[1], config) + if force and os.path.isdir(buildDir): + shutil.rmtree(buildDir) + if not os.path.isdir(buildDir): + os.makedirs(buildDir) + + instDir = os.path.join(context.externalsInstDir, config) + + with CurrentWorkingDirectory(buildDir): + # We use -DCMAKE_BUILD_TYPE for single-configuration generators + # (Ninja, make), and --config for multi-configuration generators + # (Visual Studio); technically we don't need BOTH at the same + # time, but specifying both is simpler than branching + Run('cmake ' + '-DCMAKE_INSTALL_PREFIX="{instDir}" ' + '-DCMAKE_PREFIX_PATH="{instDir}" ' + '-DCMAKE_BUILD_TYPE={config} ' + '{generator} ' + '{toolset} ' + '{extraArgs} ' + '{configExtraArgs} ' + '"{srcDir}"' + .format(instDir=instDir, + config=config, + srcDir=srcDir, + generator=(generator or ""), + toolset=(toolset or ""), + extraArgs=(" ".join(extraArgs) if extraArgs else ""), + configExtraArgs=(configExtraArgs[config] if configExtraArgs else ""))) + + Run("cmake --build . --config {config} {install} -- {multiproc}" + .format(config=config, + install=("--target install" if install else ""), + multiproc=FormatMultiProcs(context.numJobs, generator))) + +def GetCMakeVersion(): + """ + Returns the CMake version as tuple of integers (major, minor) or + (major, minor, patch) or None if an error occurred while launching cmake and + parsing its output. + """ + output_string = GetCommandOutput("cmake --version") + if not output_string: + PrintWarning("Could not determine cmake version -- please install it " + "and adjust your PATH") + return None + + # cmake reports, e.g., "... version 3.14.3" + match = re.search(r"version (\d+)\.(\d+)(\.(\d+))?", output_string) + if not match: + PrintWarning("Could not determine cmake version") + return None + + major, minor, patch_group, patch = match.groups() + if patch_group is None: + return (int(major), int(minor)) + else: + return (int(major), int(minor), int(patch)) + +def PatchFile(filename, patches, multiLineMatches=False): + """ + Applies patches to the specified file. patches is a list of tuples + (old string, new string). + """ + if multiLineMatches: + oldLines = [open(filename, 'r').read()] + else: + oldLines = open(filename, 'r').readlines() + newLines = oldLines + for (oldString, newString) in patches: + newLines = [s.replace(oldString, newString) for s in newLines] + if newLines != oldLines: + PrintInfo("Patching file {filename} (original in {oldFilename})..." + .format(filename=filename, oldFilename=filename + ".old")) + shutil.copy(filename, filename + ".old") + open(filename, 'w').writelines(newLines) + +def DownloadFileWithUrllib(url, outputFilename): + r = urlopen(url) + with open(outputFilename, "wb") as outfile: + outfile.write(r.read()) + +def DownloadURL(url, context, force, extractDir = None, dontExtract = None, destDir = None): + """ + Download and extract the archive file at given URL to the + source directory specified in the context. + + dontExtract may be a sequence of path prefixes that will + be excluded when extracting the archive. + + Returns the absolute path to the directory where files have + been extracted. + """ + with CurrentWorkingDirectory(context.externalsSrcDir): + # Extract filename from URL and see if file already exists. + filename = url.split("/")[-1] + if force and os.path.exists(filename): + os.remove(filename) + + if os.path.exists(filename): + PrintInfo("{0} already exists, skipping download" + .format(os.path.abspath(filename))) + else: + PrintInfo("Downloading {0} to {1}" + .format(url, os.path.abspath(filename))) + + # To work around occasional hiccups with downloading from websites + # (SSL validation errors, etc.), retry a few times if we don't + # succeed in downloading the file. + maxRetries = 5 + lastError = None + + # Download to a temporary file and rename it to the expected + # filename when complete. This ensures that incomplete downloads + # will be retried if the script is run again. + tmpFilename = filename + ".tmp" + if os.path.exists(tmpFilename): + os.remove(tmpFilename) + + for i in range(maxRetries): + try: + context.downloader(url, tmpFilename) + break + except Exception as e: + PrintCommandOutput("Retrying download due to error: {err}\n" + .format(err=e)) + lastError = e + else: + errorMsg = str(lastError) + raise RuntimeError("Failed to download {url}: {err}" + .format(url=url, err=errorMsg)) + + shutil.move(tmpFilename, filename) + + # Open the archive and retrieve the name of the top-most directory. + # This assumes the archive contains a single directory with all + # of the contents beneath it, unless a specific extractDir is specified, + # which is to be used. + archive = None + rootDir = None + members = None + try: + if tarfile.is_tarfile(filename): + archive = tarfile.open(filename) + if extractDir: + rootDir = extractDir + else: + rootDir = archive.getnames()[0].split('/')[0] + if dontExtract != None: + members = (m for m in archive.getmembers() + if not any((fnmatch.fnmatch(m.name, p) + for p in dontExtract))) + elif zipfile.is_zipfile(filename): + archive = zipfile.ZipFile(filename) + if extractDir: + rootDir = extractDir + else: + rootDir = archive.namelist()[0].split('/')[0] + if dontExtract != None: + members = (m for m in archive.getnames() + if not any((fnmatch.fnmatch(m, p) + for p in dontExtract))) + else: + raise RuntimeError("unrecognized archive file type") + + with archive: + extractedPath = os.path.abspath(destDir if destDir else rootDir) + + if force and os.path.isdir(extractedPath): + shutil.rmtree(extractedPath) + + if os.path.isdir(extractedPath): + PrintInfo("Directory {0} already exists, skipping extract" + .format(extractedPath)) + else: + PrintInfo("Extracting archive to {0}".format(extractedPath)) + + # Extract to a temporary directory then move the contents + # to the expected location when complete. This ensures that + # incomplete extracts will be retried if the script is run + # again. + tmpExtractedPath = os.path.abspath("extract_dir") + if os.path.isdir(tmpExtractedPath): + shutil.rmtree(tmpExtractedPath) + + if destDir: + archive.extractall(os.path.join(tmpExtractedPath, destDir), members=members) + shutil.move(os.path.join(tmpExtractedPath, destDir), extractedPath) + else: + archive.extractall(tmpExtractedPath, members=members) + shutil.move(os.path.join(tmpExtractedPath, rootDir), extractedPath) + + if os.path.isdir(tmpExtractedPath): + shutil.rmtree(tmpExtractedPath) + + return extractedPath + except Exception as e: + # If extraction failed for whatever reason, assume the + # archive file was bad and move it aside so that re-running + # the script will try downloading and extracting again. + shutil.move(filename, filename + ".bad") + raise RuntimeError("Failed to extract archive {filename}: {err}" + .format(filename=filename, err=e)) + +def IsGitFolder(path = '.'): + return subprocess.call(['git', '-C', path, 'status'], + stderr=subprocess.STDOUT, + stdout = open(os.devnull, 'w')) == 0 + +def GitClone(url, tag, cloneDir, context): + try: + with CurrentWorkingDirectory(context.externalsSrcDir): + # TODO check if cloneDir is a cloned folder of url + if not os.path.exists(cloneDir): + Run("git clone --recurse-submodules -b {tag} {url} {folder}".format( + tag=tag, url=url, folder=cloneDir)) + elif not IsGitFolder(cloneDir): + raise RuntimeError("Failed to clone repo {url} ({tag}): non-git folder {folder} exists".format( + url=url, tag=tag, folder=cloneDir)) + return os.path.abspath(cloneDir) + except Exception as e: + raise RuntimeError("Failed to clone repo {url} ({tag}): {err}".format( + url=url, tag=tag, err=e)) + + +############################################################ +# External dependencies required by Aurora + +AllDependencies = list() +AllDependenciesByName = dict() + +class Dependency(object): + def __init__(self, name, installer, *files): + self.name = name + self.installer = installer + self.filesToCheck = files + + AllDependencies.append(self) + AllDependenciesByName.setdefault(name.lower(), self) + + def Exists(self, context): + return all([os.path.isfile(os.path.join(context.externalsInstDir, config, f)) + for f in self.filesToCheck for config in BuildConfigs(context)]) + +############################################################ +# zlib + +ZLIB_URL = "https://github.com/madler/zlib/archive/v1.2.11.zip" + +def InstallZlib(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(ZLIB_URL, context, force)): + RunCMake(context, force, buildArgs) + +ZLIB = Dependency("zlib", InstallZlib, "include/zlib.h") + +############################################################ +# boost + +if Linux(): + BOOST_URL = "https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.gz" + BOOST_VERSION_FILE = "include/boost/version.hpp" +elif Windows(): + # The default installation of boost on Windows puts headers in a versioned + # subdirectory, which we have to account for here. In theory, specifying + # "layout=system" would make the Windows install match Linux, but that + # causes problems for other dependencies that look for boost. + # + # boost 1.70 is required for Visual Studio 2019. For simplicity, we use + # this version for all older Visual Studio versions as well. + BOOST_URL = "https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.gz" + BOOST_VERSION_FILE = "include/boost-1_70/boost/version.hpp" + +def InstallBoost_Helper(context, force, buildArgs): + # Documentation files in the boost archive can have exceptionally + # long paths. This can lead to errors when extracting boost on Windows, + # since paths are limited to 260 characters by default on that platform. + # To avoid this, we skip extracting all documentation. + # + # For some examples, see: https://svn.boost.org/trac10/ticket/11677 + dontExtract = [ + "*/doc/*", + "*/libs/*/doc/*", + "*/libs/wave/test/testwave/testfiles/utf8-test-*" + ] + + with CurrentWorkingDirectory(DownloadURL(BOOST_URL, context, force, + dontExtract=dontExtract)): + bootstrap = "bootstrap.bat" if Windows() else "./bootstrap.sh" + Run(f'{bootstrap}') + + # b2 supports at most -j64 and will error if given a higher value. + numProc = min(64, context.numJobs) + + b2Settings = [ + f'--build-dir="{context.buildDir}"', + f'-j{numProc}', + 'address-model=64', + 'link=shared', + 'runtime-link=shared', + 'threading=multi', + '--with-atomic', + '--with-program_options', + '--with-regex' + ] + + # Required by OpenImageIO + b2Settings.append("--with-date_time") + b2Settings.append("--with-system") + b2Settings.append("--with-thread") + b2Settings.append("--with-filesystem") + + if force: + b2Settings.append("-a") + + if Windows(): + # toolset parameter for Visual Studio documented here: + # https://github.com/boostorg/build/blob/develop/src/tools/msvc.jam + if context.cmakeToolset == "v142" or IsVisualStudio2019OrGreater(): + b2Settings.append("toolset=msvc-14.2") + # elif IsVisualStudio2022OrGreater(): + # b2Settings.append("toolset=msvc-14.x") + else: + b2Settings.append("toolset=msvc-14.2") + + # Add on any user-specified extra arguments. + b2Settings += buildArgs + + b2 = "b2" if Windows() else "./b2" + + # boost only accepts three variants: debug, release, profile + b2ExtraSettings = [] + if context.buildDebug: + b2ExtraSettings.append('--prefix="{}" variant=debug --debug-configuration'.format( + os.path.join(context.externalsInstDir, 'Debug'))) + if context.buildRelease: + b2ExtraSettings.append('--prefix="{}" variant=release'.format( + os.path.join(context.externalsInstDir, 'Release'))) + if context.buildRelWithDebInfo: + b2ExtraSettings.append('--prefix="{}" variant=profile'.format( + os.path.join(context.externalsInstDir, 'RelWithDebInfo'))) + + for extraSettings in b2ExtraSettings: + b2Settings.append(extraSettings) + Run('{b2} {options} install'.format(b2=b2, options=" ".join(b2Settings))) + b2Settings.pop() + +def InstallBoost(context, force, buildArgs): + # Boost's build system will install the version.hpp header before + # building its libraries. We make sure to remove it in case of + # any failure to ensure that the build script detects boost as a + # dependency to build the next time it's run. + try: + InstallBoost_Helper(context, force, buildArgs) + except: + for config in BuildConfigs(context): + versionHeader = os.path.join(context.externalsInstDir, config, BOOST_VERSION_FILE) + if os.path.isfile(versionHeader): + try: os.remove(versionHeader) + except: pass + raise + +BOOST = Dependency("boost", InstallBoost, BOOST_VERSION_FILE) + +############################################################ +# Intel TBB + +if Windows(): + TBB_URL = "https://github.com/oneapi-src/oneTBB/releases/download/2019_U6/tbb2019_20190410oss_win.zip" + TBB_ROOT_DIR_NAME = "tbb2019_20190410oss" +else: + TBB_URL = "https://github.com/oneapi-src/oneTBB/archive/refs/tags/2019_U6.tar.gz" + +def InstallTBB(context, force, buildArgs): + if Windows(): + InstallTBB_Windows(context, force, buildArgs) + elif Linux(): + InstallTBB_Linux(context, force, buildArgs) + +def InstallTBB_Windows(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(TBB_URL, context, force, TBB_ROOT_DIR_NAME)): + # On Windows, we simply copy headers and pre-built DLLs to + # the appropriate location. + if buildArgs: + PrintWarning("Ignoring build arguments {}, TBB is " + "not built from source on this platform." + .format(buildArgs)) + + for config in BuildConfigs(context): + CopyFiles(context, "bin/intel64/vc14/*.*", "bin", config) + CopyFiles(context, "lib/intel64/vc14/*.*", "lib", config) + CopyDirectory(context, "include/serial", "include/serial", config) + CopyDirectory(context, "include/tbb", "include/tbb", config) + +def InstallTBB_Linux(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(TBB_URL, context, force)): + # TBB does not support out-of-source builds in a custom location. + Run('make -j{procs} {buildArgs}' + .format(procs=context.numJobs, + buildArgs=" ".join(buildArgs))) + + for config in BuildConfigs(context): + if (config == "Release" or config == "RelWithDebInfo"): + CopyFiles(context, "build/*_release/libtbb*.*", "lib", config) + if (config == "Debug"): + CopyFiles(context, "build/*_debug/libtbb*.*", "lib", config) + CopyDirectory(context, "include/serial", "include/serial", config) + CopyDirectory(context, "include/tbb", "include/tbb", config) + +TBB = Dependency("TBB", InstallTBB, "include/tbb/tbb.h") + +############################################################ +# JPEG + +JPEG_URL = "https://github.com/libjpeg-turbo/libjpeg-turbo/archive/1.5.1.zip" + +def InstallJPEG(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(JPEG_URL, context, force)): + RunCMake(context, force, buildArgs) + +JPEG = Dependency("JPEG", InstallJPEG, "include/jpeglib.h") + +############################################################ +# TIFF + +TIFF_URL = "https://gitlab.com/libtiff/libtiff/-/archive/v4.0.7/libtiff-v4.0.7.tar.gz" + +def InstallTIFF(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(TIFF_URL, context, force)): + # libTIFF has a build issue on Windows where tools/tiffgt.c + # unconditionally includes unistd.h, which does not exist. + # To avoid this, we patch the CMakeLists.txt to skip building + # the tools entirely. We do this on Linux as well + # to avoid requiring some GL and X dependencies. + # + # We also need to skip building tests, since they rely on + # the tools we've just elided. + PatchFile("CMakeLists.txt", + [("add_subdirectory(tools)", "# add_subdirectory(tools)"), + ("add_subdirectory(test)", "# add_subdirectory(test)")]) + + # The libTIFF CMakeScript says the ld-version-script + # functionality is only for compilers using GNU ld on + # ELF systems or systems which provide an emulation; therefore + # skipping it completely on mac and windows. + if Windows(): + extraArgs = ["-Dld-version-script=OFF"] + else: + extraArgs = [] + extraArgs += buildArgs + RunCMake(context, force, extraArgs) + +TIFF = Dependency("TIFF", InstallTIFF, "include/tiff.h") + +############################################################ +# PNG + +PNG_URL = "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.29.tar.gz" + +def InstallPNG(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(PNG_URL, context, force)): + RunCMake(context, force, buildArgs) + +PNG = Dependency("PNG", InstallPNG, "include/png.h") + +############################################################ +# GLM + +GLM_URL = "https://github.com/g-truc/glm/archive/refs/tags/0.9.9.8.zip" + +# TODO is the install structure proper? +def InstallGLM(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(GLM_URL, context, force)): + for config in BuildConfigs(context): + CopyDirectory(context, "glm", "glm", config) + CopyDirectory(context, "cmake/glm", "cmake/glm", config) + +GLM = Dependency("GLM", InstallGLM, "glm/glm.hpp") + +############################################################ +# STB + +STB_URL = "https://github.com/nothings/stb/archive/refs/heads/master.zip" + +def InstallSTB(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(STB_URL, context, force)): + for config in BuildConfigs(context): + CopyFiles(context, "*.h", "include", config) + +STB = Dependency("STB", InstallSTB, "include/stb_image.h") + +############################################################ +# TinyGLTF + +TinyGLTF_URL = "https://github.com/syoyo/tinygltf/archive/refs/tags/v2.5.0.zip" + +def InstallTinyGLTF(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(TinyGLTF_URL, context, force)): + RunCMake(context, force, buildArgs) + +TINYGLTF = Dependency("TinyGLTF", InstallTinyGLTF, "include/tiny_gltf.h") + +############################################################ +# TinyObjLoader + +TinyObjLoader_URL = "https://github.com/tinyobjloader/tinyobjloader/archive/refs/tags/v2.0-rc1.zip" + +def InstallTinyObjLoader(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(TinyObjLoader_URL, context, force)): + RunCMake(context, force, buildArgs) + +TINYOBJLOADER = Dependency("TinyObjLoader", InstallTinyObjLoader, "include/tiny_obj_loader.h") + +############################################################ +# IlmBase/OpenEXR + +if Windows(): + OPENEXR_URL = "https://github.com/AcademySoftwareFoundation/openexr/archive/refs/tags/v2.5.2.zip" +else: + OPENEXR_URL = "https://github.com/AcademySoftwareFoundation/openexr/archive/refs/tags/v2.4.3.zip" + +def InstallOpenEXR(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(OPENEXR_URL, context, force)): + extraArgs = [ + '-DPYILMBASE_ENABLE=OFF', + '-DOPENEXR_VIEWERS_ENABLE=OFF', + '-DBUILD_TESTING=OFF', + '-DOPENEXR_BUILD_PYTHON_LIBS=OFF' + ] + + # Add on any user-specified extra arguments. + extraArgs += buildArgs + + packageConfigs = { + "Debug": '-DOPENEXR_PACKAGE_PREFIX="{}"'.format( + os.path.join(context.externalsInstDir, 'Debug')), + "Release": '-DOPENEXR_PACKAGE_PREFIX="{}"'.format( + os.path.join(context.externalsInstDir, 'Release')), + "RelWithDebInfo": '-DOPENEXR_PACKAGE_PREFIX="{}"'.format( + os.path.join(context.externalsInstDir, 'RelWithDebInfo')) + } + + RunCMake(context, force, extraArgs, configExtraArgs = packageConfigs) + +OPENEXR = Dependency("OpenEXR", InstallOpenEXR, "include/OpenEXR/ImfVersion.h") + +############################################################ +# OpenImageIO + +OIIO_URL = "https://github.com/OpenImageIO/oiio/archive/Release-2.1.16.0.zip" + +def InstallOpenImageIO(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(OIIO_URL, context, force)): + extraArgs = ['-DOIIO_BUILD_TOOLS=OFF', + '-DOIIO_BUILD_TESTS=OFF', + '-DUSE_PYTHON=OFF', + '-DSTOP_ON_WARNING=OFF', + '-DUSE_PTEX=OFF'] + + # Make sure to use boost installed by the build script and not any + # system installed boost + extraArgs.append('-DBoost_NO_BOOST_CMAKE=On') + extraArgs.append('-DBoost_NO_SYSTEM_PATHS=True') + + # Add on any user-specified extra arguments. + extraArgs += buildArgs + + # OIIO's FindOpenEXR module circumvents CMake's normal library + # search order, which causes versions of OpenEXR installed in + # /usr/local or other hard-coded locations in the module to + # take precedence over the version we've built, which would + # normally be picked up when we specify CMAKE_PREFIX_PATH. + # This may lead to undefined symbol errors at build or runtime. + # So, we explicitly specify the OpenEXR we want to use here. + openEXRConfigs = { + "Debug": '-DOPENEXR_ROOT="{}"'.format( + os.path.join(context.externalsInstDir, 'Debug')), + "Release": '-DOPENEXR_ROOT="{}"'.format( + os.path.join(context.externalsInstDir, 'Release')), + "RelWithDebInfo": '-DOPENEXR_ROOT="{}"'.format( + os.path.join(context.externalsInstDir, 'RelWithDebInfo')), + } + + RunCMake(context, force, extraArgs, configExtraArgs = openEXRConfigs) + +OPENIMAGEIO = Dependency("OpenImageIO", InstallOpenImageIO, + "include/OpenImageIO/oiioversion.h") + +############################################################ +# OpenSubdiv + +OPENSUBDIV_URL = "https://github.com/PixarAnimationStudios/OpenSubdiv/archive/v3_4_4.zip" + +def InstallOpenSubdiv(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(OPENSUBDIV_URL, context, force)): + extraArgs = [ + '-DNO_EXAMPLES=ON', + '-DNO_TUTORIALS=ON', + '-DNO_REGRESSION=ON', + '-DNO_DOC=ON', + '-DNO_OMP=ON', + '-DNO_CUDA=ON', + '-DNO_OPENCL=ON', + '-DNO_DX=ON', + '-DNO_TESTS=ON', + '-DNO_GLEW=ON', + '-DNO_GLFW=ON', + '-DNO_PTEX=ON', + ] + + # NOTE: For now, we disable TBB in our OpenSubdiv build. + # This avoids an issue where OpenSubdiv will link against + # all TBB libraries it finds, including libtbbmalloc and + # libtbbmalloc_proxy. On Linux, this has the + # unwanted effect of replacing the system allocator with + # tbbmalloc. + extraArgs.append('-DNO_TBB=ON') + + # Add on any user-specified extra arguments. + extraArgs += buildArgs + + # OpenSubdiv seems to error when building on windows w/ Ninja... + # ...so just use the default generator (ie, Visual Studio on Windows) + # until someone can sort it out + oldGenerator = context.cmakeGenerator + if oldGenerator == "Ninja" and Windows(): + context.cmakeGenerator = None + + oldNumJobs = context.numJobs + + try: + RunCMake(context, force, extraArgs) + finally: + context.cmakeGenerator = oldGenerator + context.numJobs = oldNumJobs + +OPENSUBDIV = Dependency("OpenSubdiv", InstallOpenSubdiv, + "include/opensubdiv/version.h") + +############################################################ +# MaterialX + +MATERIALX_URL = "https://github.com/materialx/MaterialX/archive/v1.38.5.zip" + +def InstallMaterialX(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(MATERIALX_URL, context, force)): + cmakeOptions = ['-DMATERIALX_BUILD_SHARED_LIBS=ON'] + + cmakeOptions += buildArgs + + RunCMake(context, force, cmakeOptions) + +MATERIALX = Dependency("MaterialX", InstallMaterialX, "include/MaterialXCore/Library.h") + +############################################################ +# USD +USD_URL = "https://github.com/autodesk-forks/USD/archive/refs/tags/v22.08-Aurora-v22.11.zip" +def InstallUSD(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(USD_URL, context, force)): +# USD_URL = "https://github.com/autodesk-forks/USD.git" +# USD_TAG = "v22.08-Aurora-v22.11" +# def InstallUSD(context, force, buildArgs): +# USD_FOLDER = "USD-"+USD_TAG +# with CurrentWorkingDirectory(GitClone(USD_URL, USD_TAG, USD_FOLDER, context)): + extraArgs = [] + + if Linux(): + extraArgs.append('-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++') + + extraArgs.append('-DPXR_PREFER_SAFETY_OVER_SPEED=ON') + extraArgs.append('-DBUILD_SHARED_LIBS=ON') + + extraArgs.append('-DPXR_BUILD_DOCUMENTATION=OFF') + extraArgs.append('-DPXR_BUILD_TESTS=OFF') + extraArgs.append('-DPXR_BUILD_EXAMPLES=OFF') + extraArgs.append('-DPXR_BUILD_TUTORIALS=OFF') + + extraArgs.append('-DPXR_ENABLE_VULKAN_SUPPORT=ON') + + extraArgs.append('-DPXR_BUILD_USD_TOOLS=OFF') + + extraArgs.append('-DPXR_BUILD_IMAGING=ON') + extraArgs.append('-DPXR_ENABLE_PTEX_SUPPORT=OFF') + extraArgs.append('-DPXR_ENABLE_OPENVDB_SUPPORT=OFF') + extraArgs.append('-DPXR_BUILD_EMBREE_PLUGIN=OFF') + extraArgs.append('-DPXR_BUILD_PRMAN_PLUGIN=OFF') + extraArgs.append('-DPXR_BUILD_OPENIMAGEIO_PLUGIN=OFF') + extraArgs.append('-DPXR_BUILD_OPENCOLORIO_PLUGIN=OFF') + + extraArgs.append('-DPXR_BUILD_USD_IMAGING=ON') + + extraArgs.append('-DPXR_BUILD_USDVIEW=OFF') + + extraArgs.append('-DPXR_BUILD_ALEMBIC_PLUGIN=OFF') + extraArgs.append('-DPXR_BUILD_DRACO_PLUGIN=OFF') + + extraArgs.append('-DPXR_ENABLE_MATERIALX_SUPPORT=OFF') + + extraArgs.append('-DPXR_ENABLE_PYTHON_SUPPORT=OFF') + + # Turn off the text system in USD (Autodesk extension) + extraArgs.append('-DPXR_ENABLE_TEXT_SUPPORT=OFF') + + if Windows(): + # Increase the precompiled header buffer limit. + extraArgs.append('-DCMAKE_CXX_FLAGS="/Zm150"') + + # Make sure to use boost installed by the build script and not any + # system installed boost + extraArgs.append('-DBoost_NO_BOOST_CMAKE=On') + extraArgs.append('-DBoost_NO_SYSTEM_PATHS=True') + + extraArgs.append('-DPXR_LIB_PREFIX=') + + extraArgs += buildArgs + + tbbConfigs = { + "Debug": '-DTBB_USE_DEBUG_BUILD=ON', + "Release": '-DTBB_USE_DEBUG_BUILD=OFF', + "RelWithDebInfo": '-DTBB_USE_DEBUG_BUILD=OFF', + } + RunCMake(context, force, extraArgs, configExtraArgs=tbbConfigs) + +USD = Dependency("USD", InstallUSD, "include/pxr/pxr.h") + +############################################################ +# Slang + +if Windows(): + Slang_URL = "https://github.com/shader-slang/slang/releases/download/v0.24.35/slang-0.24.35-win64.zip" +else: + Slang_URL = "https://github.com/shader-slang/slang/releases/download/v0.24.35/slang-0.24.35-linux-x86_64.zip" + +def InstallSlang(context, force, buildArgs): + Slang_FOLDER = DownloadURL(Slang_URL, context, force, destDir="Slang") + for config in BuildConfigs(context): + CopyDirectory(context, Slang_FOLDER, "Slang", config) + +SLANG = Dependency("Slang", InstallSlang, "Slang/slang.h") + +############################################################ +# NRD + +NRD_URL = "https://github.com/NVIDIAGameWorks/RayTracingDenoiser.git" +NRD_TAG = "v3.8.0" + +def InstallNRD(context, force, buildArgs): + NRD_FOLDER = "NRD-"+NRD_TAG + with CurrentWorkingDirectory(GitClone(NRD_URL, NRD_TAG, NRD_FOLDER, context)): + RunCMake(context, force, buildArgs, install=False) + + for config in BuildConfigs(context): + CopyDirectory(context, "Include", "NRD/Include", config) + CopyDirectory(context, "Integration", "NRD/Integration", config) + if context.buildRelease or context.buildRelWithDebInfo : + if Windows(): + CopyFiles(context, "_Build/Release/*.dll", "bin", config) + CopyDirectory(context, "_Build/Release", "NRD/Lib/Release", config) + if context.buildDebug: + if Windows(): + CopyFiles(context, "_Build/Debug/*.dll", "bin", config) + CopyDirectory(context, "_Build/Debug", "NRD/Lib/Debug", config) + + # NRD v2.x.x #TODO need to use config as part of installation path + # CopyDirectory(context, "Shaders", "NRD/Shaders", config) + # CopyFiles(context, "Source/Shaders/Include/*.*", "NRD/Shaders", config) + # CopyFiles(context, "External/MathLib/*.*", "NRD/Shaders", config) + # CopyFiles(context, "Include/*.*", "NRD/Shaders", config) + + # NRD v3.x.x + CopyDirectory(context, "Shaders", "NRD/Shaders", config) + CopyFiles(context, "Shaders/Include/NRD.hlsli", "NRD/Shaders/Include", config) + CopyFiles(context, "External/MathLib/*.hlsli", "NRD/Shaders/Source", config) + +NRD = Dependency("NRD", InstallNRD, "NRD/Include/NRD.h") + +############################################################ +# NRI + +NRI_URL = "https://github.com/NVIDIAGameWorks/NRI.git" +NRI_TAG = "v1.87" + +def InstallNRI(context, force, buildArgs): + NRI_FOLDER = "NRI-"+NRI_TAG + with CurrentWorkingDirectory(GitClone(NRI_URL, NRI_TAG, NRI_FOLDER, context)): + RunCMake(context, force, buildArgs, install=False) + + for config in BuildConfigs(context): + CopyDirectory(context, "Include", "NRI/Include", config) + CopyDirectory(context, "Include/Extensions", "NRI/Include/Extensions", config) + if context.buildRelease or context.buildRelWithDebInfo : + if Windows(): + CopyFiles(context, "_Build/Release/*.dll", "bin", config) + CopyDirectory(context, "_Build/Release", "NRI/Lib/Release", config) + if context.buildDebug: + if Windows(): + CopyFiles(context, "_Build/Debug/*.dll", "bin", config) + CopyDirectory(context, "_Build/Debug", "NRI/Lib/Debug", config) + +NRI = Dependency("NRI", InstallNRI, "NRI/Include/NRI.h") + +############################################################ +# GLEW + +GLEW_URL = "https://github.com/nigels-com/glew/releases/download/glew-2.2.0/glew-2.2.0-win32.zip" + +def InstallGLEW(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(GLEW_URL, context, force)): + for config in BuildConfigs(context): + CopyDirectory(context, "include/GL", "include/GL", config) + CopyFiles(context, "bin/Release/x64/*.dll", "bin", config) + CopyFiles(context, "lib/Release/x64/*.lib", "lib", config) + # TODO: shall we support Debug build of glew? + # buildVariant=("Debug" if context.buildDebug or context.buildRelWithDebInfo else "Release"), + # CopyFiles(context, f'bin/{buildVariant}/x64/*.dll', "bin") + # CopyFiles(context, f'lib/{buildVariant}/x64/*.lib', "lib") + +GLEW = Dependency("GLEW", InstallGLEW, "include/GL/glew.h") + +############################################################ +# GLFW + +GLFW_URL = "https://github.com/glfw/glfw/archive/refs/tags/3.3.8.zip" + +def InstallGLFW(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(GLFW_URL, context, force)): + RunCMake(context, force, buildArgs) + +GLFW = Dependency("GLFW", InstallGLFW, "include/GLFW/glfw3.h") + +############################################################ +# CXXOPTS + +CXXOPTS_URL = "https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.0.0.zip" + +def InstallCXXOPTS(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(CXXOPTS_URL, context, force)): + RunCMake(context, force, buildArgs) + +CXXOPTS = Dependency("CXXOPTS", InstallCXXOPTS, "include/cxxopts.hpp") + +############################################################ +# GTEST + +GTEST_URL = "https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip" + +def InstallGTEST(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(GTEST_URL, context, force)): + extraArgs = [*buildArgs, '-Dgtest_force_shared_crt=ON'] + RunCMake(context, force, extraArgs) + +GTEST = Dependency("GTEST", InstallGTEST, "include/gtest/gtest.h") + +############################################################ +# Installation script + +programDescription = """\ +Installation Script for external libraries required by Aurora + +- Libraries: +The following is a list of libraries that this script will download and build +as needed. These names can be used to identify libraries for various script +options, like --force or --build-args. + +{libraryList} + +- Specifying Custom Build Arguments: +Users may specify custom build arguments for libraries using the --build-args +option. This values for this option must take the form ,