From c0c0ec5ebd156ebeb14e9fa602ae8bd6a79fc4d4 Mon Sep 17 00:00:00 2001 From: Tomas Davidovic Date: Mon, 19 Aug 2024 09:24:25 +0200 Subject: [PATCH] Automatic update --- .editorconfig | 3 + .gitignore | 1 + CMakeLists.txt | 4 +- CMakePresets.json | 5 +- Source/Falcor/CMakeLists.txt | 13 +- Source/Falcor/Core/API/Buffer.cpp | 85 +- Source/Falcor/Core/API/Buffer.h | 36 +- Source/Falcor/Core/API/Device.cpp | 117 +- Source/Falcor/Core/API/Device.h | 29 +- Source/Falcor/Core/API/GpuMemoryHeap.cpp | 6 +- Source/Falcor/Core/API/ParameterBlock.cpp | 43 +- Source/Falcor/Core/API/ParameterBlock.h | 4 +- Source/Falcor/Core/API/Resource.h | 4 +- Source/Falcor/Core/API/ResourceViews.cpp | 51 +- Source/Falcor/Core/API/ResourceViews.h | 38 +- Source/Falcor/Core/Pass/ComputePass.cpp | 2 +- Source/Falcor/Core/Program/Program.h | 8 +- Source/Falcor/Core/Program/ProgramManager.cpp | 42 +- Source/Falcor/Core/State/StateGraph.h | 3 +- Source/Falcor/Core/Testbed.cpp | 103 +- Source/Falcor/Core/Testbed.h | 25 +- Source/Falcor/Core/Version.h | 2 +- Source/Falcor/Core/Window.cpp | 3 +- .../Falcor/DiffRendering/DiffSceneQuery.slang | 9 +- Source/Falcor/RenderGraph/RenderGraph.cpp | 35 +- Source/Falcor/RenderGraph/RenderGraph.h | 10 +- Source/Falcor/RenderGraph/RenderGraphExe.cpp | 11 +- Source/Falcor/RenderGraph/RenderGraphExe.h | 7 +- Source/Falcor/RenderGraph/RenderPass.h | 9 +- .../Rendering/Lights/EmissiveLightSampler.cpp | 30 +- .../Rendering/Lights/EmissiveLightSampler.h | 15 +- .../Lights/EmissiveLightSamplerHelpers.slang | 9 +- .../Rendering/Lights/EmissivePowerSampler.cpp | 23 +- .../Rendering/Lights/EmissivePowerSampler.h | 9 +- .../Lights/EmissiveUniformSampler.cpp | 8 +- .../Rendering/Lights/EmissiveUniformSampler.h | 4 +- .../Rendering/Lights/EnvMapSampler.slang | 21 +- Source/Falcor/Rendering/Lights/LightBVH.cpp | 4 +- Source/Falcor/Rendering/Lights/LightBVH.h | 10 +- .../Rendering/Lights/LightBVHSampler.cpp | 26 +- .../Falcor/Rendering/Lights/LightBVHSampler.h | 7 +- .../Rendering/Materials/BSDFs/BeerBTDF.slang | 18 +- .../Materials/BSDFs/DielectricPlateBSDF.slang | 22 +- .../Materials/BSDFs/DiffuseSpecularBRDF.slang | 20 +- .../Materials/BSDFs/DisneyDiffuseBRDF.slang | 18 +- .../BSDFs/FrostbiteDiffuseBRDF.slang | 18 +- .../Materials/BSDFs/LambertDiffuseBRDF.slang | 18 +- .../Materials/BSDFs/LambertDiffuseBTDF.slang | 18 +- .../Materials/BSDFs/OrenNayarBRDF.slang | 20 +- .../Rendering/Materials/BSDFs/SheenBSDF.slang | 20 +- .../Materials/BSDFs/SimpleBTDF.slang | 18 +- .../Materials/BSDFs/SpecularMicrofacet.slang | 38 +- .../Materials/BSDFs/StandardBSDF.slang | 66 +- .../Rendering/Materials/ClothBRDF.slang | 13 +- .../Materials/ClothMaterialInstance.slang | 6 +- .../Falcor/Rendering/Materials/Fresnel.slang | 17 +- .../Rendering/Materials/HairChiang16.slang | 17 +- .../Materials/HairMaterialInstance.slang | 8 +- Source/Falcor/Rendering/Materials/IBSDF.slang | 34 +- .../Materials/IMaterialInstance.slang | 22 +- .../Rendering/Materials/IsotropicGGX.slang | 38 +- .../Rendering/Materials/LayeredBSDF.slang | 110 +- .../Materials/MERLMaterialInstance.slang | 8 +- .../Materials/MERLMixMaterialInstance.slang | 6 +- .../Materials/MaterialInstanceHints.slang | 47 + .../PBRT/PBRTCoatedConductorMaterial.slang | 4 +- .../PBRTCoatedConductorMaterialInstance.slang | 13 +- .../PBRT/PBRTCoatedDiffuseMaterial.slang | 4 +- .../PBRTCoatedDiffuseMaterialInstance.slang | 13 +- .../PBRT/PBRTConductorMaterialInstance.slang | 21 +- .../PBRT/PBRTDielectricMaterial.slang | 4 +- .../PBRT/PBRTDielectricMaterialInstance.slang | 58 +- .../PBRT/PBRTDiffuseMaterialInstance.slang | 21 +- ...TDiffuseTransmissionMaterialInstance.slang | 2 +- .../Materials/RGLMaterialInstance.slang | 2 +- .../Materials/StandardMaterialInstance.slang | 12 +- .../Rendering/RTXDI/EnvLightUpdater.cs.slang | 2 +- .../Rendering/RTXDI/LightUpdater.cs.slang | 2 +- Source/Falcor/Rendering/RTXDI/RTXDI.cpp | 48 +- Source/Falcor/Rendering/RTXDI/RTXDI.h | 10 +- Source/Falcor/Rendering/RTXDI/RTXDI.slang | 4 +- .../RTXDI/RTXDIApplicationBridge.slangh | 18 +- .../Rendering/Volumes/GridVolumeSampler.cpp | 4 +- .../Rendering/Volumes/GridVolumeSampler.h | 6 +- .../Scene/Animation/AnimatedVertexCache.cpp | 11 +- Source/Falcor/Scene/Animation/Animation.h | 8 +- .../Scene/Animation/AnimationController.cpp | 45 +- .../Scene/Animation/AnimationController.h | 14 +- Source/Falcor/Scene/Animation/Skinning.slang | 6 +- .../UpdateCurvePolyTubeVertices.slang | 4 +- .../Scene/Animation/UpdateMeshVertices.slang | 4 +- Source/Falcor/Scene/HitInfo.slang | 16 +- Source/Falcor/Scene/IScene.cpp | 45 + Source/Falcor/Scene/IScene.h | 199 ++++ .../Scene/Lights/BuildTriangleList.cs.slang | 5 +- .../Scene/Lights/EmissiveIntegrator.3d.slang | 5 +- Source/Falcor/Scene/Lights/EnvMap.cpp | 6 +- Source/Falcor/Scene/Lights/EnvMap.h | 4 +- Source/Falcor/Scene/Lights/EnvMap.slang | 4 +- .../Scene/Lights/FinalizeIntegration.cs.slang | 2 +- Source/Falcor/Scene/Lights/ILightCollection.h | 171 +++ .../Falcor/Scene/Lights/LightCollection.cpp | 11 +- Source/Falcor/Scene/Lights/LightCollection.h | 89 +- .../Falcor/Scene/Lights/LightCollection.slang | 3 +- Source/Falcor/Scene/Lights/LightProfile.cpp | 4 +- Source/Falcor/Scene/Lights/LightProfile.h | 4 +- .../Falcor/Scene/Lights/MeshLightData.slang | 3 +- .../Lights/UpdateTriangleVertices.cs.slang | 6 +- Source/Falcor/Scene/Material/Material.cpp | 3 +- Source/Falcor/Scene/Material/Material.h | 8 +- .../Scene/Material/MaterialFactory.slang | 5 +- .../Scene/Material/MaterialParamLayout.h | 3 +- .../Falcor/Scene/Material/MaterialSystem.cpp | 49 +- Source/Falcor/Scene/Material/MaterialSystem.h | 9 +- .../Scene/Material/MaterialSystem.slang | 31 +- Source/Falcor/Scene/MeshIO.cs.slang | 4 +- Source/Falcor/Scene/Raster.slang | 7 +- Source/Falcor/Scene/Raytracing.slang | 4 +- Source/Falcor/Scene/RaytracingInline.slang | 33 +- Source/Falcor/Scene/SDFs/SDF3DPrimitive.slang | 10 +- Source/Falcor/Scene/SDFs/SDFGrid.cpp | 35 +- .../Scene/SDFs/SparseVoxelSet/SDFSVS.cpp | 11 +- .../Scene/SDFs/SparseVoxelSet/SDFSVS.slang | 5 +- Source/Falcor/Scene/Scene.cpp | 326 +++--- Source/Falcor/Scene/Scene.h | 252 ++-- Source/Falcor/Scene/Scene.slang | 22 +- Source/Falcor/Scene/SceneBuilder.cpp | 75 +- Source/Falcor/Scene/SceneBuilder.h | 6 +- Source/Falcor/Scene/SceneBuilderDump.cpp | 4 +- Source/Falcor/Scene/SceneCache.cpp | 69 +- Source/Falcor/Scene/SceneCache.h | 10 +- .../Falcor/Scene/SceneRayQueryInterface.slang | 9 +- Source/Falcor/Scene/SceneTypes.slang | 178 ++- Source/Falcor/Scene/ShadingData.slang | 5 +- Source/Falcor/Scene/VertexData.slang | 49 + Source/Falcor/Scene/Volume/Grid.h | 6 +- Source/Falcor/Scene/Volume/GridConverter.h | 3 +- Source/Falcor/Testing/UnitTest.cpp | 42 +- Source/Falcor/Utils/Algorithm/UnionFind.h | 3 +- Source/Falcor/Utils/Color/ColorHelpers.slang | 105 +- Source/Falcor/Utils/CudaUtils.cpp | 60 +- Source/Falcor/Utils/CudaUtils.h | 8 +- Source/Falcor/Utils/HostDeviceShared.slangh | 8 +- Source/Falcor/Utils/Image/Bitmap.cpp | 86 +- Source/Falcor/Utils/Image/Bitmap.h | 7 +- Source/Falcor/Utils/Math/AABB.h | 7 +- Source/Falcor/Utils/Math/FNVHash.h | 22 +- Source/Falcor/Utils/Math/Float16.h | 7 +- Source/Falcor/Utils/Math/MathConstants.slangh | 3 +- Source/Falcor/Utils/Math/MathHelpers.slang | 5 +- Source/Falcor/Utils/Math/MatrixJson.h | 66 ++ Source/Falcor/Utils/Math/MatrixMath.h | 4 +- Source/Falcor/Utils/Math/MatrixTypes.h | 3 +- Source/Falcor/Utils/Math/Quaternion.slang | 4 +- Source/Falcor/Utils/Math/ScalarJson.h | 56 + .../Utils/Math/SphericalHarmonics.slang | 37 +- Source/Falcor/Utils/Math/VectorJson.h | 60 + Source/Falcor/Utils/ObjectID.h | 6 +- Source/Falcor/Utils/Scripting/Scripting.cpp | 14 +- Source/Falcor/Utils/Scripting/Scripting.h | 4 +- .../Falcor/Utils/Settings/AttributeFilters.h | 3 +- Source/Falcor/Utils/Settings/Attributes.h | 22 +- Source/Falcor/Utils/Settings/Settings.cpp | 24 +- Source/Falcor/Utils/Settings/Settings.h | 13 +- Source/Falcor/Utils/Settings/SettingsUtils.h | 14 +- Source/Falcor/Utils/SplitBuffer.h | 298 +++++ Source/Falcor/Utils/TaskManager.h | 4 +- Source/Falcor/Utils/Timing/Profiler.cpp | 25 +- Source/Falcor/Utils/Timing/Profiler.h | 10 +- Source/Falcor/Utils/UI/Gui.cpp | 17 +- Source/Falcor/Utils/UI/ImGuiConfig.h | 48 + Source/Falcor/Utils/UI/PythonUI.cpp | 16 +- Source/Modules/USDUtils/CMakeLists.txt | 4 + Source/Modules/USDUtils/ConvertedInput.cpp | 429 +++++++ Source/Modules/USDUtils/ConvertedInput.h | 157 +++ .../USDUtils/ConvertedMaterialCache.cpp | 28 + .../Modules/USDUtils/ConvertedMaterialCache.h | 176 +++ .../PreviewSurfaceConverter.cpp | 621 ++-------- .../PreviewSurfaceConverter.h | 66 +- .../StandardMaterialSpec.h | 157 +-- .../USDUtils/Tessellator/Tessellation.cpp | 7 +- Source/Modules/USDUtils/USDUtils.cpp | 2 +- Source/Modules/USDUtils/USDUtils.h | 71 +- Source/Mogwai/Mogwai.cpp | 27 +- Source/Mogwai/Mogwai.h | 9 +- Source/Mogwai/MogwaiScripting.cpp | 4 +- Source/Mogwai/MogwaiSettings.cpp | 10 +- Source/Mogwai/MogwaiSettings.h | 4 +- .../AccumulatePass/AccumulatePass.cpp | 6 +- .../BSDFOptimizer/BSDFOptimizer.cpp | 4 +- .../BSDFOptimizer/BSDFOptimizer.cs.slang | 9 +- .../BSDFOptimizer/BSDFViewer.cs.slang | 8 +- Source/RenderPasses/BSDFViewer/BSDFViewer.cpp | 4 +- .../BSDFViewer/BSDFViewer.cs.slang | 10 +- Source/RenderPasses/CMakeLists.txt | 1 + .../GBuffer/GBuffer/GBufferRT.cpp | 10 +- .../GBuffer/GBuffer/GBufferRT.cs.slang | 6 +- .../GBuffer/GBuffer/GBufferRT.slang | 6 +- .../GBuffer/GBuffer/GBufferRaster.3d.slang | 4 +- .../GBuffer/GBuffer/GBufferRaster.cpp | 6 +- .../GBuffer/GBuffer/GBufferRaster.h | 4 +- Source/RenderPasses/GBuffer/GBufferBase.cpp | 8 +- Source/RenderPasses/GBuffer/GBufferBase.h | 6 +- .../GBuffer/VBuffer/VBufferRT.cpp | 50 +- .../GBuffer/VBuffer/VBufferRT.cs.slang | 6 +- .../RenderPasses/GBuffer/VBuffer/VBufferRT.h | 2 +- .../GBuffer/VBuffer/VBufferRT.rt.slang | 7 +- .../GBuffer/VBuffer/VBufferRT.slang | 6 +- .../GBuffer/VBuffer/VBufferRaster.cpp | 4 +- .../MinimalPathTracer/MinimalPathTracer.cpp | 6 +- .../MinimalPathTracer.rt.slang | 15 +- .../OptixDenoiser/OptixDenoiser.cpp | 4 +- .../OverlaySamplePass/CMakeLists.txt | 10 + .../OverlaySamplePass/OverlaySamplePass.cpp | 276 +++++ .../OverlaySamplePass/OverlaySamplePass.h | 62 + .../PathTracer/GeneratePaths.cs.slang | 9 +- .../PathTracer/LoadShadingData.slang | 7 +- .../RenderPasses/PathTracer/PathState.slang | 2 +- Source/RenderPasses/PathTracer/PathTracer.cpp | 74 +- Source/RenderPasses/PathTracer/PathTracer.h | 15 +- .../RenderPasses/PathTracer/PathTracer.slang | 48 +- .../PathTracer/PathTracerNRD.slang | 18 +- .../PathTracer/TracePass.rt.slang | 19 +- .../PixelInspector.cs.slang | 10 +- .../PixelInspectorPass/PixelInspectorPass.cpp | 6 +- .../RTXDIPass/FinalShading.cs.slang | 8 +- .../RTXDIPass/LoadShadingData.slang | 17 +- .../RTXDIPass/PrepareSurfaceData.cs.slang | 6 +- Source/RenderPasses/RTXDIPass/RTXDIPass.cpp | 6 +- .../SceneDebugger/SceneDebugger.cpp | 94 +- .../SceneDebugger/SceneDebugger.cs.slang | 229 +++- .../SceneDebugger/SceneDebugger.h | 11 +- .../SceneDebugger/SharedTypes.slang | 21 +- .../RenderPasses/TestPasses/TestRtProgram.cpp | 6 +- Source/RenderPasses/ToneMapper/ToneMapper.cpp | 4 +- Source/RenderPasses/ToneMapper/ToneMapper.h | 2 +- .../Utils/CrossFade/CrossFade.cpp | 8 +- .../WARDiffPathTracer/PTUtils.slang | 15 +- .../WARDiffPathTracer/WARDiffPathTracer.cpp | 11 +- .../WhittedRayTracer/WhittedRayTracer.cpp | 6 +- .../WhittedRayTracer.rt.slang | 21 +- Source/Samples/HelloDXR/HelloDXR.3d.slang | 4 +- Source/Samples/HelloDXR/HelloDXR.cpp | 8 +- Source/Samples/HelloDXR/HelloDXR.rt.slang | 7 +- Source/Tools/FalcorTest/CMakeLists.txt | 8 + .../Tests/Core/AssetResolverTests.cpp | 74 +- .../FalcorTest/Tests/Core/BufferTests.cpp | 19 +- .../Tests/Core/BufferTests.cs.slang | 40 +- .../FalcorTest/Tests/Core/TextureArrays.cpp | 132 +++ .../Tests/Core/TextureArrays.cs.slang | 62 + .../Tests/Scene/Material/BSDFTests.cs.slang | 12 +- .../Scene/Material/HairChiang16Tests.cs.slang | 14 +- .../Tools/FalcorTest/Tests/Slang/Atomics.cpp | 196 ++++ .../FalcorTest/Tests/Slang/Atomics.cs.slang | 92 ++ .../FalcorTest/Tests/Slang/CastFloat16.cpp | 8 +- .../FalcorTest/Tests/Slang/Float16Tests.cpp | 58 +- .../Tests/Slang/Float16Tests.cs.slang | 4 +- .../FalcorTest/Tests/Slang/SlangGenerics.cpp | 76 ++ .../Tests/Slang/SlangGenerics.cs.slang | 115 ++ .../Tests/Slang/StructuredBufferMatrix.cpp | 6 +- .../Slang/StructuredBufferMatrix.cs.slang | 7 +- .../FalcorTest/Tests/Slang/WaveOps.cs.slang | 7 +- .../FalcorTest/Tests/Utils/MatrixTests.cpp | 28 +- .../Tests/Utils/SplitBufferTests.cpp | 655 +++++++++++ .../Tests/Utils/SplitBufferTests.cs.slang | 179 +++ .../FalcorTest/Tests/Utils/VectorTests.cpp | 44 +- .../AssimpImporter/AssimpImporter.cpp | 22 +- Source/plugins/importers/CMakeLists.txt | 1 + .../importers/MitsubaImporter/CMakeLists.txt | 21 + .../MitsubaImporter/MitsubaImporter.cpp | 834 +++++++++++++ .../MitsubaImporter/MitsubaImporter.h | 54 + .../importers/MitsubaImporter/Parser.h | 1040 +++++++++++++++++ .../importers/MitsubaImporter/README.md | 244 ++++ .../importers/MitsubaImporter/Resolver.h | 77 ++ .../importers/MitsubaImporter/Tables.h | 84 ++ .../importers/USDImporter/CMakeLists.txt | 1 + .../importers/USDImporter/ImporterContext.cpp | 41 +- .../importers/USDImporter/ImporterContext.h | 3 +- .../importers/USDImporter/USDImporter.cpp | 8 +- .../importers/USDImporter/USDImporter.h | 4 +- build_scripts/deploycommon.bat | 4 +- build_scripts/deploycommon.sh | 4 +- dependencies.xml | 10 +- docs/usage/scenes.md | 2 +- external/CMakeLists.txt | 48 +- .../{ => BS_thread_pool}/BS_thread_pool.hpp | 0 .../BS_thread_pool_light.hpp | 0 external/include/sigs/sigs.h | 515 ++++++++ tests/environment/gitlab_linux.json | 7 + tests/environment/gitlab_windows.json | 7 + .../renderpasses/graphs/PathTracerAdaptive.py | 2 +- tests/run_image_tests.sh | 2 +- tests/run_python_tests.sh | 2 +- tests/run_unit_tests.sh | 2 +- tests/testing/core/environment.py | 29 +- tests/testing/core/helpers.py | 10 +- tests/testing/run_image_tests.py | 56 +- tests/testing/run_unit_tests.py | 2 +- tests/testing/view_image_tests.py | 4 +- tools/packman/bootstrap/configure.bat | 2 +- tools/packman/bootstrap/install_package.py | 24 +- tools/packman/packman | 2 +- tools/packman/packmanconf.py | 33 +- 303 files changed, 10393 insertions(+), 2401 deletions(-) create mode 100644 Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang create mode 100644 Source/Falcor/Scene/IScene.cpp create mode 100644 Source/Falcor/Scene/IScene.h create mode 100644 Source/Falcor/Scene/Lights/ILightCollection.h create mode 100644 Source/Falcor/Scene/VertexData.slang create mode 100644 Source/Falcor/Utils/Math/MatrixJson.h create mode 100644 Source/Falcor/Utils/Math/ScalarJson.h create mode 100644 Source/Falcor/Utils/Math/VectorJson.h create mode 100644 Source/Falcor/Utils/SplitBuffer.h create mode 100644 Source/Falcor/Utils/UI/ImGuiConfig.h create mode 100644 Source/Modules/USDUtils/ConvertedInput.cpp create mode 100644 Source/Modules/USDUtils/ConvertedInput.h create mode 100644 Source/Modules/USDUtils/ConvertedMaterialCache.cpp create mode 100644 Source/Modules/USDUtils/ConvertedMaterialCache.h create mode 100644 Source/RenderPasses/OverlaySamplePass/CMakeLists.txt create mode 100644 Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.cpp create mode 100644 Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.h create mode 100644 Source/Tools/FalcorTest/Tests/Core/TextureArrays.cpp create mode 100644 Source/Tools/FalcorTest/Tests/Core/TextureArrays.cs.slang create mode 100644 Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp create mode 100644 Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang create mode 100644 Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cpp create mode 100644 Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cs.slang create mode 100644 Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp create mode 100644 Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cs.slang create mode 100644 Source/plugins/importers/MitsubaImporter/CMakeLists.txt create mode 100644 Source/plugins/importers/MitsubaImporter/MitsubaImporter.cpp create mode 100644 Source/plugins/importers/MitsubaImporter/MitsubaImporter.h create mode 100644 Source/plugins/importers/MitsubaImporter/Parser.h create mode 100644 Source/plugins/importers/MitsubaImporter/README.md create mode 100644 Source/plugins/importers/MitsubaImporter/Resolver.h create mode 100644 Source/plugins/importers/MitsubaImporter/Tables.h rename external/include/{ => BS_thread_pool}/BS_thread_pool.hpp (100%) rename external/include/{ => BS_thread_pool}/BS_thread_pool_light.hpp (100%) create mode 100644 external/include/sigs/sigs.h create mode 100644 tests/environment/gitlab_linux.json create mode 100644 tests/environment/gitlab_windows.json diff --git a/.editorconfig b/.editorconfig index a0849f9f2..15ba26bd0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,3 +15,6 @@ insert_final_newline = true # Override trailing whitespace setting for Markdown since there it's actually useful [*.{md}] trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.gitignore b/.gitignore index cee907802..eca595be0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ *.mp4 *.tlog *.bak +*.swp diff --git a/CMakeLists.txt b/CMakeLists.txt index f4ce72d1c..365785a2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -349,11 +349,11 @@ file(TO_NATIVE_PATH ${output_dir} output_dir) if(FALCOR_WINDOWS) add_custom_target(deploy_dependencies ALL - COMMAND ${CMAKE_SOURCE_DIR}/build_scripts/deploycommon.bat ${source_dir} ${output_dir} $ ${FALCOR_SLANG_CONFIG} ${FALCOR_DLSS_DIR} + COMMAND ${CMAKE_SOURCE_DIR}/build_scripts/deploycommon.bat ${source_dir} ${output_dir} $ ${FALCOR_SLANG_DIR} ${FALCOR_DLSS_DIR} ) elseif(FALCOR_LINUX) add_custom_target(deploy_dependencies ALL - COMMAND ${CMAKE_SOURCE_DIR}/build_scripts/deploycommon.sh ${source_dir} ${output_dir} $ ${FALCOR_SLANG_CONFIG} ${FALCOR_DLSS_DIR} + COMMAND ${CMAKE_SOURCE_DIR}/build_scripts/deploycommon.sh ${source_dir} ${output_dir} $ ${FALCOR_SLANG_DIR} ${FALCOR_DLSS_DIR} ) endif() set_target_properties(deploy_dependencies PROPERTIES FOLDER "Misc") diff --git a/CMakePresets.json b/CMakePresets.json index 120965679..8d52e7b13 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -69,15 +69,12 @@ "hidden": true, "inherits": "windows-base", "generator": "Ninja Multi-Config", - "environment": { - "VCToolsVersion": "14.30" - }, "architecture": { "value": "x64", "strategy": "external" }, "toolset": { - "value": "host=x86,version=14.3", + "value": "host=x86,v143", "strategy": "external" }, "cacheVariables": { diff --git a/Source/Falcor/CMakeLists.txt b/Source/Falcor/CMakeLists.txt index 4fa060384..55c098a55 100644 --- a/Source/Falcor/CMakeLists.txt +++ b/Source/Falcor/CMakeLists.txt @@ -239,6 +239,7 @@ target_sources(Falcor PRIVATE Rendering/Materials/IsotropicGGX.slang Rendering/Materials/LayeredBSDF.slang Rendering/Materials/LobeType.slang + Rendering/Materials/MaterialInstanceHints.slang Rendering/Materials/MERLCommon.slang Rendering/Materials/MERLMaterialInstance.slang Rendering/Materials/MERLMaterial.slang @@ -326,6 +327,8 @@ target_sources(Falcor PRIVATE Scene/Importer.h Scene/ImporterError.h Scene/Intersection.slang + Scene/IScene.cpp + Scene/IScene.h Scene/MeshIO.cs.slang Scene/NullTrace.cs.slang Scene/Raster.slang @@ -352,6 +355,7 @@ target_sources(Falcor PRIVATE Scene/TriangleMesh.cpp Scene/TriangleMesh.h Scene/VertexAttrib.slangh + Scene/VertexData.slang Scene/Animation/Animatable.cpp Scene/Animation/Animatable.h @@ -392,6 +396,7 @@ target_sources(Falcor PRIVATE Scene/Lights/EnvMap.slang Scene/Lights/EnvMapData.slang Scene/Lights/FinalizeIntegration.cs.slang + Scene/Lights/ILightCollection.h Scene/Lights/Light.cpp Scene/Lights/Light.h Scene/Lights/LightCollection.cpp @@ -558,6 +563,7 @@ target_sources(Falcor PRIVATE Utils/Properties.h Utils/SharedCache.h Utils/SlangUtils.slang + Utils/SplitBuffer.h Utils/StringFormatters.h Utils/StringUtils.cpp Utils/StringUtils.h @@ -639,6 +645,7 @@ target_sources(Falcor PRIVATE Utils/Math/MathHelpers.h Utils/Math/MathHelpers.slang Utils/Math/Matrix.h + Utils/Math/MatrixJson.h Utils/Math/MatrixMath.h Utils/Math/MatrixTypes.h Utils/Math/MatrixUtils.slang @@ -652,11 +659,13 @@ target_sources(Falcor PRIVATE Utils/Math/Ray.slang Utils/Math/Rectangle.cpp Utils/Math/Rectangle.h + Utils/Math/ScalarJson.h Utils/Math/ScalarMath.h Utils/Math/ScalarTypes.h Utils/Math/ShadingFrame.slang Utils/Math/SphericalHarmonics.slang Utils/Math/Vector.h + Utils/Math/VectorJson.h Utils/Math/VectorMath.h Utils/Math/VectorTypes.h @@ -820,8 +829,8 @@ target_compile_options(Falcor # Configure warnings /WX # warnings as errors /W4 # increase warning level + /wd4819 # the file contains a character that cannot be represented in the current code page(936) /wd4251 # 'type' : class 'type1' needs to have dll-interface to be used by clients of class 'type2' - /wd4819 # The file contains a character that cannot be represented in the current code page(936) /wd4244 # 'conversion' conversion from 'type1' to 'type2', possible loss of data /wd4267 # 'var' : conversion from 'size_t' to 'type', possible loss of data /wd4100 # unreferenced formal parameter @@ -947,6 +956,7 @@ target_compile_definitions(Falcor FALCOR_HAS_D3D12_AGILITY_SDK=$ # TODO: RTXDI is always available, we might want to remove the feature flag. FALCOR_HAS_RTXDI=1 + IMGUI_USER_CONFIG="Utils/UI/ImGuiConfig.h" PRIVATE #$<$:_ITERATOR_DEBUG_LEVEL=0> FALCOR_PROJECT_DIR="${CMAKE_SOURCE_DIR}/" @@ -989,7 +999,6 @@ target_link_libraries(Falcor $<$:gtk3> ) - target_include_directories(Falcor PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/Source/Falcor/Core/API/Buffer.cpp b/Source/Falcor/Core/API/Buffer.cpp index e89bf27ba..df6e8ee3e 100644 --- a/Source/Falcor/Core/API/Buffer.cpp +++ b/Source/Falcor/Core/API/Buffer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -28,6 +28,7 @@ #include "Buffer.h" #include "Device.h" #include "GFXAPI.h" +#include "GFXHelpers.h" #include "NativeHandleTraits.h" #include "PythonHelpers.h" #include "Core/Error.h" @@ -47,9 +48,18 @@ namespace Falcor // TODO: Replace with include? void getGFXResourceState(ResourceBindFlags flags, gfx::ResourceState& defaultState, gfx::ResourceStateSet& allowedStates); -static void prepareGFXBufferDesc(gfx::IBufferResource::Desc& bufDesc, size_t size, ResourceBindFlags bindFlags, MemoryType memoryType) +static void prepareGFXBufferDesc( + gfx::IBufferResource::Desc& bufDesc, + size_t size, + size_t elementSize, + ResourceFormat format, + ResourceBindFlags bindFlags, + MemoryType memoryType +) { bufDesc.sizeInBytes = size; + bufDesc.elementSize = elementSize; + bufDesc.format = getGFXFormat(format); switch (memoryType) { case MemoryType::DeviceLocal: @@ -74,6 +84,8 @@ Slang::ComPtr createBufferResource( ref pDevice, Buffer::State initState, size_t size, + size_t elementSize, + ResourceFormat format, ResourceBindFlags bindFlags, MemoryType memoryType ) @@ -82,7 +94,7 @@ Slang::ComPtr createBufferResource( // Create the buffer gfx::IBufferResource::Desc bufDesc = {}; - prepareGFXBufferDesc(bufDesc, size, bindFlags, memoryType); + prepareGFXBufferDesc(bufDesc, size, elementSize, format, bindFlags, memoryType); Slang::ComPtr pApiHandle; FALCOR_GFX_CALL(pDevice->getGfxDevice()->createBufferResource(bufDesc, nullptr, pApiHandle.writeRef())); @@ -91,17 +103,22 @@ Slang::ComPtr createBufferResource( return pApiHandle; } -Buffer::Buffer(ref pDevice, size_t size, ResourceBindFlags bindFlags, MemoryType memoryType, const void* pInitData) +Buffer::Buffer( + ref pDevice, + size_t size, + size_t structSize, + ResourceFormat format, + ResourceBindFlags bindFlags, + MemoryType memoryType, + const void* pInitData +) : Resource(pDevice, Type::Buffer, bindFlags, size), mMemoryType(memoryType) { FALCOR_CHECK(size > 0, "Can't create GPU buffer of size zero"); // Check that buffer size is within 4GB limit. Larger buffers are currently not well supported in D3D12. // TODO: Revisit this check in the future. - if (size > (1ull << 32)) - { - logWarning("Creating GPU buffer of size {} bytes. Buffers above 4GB are not currently well supported.", size); - } + FALCOR_CHECK(size <= (1ull << 32), "Creating GPU buffer of size {} bytes. Buffers above 4GB are not currently well supported.", size); if (mMemoryType != MemoryType::DeviceLocal && is_set(mBindFlags, ResourceBindFlags::Shared)) { @@ -109,6 +126,8 @@ Buffer::Buffer(ref pDevice, size_t size, ResourceBindFlags bindFlags, Me } mSize = align_to(mpDevice->getBufferDataAlignment(bindFlags), mSize); + mStructSize = structSize; + mFormat = format; if (mMemoryType == MemoryType::DeviceLocal) { @@ -125,7 +144,7 @@ Buffer::Buffer(ref pDevice, size_t size, ResourceBindFlags bindFlags, Me mState.global = Resource::State::CopyDest; } - mGfxBufferResource = createBufferResource(mpDevice, mState.global, mSize, mBindFlags, mMemoryType); + mGfxBufferResource = createBufferResource(mpDevice, mState.global, mSize, mStructSize, mFormat, mBindFlags, mMemoryType); if (pInitData) setBlob(pInitData, 0, size); @@ -133,6 +152,10 @@ Buffer::Buffer(ref pDevice, size_t size, ResourceBindFlags bindFlags, Me mElementCount = uint32_t(size); } +Buffer::Buffer(ref pDevice, size_t size, ResourceBindFlags bindFlags, MemoryType memoryType, const void* pInitData) + : Buffer(pDevice, size, 0, ResourceFormat::Unknown, bindFlags, memoryType, pInitData) +{} + Buffer::Buffer( ref pDevice, ResourceFormat format, @@ -141,9 +164,8 @@ Buffer::Buffer( MemoryType memoryType, const void* pInitData ) - : Buffer(pDevice, (size_t)getFormatBytesPerBlock(format) * elementCount, bindFlags, memoryType, pInitData) + : Buffer(pDevice, (size_t)getFormatBytesPerBlock(format) * elementCount, 0, format, bindFlags, memoryType, pInitData) { - mFormat = format; mElementCount = elementCount; } @@ -156,20 +178,28 @@ Buffer::Buffer( const void* pInitData, bool createCounter ) - : Buffer(pDevice, (size_t)structSize * elementCount, bindFlags, memoryType, pInitData) + : Buffer(pDevice, (size_t)structSize * elementCount, structSize, ResourceFormat::Unknown, bindFlags, memoryType, pInitData) { mElementCount = elementCount; - mStructSize = structSize; static const uint32_t zero = 0; if (createCounter) { - mpUAVCounter = make_ref(mpDevice, sizeof(uint32_t), ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, &zero); + FALCOR_CHECK(mStructSize > 0, "Can't create a counter buffer with struct size of 0."); + mpUAVCounter = make_ref( + mpDevice, + sizeof(uint32_t), + sizeof(uint32_t), + ResourceFormat::Unknown, + ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + &zero + ); } } // TODO: Its wasteful to create a buffer just to replace it afterwards with the supplied one! Buffer::Buffer(ref pDevice, gfx::IBufferResource* pResource, size_t size, ResourceBindFlags bindFlags, MemoryType memoryType) - : Buffer(pDevice, size, bindFlags, memoryType, nullptr) + : Buffer(pDevice, size, 0, ResourceFormat::Unknown, bindFlags, memoryType, nullptr) { FALCOR_ASSERT(pResource); mGfxBufferResource = pResource; @@ -184,7 +214,7 @@ inline Slang::ComPtr gfxResourceFromNativeHandle( ) { gfx::IBufferResource::Desc bufDesc = {}; - prepareGFXBufferDesc(bufDesc, size, bindFlags, memoryType); + prepareGFXBufferDesc(bufDesc, size, 0, ResourceFormat::Unknown, bindFlags, memoryType); gfx::InteropHandle gfxNativeHandle = {}; #if FALCOR_HAS_D3D12 @@ -225,12 +255,12 @@ gfx::IResource* Buffer::getGfxResource() const return mGfxBufferResource; } -ref Buffer::getSRV(uint32_t firstElement, uint32_t elementCount) +ref Buffer::getSRV(uint64_t offset, uint64_t size) { - ResourceViewInfo view = ResourceViewInfo(firstElement, elementCount); + ResourceViewInfo view = ResourceViewInfo(offset, size); if (mSrvs.find(view) == mSrvs.end()) - mSrvs[view] = ShaderResourceView::create(getDevice().get(), this, firstElement, elementCount); + mSrvs[view] = ShaderResourceView::create(getDevice().get(), this, offset, size); return mSrvs[view]; } @@ -240,12 +270,12 @@ ref Buffer::getSRV() return getSRV(0); } -ref Buffer::getUAV(uint32_t firstElement, uint32_t elementCount) +ref Buffer::getUAV(uint64_t offset, uint64_t size) { - ResourceViewInfo view = ResourceViewInfo(firstElement, elementCount); + ResourceViewInfo view = ResourceViewInfo(offset, size); if (mUavs.find(view) == mUavs.end()) - mUavs[view] = UnorderedAccessView::create(getDevice().get(), this, firstElement, elementCount); + mUavs[view] = UnorderedAccessView::create(getDevice().get(), this, offset, size); return mUavs[view]; } @@ -323,16 +353,6 @@ void Buffer::unmap() const } } -uint32_t Buffer::getElementSize() const -{ - if (mStructSize != 0) - return mStructSize; - if (mFormat == ResourceFormat::Unknown) - return 1; - - FALCOR_THROW("Inferring element size from resource format is not implemented"); -} - bool Buffer::adjustSizeOffsetParams(size_t& size, size_t& offset) const { if (offset >= mSize) @@ -455,7 +475,6 @@ FALCOR_SCRIPT_BINDING(Buffer) buffer.def_property_readonly("is_typed", &Buffer::isTyped); buffer.def_property_readonly("is_structured", &Buffer::isStructured); buffer.def_property_readonly("format", &Buffer::getFormat); - buffer.def_property_readonly("element_size", &Buffer::getElementSize); buffer.def_property_readonly("element_count", &Buffer::getElementCount); buffer.def_property_readonly("struct_size", &Buffer::getStructSize); diff --git a/Source/Falcor/Core/API/Buffer.h b/Source/Falcor/Core/API/Buffer.h index 83c1d5bd3..7e3ad26f1 100644 --- a/Source/Falcor/Core/API/Buffer.h +++ b/Source/Falcor/Core/API/Buffer.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -145,6 +145,19 @@ class FALCOR_API Buffer : public Resource { FALCOR_OBJECT(Buffer) public: + static constexpr uint64_t kEntireBuffer = ResourceViewInfo::kEntireBuffer; + + /// Constructor. + Buffer( + ref pDevice, + size_t size, + size_t structSize, + ResourceFormat format, + ResourceBindFlags bindFlags, + MemoryType memoryType, + const void* pInitData + ); + /// Constructor for raw buffer. Buffer(ref pDevice, size_t size, ResourceBindFlags bindFlags, MemoryType memoryType, const void* pInitData); @@ -184,17 +197,17 @@ class FALCOR_API Buffer : public Resource /** * Get a shader-resource view. - * @param[in] firstElement The first element of the view. For raw buffers, an element is a single float - * @param[in] elementCount The number of elements to bind + * @param[in] offset Offset in bytes. + * @param[in] size Size in bytes. */ - ref getSRV(uint32_t firstElement, uint32_t elementCount = kMaxPossible); + ref getSRV(uint64_t offset, uint64_t size = kEntireBuffer); /** * Get an unordered access view. - * @param[in] firstElement The first element of the view. For raw buffers, an element is a single float - * @param[in] elementCount The number of elements to bind + * @param[in] offset Offset in bytes. + * @param[in] size size in bytes. */ - ref getUAV(uint32_t firstElement, uint32_t elementCount = kMaxPossible); + ref getUAV(uint64_t offset, uint64_t size = kEntireBuffer); /** * Get a shader-resource view for the entire resource @@ -206,15 +219,6 @@ class FALCOR_API Buffer : public Resource */ virtual ref getUAV() override; - /** - * Get the size of each element in this buffer. - * - * For a typed buffer, this will be the size of the format. - * For a structured buffer, this will be the same value as `getStructSize()`. - * For a raw buffer, this will be the number of bytes. - */ - uint32_t getElementSize() const; - /** * Update the buffer's data * @param[in] pData Pointer to the source data. diff --git a/Source/Falcor/Core/API/Device.cpp b/Source/Falcor/Core/API/Device.cpp index f804bb646..38db0faf4 100644 --- a/Source/Falcor/Core/API/Device.cpp +++ b/Source/Falcor/Core/API/Device.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -59,6 +59,7 @@ #include // Required for checking SER support. #endif + #include namespace Falcor { @@ -390,6 +391,7 @@ inline Device::SupportedFeatures querySupportedFeatures(gfx::IDevice* pDevice) result |= Device::SupportedFeatures::WaveOperations; } + return result; } @@ -502,6 +504,11 @@ Device::Device(const Desc& desc) : mDesc(desc) // Get list of available GPUs. const auto gpus = getGPUs(mDesc.type); + if (gpus.size() == 0) + { + FALCOR_THROW("Did not find any GPUs for device type '{}'.", enumToString(mDesc.type)); + } + if (mDesc.gpu >= gpus.size()) { logWarning("GPU index {} is out of range, using first GPU instead.", mDesc.gpu); @@ -530,6 +537,10 @@ Device::Device(const Desc& desc) : mDesc(desc) mLimits = queryLimits(mGfxDevice); mSupportedFeatures = querySupportedFeatures(mGfxDevice); + // Attempt to enable ray tracing validation if requested + if (mDesc.enableRaytracingValidation) + enableRaytracingValidation(); + #if FALCOR_HAS_AFTERMATH if (mDesc.enableAftermath) { @@ -681,6 +692,8 @@ Device::~Device() mpProfiler.reset(); + disableRaytracingValidation(); + // Release all the bound resources. Need to do that before deleting the RenderContext mGfxCommandQueue.setNull(); mDeferredReleases = decltype(mDeferredReleases)(); @@ -755,10 +768,11 @@ ref Device::createStructuredBuffer( FALCOR_THROW("Can't create a structured buffer from type '{}'.", pType->getClassName()); } - FALCOR_ASSERT(pResourceType->getSize() <= std::numeric_limits::max()); - return make_ref( - ref(this), (uint32_t)pResourceType->getSize(), elementCount, bindFlags, memoryType, pInitData, createCounter - ); + // Read the stride directly from the slang type layout, as the stored 'byte size' may not be the same + auto structStride = pResourceType->getStructType()->getSlangTypeLayout()->getStride(); + + FALCOR_ASSERT(structStride <= std::numeric_limits::max()); + return make_ref(ref(this), (uint32_t)structStride, elementCount, bindFlags, memoryType, pInitData, createCounter); } ref Device::createStructuredBuffer( @@ -1054,6 +1068,7 @@ cuda_utils::CudaDevice* Device::getCudaDevice() const #endif + void Device::reportLiveObjects() { gfx::gfxReportLiveObjects(); @@ -1146,6 +1161,9 @@ void Device::endFrame() if (mpFrameFence->getSignaledValue() > kInFlightFrameCount) mpFrameFence->wait(mpFrameFence->getSignaledValue() - kInFlightFrameCount); + // Flush ray tracing validation if enabled + flushRaytracingValidation(); + // Switch to next transient resource heap. getCurrentTransientResourceHeap()->finish(); mCurrentTransientResourceHeapIndex = (mCurrentTransientResourceHeapIndex + 1) % kInFlightFrameCount; @@ -1186,6 +1204,86 @@ NativeHandle Device::getNativeHandle(uint32_t index) const return {}; } +// Debug log message from ray tracing validation system +#if FALCOR_NVAPI_AVAILABLE && FALCOR_HAS_D3D12 +static void RaytracingValidationCallback( + void* pUserData, + NVAPI_D3D12_RAYTRACING_VALIDATION_MESSAGE_SEVERITY severity, + const char* messageCode, + const char* message, + const char* messageDetails +) +{ + if (severity == NVAPI_D3D12_RAYTRACING_VALIDATION_MESSAGE_SEVERITY_ERROR) + logError("Raytracing validation error {}:\n{}\n{}", messageCode, message, messageDetails); + else + logWarning("Raytracing validation warning {}:\n{}\n{}", messageCode, message, messageDetails); +} +#endif + +void Device::enableRaytracingValidation() +{ +#if FALCOR_NVAPI_AVAILABLE + if (!isFeatureSupported(Device::SupportedFeatures::Raytracing)) + FALCOR_THROW("Ray tracing validation is requested, but ray tracing is not supported"); + +#if FALCOR_HAS_D3D12 + if (mDesc.type == Type::D3D12) + { + // Get D3D device and attempt to enable ray tracing + ID3D12Device5* pD3D12Device5 = static_cast(getNativeHandle().as()); + auto res = NvAPI_D3D12_EnableRaytracingValidation(pD3D12Device5, NVAPI_D3D12_RAYTRACING_VALIDATION_FLAG_NONE); + if (res == NVAPI_NOT_PERMITTED) + FALCOR_THROW( + "Failed to enable raytracing validation. Error code: {}.\nThis is typically caused when the " + "NV_ALLOW_RAYTRACING_VALIDATION=1 environment variable is not set", + (int)res + ); + else if (res != NVAPI_OK) + FALCOR_THROW("Failed to enable raytracing validation. Error code: {}", (int)res); + + // Attempt to register the debug callback + res = NvAPI_D3D12_RegisterRaytracingValidationMessageCallback( + pD3D12Device5, &RaytracingValidationCallback, nullptr, &mpRayTraceValidationHandle + ); + if (res != NVAPI_OK) + FALCOR_THROW("Failed to register raytracing validation callback. Error code: {}", (int)res); + } +#endif + +#if FALCOR_HAS_VULKAN + if (mDesc.type == Type::Vulkan) + { + // TODO + } +#endif + +#endif +} + +void Device::disableRaytracingValidation() +{ +#if FALCOR_NVAPI_AVAILABLE && FALCOR_HAS_D3D12 + if (mpRayTraceValidationHandle) + { + ID3D12Device5* pD3D12Device5 = static_cast(getNativeHandle().as()); + NvAPI_D3D12_UnregisterRaytracingValidationMessageCallback(pD3D12Device5, mpRayTraceValidationHandle); + mpRayTraceValidationHandle = nullptr; + } +#endif +} + +void Device::flushRaytracingValidation() +{ +#if FALCOR_NVAPI_AVAILABLE && FALCOR_HAS_D3D12 + if (mpRayTraceValidationHandle) + { + ID3D12Device5* pD3D12Device5 = static_cast(getNativeHandle().as()); + NvAPI_D3D12_FlushRaytracingValidationMessages(pD3D12Device5); + } +#endif +} + FALCOR_SCRIPT_BINDING(Device) { using namespace pybind11::literals; @@ -1198,6 +1296,12 @@ FALCOR_SCRIPT_BINDING(Device) FALCOR_SCRIPT_BINDING_DEPENDENCY(RenderContext) FALCOR_SCRIPT_BINDING_DEPENDENCY(Program) + pybind11::class_ adapterInfo(m, "AdapterInfo"); + adapterInfo.def_readonly("device_id", &AdapterInfo::deviceID); + adapterInfo.def_readonly("name", &AdapterInfo::name); + adapterInfo.def_readonly("vendor_id", &AdapterInfo::vendorID); + adapterInfo.def_readonly("luid", &AdapterInfo::luid); + pybind11::class_> device(m, "Device"); pybind11::enum_ deviceType(m, "DeviceType"); @@ -1347,11 +1451,14 @@ FALCOR_SCRIPT_BINDING(Device) ); device.def("wait", &Device::wait); + device.def("end_frame", &Device::endFrame); device.def_property_readonly("profiler", &Device::getProfiler); device.def_property_readonly("type", &Device::getType); device.def_property_readonly("info", &Device::getInfo); device.def_property_readonly("limits", &Device::getLimits); device.def_property_readonly("render_context", &Device::getRenderContext); + + device.def_static("get_gpus", &Device::getGPUs); } } // namespace Falcor diff --git a/Source/Falcor/Core/API/Device.h b/Source/Falcor/Core/API/Device.h index 2b227bb0b..987bb9828 100644 --- a/Source/Falcor/Core/API/Device.h +++ b/Source/Falcor/Core/API/Device.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -65,6 +65,7 @@ class ProgramManager; class Profiler; class AftermathContext; + namespace cuda_utils { class CudaDevice; @@ -151,6 +152,9 @@ class FALCOR_API Device : public Object /// GUID list for experimental features std::vector experimentalFeatures; #endif + + /// Whether to enable ray tracing validation (requires NVAPI) + bool enableRaytracingValidation = false; }; struct Info @@ -260,7 +264,7 @@ class FALCOR_API Device : public Object ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType memoryType = MemoryType::DeviceLocal, const void* pInitData = nullptr, - bool createCounter = true + bool createCounter = false ); /** @@ -279,7 +283,7 @@ class FALCOR_API Device : public Object ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType memoryType = MemoryType::DeviceLocal, const void* pInitData = nullptr, - bool createCounter = true + bool createCounter = false ); /** @@ -298,7 +302,7 @@ class FALCOR_API Device : public Object ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType memoryType = MemoryType::DeviceLocal, const void* pInitData = nullptr, - bool createCounter = true + bool createCounter = false ); /** @@ -618,6 +622,7 @@ class FALCOR_API Device : public Object cuda_utils::CudaDevice* getCudaDevice() const; #endif + /// Report live objects in GFX. /// This is useful for checking clean shutdown where all resources are properly released. static void reportLiveObjects(); @@ -651,6 +656,14 @@ class FALCOR_API Device : public Object */ std::mutex& getGlobalGfxMutex() { return mGlobalGfxMutex; } + /** + * When ray tracing validation is enabled, call this to force validation messages to be flushed. + * It is automatically called at the end of each frame. Only messages from completed work + * will flush, so to guaruntee all are printed, it must be called after a fence. + * NOTE: This has no effect on Vulkan, in which the driver flushes automatically at device idle/lost. + */ + void flushRaytracingValidation(); + private: struct ResourceRelease { @@ -661,6 +674,9 @@ class FALCOR_API Device : public Object void executeDeferredReleases(); + void enableRaytracingValidation(); + void disableRaytracingValidation(); + Desc mDesc; Slang::ComPtr mSlangGlobalSession; Slang::ComPtr mGfxDevice; @@ -698,11 +714,16 @@ class FALCOR_API Device : public Object std::unique_ptr mpProgramManager; std::unique_ptr mpProfiler; +#if FALCOR_NVAPI_AVAILABLE && FALCOR_HAS_D3D12 + void* mpRayTraceValidationHandle = nullptr; +#endif + #if FALCOR_HAS_CUDA /// CUDA device sharing the same adapter as the graphics device. mutable ref mpCudaDevice; #endif + std::mutex mGlobalGfxMutex; }; diff --git a/Source/Falcor/Core/API/GpuMemoryHeap.cpp b/Source/Falcor/Core/API/GpuMemoryHeap.cpp index db47b8e67..4d215e950 100644 --- a/Source/Falcor/Core/API/GpuMemoryHeap.cpp +++ b/Source/Falcor/Core/API/GpuMemoryHeap.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -154,6 +154,8 @@ Slang::ComPtr createBufferResource( ref pDevice, Buffer::State initState, size_t size, + size_t elementSize, + ResourceFormat format, ResourceBindFlags bindFlags, MemoryType memoryType ); @@ -183,6 +185,8 @@ void GpuMemoryHeap::initBasePageData(BaseData& data, size_t size) mpDevice, getInitState(mMemoryType), size, + 0, + ResourceFormat::Unknown, ResourceBindFlags::Vertex | ResourceBindFlags::Index | ResourceBindFlags::Constant, mMemoryType ); diff --git a/Source/Falcor/Core/API/ParameterBlock.cpp b/Source/Falcor/Core/API/ParameterBlock.cpp index fa77b3de0..d1342d026 100644 --- a/Source/Falcor/Core/API/ParameterBlock.cpp +++ b/Source/Falcor/Core/API/ParameterBlock.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -275,6 +275,13 @@ ParameterBlock::ParameterBlock( void ParameterBlock::initializeResourceBindings() { + // On Vulkan nested arrays of textures resources fail silently, + // so use reflection to catch errors early + if (mpDevice->getType() == Device::Type::Vulkan) + { + checkForNestedTextureArrayResources(); + } + for (uint32_t i = 0; i < mpReflector->getResourceRangeCount(); i++) { auto info = mpReflector->getResourceRangeBindingInfo(i); @@ -305,6 +312,40 @@ void ParameterBlock::initializeResourceBindings() } } +// Check for nested texture arrays and throw an exception if found. +void ParameterBlock::checkForNestedTextureArrayResources() +{ + auto reflectorStruct = mpReflector->getElementType()->asStructType(); + if (reflectorStruct) + { + for (uint32_t i = 0; i < reflectorStruct->getMemberCount(); i++) + { + const ref& member = reflectorStruct->getMember(i); + + // Recurse through arrays to extract nesting depth + final element type + auto elementType = member->getType(); + int depth = 0; + while (elementType->getKind() == ReflectionType::Kind::Array) + { + elementType = elementType->asArrayType()->getElementType(); + depth++; + } + + // If nesting is > 1 and array element is a texture resource, raise error + if (depth > 1) + { + auto resourceType = elementType->asResourceType(); + if (resourceType && resourceType->getType() == ReflectionResourceType::Type::Texture) + { + FALCOR_THROW( + "Nested texture array '{}' detected in parameter block. This will fail silently on Vulkan.", member->getName() + ); + } + } + } + } +} + void ParameterBlock::setBlob(const void* pSrc, const BindLocation& bindLocation, size_t size) { if (!isConstantBufferType(bindLocation.getType())) diff --git a/Source/Falcor/Core/API/ParameterBlock.h b/Source/Falcor/Core/API/ParameterBlock.h index d2538be97..4e836794e 100644 --- a/Source/Falcor/Core/API/ParameterBlock.h +++ b/Source/Falcor/Core/API/ParameterBlock.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -385,6 +385,8 @@ class FALCOR_API ParameterBlock : public Object void initializeResourceBindings(); void createConstantBuffers(const ShaderVar& var); + void checkForNestedTextureArrayResources(); + static void prepareResource(CopyContext* pContext, Resource* pResource, bool isUav); /// Note: We hold an unowned pointer to the device but a strong pointer to the program version. diff --git a/Source/Falcor/Core/API/Resource.h b/Source/Falcor/Core/API/Resource.h index 54df43f3a..34b2246a8 100644 --- a/Source/Falcor/Core/API/Resource.h +++ b/Source/Falcor/Core/API/Resource.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -147,7 +147,7 @@ class FALCOR_API Resource : public Object { return ((std::hash()(v.firstArraySlice) ^ (std::hash()(v.arraySize) << 1)) >> 1) ^ (std::hash()(v.mipCount) << 1) ^ (std::hash()(v.mostDetailedMip) << 3) ^ - (std::hash()(v.firstElement) << 5) ^ (std::hash()(v.elementCount) << 7); + (std::hash()(v.offset) << 5) ^ (std::hash()(v.size) << 7); } }; diff --git a/Source/Falcor/Core/API/ResourceViews.cpp b/Source/Falcor/Core/API/ResourceViews.cpp index 067f28be4..913872ba6 100644 --- a/Source/Falcor/Core/API/ResourceViews.cpp +++ b/Source/Falcor/Core/API/ResourceViews.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -109,48 +109,17 @@ ref ShaderResourceView::create( ); } -static void fillBufferViewDesc(gfx::IResourceView::Desc& desc, Buffer* pBuffer, uint32_t firstElement, uint32_t elementCount) -{ - auto format = depthToColorFormat(pBuffer->getFormat()); - desc.format = getGFXFormat(format); - - uint32_t bufferElementSize = 0; - uint64_t bufferElementCount = 0; - if (pBuffer->isTyped()) - { - FALCOR_ASSERT(getFormatPixelsPerBlock(format) == 1); - bufferElementSize = getFormatBytesPerBlock(format); - bufferElementCount = pBuffer->getElementCount(); - } - else if (pBuffer->isStructured()) - { - bufferElementSize = pBuffer->getStructSize(); - bufferElementCount = pBuffer->getElementCount(); - desc.format = gfx::Format::Unknown; - desc.bufferElementSize = bufferElementSize; - } - else - { - desc.format = gfx::Format::Unknown; - bufferElementSize = 4; - bufferElementCount = pBuffer->getSize(); - } - - bool useDefaultCount = (elementCount == ShaderResourceView::kMaxPossible); - FALCOR_ASSERT(useDefaultCount || (firstElement + elementCount) <= bufferElementCount); // Check range - desc.bufferRange.firstElement = firstElement; - desc.bufferRange.elementCount = useDefaultCount ? (bufferElementCount - firstElement) : elementCount; -} - -ref ShaderResourceView::create(Device* pDevice, Buffer* pBuffer, uint32_t firstElement, uint32_t elementCount) +ref ShaderResourceView::create(Device* pDevice, Buffer* pBuffer, uint64_t offset, uint64_t size) { Slang::ComPtr handle; gfx::IResourceView::Desc desc = {}; desc.type = gfx::IResourceView::Type::ShaderResource; - fillBufferViewDesc(desc, pBuffer, firstElement, elementCount); + desc.format = getGFXFormat(pBuffer->getFormat()); + desc.bufferRange.offset = offset; + desc.bufferRange.size = size == kEntireBuffer ? 0 : size; FALCOR_GFX_CALL(pDevice->getGfxDevice()->createBufferView(pBuffer->getGfxBufferResource(), nullptr, desc, handle.writeRef())); - return ref(new ShaderResourceView(pDevice, pBuffer, handle, firstElement, elementCount)); + return ref(new ShaderResourceView(pDevice, pBuffer, handle, offset, size)); } ref ShaderResourceView::create(Device* pDevice, Dimension dimension) @@ -208,19 +177,21 @@ ref UnorderedAccessView::create( return ref(new UnorderedAccessView(pDevice, pTexture, handle, mipLevel, firstArraySlice, arraySize)); } -ref UnorderedAccessView::create(Device* pDevice, Buffer* pBuffer, uint32_t firstElement, uint32_t elementCount) +ref UnorderedAccessView::create(Device* pDevice, Buffer* pBuffer, uint64_t offset, uint64_t size) { Slang::ComPtr handle; gfx::IResourceView::Desc desc = {}; desc.type = gfx::IResourceView::Type::UnorderedAccess; - fillBufferViewDesc(desc, pBuffer, firstElement, elementCount); + desc.format = getGFXFormat(pBuffer->getFormat()); + desc.bufferRange.offset = offset; + desc.bufferRange.size = size == kEntireBuffer ? 0 : size; FALCOR_GFX_CALL(pDevice->getGfxDevice()->createBufferView( pBuffer->getGfxBufferResource(), pBuffer->getUAVCounter() ? pBuffer->getUAVCounter()->getGfxBufferResource() : nullptr, desc, handle.writeRef() )); - return ref(new UnorderedAccessView(pDevice, pBuffer, handle, firstElement, elementCount)); + return ref(new UnorderedAccessView(pDevice, pBuffer, handle, offset, size)); } ref UnorderedAccessView::create(Device* pDevice, Dimension dimension) diff --git a/Source/Falcor/Core/API/ResourceViews.h b/Source/Falcor/Core/API/ResourceViews.h index 0a12f1c03..15a30ed37 100644 --- a/Source/Falcor/Core/API/ResourceViews.h +++ b/Source/Falcor/Core/API/ResourceViews.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -48,9 +48,10 @@ struct FALCOR_API ResourceViewInfo : mostDetailedMip(mostDetailedMip), mipCount(mipCount), firstArraySlice(firstArraySlice), arraySize(arraySize) {} - ResourceViewInfo(uint32_t firstElement, uint32_t elementCount) : firstElement(firstElement), elementCount(elementCount) {} + ResourceViewInfo(uint64_t offset, uint64_t size) : offset(offset), size(size) {} static constexpr uint32_t kMaxPossible = -1; + static constexpr uint64_t kEntireBuffer = -1; // Textures uint32_t mostDetailedMip = 0; @@ -59,13 +60,13 @@ struct FALCOR_API ResourceViewInfo uint32_t arraySize = kMaxPossible; // Buffers - uint32_t firstElement = 0; - uint32_t elementCount = kMaxPossible; + uint64_t offset = 0; + uint64_t size = kEntireBuffer; bool operator==(const ResourceViewInfo& other) const { return (firstArraySlice == other.firstArraySlice) && (arraySize == other.arraySize) && (mipCount == other.mipCount) && - (mostDetailedMip == other.mostDetailedMip) && (firstElement == other.firstElement) && (elementCount == other.elementCount); + (mostDetailedMip == other.mostDetailedMip) && (offset == other.offset) && (size == other.size); } }; @@ -78,6 +79,7 @@ class FALCOR_API ResourceView : public Object public: using Dimension = ReflectionResourceType::Dimensions; static const uint32_t kMaxPossible = -1; + static constexpr uint64_t kEntireBuffer = -1; virtual ~ResourceView(); ResourceView( @@ -95,14 +97,8 @@ class FALCOR_API ResourceView : public Object , mpResource(pResource) {} - ResourceView( - Device* pDevice, - Resource* pResource, - Slang::ComPtr gfxResourceView, - uint32_t firstElement, - uint32_t elementCount - ) - : mpDevice(pDevice), mGfxResourceView(gfxResourceView), mViewInfo(firstElement, elementCount), mpResource(pResource) + ResourceView(Device* pDevice, Resource* pResource, Slang::ComPtr gfxResourceView, uint64_t offset, uint64_t size) + : mpDevice(pDevice), mGfxResourceView(gfxResourceView), mViewInfo(offset, size), mpResource(pResource) {} ResourceView(Device* pDevice, Resource* pResource, Slang::ComPtr gfxResourceView) @@ -150,7 +146,7 @@ class FALCOR_API ShaderResourceView : public ResourceView uint32_t firstArraySlice, uint32_t arraySize ); - static ref create(Device* pDevice, Buffer* pBuffer, uint32_t firstElement, uint32_t elementCount); + static ref create(Device* pDevice, Buffer* pBuffer, uint64_t offset, uint64_t size); static ref create(Device* pDevice, Dimension dimension); private: @@ -169,10 +165,10 @@ class FALCOR_API ShaderResourceView : public ResourceView Device* pDevice, Resource* pResource, Slang::ComPtr gfxResourceView, - uint32_t firstElement, - uint32_t elementCount + uint64_t offset, + uint64_t size ) - : ResourceView(pDevice, pResource, gfxResourceView, firstElement, elementCount) + : ResourceView(pDevice, pResource, gfxResourceView, offset, size) {} ShaderResourceView(Device* pDevice, Resource* pResource, Slang::ComPtr gfxResourceView) : ResourceView(pDevice, pResource, gfxResourceView) @@ -214,7 +210,7 @@ class FALCOR_API UnorderedAccessView : public ResourceView uint32_t firstArraySlice, uint32_t arraySize ); - static ref create(Device* pDevice, Buffer* pBuffer, uint32_t firstElement, uint32_t elementCount); + static ref create(Device* pDevice, Buffer* pBuffer, uint64_t offset, uint64_t size); static ref create(Device* pDevice, Dimension dimension); private: @@ -233,10 +229,10 @@ class FALCOR_API UnorderedAccessView : public ResourceView Device* pDevice, Resource* pResource, Slang::ComPtr gfxResourceView, - uint32_t firstElement, - uint32_t elementCount + uint64_t offset, + uint64_t size ) - : ResourceView(pDevice, pResource, gfxResourceView, firstElement, elementCount) + : ResourceView(pDevice, pResource, gfxResourceView, offset, size) {} }; diff --git a/Source/Falcor/Core/Pass/ComputePass.cpp b/Source/Falcor/Core/Pass/ComputePass.cpp index 0ea9b13fb..87b4fe536 100644 --- a/Source/Falcor/Core/Pass/ComputePass.cpp +++ b/Source/Falcor/Core/Pass/ComputePass.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Falcor/Core/Program/Program.h b/Source/Falcor/Core/Program/Program.h index ff73820f2..f3fc47117 100644 --- a/Source/Falcor/Core/Program/Program.h +++ b/Source/Falcor/Core/Program/Program.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -314,6 +314,9 @@ struct ProgramDesc /// Raytracing pipeline flags (only used for raytracing programs). RtPipelineFlags rtPipelineFlags = RtPipelineFlags::None; + /// Use SPIR-V backend when compiling for Vulkan. + bool useSPIRVBackend = false; + /// Add a new empty shader module description. /// @param[in] name Optional name of the shader module. /// @return Returns a reference to the newly created shader module for adding sources. @@ -556,6 +559,9 @@ struct ProgramDesc return *this; } + /// Set program to use SPIR-V backend when compiling for Vulkan. + void setUseSPIRVBackend(bool b = true) { useSPIRVBackend = b; } + void finalize(); }; diff --git a/Source/Falcor/Core/Program/ProgramManager.cpp b/Source/Falcor/Core/Program/ProgramManager.cpp index a1f662a53..180d2da6a 100644 --- a/Source/Falcor/Core/Program/ProgramManager.cpp +++ b/Source/Falcor/Core/Program/ProgramManager.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -676,10 +676,14 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr targetDesc.forceGLSLScalarBufferLayout = true; - if (getEnvironmentVariable("FALCOR_USE_SLANG_SPIRV_BACKEND") == "1") + if (getEnvironmentVariable("FALCOR_USE_SLANG_SPIRV_BACKEND") == "1" || program.mDesc.useSPIRVBackend) { targetDesc.flags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; } + else + { + targetDesc.flags &= ~SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; + } const char* targetMacroName; @@ -725,12 +729,36 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr sessionDesc.targets = &targetDesc; sessionDesc.targetCount = 1; + // Setup additional compiler options. + std::vector compilerOptionEntries; + auto addIntOption = [&compilerOptionEntries](slang::CompilerOptionName name, int value) { + compilerOptionEntries.push_back({name, {slang::CompilerOptionValueKind::Int, 1, value, nullptr, nullptr}}); + }; + auto addStringOption = [&compilerOptionEntries](slang::CompilerOptionName name, const char* value) { + compilerOptionEntries.push_back({name, {slang::CompilerOptionValueKind::String, 0, 0, value, nullptr}}); + }; + // We always use row-major matrix layout in Falcor so by default that's what we pass to Slang // to allow it to compute correct reflection information. Slang then invokes the downstream compiler. // Column major option can be useful when compiling external shader sources that don't depend // on anything Falcor. bool useColumnMajor = is_set(compilerFlags, SlangCompilerFlags::MatrixLayoutColumnMajor); - sessionDesc.defaultMatrixLayoutMode = useColumnMajor ? SLANG_MATRIX_LAYOUT_COLUMN_MAJOR : SLANG_MATRIX_LAYOUT_ROW_MAJOR; + addIntOption(useColumnMajor ? slang::CompilerOptionName::MatrixLayoutColumn : slang::CompilerOptionName::MatrixLayoutRow, 1); + + // New versions of slang default to short-circuiting for logical and/or operators. + // Facor is still written with the assumption that these operators do not short-circuit. + // We want to transition to the new behavior, but for now we disable it. + addIntOption(slang::CompilerOptionName::DisableShortCircuit, 1); + + // Disable noisy warnings enabled in newer slang versions. + addStringOption(slang::CompilerOptionName::DisableWarning, "15602"); // #pragma once in modules + addStringOption(slang::CompilerOptionName::DisableWarning, "30056"); // non-short-circuiting `?:` operator is deprecated, use 'select' + // instead + addStringOption(slang::CompilerOptionName::DisableWarning, "30081"); // implicit conversion + addStringOption(slang::CompilerOptionName::DisableWarning, "41203"); // reinterpret<> into not equally sized types + + sessionDesc.compilerOptionEntries = compilerOptionEntries.data(); + sessionDesc.compilerOptionEntryCount = (uint32_t)compilerOptionEntries.size(); Slang::ComPtr pSlangSession; pSlangGlobalSession->createSession(sessionDesc, pSlangSession.writeRef()); @@ -742,10 +770,6 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr pSlangSession->createCompileRequest(&pSlangRequest); FALCOR_ASSERT(pSlangRequest); - // Disable noisy warnings enabled in newer slang versions. - spOverrideDiagnosticSeverity(pSlangRequest, 15602, SLANG_SEVERITY_DISABLED); // #pragma once in modules - spOverrideDiagnosticSeverity(pSlangRequest, 30081, SLANG_SEVERITY_DISABLED); // implicit conversion - // Enable/disable intermediates dump bool dumpIR = is_set(program.mDesc.compilerFlags, SlangCompilerFlags::DumpIntermediates); spSetDumpIntermediates(pSlangRequest, dumpIR); @@ -815,7 +839,9 @@ SlangCompileRequest* ProgramManager::createSlangCompileRequest(const Program& pr else { FALCOR_ASSERT(source.type == ProgramDesc::ShaderSource::Type::String); - spAddTranslationUnitSourceString(pSlangRequest, translationUnitIndex, source.path.string().c_str(), source.string.c_str()); + spAddTranslationUnitSourceString( + pSlangRequest, translationUnitIndex, source.path.empty() ? "empty" : source.path.string().c_str(), source.string.c_str() + ); } } } diff --git a/Source/Falcor/Core/State/StateGraph.h b/Source/Falcor/Core/State/StateGraph.h index 793f7f52b..76e964741 100644 --- a/Source/Falcor/Core/State/StateGraph.h +++ b/Source/Falcor/Core/State/StateGraph.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,6 +29,7 @@ #include #include #include +#include namespace Falcor { diff --git a/Source/Falcor/Core/Testbed.cpp b/Source/Falcor/Core/Testbed.cpp index a0ddb3fa0..dd28180c3 100644 --- a/Source/Falcor/Core/Testbed.cpp +++ b/Source/Falcor/Core/Testbed.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -92,8 +92,8 @@ void Testbed::frame() // Update the scene. if (mpScene) { - Scene::UpdateFlags sceneUpdates = mpScene->update(pRenderContext, mClock.getTime()); - if (mpRenderGraph && sceneUpdates != Scene::UpdateFlags::None) + IScene::UpdateFlags sceneUpdates = mpScene->update(pRenderContext, mClock.getTime()); + if (mpRenderGraph && sceneUpdates != IScene::UpdateFlags::None) mpRenderGraph->onSceneUpdates(pRenderContext, sceneUpdates); } @@ -220,6 +220,9 @@ void Testbed::handleWindowSizeChange() mpSwapchain->resize(width, height); resizeTargetFBO(width, height); + + if (mWindowSizeChangeCallback) + mWindowSizeChangeCallback(width, height); } void Testbed::handleRenderFrame() {} @@ -249,6 +252,8 @@ void Testbed::handleKeyboardEvent(const KeyboardEvent& keyEvent) } } + if (mKeyboardEventCallback && mKeyboardEventCallback(keyEvent)) + return; if (mpRenderGraph && mpRenderGraph->onKeyEvent(keyEvent)) return; if (mpScene && mpScene->onKeyEvent(keyEvent)) @@ -259,6 +264,8 @@ void Testbed::handleMouseEvent(const MouseEvent& mouseEvent) { if (mpGui->onMouseEvent(mouseEvent)) return; + if (mMouseEventCallback && mMouseEventCallback(mouseEvent)) + return; if (mpRenderGraph && mpRenderGraph->onMouseEvent(mouseEvent)) return; if (mpScene && mpScene->onMouseEvent(mouseEvent)) @@ -322,6 +329,7 @@ void Testbed::internalInit(const Options& options) // Create python UI screen. mpScreen = make_ref(); + mUI.showFPS = options.showFPS; mFrameRate.reset(); } @@ -603,6 +611,46 @@ void Testbed::captureOutput(const std::filesystem::path& path, uint32_t outputIn } } +std::vector Testbed::getImportPaths() const +{ + if (mpScene == nullptr) + return std::vector(); + + const std::vector& paths(mpScene->getImportPaths()); + std::vector ret; + + // Convert vector of std::filesytem::paths to vector of std::string + std::for_each(paths.begin(), paths.end(), [&](const std::filesystem::path& path) { ret.push_back(path.string()); }); + + return ret; +} + +std::vector Testbed::getImportDicts() const +{ + if (mpScene == nullptr) + return std::vector(); + + const std::vector& dicts(mpScene->getImportDicts()); + std::vector ret; + + // Convert vector of std::map to vector of pybind11::dict + std::for_each( + dicts.begin(), + dicts.end(), + [&](const std::map& m) + { + pybind11::dict d; + for (const auto& e : m) + { + d[e.first.c_str()] = e.second.c_str(); + } + ret.push_back(d); + } + ); + + return ret; +} + FALCOR_SCRIPT_BINDING(Testbed) { FALCOR_SCRIPT_BINDING_DEPENDENCY(Device) @@ -615,6 +663,43 @@ FALCOR_SCRIPT_BINDING(Testbed) using namespace pybind11::literals; + pybind11::enum_(m, "MouseButton") + .value("Left", Input::MouseButton::Left) + .value("Right", Input::MouseButton::Right) + .value("Middle", Input::MouseButton::Middle); + pybind11::enum_(m, "ModifierFlags", pybind11::arithmetic()) + .value("None", Input::ModifierFlags::None) + .value("Shift", Input::ModifierFlags::Shift) + .value("Ctrl", Input::ModifierFlags::Ctrl) + .value("Alt", Input::ModifierFlags::Alt) + .export_values(); + + pybind11::enum_(m, "Key").value("Space", Input::Key::Space).value("E", Input::Key::E).value("R", Input::Key::R); + + pybind11::class_ mouseEvent(m, "MouseEvent"); + pybind11::enum_(mouseEvent, "Type") + .value("ButtonDown", MouseEvent::Type::ButtonDown) + .value("ButtonUp", MouseEvent::Type::ButtonUp) + .value("Move", MouseEvent::Type::Move) + .value("Wheel", MouseEvent::Type::Wheel); + mouseEvent.def_readonly("type", &MouseEvent::type); + mouseEvent.def_readonly("pos", &MouseEvent::pos); + mouseEvent.def_readonly("screen_pos", &MouseEvent::screenPos); + mouseEvent.def_readonly("wheel_delta", &MouseEvent::wheelDelta); + mouseEvent.def_readonly("mods", &MouseEvent::mods); + mouseEvent.def_readonly("button", &MouseEvent::button); + + pybind11::class_ keyboardEvent(m, "KeyboardEvent"); + pybind11::enum_(keyboardEvent, "Type") + .value("KeyPressed", KeyboardEvent::Type::KeyPressed) + .value("KeyReleased", KeyboardEvent::Type::KeyReleased) + .value("KeyRepeated", KeyboardEvent::Type::KeyRepeated) + .value("Input", KeyboardEvent::Type::Input); + keyboardEvent.def_readonly("type", &KeyboardEvent::type); + keyboardEvent.def_readonly("key", &KeyboardEvent::key); + keyboardEvent.def_readonly("mods", &KeyboardEvent::mods); + keyboardEvent.def_readonly("codepoint", &KeyboardEvent::codepoint); + pybind11::class_> testbed(m, "Testbed"); testbed.def( @@ -626,17 +711,21 @@ FALCOR_SCRIPT_BINDING(Testbed) uint32_t gpu, bool enable_debug_layers, bool enable_aftermath, + const std::string& title, + bool show_fps, ref device) { Testbed::Options options; options.pDevice = device; options.windowDesc.width = width; options.windowDesc.height = height; + options.windowDesc.title = title; options.createWindow = create_window; options.deviceDesc.type = device_type; options.deviceDesc.gpu = gpu; options.deviceDesc.enableDebugLayer = enable_debug_layers; options.deviceDesc.enableAftermath = enable_aftermath; + options.showFPS = show_fps; return Testbed::create(options); } ), @@ -647,6 +736,8 @@ FALCOR_SCRIPT_BINDING(Testbed) "gpu"_a = 0, "enable_debug_layers"_a = false, "enable_aftermath"_a = false, + "title"_a = "Falcor Sample", + "show_fps"_a = true, "device"_a = nullptr ); testbed.def("run", &Testbed::run); @@ -663,6 +754,8 @@ FALCOR_SCRIPT_BINDING(Testbed) testbed.def("create_render_graph", &Testbed::createRenderGraph, "name"_a = ""); testbed.def("load_render_graph", &Testbed::loadRenderGraph, "path"_a); testbed.def("capture_output", &Testbed::captureOutput, "path"_a, "output_index"_a = uint32_t(0)); // PYTHONDEPRECATED + testbed.def("get_import_paths", &Testbed::getImportPaths); + testbed.def("get_import_dicts", &Testbed::getImportDicts); testbed.def_property_readonly("profiler", [](Testbed* pTestbed) { return pTestbed->getDevice()->getProfiler(); }); testbed.def_property_readonly("device", &Testbed::getDevice); @@ -670,9 +763,13 @@ FALCOR_SCRIPT_BINDING(Testbed) testbed.def_property_readonly("clock", &Testbed::getClock); // PYTHONDEPRECATED testbed.def_property("render_graph", &Testbed::getRenderGraph, &Testbed::setRenderGraph); testbed.def_property("render_texture", &Testbed::getRenderTexture, &Testbed::setRenderTexture); + testbed.def_property_readonly("window", &Testbed::getWindow); testbed.def_property_readonly("screen", &Testbed::getScreen); testbed.def_property("show_ui", &Testbed::getShowUI, &Testbed::setShowUI); testbed.def_property_readonly("should_close", &Testbed::shouldClose); + testbed.def_property("keyboard_event_callback", &Testbed::getKeyboardEventcallback, &Testbed::setKeyboardEventCallback); + testbed.def_property("mouse_event_callback", &Testbed::getMouseEventCallback, &Testbed::setMouseEventCallback); + testbed.def_property("window_size_change_callback", &Testbed::getWindowSizeChangeCallback, &Testbed::setWindowSizeChangeCallback); } } // namespace Falcor diff --git a/Source/Falcor/Core/Testbed.h b/Source/Falcor/Core/Testbed.h index 3d2d27404..8ab2c72b4 100644 --- a/Source/Falcor/Core/Testbed.h +++ b/Source/Falcor/Core/Testbed.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -61,6 +61,7 @@ class Testbed : public Object, private Window::ICallbacks Device::Desc deviceDesc; Window::Desc windowDesc; bool createWindow = false; + bool showFPS = true; /// Color format of the frame buffer. ResourceFormat colorFormat = ResourceFormat::BGRA8UnormSrgb; @@ -118,6 +119,9 @@ class Testbed : public Object, private Window::ICallbacks /// Get the active render graph. const ref& getRenderGraph() const; + /// Get the window. + const ref& getWindow() const { return mpWindow; } + /// Get the python UI screen. const ref& getScreen() const { return mpScreen; } @@ -140,6 +144,21 @@ class Testbed : public Object, private Window::ICallbacks /// This is true if the window was closed or escape was pressed. bool shouldClose() const { return mShouldClose || (mpWindow && mpWindow->shouldClose()); } + std::function getKeyboardEventcallback() const { return mKeyboardEventCallback; } + void setKeyboardEventCallback(std::function& cb) { mKeyboardEventCallback = cb; } + + std::function getMouseEventCallback() const { return mMouseEventCallback; } + void setMouseEventCallback(std::function& cb) { mMouseEventCallback = cb; } + + std::function getWindowSizeChangeCallback() const { return mWindowSizeChangeCallback; } + void setWindowSizeChangeCallback(std::function& cb) { mWindowSizeChangeCallback = cb; } + + /// Get paths to assets loaded in creating the scene + std::vector getImportPaths() const; + + /// Get dictionaries associated with assets loaded in creating the scene + std::vector getImportDicts() const; + private: // Implementation of Window::ICallbacks @@ -175,6 +194,10 @@ class Testbed : public Object, private Window::ICallbacks FrameRate mFrameRate; Clock mClock; + std::function mKeyboardEventCallback; + std::function mMouseEventCallback; + std::function mWindowSizeChangeCallback; + bool mShouldInterrupt{false}; bool mShouldClose{false}; struct diff --git a/Source/Falcor/Core/Version.h b/Source/Falcor/Core/Version.h index 808835dc8..c50685793 100644 --- a/Source/Falcor/Core/Version.h +++ b/Source/Falcor/Core/Version.h @@ -31,7 +31,7 @@ #include -#define FALCOR_MAJOR_VERSION 7 +#define FALCOR_MAJOR_VERSION 8 #define FALCOR_MINOR_VERSION 0 namespace Falcor diff --git a/Source/Falcor/Core/Window.cpp b/Source/Falcor/Core/Window.cpp index d964e4cc1..61b4f9c75 100644 --- a/Source/Falcor/Core/Window.cpp +++ b/Source/Falcor/Core/Window.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -660,6 +660,7 @@ FALCOR_SCRIPT_BINDING(Window) { pybind11::class_> window(m, "Window"); window.def("setWindowPos", &Window::setWindowPos); + window.def("set_window_pos", &Window::setWindowPos); pybind11::enum_ windowMode(m, "WindowMode"); windowMode.value("Normal", Window::WindowMode::Normal); diff --git a/Source/Falcor/DiffRendering/DiffSceneQuery.slang b/Source/Falcor/DiffRendering/DiffSceneQuery.slang index 2a03b524d..b22057cc7 100644 --- a/Source/Falcor/DiffRendering/DiffSceneQuery.slang +++ b/Source/Falcor/DiffRendering/DiffSceneQuery.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -104,10 +104,9 @@ struct SceneQueryAD : IDifferentiable float hitT = 0.f; SceneRayQuery<0> sceneRayQuery; - HitInfo hitInfo; - bool isHit = sceneRayQuery.traceRay(ray, hitInfo, hitT, RAY_FLAG_NONE, 0xff); + const HitInfo hitInfo = sceneRayQuery.traceRay(ray, hitT); - if (!isHit) + if (!hitInfo.isValid()) { isect = IntersectionAD(); return false; @@ -123,7 +122,7 @@ struct SceneQueryAD : IDifferentiable bool traceVisibilityRay(const Ray ray) { SceneRayQuery<0> sceneRayQuery; - return sceneRayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff); + return sceneRayQuery.traceVisibilityRay(ray); } // Compute differentiable intersection from non-differentiable HitInfo. diff --git a/Source/Falcor/RenderGraph/RenderGraph.cpp b/Source/Falcor/RenderGraph/RenderGraph.cpp index 5b67a5fa0..4a6be3c3c 100644 --- a/Source/Falcor/RenderGraph/RenderGraph.cpp +++ b/Source/Falcor/RenderGraph/RenderGraph.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -77,6 +77,31 @@ ref RenderGraph::createFromFile(ref pDevice, const std::fil return pGraph; } +ref RenderGraph::createFromString(ref pDevice, const std::string_view script) +{ + using namespace pybind11::literals; + + ref pGraph; + + // Setup a temporary scripting context that defines a local variable 'm' that + // has a 'addGraph' function exposed. This mimmicks the old Mogwai Python API + // allowing to load python based render graph scripts. + auto addGraph = pybind11::cpp_function([&pGraph](ref graph) { pGraph = graph; }); + + auto SimpleNamespace = pybind11::module_::import("types").attr("SimpleNamespace"); + pybind11::object m = SimpleNamespace("addGraph"_a = addGraph); + + Scripting::Context ctx; + ctx.setObject("m", m); + + ref pPrevDevice = getActivePythonRenderGraphDevice(); + setActivePythonRenderGraphDevice(pDevice); + Scripting::runScript(script, ctx); + setActivePythonRenderGraphDevice(pPrevDevice); + + return pGraph; +} + uint32_t RenderGraph::getPassIndex(const std::string& name) const { auto it = mNameToIndex.find(name); @@ -655,7 +680,13 @@ void RenderGraph::renderUI(RenderContext* pRenderContext, Gui::Widgets& widget) mpExe->renderUI(pRenderContext, widget); } -void RenderGraph::onSceneUpdates(RenderContext* pRenderContext, Scene::UpdateFlags sceneUpdates) +void RenderGraph::renderOverlayUI(RenderContext* pRenderContext) +{ + if (mpExe) + mpExe->renderOverlayUI(pRenderContext); +} + +void RenderGraph::onSceneUpdates(RenderContext* pRenderContext, IScene::UpdateFlags sceneUpdates) { // Notify all passes in graph about scene updates. // Note we don't rely on `mpExe` here because it is not created until the graph is compiled in `execute()`. diff --git a/Source/Falcor/RenderGraph/RenderGraph.h b/Source/Falcor/RenderGraph/RenderGraph.h index e5a174761..d52649523 100644 --- a/Source/Falcor/RenderGraph/RenderGraph.h +++ b/Source/Falcor/RenderGraph/RenderGraph.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -74,6 +74,7 @@ class FALCOR_API RenderGraph : public Object * @return New object, or throws an exception if creation failed. */ static ref createFromFile(ref pDevice, const std::filesystem::path& path); + static ref createFromString(ref pDevice, const std::string_view script); /** * Return the associated GPU device. @@ -255,12 +256,17 @@ class FALCOR_API RenderGraph : public Object */ void renderUI(RenderContext* pRenderContext, Gui::Widgets& widget); + /** + * Render the graph overlay UI (meant for rasterizing lines, boxes, etc, independent of the widget). + */ + void renderOverlayUI(RenderContext* pRenderContext); + /** * Called upon scene updates. * @param[in] pRenderContext The render context. * @param[in] sceneUpdates Accumulated scene update flags. */ - void onSceneUpdates(RenderContext* pRenderContext, Scene::UpdateFlags sceneUpdates); + void onSceneUpdates(RenderContext* pRenderContext, IScene::UpdateFlags sceneUpdates); /** * Mouse event handler. diff --git a/Source/Falcor/RenderGraph/RenderGraphExe.cpp b/Source/Falcor/RenderGraph/RenderGraphExe.cpp index a6c24fae0..44cce87fa 100644 --- a/Source/Falcor/RenderGraph/RenderGraphExe.cpp +++ b/Source/Falcor/RenderGraph/RenderGraphExe.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -62,6 +62,15 @@ void RenderGraphExe::renderUI(RenderContext* pRenderContext, Gui::Widgets& widge } } +void RenderGraphExe::renderOverlayUI(RenderContext* pRenderContext) +{ + for (const auto& p : mExecutionList) + { + const auto& pPass = p.pPass; + pPass->renderOverlayUI(pRenderContext); + } +} + bool RenderGraphExe::onMouseEvent(const MouseEvent& mouseEvent) { bool b = false; diff --git a/Source/Falcor/RenderGraph/RenderGraphExe.h b/Source/Falcor/RenderGraph/RenderGraphExe.h index a1372852b..78d70ca63 100644 --- a/Source/Falcor/RenderGraph/RenderGraphExe.h +++ b/Source/Falcor/RenderGraph/RenderGraphExe.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -64,6 +64,11 @@ class FALCOR_API RenderGraphExe */ void renderUI(RenderContext* pRenderContext, Gui::Widgets& widget); + /** + * Render the overlay UI + */ + void renderOverlayUI(RenderContext* pRenderContext); + /** * Mouse event handler. * Returns true if the event was handled by the object, false otherwise diff --git a/Source/Falcor/RenderGraph/RenderPass.h b/Source/Falcor/RenderGraph/RenderPass.h index 3bc89a748..0c2c27ee2 100644 --- a/Source/Falcor/RenderGraph/RenderPass.h +++ b/Source/Falcor/RenderGraph/RenderPass.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -199,6 +199,11 @@ class FALCOR_API RenderPass : public Object */ virtual void renderUI(RenderContext* pRenderContext, Gui::Widgets& widget) { renderUI(widget); } + /** + * Render the pass's Overlay UI. Use ImGui::GetBackgroundDrawList() to render. + */ + virtual void renderOverlayUI(RenderContext* pRenderContext) {} + /** * Set a scene into the render pass. * This function is called when a new scene is loaded. @@ -214,7 +219,7 @@ class FALCOR_API RenderPass : public Object * @param[in] pRenderContext The render context. * @param[in] sceneUpdates Accumulated scene update flags since the last call, or since `setScene()` for a new scene. */ - virtual void onSceneUpdates(RenderContext* pRenderContext, Scene::UpdateFlags sceneUpdates) {} + virtual void onSceneUpdates(RenderContext* pRenderContext, IScene::UpdateFlags sceneUpdates) {} /** * Mouse event handler. diff --git a/Source/Falcor/Rendering/Lights/EmissiveLightSampler.cpp b/Source/Falcor/Rendering/Lights/EmissiveLightSampler.cpp index edfa62b15..aa63289cc 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveLightSampler.cpp +++ b/Source/Falcor/Rendering/Lights/EmissiveLightSampler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -31,8 +31,28 @@ namespace Falcor { - DefineList EmissiveLightSampler::getDefines() const - { - return {{ "_EMISSIVE_LIGHT_SAMPLER_TYPE", std::to_string((uint32_t)mType) }}; - } +EmissiveLightSampler::EmissiveLightSampler(EmissiveLightSamplerType type, ref pLightCollection) + : mType(type) + , mpDevice(pLightCollection->getDevice()) +{ + setLightCollection(std::move(pLightCollection)); +} + +void EmissiveLightSampler::setLightCollection(ref pLightCollection) +{ + if (mpLightCollection == pLightCollection) + return; + + mUpdateFlagsConnection.reset(); + + mpLightCollection = std::move(pLightCollection); + + if (mpLightCollection) + mUpdateFlagsConnection = mpLightCollection->getUpdateFlagsSignal().connect([&](ILightCollection::UpdateFlags flags) { mLightCollectionUpdateFlags |= flags; }); +} + +DefineList EmissiveLightSampler::getDefines() const +{ + return {{"_EMISSIVE_LIGHT_SAMPLER_TYPE", std::to_string((uint32_t)mType)}}; } +} // namespace Falcor diff --git a/Source/Falcor/Rendering/Lights/EmissiveLightSampler.h b/Source/Falcor/Rendering/Lights/EmissiveLightSampler.h index b497330da..6ff261efc 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveLightSampler.h +++ b/Source/Falcor/Rendering/Lights/EmissiveLightSampler.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,7 +29,7 @@ #include "EmissiveLightSamplerType.slangh" #include "Core/Macros.h" #include "Core/Program/DefineList.h" -#include "Scene/Scene.h" +#include "Scene/Lights/LightCollection.h" namespace Falcor { @@ -48,9 +48,10 @@ namespace Falcor /** Updates the sampler to the current frame. \param[in] pRenderContext The render context. + \param[in] pLightCollection Updated LightCollection \return True if the sampler was updated. */ - virtual bool update(RenderContext* pRenderContext) { return false; } + virtual bool update(RenderContext* pRenderContext, ref pLightCollection) { return false; } /** Return a list of shader defines to use this light sampler. * \return Returns a list of shader defines. @@ -72,10 +73,14 @@ namespace Falcor EmissiveLightSamplerType getType() const { return mType; } protected: - EmissiveLightSampler(EmissiveLightSamplerType type, ref pScene) : mType(type), mpScene(pScene) {} + EmissiveLightSampler(EmissiveLightSamplerType type, ref pLightCollection); + void setLightCollection(ref pLightCollection); // Internal state const EmissiveLightSamplerType mType; ///< Type of emissive sampler. See EmissiveLightSamplerType.slangh. - ref mpScene; + ref mpDevice; + ref mpLightCollection; + sigs::Connection mUpdateFlagsConnection; + ILightCollection::UpdateFlags mLightCollectionUpdateFlags = ILightCollection::UpdateFlags::None; }; } diff --git a/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang b/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang index 2a8f5b9e5..dded05abd 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang +++ b/Source/Falcor/Rendering/Lights/EmissiveLightSamplerHelpers.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -79,12 +79,9 @@ bool sampleTriangle(const float3 posW, const uint triangleIndex, const float2 u, ls.Le = gScene.materials.evalEmissive(tri.materialID, uv); // DEMO21: Evaluate light profile. - if (Scene::kUseLightProfile) + if (gScene.materials.hasLightProfile(tri.materialID)) { - if (gScene.materials.getMaterialHeader(tri.materialID).isLightProfileEnabled()) - { - ls.Le *= gScene.lightProfile.eval(dot(ls.normalW, -ls.dir)); - } + ls.Le *= gScene.materials.evalLightProfile(dot(ls.normalW, -ls.dir)); } // Compute probability density with respect to solid angle from the shading point. diff --git a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp index 2fece4622..277c06178 100644 --- a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp +++ b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -31,16 +31,23 @@ namespace Falcor { - bool EmissivePowerSampler::update(RenderContext* pRenderContext) + bool EmissivePowerSampler::update(RenderContext* pRenderContext, ref pLightCollection) { FALCOR_PROFILE(pRenderContext, "EmissivePowerSampler::update"); - bool samplerChanged = false;; + bool samplerChanged = false; + + if (mpLightCollection != pLightCollection) + { + setLightCollection(std::move(pLightCollection)); + mNeedsRebuild = true; + } // Check if light collection has changed. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::LightCollectionChanged)) + if (mLightCollectionUpdateFlags != ILightCollection::UpdateFlags::None) { mNeedsRebuild = true; + mLightCollectionUpdateFlags = ILightCollection::UpdateFlags::None; } // Rebuild if necessary @@ -71,11 +78,9 @@ namespace Falcor var["_emissivePower"]["triangleAliasTable"] = mTriangleTable.fullTable; } - EmissivePowerSampler::EmissivePowerSampler(RenderContext* pRenderContext, ref pScene) - : EmissiveLightSampler(EmissiveLightSamplerType::Power, pScene) + EmissivePowerSampler::EmissivePowerSampler(RenderContext* pRenderContext, ref pLightCollection) + : EmissiveLightSampler(EmissiveLightSamplerType::Power, std::move(pLightCollection)) { - // Make sure the light collection is created. - mpLightCollection = pScene->getLightCollection(pRenderContext); } EmissivePowerSampler::AliasTable EmissivePowerSampler::generateAliasTable(std::vector weights) @@ -162,7 +167,7 @@ namespace Falcor { float(sum), N, - mpScene->getDevice()->createTypedBuffer(N), + mpDevice->createTypedBuffer(N), }; result.fullTable->setBlob(&fullTable[0], 0, N * sizeof(uint2)); diff --git a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.h b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.h index bbeb4d197..0fd16339c 100644 --- a/Source/Falcor/Rendering/Lights/EmissivePowerSampler.h +++ b/Source/Falcor/Rendering/Lights/EmissivePowerSampler.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -54,14 +54,15 @@ namespace Falcor \param[in] pScene The scene. \param[in] options The options to override the default behavior. */ - EmissivePowerSampler(RenderContext* pRenderContext, ref pScene); + EmissivePowerSampler(RenderContext* pRenderContext, ref pLightCollection); virtual ~EmissivePowerSampler() = default; /** Updates the sampler to the current frame. \param[in] pRenderContext The render context. + \param[in] pLightCollection Updated LightCollection \return True if the sampler was updated. */ - virtual bool update(RenderContext* pRenderContext) override; + virtual bool update(RenderContext* pRenderContext, ref pLightCollection) override; /** Bind the light sampler data to a given shader variable. \param[in] var Shader variable. @@ -78,8 +79,6 @@ namespace Falcor // Internal state bool mNeedsRebuild = true; ///< Trigger rebuild on the next call to update(). We should always build on the first call, so the initial value is true. - ref mpLightCollection; - std::mt19937 mAliasTableRng; AliasTable mTriangleTable; }; diff --git a/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.cpp b/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.cpp index da03339fe..f72095207 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.cpp +++ b/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,11 +29,9 @@ namespace Falcor { - EmissiveUniformSampler::EmissiveUniformSampler(RenderContext* pRenderContext, ref pScene, const Options& options) - : EmissiveLightSampler(EmissiveLightSamplerType::Uniform, pScene) + EmissiveUniformSampler::EmissiveUniformSampler(RenderContext* pRenderContext, ref pLightCollection, const Options& options) + : EmissiveLightSampler(EmissiveLightSamplerType::Uniform, std::move(pLightCollection)) , mOptions(options) { - // Make sure the light collection is created. - mpScene->getLightCollection(pRenderContext); } } diff --git a/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.h b/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.h index 388e90ed1..72a2d668b 100644 --- a/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.h +++ b/Source/Falcor/Rendering/Lights/EmissiveUniformSampler.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -59,7 +59,7 @@ namespace Falcor \param[in] pScene The scene. \param[in] options The options to override the default behavior. */ - EmissiveUniformSampler(RenderContext* pRenderContext, ref pScene, const Options& options = Options()); + EmissiveUniformSampler(RenderContext* pRenderContext, ref pLightCollection, const Options& options = Options()); virtual ~EmissiveUniformSampler() = default; /** Returns the current configuration. diff --git a/Source/Falcor/Rendering/Lights/EnvMapSampler.slang b/Source/Falcor/Rendering/Lights/EnvMapSampler.slang index ed3d7c026..18fdf0873 100644 --- a/Source/Falcor/Rendering/Lights/EnvMapSampler.slang +++ b/Source/Falcor/Rendering/Lights/EnvMapSampler.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -56,16 +56,23 @@ struct EnvMapSampler uint importanceBaseMip; ///< Mip level for 1x1 resolution. // TODO: Add scalar value for total integrated intensity, i.e., same as 1x1 mip - /** Evaluates the radiance coming from world space direction 'dir'. - */ + /** + * Evaluates the radiance coming from world space direction. + * \param[in] dir World space direction. + * \param[in] lod Texture level of detail for envmap evaluation. + */ float3 eval(float3 dir, float lod = 0.f) { return gScene.envMap.eval(dir, lod); } - /** Importance sampling of the environment map. - */ - bool sample(const float2 rnd, out EnvMapSample result) + /** + * Importance sampling of the environment map. + * \param[in] rnd Uniform random numbers in [0,1)^2. + * \param[out] result Result struct. + * \param[in] lod Texture level of detail for envmap evaluation. + */ + bool sample(const float2 rnd, out EnvMapSample result, float lod = 0.f) { float2 p = rnd; // Random sample in [0,1)^2. uint2 pos = 0; // Top-left texel pos of current 2x2 region. @@ -136,7 +143,7 @@ struct EnvMapSampler result.dir = gScene.envMap.toWorld(dir); result.pdf = pdf * M_1_4PI; - result.Le = gScene.envMap.eval(result.dir); + result.Le = gScene.envMap.eval(result.dir, lod); return true; } diff --git a/Source/Falcor/Rendering/Lights/LightBVH.cpp b/Source/Falcor/Rendering/Lights/LightBVH.cpp index 61121367c..c957a79ec 100644 --- a/Source/Falcor/Rendering/Lights/LightBVH.cpp +++ b/Source/Falcor/Rendering/Lights/LightBVH.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -37,7 +37,7 @@ namespace namespace Falcor { - LightBVH::LightBVH(ref pDevice, const ref& pLightCollection) + LightBVH::LightBVH(ref pDevice, const ref& pLightCollection) : mpDevice(pDevice) , mpLightCollection(pLightCollection) { diff --git a/Source/Falcor/Rendering/Lights/LightBVH.h b/Source/Falcor/Rendering/Lights/LightBVH.h index a1a458d75..10f207670 100644 --- a/Source/Falcor/Rendering/Lights/LightBVH.h +++ b/Source/Falcor/Rendering/Lights/LightBVH.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -76,7 +76,11 @@ namespace Falcor \param[in] pDevice GPU device. \param[in] pLightCollection The light collection around which the BVH will be built. */ - LightBVH(ref pDevice, const ref& pLightCollection); + LightBVH(ref pDevice, const ref& pLightCollection); + + /** Returns the LightCollection the LightBVH is built on. + */ + ref getLightCollection() const { return mpLightCollection; } /** Refit all the BVH nodes to the underlying geometry, without changing the hierarchy. The BVH needs to have been built before trying to refit it. @@ -143,7 +147,7 @@ namespace Falcor // Internal state ref mpDevice; - ref mpLightCollection; + ref mpLightCollection; ref mLeafUpdater; ///< Compute pass for refitting the leaf nodes. ref mInternalUpdater; ///< Compute pass for refitting internal nodes. diff --git a/Source/Falcor/Rendering/Lights/LightBVHSampler.cpp b/Source/Falcor/Rendering/Lights/LightBVHSampler.cpp index b5c46ddcb..9dedbc6a3 100644 --- a/Source/Falcor/Rendering/Lights/LightBVHSampler.cpp +++ b/Source/Falcor/Rendering/Lights/LightBVHSampler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,19 +33,31 @@ namespace Falcor { - bool LightBVHSampler::update(RenderContext* pRenderContext) + bool LightBVHSampler::update(RenderContext* pRenderContext, ref pLightCollection) { FALCOR_PROFILE(pRenderContext, "LightBVHSampler::update"); bool samplerChanged = false; bool needsRefit = false; + if (mpLightCollection != pLightCollection) + { + setLightCollection(std::move(pLightCollection)); + mNeedsRebuild = true; + mpBVH = std::make_unique(mpDevice, mpLightCollection); + } + // Check if light collection has changed. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::LightCollectionChanged)) + if (mLightCollectionUpdateFlags == ILightCollection::UpdateFlags::LayoutChanged) + { + mNeedsRebuild = true; + } + else if (mLightCollectionUpdateFlags == ILightCollection::UpdateFlags::MatrixChanged) { - if (mOptions.buildOptions.allowRefitting && !mNeedsRebuild) needsRefit = true; + if (mOptions.buildOptions.allowRefitting) needsRefit = true; else mNeedsRebuild = true; } + mLightCollectionUpdateFlags = ILightCollection::UpdateFlags::None; // Rebuild BVH if it's marked as dirty. if (mNeedsRebuild) @@ -137,12 +149,12 @@ namespace Falcor } } - LightBVHSampler::LightBVHSampler(RenderContext* pRenderContext, ref pScene, const Options& options) - : EmissiveLightSampler(EmissiveLightSamplerType::LightBVH, pScene) + LightBVHSampler::LightBVHSampler(RenderContext* pRenderContext, ref pLightCollection, const Options& options) + : EmissiveLightSampler(EmissiveLightSamplerType::LightBVH, std::move(pLightCollection)) , mOptions(options) { // Create the BVH and builder. mpBVHBuilder = std::make_unique(mOptions.buildOptions); - mpBVH = std::make_unique(pScene->getDevice(), pScene->getLightCollection(pRenderContext)); + mpBVH = std::make_unique(mpDevice, mpLightCollection); } } diff --git a/Source/Falcor/Rendering/Lights/LightBVHSampler.h b/Source/Falcor/Rendering/Lights/LightBVHSampler.h index 09d9d83d2..79b3ac973 100644 --- a/Source/Falcor/Rendering/Lights/LightBVHSampler.h +++ b/Source/Falcor/Rendering/Lights/LightBVHSampler.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -88,14 +88,15 @@ namespace Falcor \param[in] pScene The scene. \param[in] options The options to override the default behavior. */ - LightBVHSampler(RenderContext* pRenderContext, ref pScene, const Options& options = Options()); + LightBVHSampler(RenderContext* pRenderContext, ref pLightCollection, const Options& options = Options()); virtual ~LightBVHSampler() = default; /** Updates the sampler to the current frame. \param[in] pRenderContext The render context. + \param[in] pLightCollection Updated LightCollection \return True if the sampler was updated. */ - virtual bool update(RenderContext* pRenderContext) override; + virtual bool update(RenderContext* pRenderContext, ref pLightCollection) override; /** Return a list of shader defines to use this light sampler. * \return Returns a list of shader defines. diff --git a/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang index 54f168c51..dfa780fb9 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/BeerBTDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,9 +42,17 @@ struct BeerBTDF : IBSDF this.pathshortening = pathshortening; } - float3 eval(const float3 wi, const float3 wo, inout S sg) { return float3(0.f); } + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { return float3(0.f); } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; @@ -65,7 +73,7 @@ struct BeerBTDF : IBSDF return true; } - float evalPdf(const float3 wi, const float3 wo) { return 0.f; } + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { return 0.f; } AlbedoContributions evalAlbedo(const float3 wi, const LobeType lobetype) { @@ -75,6 +83,8 @@ struct BeerBTDF : IBSDF return AlbedoContributions(0.0f, 0.0f, transmittance, 1.0f - transmittance); } + float3 getIorAsReflectance() { return float3(0.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang index b4bdb7c7e..42247ad03 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/DielectricPlateBSDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -44,7 +44,7 @@ struct DielectricPlateBSDF : IBSDF AnisotropicGGX DInside; ///< Tranmission roughness float eta; ///< IOR - float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg) + float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { if (D.isSingular() || eta == 1.0f || wiLocal.z * woLocal.z == 0) return float3(0.0f); @@ -72,7 +72,15 @@ struct DielectricPlateBSDF : IBSDF abs(wiDotH * woDotH / (sqr(woDotH + wiDotH / etaRefract) * abs(wiLocal.z))); } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + float3 wiLocal, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = {}; pdf = {}; @@ -150,7 +158,7 @@ struct DielectricPlateBSDF : IBSDF return true; } - float evalPdf(const float3 wiLocal, const float3 woLocal) + float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) { if (D.isSingular() || eta == 1.0f || wiLocal.z * woLocal.z == 0) return 0.0f; @@ -190,6 +198,12 @@ struct DielectricPlateBSDF : IBSDF return AlbedoContributions(F, 0.f, 1.f - F, 0.f); } + float3 getIorAsReflectance() + { + const float rsqrt = (1.f - eta) / (1.f + eta); + return rsqrt * rsqrt; + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang index b4f4104c7..d700722c5 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/DiffuseSpecularBRDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -54,7 +54,7 @@ struct DiffuseSpecularBRDF : IBSDF this.roughness = params.roughness; } - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -64,7 +64,15 @@ struct DiffuseSpecularBRDF : IBSDF return (fd + fs) * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = {}; pdf = {}; @@ -98,11 +106,11 @@ struct DiffuseSpecularBRDF : IBSDF if (min(wi.z, wo.z) < kMinCosTheta || pdf == 0.f) return false; - weight = eval(wi, wo, sg) / pdf; + weight = eval(wi, wo, sg, bc) / pdf; return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -130,6 +138,8 @@ struct DiffuseSpecularBRDF : IBSDF return AlbedoContributions(diffuse, 1.0f - diffuse, 0.0f, 0.0f); } + float3 getIorAsReflectance() { return float3(1.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang index dddaeec37..eec3af44f 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/DisneyDiffuseBRDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -41,7 +41,7 @@ struct DisneyDiffuseBRDF : IBSDF, IDifferentiable float roughness; ///< Roughness before remapping. [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -49,7 +49,15 @@ struct DisneyDiffuseBRDF : IBSDF, IDifferentiable return evalWeight(wi, wo) * M_1_PI * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -64,7 +72,7 @@ struct DisneyDiffuseBRDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -92,6 +100,8 @@ struct DisneyDiffuseBRDF : IBSDF, IDifferentiable return AlbedoContributions(albedo, 1.0f - albedo, 0.0f, 0.0f); } + float3 getIorAsReflectance() { return float3(1.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang index 8ae77faa3..bd7890dcc 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/FrostbiteDiffuseBRDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,7 +42,7 @@ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable float roughness; ///< Roughness before remapping. [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -50,7 +50,15 @@ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable return evalWeight(wi, wo) * M_1_PI * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -65,7 +73,7 @@ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -95,6 +103,8 @@ struct FrostbiteDiffuseBRDF : IBSDF, IDifferentiable return AlbedoContributions(albedo, 1.0f - albedo, 0.0f, 0.0f); } + float3 getIorAsReflectance() { return float3(1.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang index 4a432dc61..72af6a25c 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBRDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,7 +42,7 @@ struct LambertDiffuseBRDF : IBSDF, IDifferentiable __init(float3 albedo) { this.albedo = albedo; } [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -50,7 +50,15 @@ struct LambertDiffuseBRDF : IBSDF, IDifferentiable return M_1_PI * albedo * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -65,7 +73,7 @@ struct LambertDiffuseBRDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -78,6 +86,8 @@ struct LambertDiffuseBRDF : IBSDF, IDifferentiable return AlbedoContributions(albedo, 1.0f - albedo, 0.0f, 0.0f); } + float3 getIorAsReflectance() { return float3(1.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang index d34919b2c..ec3293bab 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/LambertDiffuseBTDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -38,7 +38,7 @@ struct LambertDiffuseBTDF : IBSDF, IDifferentiable float3 albedo; ///< Diffuse albedo. [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, -wo.z) < kMinCosTheta) return float3(0.f); @@ -46,7 +46,15 @@ struct LambertDiffuseBTDF : IBSDF, IDifferentiable return M_1_PI * albedo * -wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); wo.z = -wo.z; @@ -62,7 +70,7 @@ struct LambertDiffuseBTDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, -wo.z) < kMinCosTheta) return 0.f; @@ -75,6 +83,8 @@ struct LambertDiffuseBTDF : IBSDF, IDifferentiable return AlbedoContributions(0.0f, 0.0f, albedo, 1.0f - albedo); } + float3 getIorAsReflectance() { return float3(0.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang index 64fe9a387..d6179be37 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/OrenNayarBRDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -45,7 +45,7 @@ struct OrenNayarBRDF : IBSDF this.roughness = roughness; } - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -80,7 +80,15 @@ struct OrenNayarBRDF : IBSDF return albedo * M_1_PI * (A + B * cosDiff * sinAlpha * tanBeta) * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Note that cosine sampling can become rather inefficient if we have high retro-reflectivity wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); @@ -92,11 +100,11 @@ struct OrenNayarBRDF : IBSDF return false; } - weight = eval(wi, wo, sg) / evalPdf(wi, wo); + weight = eval(wi, wo, sg, bc) / evalPdf(wi, wo, bc); return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -109,6 +117,8 @@ struct OrenNayarBRDF : IBSDF return AlbedoContributions(albedo, 1.0f - albedo, 0.0f, 0.0f); } + float3 getIorAsReflectance() { return float3(1.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang index 76ba6bb4f..48b2fa074 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -81,7 +81,7 @@ struct SheenBSDF : IBSDF return 1.f / (1.f + pow(lambda(NdotO), 1.f + 2.f * pow(1.f - NdotO, 8)) + lambda(NdotI)); } - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -96,7 +96,15 @@ struct SheenBSDF : IBSDF return Fr; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; @@ -129,11 +137,11 @@ struct SheenBSDF : IBSDF lobeType = (uint)LobeType::DiffuseReflection; - weight = (eval(wi, wo, sg) / (M_1_PI * wo.z)); + weight = (eval(wi, wo, sg, bc) / (M_1_PI * wo.z)); return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -176,6 +184,8 @@ struct SheenBSDF : IBSDF return AlbedoContributions(color * albedoR, albedoR - (albedoR * color), (1.f - albedoR), 0.f); } + float3 getIorAsReflectance() { return float3(1.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang index 893bb1e4e..688fde21c 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SimpleBTDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -37,9 +37,17 @@ struct SimpleBTDF : IBSDF __init(float3 transmittance) { this.transmittance = transmittance; } - float3 eval(const float3 wi, const float3 wo, inout S sg) { return float3(0.f); } + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { return float3(0.f); } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; @@ -57,13 +65,15 @@ struct SimpleBTDF : IBSDF return true; } - float evalPdf(const float3 wi, const float3 wo) { return 0.f; } + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { return 0.f; } AlbedoContributions evalAlbedo(const float3 wi, const LobeType lobetype) { return AlbedoContributions(0.0f, 0.0f, transmittance, 1.0f - transmittance); } + float3 getIorAsReflectance() { return float3(0.f); } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang b/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang index 4f38eff73..7b39fe222 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SpecularMicrofacet.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -55,7 +55,7 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable bool hasLobe(LobeType lobeType) { return (activeLobes & (uint)lobeType) != 0; } [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); @@ -82,7 +82,15 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable return F * D * G * 0.25f / wi.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; @@ -151,7 +159,7 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -182,6 +190,8 @@ struct SpecularMicrofacetBRDF : IBSDF, IDifferentiable return AlbedoContributions(r, 1.0f - r, 0.0f, 0.0f); } + float3 getIorAsReflectance() { return albedo; } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; @@ -203,7 +213,7 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable bool hasLobe(LobeType lobeType) { return (activeLobes & (uint)lobeType) != 0; } [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, abs(wo.z)) < kMinCosTheta) return float3(0.f); @@ -247,7 +257,15 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable } } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; @@ -379,7 +397,7 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, abs(wo.z)) < kMinCosTheta) return 0.f; @@ -438,6 +456,12 @@ struct SpecularMicrofacetBSDF : IBSDF, IDifferentiable return AlbedoContributions(0.0, 0.0, 1.0f - r, r); } + float3 getIorAsReflectance() + { + const float rsqrt = (1.f - eta) / (1.f + eta); + return float3(rsqrt * rsqrt); + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang index 917e94d0a..d686cd6eb 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/StandardBSDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -225,21 +225,29 @@ struct StandardBSDF : IBSDF, IDifferentiable } [Differentiable] - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { float3 result = 0.f; if (pDiffuseReflection > 0.f) - result += (1.f - specTrans) * (1.f - diffTrans) * diffuseReflection.eval(wi, wo, sg); + result += (1.f - specTrans) * (1.f - diffTrans) * diffuseReflection.eval(wi, wo, sg, bc); if (pDiffuseTransmission > 0.f) - result += (1.f - specTrans) * diffTrans * diffuseTransmission.eval(wi, wo, sg); + result += (1.f - specTrans) * diffTrans * diffuseTransmission.eval(wi, wo, sg, bc); if (pSpecularReflection > 0.f) - result += (1.f - specTrans) * specularReflection.eval(wi, wo, sg); + result += (1.f - specTrans) * specularReflection.eval(wi, wo, sg, bc); if (pSpecularTransmission > 0.f) - result += specTrans * (specularTransmission.eval(wi, wo, sg)); + result += specTrans * (specularTransmission.eval(wi, wo, sg, bc)); return result; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample( + const float3 wi, + out float3 wo, + out float pdf, + out float3 weight, + out uint lobeType, + inout S sg, + BSDFContext bc + ) { // Default initialization to avoid divergence at returns. wo = {}; @@ -254,67 +262,67 @@ struct StandardBSDF : IBSDF, IDifferentiable if (uSelect < pDiffuseReflection) { - valid = diffuseReflection.sample(wi, wo, pdf, weight, lobeType, sg); + valid = diffuseReflection.sample(wi, wo, pdf, weight, lobeType, sg, bc); weight /= pDiffuseReflection; weight *= (1.f - specTrans) * (1.f - diffTrans); pdf *= pDiffuseReflection; - // if (pDiffuseTransmission > 0.f) pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo); + // if (pDiffuseTransmission > 0.f) pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); if (pSpecularReflection > 0.f) - pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo); + pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) - pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo); + pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); } else if (uSelect < pDiffuseReflection + pDiffuseTransmission) { - valid = diffuseTransmission.sample(wi, wo, pdf, weight, lobeType, sg); + valid = diffuseTransmission.sample(wi, wo, pdf, weight, lobeType, sg, bc); weight /= pDiffuseTransmission; weight *= (1.f - specTrans) * diffTrans; pdf *= pDiffuseTransmission; - // if (pDiffuseReflection > 0.f) pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo); - // if (pSpecularReflection > 0.f) pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo); + // if (pDiffuseReflection > 0.f) pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo, bc); + // if (pSpecularReflection > 0.f) pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) - pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo); + pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); } else if (uSelect < pDiffuseReflection + pDiffuseTransmission + pSpecularReflection) { - valid = specularReflection.sample(wi, wo, pdf, weight, lobeType, sg); + valid = specularReflection.sample(wi, wo, pdf, weight, lobeType, sg, bc); weight /= pSpecularReflection; weight *= (1.f - specTrans); pdf *= pSpecularReflection; if (pDiffuseReflection > 0.f) - pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo); - // if (pDiffuseTransmission > 0.f) pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo); + pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo, bc); + // if (pDiffuseTransmission > 0.f) pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) - pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo); + pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); } else if (pSpecularTransmission > 0.f) { - valid = specularTransmission.sample(wi, wo, pdf, weight, lobeType, sg); + valid = specularTransmission.sample(wi, wo, pdf, weight, lobeType, sg, bc); weight /= pSpecularTransmission; weight *= specTrans; pdf *= pSpecularTransmission; if (pDiffuseReflection > 0.f) - pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo); + pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo, bc); if (pDiffuseTransmission > 0.f) - pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo); + pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); if (pSpecularReflection > 0.f) - pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo); + pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); } return valid; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { float pdf = 0.f; if (pDiffuseReflection > 0.f) - pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo); + pdf += pDiffuseReflection * diffuseReflection.evalPdf(wi, wo, bc); if (pDiffuseTransmission > 0.f) - pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo); + pdf += pDiffuseTransmission * diffuseTransmission.evalPdf(wi, wo, bc); if (pSpecularReflection > 0.f) - pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo); + pdf += pSpecularReflection * specularReflection.evalPdf(wi, wo, bc); if (pSpecularTransmission > 0.f) - pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo); + pdf += pSpecularTransmission * specularTransmission.evalPdf(wi, wo, bc); return pdf; } @@ -334,6 +342,8 @@ struct StandardBSDF : IBSDF, IDifferentiable return a; } + float3 getIorAsReflectance() { return 1.f; } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/ClothBRDF.slang b/Source/Falcor/Rendering/Materials/ClothBRDF.slang index b338ad101..a01093dad 100644 --- a/Source/Falcor/Rendering/Materials/ClothBRDF.slang +++ b/Source/Falcor/Rendering/Materials/ClothBRDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -49,14 +49,14 @@ struct ClothBRDF : IBSDF // Implementation of IBSDF interface - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return float3(0.f); return evalWeight(wi, wo) * M_1_PI * wo.z; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); lobeType = (uint)LobeType::DiffuseReflection; @@ -71,7 +71,7 @@ struct ClothBRDF : IBSDF return true; } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { if (min(wi.z, wo.z) < kMinCosTheta) return 0.f; @@ -84,6 +84,11 @@ struct ClothBRDF : IBSDF return AlbedoContributions(diffuseColor, 1.f - diffuseColor, 0.f, 0.f); } + float3 getIorAsReflectance() + { + return float3(1.f); + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang b/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang index 36b0a0b26..024f91577 100644 --- a/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/ClothMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -51,7 +51,7 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); - return brdf.eval(wiLocal, woLocal, sg); + return brdf.eval(wiLocal, woLocal, sg, BSDFContext()); } [Differentiable] @@ -110,7 +110,7 @@ struct ClothMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; - result.weight = brdf.eval(wiLocal, woLocal, sg) / result.pdf; + result.weight = brdf.eval(wiLocal, woLocal, sg, BSDFContext()) / result.pdf; result.lobeType = (uint)LobeType::DiffuseReflection; return true; diff --git a/Source/Falcor/Rendering/Materials/Fresnel.slang b/Source/Falcor/Rendering/Materials/Fresnel.slang index a2b45530c..f4ebcc3a0 100644 --- a/Source/Falcor/Rendering/Materials/Fresnel.slang +++ b/Source/Falcor/Rendering/Materials/Fresnel.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,6 +25,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ +import Utils.Color.ColorHelpers; /** Evaluates the Fresnel term using Schlick's approximation. Introduced in http://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf @@ -149,3 +150,17 @@ float3 evalFresnelConductor(float3 eta, float3 k, float cosThetaI) evalFresnelConductor(eta.z, k.z, cosThetaI) ); } + +/** Converts reflectance into IOR assuming normal incidence and n_i = 1.0 + + \param[in] reflectance Reflectance at normal incidence + \return Returns index or refraction. +*/ +float reflectanceToIOR(float3 reflectance) +{ + const float r = min(luminance(reflectance), 0.999f); + const float iorI = 1.f; + + return (-iorI - 2.f * iorI * sqrt(r) - iorI * r) / (r - 1.f); +} + diff --git a/Source/Falcor/Rendering/Materials/HairChiang16.slang b/Source/Falcor/Rendering/Materials/HairChiang16.slang index 3f528877c..25c2b2078 100644 --- a/Source/Falcor/Rendering/Materials/HairChiang16.slang +++ b/Source/Falcor/Rendering/Materials/HairChiang16.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -117,7 +117,7 @@ struct HairChiang16 : IBSDF precompute(); } - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { float sinThetaI = wi.x; float cosThetaI = sqrt(max(0.f, 1.f - sinThetaI * sinThetaI)); @@ -182,12 +182,12 @@ struct HairChiang16 : IBSDF return result; } - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) { #if !USE_BCSDF_IMPORTANCE_SAMPLING wo = sample_sphere(sampleNext2D(sg)); pdf = M_1_4PI; - weight = eval(wi, wo, sg) / pdf; + weight = eval(wi, wo, sg, bc) / pdf; lobeType = wo.z > 0 ? (uint)LobeType::DiffuseReflection : (uint)LobeType::DiffuseTransmission; return true; #endif @@ -309,7 +309,7 @@ struct HairChiang16 : IBSDF if (!isnan(pdf)) { - weight = eval(wi, wo, sg) / pdf; + weight = eval(wi, wo, sg, bc) / pdf; } else { @@ -319,7 +319,7 @@ struct HairChiang16 : IBSDF return (pdf > 0.f); } - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { #if !USE_BCSDF_IMPORTANCE_SAMPLING return M_1_4PI; @@ -387,6 +387,11 @@ struct HairChiang16 : IBSDF return AlbedoContributions(1.f, 0.f, 0.f, 0.f); } + float3 getIorAsReflectance() + { + return float3(1.f); + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; diff --git a/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang b/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang index d9e936290..385ea82c8 100644 --- a/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/HairMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -59,7 +59,7 @@ struct HairMaterialInstance : MaterialInstanceBase, IMaterialInstance HairChiang16 bcsdf = HairChiang16(sd, data); - return bcsdf.eval(wiLocal, woLocal, sg); + return bcsdf.eval(wiLocal, woLocal, sg, BSDFContext()); #else // Diffuse BRDF return data.baseColor * (float)M_1_PI * saturate(dot(sf.N, wo)); @@ -80,7 +80,7 @@ struct HairMaterialInstance : MaterialInstanceBase, IMaterialInstance HairChiang16 bcsdf = HairChiang16(sd, data); - bool valid = bcsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg); + bool valid = bcsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext()); result.wo = sf.fromLocal(woLocal); return valid; @@ -102,7 +102,7 @@ struct HairMaterialInstance : MaterialInstanceBase, IMaterialInstance HairChiang16 bcsdf = HairChiang16(sd, data); - return bcsdf.evalPdf(wiLocal, woLocal); + return bcsdf.evalPdf(wiLocal, woLocal, BSDFContext()); #else // Diffuse BRDF return saturate(dot(sf.N, wo)); diff --git a/Source/Falcor/Rendering/Materials/IBSDF.slang b/Source/Falcor/Rendering/Materials/IBSDF.slang index d4be2195f..e6e28915c 100644 --- a/Source/Falcor/Rendering/Materials/IBSDF.slang +++ b/Source/Falcor/Rendering/Materials/IBSDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -63,6 +63,27 @@ struct AlbedoContributions } } +struct BSDFContext +{ + float iorI; ///< IOR from incidence medium + float iorT; ///< IOR trom transmission medium + bool inited; ///< Flag to indicate if the struct was initialized + + __init(float iorI_, float iorT_) + { + iorI = iorI_; + iorT = iorT_; + inited = true; + } + + __init() + { + iorI = 1.f; + iorT = 1.f; + inited = false; + } +} + AlbedoContributions operator+(const AlbedoContributions a, const AlbedoContributions b) { return AlbedoContributions( @@ -121,7 +142,7 @@ interface IBSDF \param[in,out] sg Sample generator. \return Returns f(wi, wo) * dot(wo, n). */ - float3 eval(const float3 wi, const float3 wo, inout S sg); + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc); /** Samples the BSDF. \param[in] wi Incident direction. @@ -132,14 +153,14 @@ interface IBSDF \param[in,out] sg Sample generator. \return Returns true if successful. */ - bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg); + bool sample(const float3 wi, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc); /** Evaluates the directional pdf for sampling outgoing direction wo. \param[in] wi Incident direction. \param[in] wo Outgoing direction. \return Returns the pdf with respect to solid angle for sampling outgoing direction wo (0 for delta events). */ - float evalPdf(const float3 wi, const float3 wo); + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc); /** Albedo (hemispherical reflectance) of the BSDF. Relfection+transmission hemisphere should be <= 1.0. \param[in] wi Incident direction. @@ -148,6 +169,11 @@ interface IBSDF */ AlbedoContributions evalAlbedo(const float3 wi, const LobeType lobetype); + /** Unattenuated reflectance at normal incidence primarily used to get a common-form IOR + \return Returns the unattenuated albedo at normal incidence. + */ + float3 getIorAsReflectance(); + /** Information about roughness of the BSDF in (various) forms. \param[in] wi Incident direction. \return Returns the roughness. diff --git a/Source/Falcor/Rendering/Materials/IMaterialInstance.slang b/Source/Falcor/Rendering/Materials/IMaterialInstance.slang index be0445b39..8385960a2 100644 --- a/Source/Falcor/Rendering/Materials/IMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/IMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,28 +33,10 @@ __exported import Rendering.Materials.IBSDF; // For kMinCosTheta __exported import Rendering.Materials.LobeType; __exported import Utils.Sampling.SampleGeneratorInterface; __exported import DiffRendering.DiffMaterialData; +__exported import MaterialInstanceHints; import Rendering.Volumes.PhaseFunction; import Scene.Material.VolumeProperties; -/** Hints passed in when creating a material instance. -*/ -enum MaterialInstanceHints : uint -{ - None = 0, - DisableNormalMapping = 0x1, ///< Turn off normal mapping if supported. - AdjustShadingNormal = 0x2, ///< Adjust shading normal to reduce risk of back-facing hits. -}; - -bool isNormalMappingEnabled(uint hints) -{ - return !(hints & (uint)MaterialInstanceHints::DisableNormalMapping); -} - -bool isAdjustShadingNormalEnabled(uint hints) -{ - return hints & (uint)MaterialInstanceHints::AdjustShadingNormal; -} - /** Describes a BSDF sample. */ struct BSDFSample diff --git a/Source/Falcor/Rendering/Materials/IsotropicGGX.slang b/Source/Falcor/Rendering/Materials/IsotropicGGX.slang index fc69627e7..af1e47759 100644 --- a/Source/Falcor/Rendering/Materials/IsotropicGGX.slang +++ b/Source/Falcor/Rendering/Materials/IsotropicGGX.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -100,10 +100,23 @@ float evalPdfGGX_VNDF(float alpha, float3 wi, float3 h) return G1 * D * max(0.f, dot(wi, h)) / wi.z; } +float3 SampleVndf_Hemisphere(float2 u, float3 Vh) +{ + float phi = 2.f * M_PI * u.x; + float z = fma((1.f - u.y), (1.f + Vh.z), -Vh.z); + float sinTheta = sqrt(saturate(1.f - z * z)); + float x = sinTheta * cos(phi); + float y = sinTheta * sin(phi); + float3 c = float3(x, y, z); + float3 h = c + Vh; + return h; +} /** Samples the GGX (Trowbridge-Reitz) using the distribution of visible normals (VNDF). The GGX VDNF yields significant variance reduction compared to sampling of the GGX NDF. See http://jcgt.org/published/0007/04/01/paper.pdf - + The visible hemisphere sampling procedure is now performed with the technique in + "Sampling Visible GGX Normals with Spherical Caps" by Dupuy and Benyoub [2023] + See https://arxiv.org/abs/2306.05044 \param[in] alpha Isotropic GGX width parameter (should be clamped to small epsilon beforehand). \param[in] wi Incident direction in local space, in the positive hemisphere. \param[in] u Uniform random number (2D). @@ -113,28 +126,11 @@ float evalPdfGGX_VNDF(float alpha, float3 wi, float3 h) float3 sampleGGX_VNDF(float alpha, float3 wi, float2 u, out float pdf) { float alpha_x = alpha, alpha_y = alpha; - // Transform the view vector to the hemisphere configuration. float3 Vh = normalize(float3(alpha_x * wi.x, alpha_y * wi.y, wi.z)); - - // Construct orthonormal basis (Vh,T1,T2). - float3 T1 = (Vh.z < 0.9999f) ? normalize(cross(float3(0, 0, 1), Vh)) : float3(1, 0, 0); // TODO: fp32 precision - float3 T2 = cross(Vh, T1); - - // Parameterization of the projected area of the hemisphere. - float r = sqrt(u.x); - float phi = (2.f * M_PI) * u.y; - float t1 = r * cos(phi); - float t2 = r * sin(phi); - float s = 0.5f * (1.f + Vh.z); - t2 = (1.f - s) * sqrt(1.f - t1 * t1) + s * t2; - - // Reproject onto hemisphere. - float3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.f, 1.f - t1 * t1 - t2 * t2)) * Vh; - + float3 Nh = SampleVndf_Hemisphere(u, Vh); // Transform the normal back to the ellipsoid configuration. This is our half vector. - float3 h = normalize(float3(alpha_x * Nh.x, alpha_y * Nh.y, max(0.f, Nh.z))); - + float3 h = normalize(float3(alpha_x * Nh.x, alpha_y * Nh.y, Nh.z)); pdf = evalPdfGGX_VNDF(alpha, wi, h); return h; } diff --git a/Source/Falcor/Rendering/Materials/LayeredBSDF.slang b/Source/Falcor/Rendering/Materials/LayeredBSDF.slang index 4e2f96e03..70516129a 100644 --- a/Source/Falcor/Rendering/Materials/LayeredBSDF.slang +++ b/Source/Falcor/Rendering/Materials/LayeredBSDF.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,8 +29,10 @@ __exported import Rendering.Materials.IMaterialInstance; __exported import Rendering.Materials.IBSDF; +import Rendering.Materials.Fresnel; import Rendering.Volumes.PhaseFunction; import Utils.Color.ColorHelpers; +import Utils.Debug.PixelDebug; /** This class implements a layered material based on "Position-Free Monte Carlo Simulation for Arbitrary Layered BSDFs" by Guo et al. @@ -65,43 +67,81 @@ struct LayeredBSDF : IBSDF this.maxDepth = maxDepth; } - float3 evalTopOrBottom(bool evalTop, float3 wi, float3 wo, inout S sg) + BSDFContext initBSDFContext( + const bool scatterTop, + const float3 wi, + const float iors[2], + const float iorOutside, + ) + { + BSDFContext bc; + if (wi.z >= 0.f) + { + if (scatterTop) + { + bc.iorI = iorOutside; + bc.iorT = iors[0]; + } + else + { + bc.iorI = iors[0]; + bc.iorT = iors[1]; + } + } + else + { + if (scatterTop) + { + bc.iorI = iors[0]; + bc.iorT = iorOutside; + } + else + { + bc.iorI = iors[1]; + bc.iorT = iors[0]; + } + } + + return bc; + } + + float3 evalTopOrBottom(bool evalTop, float3 wi, float3 wo, inout S sg, BSDFContext bc) { if (evalTop) { - return top.eval(wi, wo, sg); + return top.eval(wi, wo, sg, bc); } else { - return bottom.eval(wi, wo, sg); + return bottom.eval(wi, wo, sg, bc); } } - bool sampleTopOrBottom(bool sampleTop, float3 wi, out BSDFSample bs, inout S sg) + bool sampleTopOrBottom(bool sampleTop, float3 wi, out BSDFSample bs, inout S sg, BSDFContext bc) { if (sampleTop) { - return top.sample(wi, bs.wo, bs.pdf, bs.weight, bs.lobeType, sg); + return top.sample(wi, bs.wo, bs.pdf, bs.weight, bs.lobeType, sg, bc); } else { - return bottom.sample(wi, bs.wo, bs.pdf, bs.weight, bs.lobeType, sg); + return bottom.sample(wi, bs.wo, bs.pdf, bs.weight, bs.lobeType, sg, bc); } } - float evalPdfTopOrBottom(bool evalTop, float3 wi, float3 wo) + float evalPdfTopOrBottom(bool evalTop, float3 wi, float3 wo, BSDFContext bc) { if (evalTop) { - return top.evalPdf(wi, wo); + return top.evalPdf(wi, wo, bc); } else { - return bottom.evalPdf(wi, wo); + return bottom.evalPdf(wi, wo, bc); } } - float3 eval(const float3 wi, const float3 wo, inout S sg) + float3 eval(const float3 wi, const float3 wo, inout S sg, BSDFContext bc) { bool enterTop = wi.z > 0.0f; bool exitTop = wo.z > 0.0f; @@ -110,10 +150,17 @@ struct LayeredBSDF : IBSDF float z = enterTop ? 1.0f : 0.0f; float exitZ = exitTop ? 1.0f : 0.0f; - if (z == exitZ) result += evalTopOrBottom(exitTop, wi, wo, sg); + float iors[2]; + iors[0] = reflectanceToIOR(top.getIorAsReflectance()); + iors[1] = reflectanceToIOR(bottom.getIorAsReflectance()); + const float iorOutside = bc.iorI; + + BSDFContext bcLocal = BSDFContext(bc.iorI, bc.iorT); + + if (z == exitZ) result += evalTopOrBottom(exitTop, wi, wo, sg, bcLocal); BSDFSample bs; - if (!sampleTopOrBottom(enterTop, wi, bs, sg)) return result; + if (!sampleTopOrBottom(enterTop, wi, bs, sg, bcLocal)) return result; if (bs.isLobe(LobeType::Reflection)) return result; float3 w = bs.wo; @@ -148,8 +195,9 @@ struct LayeredBSDF : IBSDF } bool scatterTop = z == 1.0f; - if (z == exitZ) result += weight * evalTopOrBottom(exitTop, -w, wo, sg); - if (!sampleTopOrBottom(scatterTop, -w, bs, sg)) return result; + bcLocal = initBSDFContext(scatterTop, -w, iors, iorOutside); + if (z == exitZ) result += weight * evalTopOrBottom(exitTop, -w, wo, sg, bcLocal); + if (!sampleTopOrBottom(scatterTop, -w, bs, sg, bcLocal)) return result; weight *= bs.weight; w = bs.wo; @@ -165,7 +213,7 @@ struct LayeredBSDF : IBSDF return result; } - bool sample(const float3 wi, out float3 wo, out float pdfOut, out float3 weightOut, out uint lobeType, inout S sg) + bool sample(const float3 wi, out float3 wo, out float pdfOut, out float3 weightOut, out uint lobeType, inout S sg, BSDFContext bc) { wo = {}; pdfOut = {}; @@ -174,8 +222,15 @@ struct LayeredBSDF : IBSDF bool enterTop = wi.z > 0.0f; + float iors[2]; + iors[0] = reflectanceToIOR(top.getIorAsReflectance()); + iors[1] = reflectanceToIOR(bottom.getIorAsReflectance()); + const float iorOutside = bc.iorI; + + BSDFContext bcLocal = BSDFContext(bc.iorI, bc.iorT); + BSDFSample bs; - if (!sampleTopOrBottom(enterTop, wi, bs, sg)) return false; + if (!sampleTopOrBottom(enterTop, wi, bs, sg, bcLocal)) return false; if (bs.isLobe(LobeType::Reflection)) { wo = bs.wo; @@ -219,7 +274,8 @@ struct LayeredBSDF : IBSDF weight *= exp(-sigma); } bool scatterTop = z == 1.0f; - if (!sampleTopOrBottom(scatterTop, -w, bs, sg)) return false; + bcLocal = initBSDFContext(scatterTop, -w, iors, iorOutside); + if (!sampleTopOrBottom(scatterTop, -w, bs, sg, bcLocal)) return false; weight *= bs.weight; w = bs.wo; pdf *= bs.pdf; @@ -227,7 +283,7 @@ struct LayeredBSDF : IBSDF if (bs.isLobe(LobeType::Transmission)) { wo = w; - pdfOut = evalPdf(wi, w); + pdfOut = evalPdf(wi, w, bc); weightOut = weight; lobeType = (uint)(w.z*wi.z > 0.0f ? LobeType::Reflection : LobeType::Transmission) & @@ -254,14 +310,21 @@ struct LayeredBSDF : IBSDF do without invoking the sampling routine. We don't have sg available here, and a stochastic PDF could cause problems with MIS. */ - float evalPdf(const float3 wi, const float3 wo) + float evalPdf(const float3 wi, const float3 wo, BSDFContext bc) { bool enterTop = wi.z > 0.0f; bool exitTop = wo.z > 0.0f; + float iors[2]; + iors[0] = reflectanceToIOR(top.getIorAsReflectance()); + iors[1] = reflectanceToIOR(bottom.getIorAsReflectance()); + const float iorOutside = bc.iorI; + + BSDFContext bcLocal = BSDFContext(bc.iorI, bc.iorT); + float pdfCos = abs(wi.z) * M_1_PI; float pdfSingle = 0.0f; - if (enterTop == exitTop) pdfSingle = evalPdfTopOrBottom(exitTop, wi, wo); + if (enterTop == exitTop) pdfSingle = evalPdfTopOrBottom(exitTop, wi, wo, bcLocal); return lerp(pdfSingle, pdfCos, 0.5f); } @@ -272,6 +335,11 @@ struct LayeredBSDF : IBSDF return AlbedoContributions(1.f, 0.f, 0.f, 0.f); } + float3 getIorAsReflectance() + { + return float3(1.f); + } + RoughnessInformation getRoughnessInformation(const float3 wi) { // Return the roughness information for the top or bottom BSDF diff --git a/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang b/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang index ecf8ee89f..46adefbbf 100644 --- a/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/MERLMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -67,7 +67,7 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); #if VISUALIZE_FITTED_BRDF - return fittedBrdf.eval(wiLocal, woLocal, sg); + return fittedBrdf.eval(wiLocal, woLocal, sg, BSDFContext()); #else return evalLocal(wiLocal, woLocal); #endif @@ -85,7 +85,7 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance #if SAMPLE_FITTED_BRDF float3 woLocal = {}; - if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg)) return false; + if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) return false; result.wo = sf.fromLocal(woLocal); if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; @@ -121,7 +121,7 @@ struct MERLMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; #if SAMPLE_FITTED_BRDF - return fittedBrdf.evalPdf(wiLocal, woLocal); + return fittedBrdf.evalPdf(wiLocal, woLocal, BSDFContext()); #else return woLocal.z * M_1_PI; // pdf = cos(theta) / pi #endif diff --git a/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang b/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang index a512d53bb..ee3b7dcdd 100644 --- a/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/MERLMixMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -82,7 +82,7 @@ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; #if SAMPLE_FITTED_BRDF - if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg)) return false; + if (!fittedBrdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) return false; #else woLocal = sample_cosine_hemisphere_concentric(sampleNext2D(sg), result.pdf); result.lobeType = (uint)LobeType::DiffuseReflection; @@ -104,7 +104,7 @@ struct MERLMixMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; #if SAMPLE_FITTED_BRDF - return fittedBrdf.evalPdf(wiLocal, woLocal); + return fittedBrdf.evalPdf(wiLocal, woLocal, BSDFContext()); #else return woLocal.z * M_1_PI; // pdf = cos(theta) / pi #endif diff --git a/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang b/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang new file mode 100644 index 000000000..c735a0373 --- /dev/null +++ b/Source/Falcor/Rendering/Materials/MaterialInstanceHints.slang @@ -0,0 +1,47 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "Utils/HostDeviceShared.slangh" // For bit packing macros + +/** Hints passed in when creating a material instance. +*/ +enum MaterialInstanceHints : uint +{ + None = 0, + DisableNormalMapping = 0x1, ///< Turn off normal mapping if supported. + AdjustShadingNormal = 0x2, ///< Adjust shading normal to reduce risk of back-facing hits. +}; + +bool isNormalMappingEnabled(uint hints) +{ + return !bool(hints & (uint)MaterialInstanceHints::DisableNormalMapping); +} + +bool isAdjustShadingNormalEnabled(uint hints) +{ + return bool(hints & (uint)MaterialInstanceHints::AdjustShadingNormal); +} diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang index 8b6043f76..41efd7efc 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterial.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -46,7 +46,7 @@ struct PBRTCoatedConductorMaterial : MaterialBase, IMaterial // RG: Interface X&Y roughness // BA: Conductor X&Y roughness const float4 roughnesses = ms.sampleTexture(data.texSpecular, s, sd.uv, data.specular, lod).rgba; - float eta = 1.0f / header.getIoR(); + float eta = header.getIoR(); // Compute final shading frame. ShadingFrame sf = sd.frame; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang index e6fea638c..9c71b4270 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedConductorMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -51,7 +51,8 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst PBRTDielectricBSDF top = {interfaceD, interfaceEta}; PBRTConductorBSDF bottom = {conductorD, conductorEta, conductorK}; - return LayeredBSDF(top, bottom).eval(wiLocal, woLocal, sg); + const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); + return LayeredBSDF(top, bottom).eval(wiLocal, woLocal, sg, bc); } [Differentiable] @@ -68,7 +69,10 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst PBRTDielectricBSDF top = {interfaceD, interfaceEta}; PBRTConductorBSDF bottom = {conductorD, conductorEta, conductorK}; - bool valid = LayeredBSDF(top, bottom).sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg); + // We flip the material so that on both sides we coat the conductor with the dielectric. Note that this has little implications + // since we evaluate black when hitting the inside. + const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); + bool valid = LayeredBSDF(top, bottom).sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, bc); result.wo = sf.fromLocal(woLocal); if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; @@ -86,7 +90,8 @@ struct PBRTCoatedConductorMaterialInstance : MaterialInstanceBase, IMaterialInst PBRTDielectricBSDF top = {interfaceD, interfaceEta}; PBRTConductorBSDF bottom = {conductorD, conductorEta, conductorK}; - return LayeredBSDF(top, bottom).evalPdf(wiLocal, woLocal); + const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); + return LayeredBSDF(top, bottom).evalPdf(wiLocal, woLocal, bc); } BSDFProperties getProperties(const ShadingData sd) diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang index 3d68aedc3..040fef749 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterial.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -43,7 +43,7 @@ struct PBRTCoatedDiffuseMaterial : MaterialBase, IMaterial const float3 baseColor = ms.sampleTexture(data.texBaseColor, s, sd.uv, data.baseColor, lod).rgb; // RG: Interface X&Y roughness const float2 roughness = ms.sampleTexture(data.texSpecular, s, sd.uv, data.specular, lod).rg; - float eta = 1.0f / header.getIoR(); + float eta = header.getIoR(); // Compute final shading frame. ShadingFrame sf = sd.frame; diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang index bd21e3c35..23f4ac540 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTCoatedDiffuseMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -49,7 +49,8 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan PBRTDielectricBSDF top = {interfaceD, interfaceEta}; PBRTDiffuseBSDF bottom = {diffuseAlbedo}; - return LayeredBSDF(top, bottom).eval(wiLocal, woLocal, sg); + const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); + return LayeredBSDF(top, bottom).eval(wiLocal, woLocal, sg, bc); } [Differentiable] @@ -66,7 +67,10 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan PBRTDielectricBSDF top = {interfaceD, interfaceEta}; PBRTDiffuseBSDF bottom = {diffuseAlbedo}; - bool valid = LayeredBSDF(top, bottom).sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg); + // We flip the material so that on both sides we coat the conductor with the dielectric. Note that this has little implications + // since we evaluate black when hitting the inside. + const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); + bool valid = LayeredBSDF(top, bottom).sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, bc); result.wo = sf.fromLocal(woLocal); if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; @@ -84,7 +88,8 @@ struct PBRTCoatedDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstan PBRTDielectricBSDF top = {interfaceD, interfaceEta}; PBRTDiffuseBSDF bottom = {diffuseAlbedo}; - return LayeredBSDF(top, bottom).evalPdf(wiLocal, woLocal); + const BSDFContext bc = BSDFContext(sd.IoR, top.getEta()); + return LayeredBSDF(top, bottom).evalPdf(wiLocal, woLocal, bc); } BSDFProperties getProperties(const ShadingData sd) diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang index b3abdd33e..38c161a0f 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTConductorMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -48,7 +48,7 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable } [Differentiable] - float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg) + float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { if (D.isSingular() || min(wiLocal.z, woLocal.z) < 0.0f) return float3(0.0f); @@ -59,7 +59,7 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable return D.G2(woLocal, wiLocal) * D.evalNDF(h) * F / (4.0f * abs(wiLocal.z)); } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) { wo = {}; pdf = {}; @@ -92,7 +92,7 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wiLocal, const float3 woLocal) + float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) { if (D.isSingular() || min(wiLocal.z, woLocal.z) < 0.0f) return 0.0f; float3 h = normalize(wiLocal + woLocal); @@ -105,6 +105,11 @@ struct PBRTConductorBSDF : IBSDF, IDifferentiable return AlbedoContributions(r, 1.0f - r, 0.0f, 0.0f); } + float3 getIorAsReflectance() + { + return float3(1.f); + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; @@ -125,7 +130,7 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); - return bsdf.eval(wiLocal, woLocal, sg); + return bsdf.eval(wiLocal, woLocal, sg, BSDFContext()); } [Differentiable] @@ -137,7 +142,7 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); PBRTConductorBSDF bsdfAD = PBRTConductorBSDF(diffData); - return bsdfAD.eval(wiLocal, woLocal, sg); + return bsdfAD.eval(wiLocal, woLocal, sg, BSDFContext()); } bool sample(const ShadingData sd, inout S sg, out BSDFSample result, bool useImportanceSampling = true) @@ -145,7 +150,7 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; - bool valid = bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg); + bool valid = bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext()); result.wo = sf.fromLocal(woLocal); if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; @@ -160,7 +165,7 @@ struct PBRTConductorMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; - return bsdf.evalPdf(wiLocal, woLocal); + return bsdf.evalPdf(wiLocal, woLocal, BSDFContext()); } BSDFProperties getProperties(const ShadingData sd) diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang index 50651e587..68b4a7cc7 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterial.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,7 +42,7 @@ struct PBRTDielectricMaterial : MaterialBase, IMaterial const float2 roughness = ms.sampleTexture(data.texSpecular, s, sd.uv, data.specular, lod).rg; float16_t IoR = header.getIoR(); - float eta = !sd.frontFacing ? 1.0f / IoR : IoR; + float eta = IoR; // Compute final shading frame. // Do not flip the shading normal for backfacing hits. The BSDF handles transmission from both sides. diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang index 409ae63a8..fc371d138 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDielectricMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,41 +29,45 @@ __exported import Rendering.Materials.IMaterialInstance; __exported import Rendering.Materials.AnisotropicGGX; import Utils.Math.MathHelpers; import Rendering.Materials.Fresnel; +import Utils.Debug.PixelDebug; struct PBRTDielectricBSDF : IBSDF { AnisotropicGGX D; float eta; - float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg) + float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { - if (D.isSingular() || eta == 1.0f || wiLocal.z * woLocal.z == 0) return float3(0.0f); + const float relEta = bc.iorT / bc.iorI; + + if (D.isSingular() || relEta == 1.0f || wiLocal.z * woLocal.z == 0) return float3(0.0f); bool reflect = wiLocal.z * woLocal.z > 0.0f; - float relEta = 1.0 / eta; + const float relEtaInverse = 1.f / relEta; float3 h = reflect ? normalize(wiLocal + woLocal) : normalize(wiLocal / relEta + woLocal); h *= sign(h.z); float wiDotH = dot(wiLocal, h); float woDotH = dot(woLocal, h); if (wiDotH * wiLocal.z < 0.0f || woDotH * woLocal.z < 0.0f) return float3(0.0f); - float F = evalFresnelDielectric(eta, abs(wiDotH)); + float F = evalFresnelDielectric(relEtaInverse, abs(wiDotH)); return reflect ? D.G2(woLocal, wiLocal) * D.evalNDF(h) * F / (4.0f * abs(wiLocal.z)) : D.G2(woLocal, wiLocal) * D.evalNDF(h) * (1.0f - F) * abs(wiDotH * woDotH / (sqr(woDotH + wiDotH / relEta) * abs(wiLocal.z))); } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) { wo = {}; pdf = {}; weight = {}; lobeType = {}; - float relEta = 1.0 / eta; + const float relEta = bc.iorT / bc.iorI; + const float relEtaInverse = 1.f / relEta; - if (eta == 1 || D.isSingular()) + if (relEta == 1 || D.isSingular()) { - float F = evalFresnelDielectric(eta, abs(wiLocal.z)); + float F = evalFresnelDielectric(relEtaInverse, abs(wiLocal.z)); if (sampleNext1D(sg) < F) { @@ -74,7 +78,7 @@ struct PBRTDielectricBSDF : IBSDF } else { - wo = refract(-wiLocal, float3(0.0f, 0.0f, sign(wiLocal.z)), 1.0f / relEta); + wo = refract(-wiLocal, float3(0.0f, 0.0f, sign(wiLocal.z)), relEtaInverse); weight = float3(1.0f); lobeType = (uint)LobeType::DeltaTransmission; pdf = 1.0f; @@ -84,7 +88,7 @@ struct PBRTDielectricBSDF : IBSDF { float3 h = D.sample(wiLocal, sg); float wiDotH = dot(h, wiLocal); - float F = evalFresnelDielectric(eta, abs(wiDotH)); + float F = evalFresnelDielectric(relEtaInverse, abs(wiDotH)); if (sampleNext1D(sg) < F) { @@ -98,7 +102,7 @@ struct PBRTDielectricBSDF : IBSDF } else { - float3 woLocal = refract(-wiLocal, sign(wiLocal.z) * h, 1.0f/relEta); + float3 woLocal = refract(-wiLocal, sign(wiLocal.z) * h, relEtaInverse); float woDotH = dot(h, woLocal); if (wiLocal.z * woLocal.z > 0.0f) return false; @@ -111,18 +115,19 @@ struct PBRTDielectricBSDF : IBSDF return true; } - float evalPdf(const float3 wiLocal, const float3 woLocal) + float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) { - if (D.isSingular() || eta == 1.0f || wiLocal.z * woLocal.z == 0) return 0.0f; + const float relEta = bc.iorT / bc.iorI; + const float relEtaInverse = 1.f / relEta; + if (D.isSingular() || relEta == 1.0f || wiLocal.z * woLocal.z == 0) return 0.0f; bool reflect = wiLocal.z * woLocal.z > 0.0f; - float relEta = (1.0f / eta); float3 h = reflect ? normalize(wiLocal + woLocal) : normalize(wiLocal / relEta + woLocal); h *= sign(h.z); float wiDotH = dot(wiLocal, h); float woDotH = dot(woLocal, h); if (wiDotH * wiLocal.z < 0.0f || woDotH * woLocal.z < 0.0f) return 0.0f; - float F = evalFresnelDielectric(eta, abs(wiDotH)); + float F = evalFresnelDielectric(relEtaInverse, abs(wiDotH)); return reflect ? F * D.evalPDF(wiLocal, h) / (4.0f * abs(wiDotH)) : (1.0f - F) * D.evalPDF(wiLocal, h) * abs(woDotH) / sqr(woDotH + wiDotH / relEta); @@ -134,12 +139,24 @@ struct PBRTDielectricBSDF : IBSDF return AlbedoContributions(r, 0.0f, 1.0f - r, 0.0f); } + float3 getIorAsReflectance() + { + // This is the absolute IOR. At normal incidence we are side invariant. + const float rsqrt = (1.f - eta) / (1.f + eta); + return rsqrt * rsqrt; + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; r.roughnessBSDFNotation = D.alpha; return r; } + + float getEta() + { + return eta; + } }; /** PBRT dielectric material instance. @@ -158,7 +175,8 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) return float3(0.f); - return bsdf.eval(wiLocal, woLocal, sg); + BSDFContext bc = sd.frontFacing? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); + return bsdf.eval(wiLocal, woLocal, sg, bc); } [Differentiable] @@ -172,7 +190,8 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; - if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg)) + BSDFContext bc = sd.frontFacing? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); + if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, bc)) return false; result.wo = sf.fromLocal(woLocal); @@ -188,7 +207,8 @@ struct PBRTDielectricMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflectionOrTransmission(sd, sf, wiLocal, woLocal, wo, true)) return 0.f; - return bsdf.evalPdf(wiLocal, woLocal); + BSDFContext bc = sd.frontFacing? BSDFContext(sd.IoR, bsdf.getEta()) : BSDFContext(bsdf.getEta(), sd.IoR); + return bsdf.evalPdf(wiLocal, woLocal, bc); } BSDFProperties getProperties(const ShadingData sd) diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang index 776e24fa8..892eb02df 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -37,12 +37,12 @@ struct PBRTDiffuseBSDF : IBSDF, IDifferentiable float3 albedo; [Differentiable] - float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg) + float3 eval(const float3 wiLocal, const float3 woLocal, inout S sg, BSDFContext bc) { return albedo * max(woLocal.z, 0.0f) * M_1_PI; } - bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg) + bool sample(float3 wiLocal, out float3 wo, out float pdf, out float3 weight, out uint lobeType, inout S sg, BSDFContext bc) { wo = sample_cosine_hemisphere_concentric(sampleNext2D(sg), pdf); weight = albedo; @@ -51,7 +51,7 @@ struct PBRTDiffuseBSDF : IBSDF, IDifferentiable return true; } - float evalPdf(const float3 wiLocal, const float3 woLocal) + float evalPdf(const float3 wiLocal, const float3 woLocal, BSDFContext bc) { return max(woLocal.z, 0.0f) * M_1_PI; } @@ -61,6 +61,11 @@ struct PBRTDiffuseBSDF : IBSDF, IDifferentiable return AlbedoContributions(albedo, 1.0f - albedo, 0.0f, 0.0f); } + float3 getIorAsReflectance() + { + return float3(1.f); + } + RoughnessInformation getRoughnessInformation(const float3 wi) { RoughnessInformation r; @@ -81,7 +86,7 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return float3(0.f); - return bsdf.eval(wiLocal, woLocal, sg); + return bsdf.eval(wiLocal, woLocal, sg, BSDFContext()); } [Differentiable] @@ -96,7 +101,7 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 albedo = diffData.read<3>(offset); PBRTDiffuseBSDF bsdfAD = { albedo }; - return bsdfAD.eval(wiLocal, woLocal, sg); + return bsdfAD.eval(wiLocal, woLocal, sg, BSDFContext()); } bool sample(const ShadingData sd, inout S sg, out BSDFSample result, bool useImportanceSampling = true) @@ -104,7 +109,7 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance float3 wiLocal = sf.toLocal(sd.V); float3 woLocal = {}; - bool valid = bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg); + bool valid = bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext()); result.wo = sf.fromLocal(woLocal); if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, result.wo) || result.pdf == 0.f) return false; @@ -120,7 +125,7 @@ struct PBRTDiffuseMaterialInstance : MaterialInstanceBase, IMaterialInstance if (!isValidHemisphereReflection(sd, sf, wiLocal, woLocal, wo)) return 0.f; - return bsdf.evalPdf(wiLocal, woLocal); + return bsdf.evalPdf(wiLocal, woLocal, BSDFContext()); } BSDFProperties getProperties(const ShadingData sd) diff --git a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang index 01d745354..a7fa14c6f 100644 --- a/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/PBRT/PBRTDiffuseTransmissionMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang b/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang index 302c60011..cad64858b 100644 --- a/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/RGLMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang b/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang index 9a6b92514..d14cd90db 100644 --- a/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang +++ b/Source/Falcor/Rendering/Materials/StandardMaterialInstance.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -69,7 +69,7 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance StandardBSDF bsdf = StandardBSDF(wiLocal, sd.mtl, data); - return bsdf.eval(wiLocal, woLocal, sg); + return bsdf.eval(wiLocal, woLocal, sg, BSDFContext()); } [Differentiable] @@ -83,7 +83,7 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance StandardBSDFData dataAD = StandardBSDFData(diffData); StandardBSDF bsdfAD = StandardBSDF(wiLocal, sd.mtl, dataAD); - return bsdfAD.eval(wiLocal, woLocal, sg); + return bsdfAD.eval(wiLocal, woLocal, sg, BSDFContext()); } bool sample(const ShadingData sd, inout S sg, out BSDFSample result, bool useImportanceSampling = true) @@ -99,7 +99,7 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance else { StandardBSDF bsdf = StandardBSDF(wiLocal, sd.mtl, data); - if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg)) + if (!bsdf.sample(wiLocal, woLocal, result.pdf, result.weight, result.lobeType, sg, BSDFContext())) return false; } result.wo = sf.fromLocal(woLocal); @@ -123,7 +123,7 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance else { StandardBSDF bsdf = StandardBSDF(wiLocal, sd.mtl, data); - return bsdf.evalPdf(wiLocal, woLocal); + return bsdf.evalPdf(wiLocal, woLocal, BSDFContext()); } } @@ -224,7 +224,7 @@ struct StandardMaterialInstance : MaterialInstanceBase, IMaterialInstance StandardBSDF bsdf = StandardBSDF(wi, sd.mtl, data); - weight = bsdf.eval(wi, wo, sg) / pdf; + weight = bsdf.eval(wi, wo, sg, BSDFContext()) / pdf; lobeType = (uint)(wo.z > 0.f ? LobeType::DiffuseReflection : LobeType::DiffuseTransmission); return true; diff --git a/Source/Falcor/Rendering/RTXDI/EnvLightUpdater.cs.slang b/Source/Falcor/Rendering/RTXDI/EnvLightUpdater.cs.slang index f37a9cd08..67e0a7ffd 100644 --- a/Source/Falcor/Rendering/RTXDI/EnvLightUpdater.cs.slang +++ b/Source/Falcor/Rendering/RTXDI/EnvLightUpdater.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Falcor/Rendering/RTXDI/LightUpdater.cs.slang b/Source/Falcor/Rendering/RTXDI/LightUpdater.cs.slang index a6aa3cb39..8bc6c94db 100644 --- a/Source/Falcor/Rendering/RTXDI/LightUpdater.cs.slang +++ b/Source/Falcor/Rendering/RTXDI/LightUpdater.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Falcor/Rendering/RTXDI/RTXDI.cpp b/Source/Falcor/Rendering/RTXDI/RTXDI.cpp index aef6e29e1..4d9113eb3 100644 --- a/Source/Falcor/Rendering/RTXDI/RTXDI.cpp +++ b/Source/Falcor/Rendering/RTXDI/RTXDI.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -81,17 +81,19 @@ namespace Falcor }; } - RTXDI::RTXDI(const ref& pScene, const Options& options) - : mpScene(pScene) + RTXDI::RTXDI(ref pScene, const Options& options) + : mpScene(std::move(pScene)) , mpDevice(mpScene->getDevice()) , mOptions(options) { + FALCOR_ASSERT(mpScene); if (!mpDevice->isShaderModelSupported(ShaderModel::SM6_5)) FALCOR_THROW("RTXDI requires Shader Model 6.5 support."); mpPixelDebug = std::make_unique(mpDevice); - FALCOR_ASSERT(pScene); + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); + setOptions(options); if (!isInstalled()) logWarning("RTXDI SDK is not installed."); } @@ -169,14 +171,14 @@ namespace Falcor #if FALCOR_HAS_RTXDI // Check for scene changes that require shader recompilation. // TODO: We may want to reset other data that depends on the scene geometry or materials. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || + is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) { mFlags.recompileShaders = true; } // Make sure the light collection is created. - mpScene->getLightCollection(pRenderContext); + mpScene->getILightCollection(pRenderContext); // Initialize previous frame camera data. if (mFrameIndex == 0) mPrevCameraData = mpScene->getCamera()->getData(); @@ -203,20 +205,19 @@ namespace Falcor } // Determine what, if anything happened since last frame. TODO: Make more granular / flexible. - const Scene::UpdateFlags updates = mpScene->getUpdates(); // Emissive lights. - if (is_set(updates, Scene::UpdateFlags::LightCollectionChanged)) mFlags.updateEmissiveLights = true; - if (is_set(updates, Scene::UpdateFlags::EmissiveMaterialsChanged)) mFlags.updateEmissiveLightsFlux = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::LightCollectionChanged)) mFlags.updateEmissiveLights = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::EmissiveMaterialsChanged)) mFlags.updateEmissiveLightsFlux = true; // Analytic lights. - if (is_set(updates, Scene::UpdateFlags::LightCountChanged)) mFlags.updateAnalyticLights = true; - if (is_set(updates, Scene::UpdateFlags::LightPropertiesChanged)) mFlags.updateAnalyticLights = true; - if (is_set(updates, Scene::UpdateFlags::LightIntensityChanged)) mFlags.updateAnalyticLightsFlux = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::LightCountChanged)) mFlags.updateAnalyticLights = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::LightPropertiesChanged)) mFlags.updateAnalyticLights = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::LightIntensityChanged)) mFlags.updateAnalyticLightsFlux = true; // Env light. Update the env light PDF either if the env map changed or its tint/intensity changed. - if (is_set(updates, Scene::UpdateFlags::EnvMapChanged)) mFlags.updateEnvLight = true; - if (is_set(updates, Scene::UpdateFlags::EnvMapPropertiesChanged) && is_set(mpScene->getEnvMap()->getChanges(), EnvMap::Changes::Intensity)) mFlags.updateEnvLight = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::EnvMapChanged)) mFlags.updateEnvLight = true; + if (is_set(mUpdateFlags, IScene::UpdateFlags::EnvMapPropertiesChanged) && is_set(mpScene->getEnvMap()->getChanges(), EnvMap::Changes::Intensity)) mFlags.updateEnvLight = true; - if (is_set(updates, Scene::UpdateFlags::RenderSettingsChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::RenderSettingsChanged)) { mFlags.updateAnalyticLights = true; mFlags.updateAnalyticLightsFlux = true; @@ -225,6 +226,8 @@ namespace Falcor mFlags.updateEnvLight = true; } + mUpdateFlags = IScene::UpdateFlags::None; + mpPixelDebug->beginFrame(pRenderContext, mFrameDim); #endif } @@ -368,9 +371,9 @@ namespace Falcor std::vector localAnalyticLightIDs; std::vector infiniteAnalyticLightIDs; - for (uint32_t lightID = 0; lightID < mpScene->getActiveLightCount(); ++lightID) + for (uint32_t lightID = 0; lightID < mpScene->getActiveAnalyticLights().size(); ++lightID) { - const auto& pLight = mpScene->getActiveLight(lightID); + const auto& pLight = mpScene->getActiveAnalyticLights()[lightID]; switch (pLight->getType()) { case LightType::Point: @@ -419,7 +422,7 @@ namespace Falcor } // Update other light counts. - mLights.emissiveLightCount = mpScene->useEmissiveLights() ? mpScene->getLightCollection(pRenderContext)->getActiveLightCount(pRenderContext) : 0; + mLights.emissiveLightCount = mpScene->useEmissiveLights() ? mpScene->getILightCollection(pRenderContext)->getActiveLightCount(pRenderContext) : 0; mLights.envLightPresent = mpScene->useEnvLight(); uint32_t localLightCount = mLights.getLocalLightCount(); @@ -668,14 +671,15 @@ namespace Falcor // Helper for creating compute passes. auto createComputePass = [&](const std::string& file, const std::string& entryPoint) { - DefineList defines = mpScene->getSceneDefines(); + DefineList defines; + mpScene->getShaderDefines(defines); defines.add("RTXDI_INSTALLED", "1"); ProgramDesc desc; - desc.addShaderModules(mpScene->getShaderModules()); + mpScene->getShaderModules(desc.shaderModules); desc.addShaderLibrary(file); desc.csEntry(entryPoint); - desc.addTypeConformances(mpScene->getTypeConformances()); + mpScene->getTypeConformances(desc.typeConformances); ref pPass = ComputePass::create(mpDevice, desc, defines); return pPass; }; diff --git a/Source/Falcor/Rendering/RTXDI/RTXDI.h b/Source/Falcor/Rendering/RTXDI/RTXDI.h index af4da4276..eb1a609dc 100644 --- a/Source/Falcor/Rendering/RTXDI/RTXDI.h +++ b/Source/Falcor/Rendering/RTXDI/RTXDI.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -219,7 +219,7 @@ namespace Falcor \param[in] pScene Scene. \param[in] options Configuration options. */ - RTXDI(const ref& pScene, const Options& options = Options()); + RTXDI(ref pScene, const Options& options = Options()); /** Set the configuration options. \param[in] options Configuration options. @@ -273,12 +273,16 @@ namespace Falcor PixelDebug& getPixelDebug() { return *mpPixelDebug; } private: - ref mpScene; ///< Scene (set on initialization). + ref mpScene; ///< Scene (set on initialization). ref mpDevice; ///< GPU device. Options mOptions; ///< Configuration options. std::unique_ptr mpPixelDebug; ///< Pixel debug component. + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + /// IScene::UpdateFlags accumulated since last `beginFrame()` + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + // If the SDK is not installed, we leave out most of the implementation. #if FALCOR_HAS_RTXDI diff --git a/Source/Falcor/Rendering/RTXDI/RTXDI.slang b/Source/Falcor/Rendering/RTXDI/RTXDI.slang index ce80e4269..3ad3b3449 100644 --- a/Source/Falcor/Rendering/RTXDI/RTXDI.slang +++ b/Source/Falcor/Rendering/RTXDI/RTXDI.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,10 +27,10 @@ **************************************************************************/ #include "Utils/Math/MathConstants.slangh" import Scene.Scene; -import Scene.RaytracingInline; import Utils.Color.ColorHelpers; import Utils.Debug.PixelDebug; import PackedTypes; +import Scene.RaytracingInline; // Set default value for RTXDI_INSTALLED. // We want this shader module to compile even if no external defines are set. diff --git a/Source/Falcor/Rendering/RTXDI/RTXDIApplicationBridge.slangh b/Source/Falcor/Rendering/RTXDI/RTXDIApplicationBridge.slangh index 4017d0c2c..d71c0fd50 100644 --- a/Source/Falcor/Rendering/RTXDI/RTXDIApplicationBridge.slangh +++ b/Source/Falcor/Rendering/RTXDI/RTXDIApplicationBridge.slangh @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -415,7 +415,7 @@ bool RAB_GetConservativeVisibility(RAB_Surface surface, RAB_LightSample lightSam // Trace our ray (with SceneRayQuery<0> meaning no alpha testing) and cull non-opaque geometry. SceneRayQuery<0> rayQuery; - return rayQuery.traceVisibilityRay(ray, RAY_FLAG_CULL_NON_OPAQUE, 0xff); + return rayQuery.traceVisibilityRay(ray, RAY_FLAG_CULL_NON_OPAQUE); } /** Same visibility ray tracing as RAB_GetConservativeVisibility but for surfaces and light samples @@ -448,31 +448,33 @@ bool RAB_TraceRayForLocalLight(float3 origin, float3 direction, float tMin, floa o_randXY = 0; float hitT; - HitInfo hit; SceneRayQuery<0> rayQuery; Ray ray = Ray(origin, direction, tMin, tMax); - bool hitAnything = rayQuery.traceRay(ray, hit, hitT, RAY_FLAG_CULL_NON_OPAQUE, 0xff); - if (gRTXDI.params.numLocalLights && hitAnything) + HitInfo hit = rayQuery.traceRay(ray, hitT, RAY_FLAG_CULL_NON_OPAQUE); + if (gRTXDI.params.numLocalLights && hit.isValid()) { - if (hit.getType() == HitType::Triangle) + const bool isTriangleHit = hit.getType() == HitType::Triangle; + + if (isTriangleHit) { const TriangleHit triangleHit = hit.getTriangleHit(); uint triangleIndex = gScene.lightCollection.getTriangleIndex(triangleHit.instanceID, triangleHit.primitiveIndex); + float2 barycentrics = triangleHit.barycentrics; if (triangleIndex != LightCollection::kInvalidIndex) { uint activeLightIndex = gScene.lightCollection.getActiveTriangleIndex(triangleIndex); if (activeLightIndex != LightCollection::kInvalidIndex) { // hitUVs are mapped directly to randXY in Falcor see EmissiveLight::calcSample. - o_randXY = triangleHit.barycentrics; + o_randXY = barycentrics; o_lightIndex = activeLightIndex; } } } } - return hitAnything; + return hit.isValid(); } diff --git a/Source/Falcor/Rendering/Volumes/GridVolumeSampler.cpp b/Source/Falcor/Rendering/Volumes/GridVolumeSampler.cpp index ce10beffb..e43da7a2c 100644 --- a/Source/Falcor/Rendering/Volumes/GridVolumeSampler.cpp +++ b/Source/Falcor/Rendering/Volumes/GridVolumeSampler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -30,7 +30,7 @@ namespace Falcor { - GridVolumeSampler::GridVolumeSampler(RenderContext* pRenderContext, ref pScene, const Options& options) + GridVolumeSampler::GridVolumeSampler(RenderContext* pRenderContext, ref pScene, const Options& options) : mpScene(pScene) , mOptions(options) { diff --git a/Source/Falcor/Rendering/Volumes/GridVolumeSampler.h b/Source/Falcor/Rendering/Volumes/GridVolumeSampler.h index 83e792bf0..f52aa06a1 100644 --- a/Source/Falcor/Rendering/Volumes/GridVolumeSampler.h +++ b/Source/Falcor/Rendering/Volumes/GridVolumeSampler.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -69,7 +69,7 @@ namespace Falcor \param[in] pScene The scene. \param[in] options Configuration options. */ - GridVolumeSampler(RenderContext* pRenderContext, ref pScene, const Options& options = Options()); + GridVolumeSampler(RenderContext* pRenderContext, ref pScene, const Options& options = Options()); virtual ~GridVolumeSampler() = default; /** Get a list of shader defines for using the grid volume sampler. @@ -94,7 +94,7 @@ namespace Falcor void setOptions(const Options& options) { mOptions = options; } protected: - ref mpScene; ///< Scene. + ref mpScene; ///< Scene. Options mOptions; }; diff --git a/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp b/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp index c0ca512e8..9e392c61c 100644 --- a/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp +++ b/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -477,6 +477,7 @@ namespace Falcor DefineList defines; defines.add("MESH_KEYFRAME_COUNT", std::to_string(mMeshKeyframeCount)); + mpScene->getMeshStaticData().getShaderDefines(defines); mpMeshVertexUpdatePass = ComputePass::create(mpDevice, "Scene/Animation/UpdateMeshVertices.slang", "main", defines); // Bind data @@ -495,6 +496,7 @@ namespace Falcor DefineList defines; defines.add("CURVE_KEYFRAME_COUNT", std::to_string(mCurveKeyframeTimes.size())); + mpScene->getMeshStaticData().getShaderDefines(defines); mpCurveVertexUpdatePass = ComputePass::create(mpDevice, kUpdateCurveVerticesFilename, "main", defines); auto block = mpCurveVertexUpdatePass->getRootVar()["gCurveVertexUpdater"]; @@ -520,6 +522,7 @@ namespace Falcor DefineList defines; defines.add("CURVE_KEYFRAME_COUNT", std::to_string(mCurveKeyframeTimes.size())); + mpScene->getMeshStaticData().getShaderDefines(defines); mpCurvePolyTubeVertexUpdatePass = ComputePass::create(mpDevice, kUpdateCurvePolyTubeVerticesFilename, "main", defines); auto block = mpCurvePolyTubeVertexUpdatePass->getRootVar()["gCurvePolyTubeVertexUpdater"]; @@ -549,7 +552,7 @@ namespace Falcor mpMeshInterpolationBuffer->setBlob(mMeshInterpolationInfo.data(), 0, mpMeshInterpolationBuffer->getSize()); auto block = mpMeshVertexUpdatePass->getRootVar()["gMeshVertexUpdater"]; - block["sceneVertexData"] = mpScene->getMeshVao()->getVertexBuffer(Scene::kStaticDataBufferIndex); + mpScene->getMeshStaticData().bindShaderData(block["sceneVertexData"]); block["copyPrev"] = copyPrev; mpMeshVertexUpdatePass->execute(pRenderContext, mMaxMeshVertexCount, (uint32_t)mCachedMeshes.size(), 1); @@ -584,7 +587,7 @@ namespace Falcor auto block = mpCurveAABBUpdatePass->getRootVar()["gCurveAABBUpdater"]; block["curveVertices"] = mpScene->mpCurveVao->getVertexBuffer(0); - block["curveAABBs"].setUav(mpScene->mpRtAABBBuffer->getUAV(0, mCurveIndexCount)); + block["curveAABBs"].setUav(mpScene->mpRtAABBBuffer->getUAV(0, mCurveIndexCount * sizeof(RtAABB))); uint32_t dimX = (1 << 16); uint32_t dimY = (uint32_t)std::ceil((float)mCurveIndexCount / dimX); @@ -606,7 +609,7 @@ namespace Falcor block["copyPrev"] = copyPrev; block["perMeshData"] = mpCurvePolyTubeMeshMetadataBuffer; - block["sceneVertexData"] = mpScene->getMeshVao()->getVertexBuffer(Scene::kStaticDataBufferIndex); + mpScene->getMeshStaticData().bindShaderData(block["sceneVertexData"]); block["prevVertexData"] = mpPrevVertexData; block["vertexCount"] = mCurvePolyTubeVertexCount; diff --git a/Source/Falcor/Scene/Animation/Animation.h b/Source/Falcor/Scene/Animation/Animation.h index 890767f26..e146ffa11 100644 --- a/Source/Falcor/Scene/Animation/Animation.h +++ b/Source/Falcor/Scene/Animation/Animation.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,6 +33,7 @@ #include "Utils/Math/Matrix.h" #include "Utils/Math/Quaternion.h" #include "Utils/UI/Gui.h" +#include #include #include #include @@ -137,6 +138,11 @@ namespace Falcor */ const Keyframe& getKeyframe(double time) const; + /** Gets all the keyframes in the animation. + \return Returns span of keyframes. + */ + fstd::span getKeyframes() const { return mKeyframes; } + /** Check if a keyframe exists at the specified time. \param[in] time Time of the keyframe. \return Returns true if keyframe exists. diff --git a/Source/Falcor/Scene/Animation/AnimationController.cpp b/Source/Falcor/Scene/Animation/AnimationController.cpp index ff3ffd6ed..ae634490b 100644 --- a/Source/Falcor/Scene/Animation/AnimationController.cpp +++ b/Source/Falcor/Scene/Animation/AnimationController.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -41,7 +41,7 @@ namespace Falcor const std::string kPrevInverseTransposeWorldMatrices = "prevInverseTransposeWorldMatrices"; } - AnimationController::AnimationController(ref pDevice, Scene* pScene, const StaticVertexVector& staticVertexData, const SkinningVertexVector& skinningVertexData, uint32_t prevVertexCount, const std::vector>& animations) + AnimationController::AnimationController(ref pDevice, Scene* pScene, const SkinningVertexVector& skinningVertexData, uint32_t prevVertexCount, const std::vector>& animations) : mpDevice(pDevice) , mAnimations(animations) , mNodesEdited(pScene->mSceneGraph.size()) @@ -73,6 +73,7 @@ namespace Falcor // This ensures we have valid data in the buffer before the skinning pass runs for the first time. if (prevVertexCount > 0) { + const SplitVertexBuffer& staticVertexData = pScene->getMeshStaticData(); std::vector prevVertexData(prevVertexCount); for (size_t i = 0; i < skinningVertexData.size(); i++) { @@ -83,7 +84,7 @@ namespace Falcor mpPrevVertexData->setName("AnimationController::mpPrevVertexData"); } - createSkinningPass(staticVertexData, skinningVertexData); + createSkinningPass(skinningVertexData); // Determine length of global animation loop. for (const auto& pAnimation : mAnimations) @@ -92,7 +93,7 @@ namespace Falcor } } - void AnimationController::addAnimatedVertexCaches(std::vector&& cachedCurves, std::vector&& cachedMeshes, const StaticVertexVector& staticVertexData) + void AnimationController::addAnimatedVertexCaches(std::vector&& cachedCurves, std::vector&& cachedMeshes) { size_t totalAnimatedMeshVertexCount = 0; @@ -113,6 +114,7 @@ namespace Falcor // Initialize remaining previous position data std::vector prevVertexData; prevVertexData.reserve(totalAnimatedMeshVertexCount); + const SplitVertexBuffer& staticVertexData = mpScene->getMeshStaticData(); for (auto& cache : cachedMeshes) { @@ -383,30 +385,27 @@ namespace Falcor m += mpSkinningMatricesBuffer ? mpSkinningMatricesBuffer->getSize() : 0; m += mpInvTransposeSkinningMatricesBuffer ? mpInvTransposeSkinningMatricesBuffer->getSize() : 0; m += mpMeshBindMatricesBuffer ? mpMeshBindMatricesBuffer->getSize() : 0; - m += mpStaticVertexData ? mpStaticVertexData->getSize() : 0; + m += mStaticVertexData.getByteSize(); m += mpSkinningVertexData ? mpSkinningVertexData->getSize() : 0; m += mpPrevVertexData ? mpPrevVertexData->getSize() : 0; m += mpVertexCache ? mpVertexCache->getMemoryUsageInBytes() : 0; return m; } - void AnimationController::createSkinningPass(const std::vector& staticVertexData, const std::vector& skinningVertexData) + void AnimationController::createSkinningPass(const std::vector& skinningVertexData) { - if (staticVertexData.empty()) return; - - // We always copy the static data, to initialize the non-skinned vertices. - FALCOR_ASSERT(mpScene->getMeshVao()); - const ref& pVB = mpScene->getMeshVao()->getVertexBuffer(Scene::kStaticDataBufferIndex); - FALCOR_ASSERT(pVB->getSize() == staticVertexData.size() * sizeof(staticVertexData[0])); - pVB->setBlob(staticVertexData.data(), 0, pVB->getSize()); - if (!skinningVertexData.empty()) { + const SplitVertexBuffer& staticVertexData = mpScene->getMeshStaticData(); + mSkinningMatrices.resize(mpScene->mSceneGraph.size()); mInvTransposeSkinningMatrices.resize(mSkinningMatrices.size()); mMeshBindMatrices.resize(mpScene->mSceneGraph.size()); - mpSkinningPass = ComputePass::create(mpDevice, "Scene/Animation/Skinning.slang"); + DefineList defines; + staticVertexData.getShaderDefines(defines); + + mpSkinningPass = ComputePass::create(mpDevice, "Scene/Animation/Skinning.slang", "main", defines); auto block = mpSkinningPass->getRootVar()["gData"]; // Initialize mesh bind transforms @@ -417,17 +416,21 @@ namespace Falcor meshInvBindMatrices[i] = inverse(mMeshBindMatrices[i]); } - // Bind vertex data. - FALCOR_ASSERT(staticVertexData.size() <= std::numeric_limits::max()); + // Clone the static vertex data into a second set of buffers + FALCOR_ASSERT(staticVertexData.hasCpuData(), "Cannot clone without CPU data"); + mStaticVertexData = staticVertexData; + mStaticVertexData.setName("AnimationController::mStaticVertexData"); + mStaticVertexData.createGpuBuffers(mpDevice, ResourceBindFlags::ShaderResource); + mStaticVertexData.dropCpuData(); + FALCOR_ASSERT(skinningVertexData.size() <= std::numeric_limits::max()); - mpStaticVertexData = mpDevice->createStructuredBuffer(block["staticData"], (uint32_t)staticVertexData.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, staticVertexData.data(), false); - mpStaticVertexData->setName("AnimationController::mpStaticVertexData"); mpSkinningVertexData = mpDevice->createStructuredBuffer(block["skinningData"], (uint32_t)skinningVertexData.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, skinningVertexData.data(), false); mpSkinningVertexData->setName("AnimationController::mpSkinningVertexData"); - block["staticData"] = mpStaticVertexData; + mStaticVertexData.bindShaderData(block["staticData"]); + staticVertexData.bindShaderData(block["skinnedVertices"]); + block["skinningData"] = mpSkinningVertexData; - block["skinnedVertices"] = pVB; block["prevSkinnedVertices"] = mpPrevVertexData; // Bind transforms. diff --git a/Source/Falcor/Scene/Animation/AnimationController.h b/Source/Falcor/Scene/Animation/AnimationController.h index 15eb4430b..b75405614 100644 --- a/Source/Falcor/Scene/Animation/AnimationController.h +++ b/Source/Falcor/Scene/Animation/AnimationController.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,28 +33,30 @@ #include "Core/Pass/ComputePass.h" #include "Utils/Math/Matrix.h" #include "Scene/SceneTypes.slang" +#include "Utils/SplitBuffer.h" #include #include namespace Falcor { class Scene; + using SplitVertexBuffer = SplitBuffer; + using SplitIndexBuffer = SplitBuffer; class FALCOR_API AnimationController { public: ~AnimationController() = default; - using StaticVertexVector = std::vector; using SkinningVertexVector = std::vector; /** Constructor. Throws an exception if creation failed. */ - AnimationController(ref pDevice, Scene* pScene, const StaticVertexVector& staticVertexData, const SkinningVertexVector& skinningVertexData, uint32_t prevVertexCount, const std::vector>& animations); + AnimationController(ref pDevice, Scene* pScene, const SkinningVertexVector& skinningVertexData, uint32_t prevVertexCount, const std::vector>& animations); /** Add animated vertex caches (curves and meshes) to the controller. */ - void addAnimatedVertexCaches(std::vector&& cachedCurves, std::vector&& cachedMeshes, const StaticVertexVector& staticVertexData); + void addAnimatedVertexCaches(std::vector&& cachedCurves, std::vector&& cachedMeshes); /** Returns true if controller contains animations. */ @@ -153,7 +155,7 @@ namespace Falcor void bindBuffers(); - void createSkinningPass(const std::vector& staticVertexData, const SkinningVertexVector& skinningVertexData); + void createSkinningPass(const SkinningVertexVector& skinningVertexData); void executeSkinningPass(RenderContext* pRenderContext, bool initPrev = false); ref mpDevice; @@ -192,9 +194,9 @@ namespace Falcor ref mpMeshInvBindMatricesBuffer; ref mpSkinningMatricesBuffer; ref mpInvTransposeSkinningMatricesBuffer; - ref mpStaticVertexData; ref mpSkinningVertexData; ref mpPrevVertexData; + SplitVertexBuffer mStaticVertexData; // Animated vertex caches std::unique_ptr mpVertexCache; diff --git a/Source/Falcor/Scene/Animation/Skinning.slang b/Source/Falcor/Scene/Animation/Skinning.slang index da141147a..a0649b8cc 100644 --- a/Source/Falcor/Scene/Animation/Skinning.slang +++ b/Source/Falcor/Scene/Animation/Skinning.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -38,9 +38,9 @@ struct SkinningData bool initPrev; ///< Copy current frame data to previous frame data. // Vertex data - StructuredBuffer staticData; ///< Original global vertex buffer. This holds the unmodified input vertices. + SplitVertexBuffer staticData; ///< Original global vertex buffer. This holds the unmodified input vertices. StructuredBuffer skinningData; ///< Bone IDs and weights for all skinned vertices. - RWStructuredBuffer skinnedVertices; ///< Skinned global vertex buffer. We'll update the positions only for the dynamic meshes. + RWSplitVertexBuffer skinnedVertices; ///< Skinned global vertex buffer. We'll update the positions only for the dynamic meshes. RWStructuredBuffer prevSkinnedVertices; ///< Previous frame vertex positions for all dynamic meshes. // Transforms diff --git a/Source/Falcor/Scene/Animation/UpdateCurvePolyTubeVertices.slang b/Source/Falcor/Scene/Animation/UpdateCurvePolyTubeVertices.slang index 8dacb1b69..c7f87ea9b 100644 --- a/Source/Falcor/Scene/Animation/UpdateCurvePolyTubeVertices.slang +++ b/Source/Falcor/Scene/Animation/UpdateCurvePolyTubeVertices.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -60,7 +60,7 @@ struct CurvePolyTubeVertexUpdater ByteAddressBuffer curveStrandIndexData; // Output - RWStructuredBuffer sceneVertexData; + RWSplitVertexBuffer sceneVertexData; RWStructuredBuffer prevVertexData; // Accessors diff --git a/Source/Falcor/Scene/Animation/UpdateMeshVertices.slang b/Source/Falcor/Scene/Animation/UpdateMeshVertices.slang index bb95d3a67..daa1bdfe3 100644 --- a/Source/Falcor/Scene/Animation/UpdateMeshVertices.slang +++ b/Source/Falcor/Scene/Animation/UpdateMeshVertices.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -49,7 +49,7 @@ struct MeshVertexUpdater #endif // Output - RWStructuredBuffer sceneVertexData; + RWSplitVertexBuffer sceneVertexData; RWStructuredBuffer prevVertexData; StaticVertexData interpolateVertex(StaticVertexData v0, StaticVertexData v1, float t) diff --git a/Source/Falcor/Scene/HitInfo.slang b/Source/Falcor/Scene/HitInfo.slang index d14692cb8..ec6ccaa6d 100644 --- a/Source/Falcor/Scene/HitInfo.slang +++ b/Source/Falcor/Scene/HitInfo.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -427,7 +427,7 @@ struct HitInfo /** Return the packed hit info. \return Packed hit info. */ - PackedHitInfo getData() + PackedHitInfo pack() { return data; } @@ -486,3 +486,15 @@ struct HitInfo } } }; + +HitInfo makeInvalidHit() +{ + HitInfo hit = {}; + return hit; +} + +/// Unpacks packed hit info +HitInfo unpackHitInfo(PackedHitInfo packedHitInfo) +{ + return HitInfo(packedHitInfo); +} diff --git a/Source/Falcor/Scene/IScene.cpp b/Source/Falcor/Scene/IScene.cpp new file mode 100644 index 000000000..924318a71 --- /dev/null +++ b/Source/Falcor/Scene/IScene.cpp @@ -0,0 +1,45 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "IScene.h" +#include "Core/API/RenderContext.h" +#include "Core/Program/ShaderVar.h" +#include "Core/Program/ProgramVars.h" + +namespace Falcor +{ + +/// Convenience function when the Scene wants to do something besides just calling raytrace. +/// TODO: Remove when no longer useful +void IScene::raytrace(RenderContext* renderContext, Program* pProgram, const ref& pVars, uint3 dispatchDims) +{ + bindShaderDataForRaytracing(renderContext, pVars->getRootVar()["gScene"], pVars->getRayTypeCount()); + renderContext->raytrace(pProgram, pVars.get(), dispatchDims.x, dispatchDims.y, dispatchDims.z); +} + +} // namespace Falcor diff --git a/Source/Falcor/Scene/IScene.h b/Source/Falcor/Scene/IScene.h new file mode 100644 index 000000000..e7283eb8e --- /dev/null +++ b/Source/Falcor/Scene/IScene.h @@ -0,0 +1,199 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Core/Macros.h" +#include "Core/Object.h" +#include "Core/Program/Program.h" +#include "Core/API/Raytracing.h" + +#include + +namespace Falcor +{ + +struct AABB; +class EnvMap; +class ILightCollection; +class Camera; +class RenderContext; +class Light; +class MaterialSystem; +class Sampler; +struct ShaderVar; + +class FALCOR_API IScene : public Object +{ +public: + /** Flags indicating if and what was updated in the scene. + */ + enum class UpdateFlags + { + None = 0x0, ///< Nothing happened. + GeometryMoved = 0x1, ///< Geometry moved. + CameraMoved = 0x2, ///< The camera moved. + CameraPropertiesChanged = 0x4, ///< Some camera properties changed, excluding position. + CameraSwitched = 0x8, ///< Selected a different camera. + LightsMoved = 0x10, ///< Lights were moved. + LightIntensityChanged = 0x20, ///< Light intensity changed. + LightPropertiesChanged = 0x40, ///< Other light changes not included in LightIntensityChanged and LightsMoved. + SceneGraphChanged = 0x80, ///< Any transform in the scene graph changed. + LightCollectionChanged = 0x100, ///< Light collection changed (mesh lights). + MaterialsChanged = 0x200, ///< Materials changed. + EnvMapChanged = 0x400, ///< Environment map changed. + EnvMapPropertiesChanged = 0x800, ///< Environment map properties changed (check EnvMap::getChanges() for more specific information). + LightCountChanged = 0x1000, ///< Number of active lights changed. + RenderSettingsChanged = 0x2000, ///< Render settings changed. + GridVolumesMoved = 0x4000, ///< Grid volumes were moved. + GridVolumePropertiesChanged = 0x8000, ///< Grid volume properties changed. + GridVolumeGridsChanged = 0x10000, ///< Grid volume grids changed. + GridVolumeBoundsChanged = 0x20000, ///< Grid volume bounds changed. + CurvesMoved = 0x40000, ///< Curves moved. + CustomPrimitivesMoved = 0x80000, ///< Custom primitives moved. + GeometryChanged = 0x100000, ///< Scene geometry changed (added/removed). + DisplacementChanged = 0x200000, ///< Displacement mapping parameters changed. + SDFGridConfigChanged = 0x400000, ///< SDF grid config changed. + SDFGeometryChanged = 0x800000, ///< SDF grid geometry changed. + MeshesChanged = 0x1000000, ///< Mesh data changed (skinning or vertex animations). + SceneDefinesChanged = 0x2000000, ///< Scene defines changed. All programs that access the scene must be updated! + TypeConformancesChanged = 0x4000000, ///< Type conformances changed. All programs that access the scene must be updated! + ShaderCodeChanged = 0x8000000, ///< Shader code changed. All programs that access the scene must be updated! + EmissiveMaterialsChanged = 0x10000000, ///< Emissive materials changed. + + /// Flags indicating that programs that access the scene need to be recompiled. + /// This is needed if defines, type conformances, and/or the shader code has changed. + /// The goal is to minimize changes that require recompilation, as it can be costly. + RecompileNeeded = SceneDefinesChanged | TypeConformancesChanged | ShaderCodeChanged, + + All = -1 + }; + + enum class TypeConformancesKind + { + None = 0, + Material = (1 << 0), + Geometry = (1 << 1), + Other = (1 << 2), + All = -1 + }; + + /** Render settings determining how the scene is rendered. + This is used primarily by the path tracer renderers. + */ + struct RenderSettings + { + bool useEnvLight = true; ///< Enable lighting from environment map. + bool useAnalyticLights = true; ///< Enable lighting from analytic lights. + bool useEmissiveLights = true; ///< Enable lighting from emissive lights. + bool useGridVolumes = true; ///< Enable rendering of grid volumes. + + // DEMO21 + float diffuseAlbedoMultiplier = 1.f; ///< Fixed multiplier applied to material diffuse albedo. + + bool operator==(const RenderSettings& other) const + { + return (useEnvLight == other.useEnvLight) && + (useAnalyticLights == other.useAnalyticLights) && + (useEmissiveLights == other.useEmissiveLights) && + (useGridVolumes == other.useGridVolumes); + } + + bool operator!=(const RenderSettings& other) const { return !(*this == other); } + }; + + static_assert(std::is_trivially_copyable() , "RenderSettings needs to be trivially copyable"); + + virtual ~IScene() = default; + + virtual const ref& getDevice() const = 0; + + using UpdateFlagsSignal = sigs::Signal; + + virtual UpdateFlagsSignal::Interface getUpdateFlagsSignal() = 0; + + // All defines required by the Scene + virtual void getShaderDefines(DefineList& defines) const = 0; + // All type conformances required by the Scene + virtual void getTypeConformances(TypeConformanceList& conformances, TypeConformancesKind kind = TypeConformancesKind::All) const = 0; + // All shader modules required by the Scene + virtual void getShaderModules(ProgramDesc::ShaderModuleList& shaderModuleList) const = 0; + /// Assign all required variables into the Scene slang object, except TLAS and RayTypeCount. + /// Pass in the ShaderVar, the Scene will bind to the correct global var name. + virtual void bindShaderData(const ShaderVar& sceneVar) const = 0; + + /// On-demand creates TLAS and binds and the associated rayTypeCount to the scene. + /// If rayTypeCount is not specified (== 0), will bind any available existing TLAS (creating it for rayTypeCount == 1 if no TLAS exists). + /// Otherwise will bind TLAS for the specified rayTypeCount. Also calls bindShaderData(). + virtual void bindShaderDataForRaytracing(RenderContext* renderContext, const ShaderVar& sceneVar, uint32_t rayTypeCount = 0) = 0; + + /// TODO: Replace this with more generic thing + virtual const RenderSettings& getRenderSettings() const = 0; + + /// Return the RtPipelineFlags relevant for the scene. + virtual RtPipelineFlags getRtPipelineFlags() const = 0; + + /// Get AABB for the currently updated scene. + virtual const AABB& getSceneBounds() const = 0; + + /// True when emissive lights are both enablee and present in the scene. + /// Emissive lights are meshes with emissive material. + /// TODO: Rename to GeometricLights (all lights are by definition emissive...) + virtual bool useEmissiveLights() const = 0; + + /// True when environment map is both enabled and present in the scene. + virtual bool useEnvLight() const = 0; + + virtual bool useAnalyticLights() const = 0; + + virtual const ref& getEnvMap() const = 0; + /// TODO: Remove the `renderContext` when not needed + /// TODO: Ideally this wouldn't be non-const and build-on-demand (as Scene1 does) + virtual ref getILightCollection(RenderContext* renderContext) = 0; + + /// Returns list of active analytic lights. + /// TODO: This implementation requires lights to be tightly repacked every time activity + /// changes, which might be suboptimal for editing, but better for rendering. Needs to be investigated. + virtual const std::vector>& getActiveAnalyticLights() const = 0; + + virtual const ref& getCamera() const = 0; + + /// Used to get type conformances for materials. + virtual const MaterialSystem& getMaterialSystem() const = 0; + + /// Allows changing the default texture sampler based on the pass requirements. + /// This will be applied to all materials, old and new. + virtual void setDefaultTextureSampler(const ref& pSampler) = 0; + +public: /// Compatibility calls + /// Convenience function when the Scene wants to do something besides just calling raytrace. + /// TODO: Remove when no longer useful + virtual void raytrace(RenderContext* renderContext, Program* pProgram, const ref& pVars, uint3 dispatchDims); +}; + +FALCOR_ENUM_CLASS_OPERATORS(IScene::UpdateFlags); +FALCOR_ENUM_CLASS_OPERATORS(IScene::TypeConformancesKind); +} // namespace Falcor diff --git a/Source/Falcor/Scene/Lights/BuildTriangleList.cs.slang b/Source/Falcor/Scene/Lights/BuildTriangleList.cs.slang index 2ec505455..0c588d399 100644 --- a/Source/Falcor/Scene/Lights/BuildTriangleList.cs.slang +++ b/Source/Falcor/Scene/Lights/BuildTriangleList.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-21, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,8 +27,8 @@ **************************************************************************/ #include "Utils/Math/MathConstants.slangh" -import Scene.Scene; import Utils.Color.ColorHelpers; +import Scene.Scene; cbuffer CB { @@ -41,6 +41,7 @@ cbuffer CB RWStructuredBuffer gTriangleData; ///< Per-triangle data for emissive triangles. + /** Kernel building the emissive triangles list for all mesh lights. One dispatch per mesh light with one thread per triangle. */ diff --git a/Source/Falcor/Scene/Lights/EmissiveIntegrator.3d.slang b/Source/Falcor/Scene/Lights/EmissiveIntegrator.3d.slang index 1a4ebce07..249b84451 100644 --- a/Source/Falcor/Scene/Lights/EmissiveIntegrator.3d.slang +++ b/Source/Falcor/Scene/Lights/EmissiveIntegrator.3d.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -44,12 +44,13 @@ For such triangles, we approximate the average radiance as the average of the emission sampled at the three vertices. */ -import Scene.Scene; import Utils.Geometry.GeometryHelpers; +import Scene.Scene; // Check that defines are set. #ifndef _VIEWPORT_DIM #error _VIEWPORT_DIM is not defined +#define _VIEWPORT_DIM 1024 // to silence the hinter #endif ParameterBlock gLightCollection; diff --git a/Source/Falcor/Scene/Lights/EnvMap.cpp b/Source/Falcor/Scene/Lights/EnvMap.cpp index e67bfe2c7..c3123f133 100644 --- a/Source/Falcor/Scene/Lights/EnvMap.cpp +++ b/Source/Falcor/Scene/Lights/EnvMap.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -53,6 +53,10 @@ namespace Falcor widgets.var("Intensity", mData.intensity, 0.f, 1000000.f); widgets.var("Color tint", mData.tint, 0.f, 1.f); widgets.text("EnvMap: " + mpEnvMap->getSourcePath().string()); + + widgets.text(fmt::format("Resolution: {}x{}", mpEnvMap->getWidth(), mpEnvMap->getHeight())); + widgets.text(fmt::format("Mip levels: {}", mpEnvMap->getMipCount())); + widgets.text(fmt::format("Format: {}", to_string(mpEnvMap->getFormat()))); } void EnvMap::setRotation(float3 degreesXYZ) diff --git a/Source/Falcor/Scene/Lights/EnvMap.h b/Source/Falcor/Scene/Lights/EnvMap.h index 3837ce0e5..99f8fdaf4 100644 --- a/Source/Falcor/Scene/Lights/EnvMap.h +++ b/Source/Falcor/Scene/Lights/EnvMap.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -131,7 +131,7 @@ namespace Falcor ref mpDevice; ref mpEnvMap; ///< Loaded environment map (RGB). - ref mpEnvSampler; + ref mpEnvSampler; ///< Texture sampler for the environment map. EnvMapData mData; EnvMapData mPrevData; diff --git a/Source/Falcor/Scene/Lights/EnvMap.slang b/Source/Falcor/Scene/Lights/EnvMap.slang index b142f3a67..3c8d0a3ed 100644 --- a/Source/Falcor/Scene/Lights/EnvMap.slang +++ b/Source/Falcor/Scene/Lights/EnvMap.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,7 +33,7 @@ import Utils.Math.MathHelpers; struct EnvMap { Texture2D envMap; ///< Environment map texture. - SamplerState envSampler; ///< Environment map sampler. + SamplerState envSampler; ///< Environment map texture sampler. EnvMapData data; ///< Environment map data. diff --git a/Source/Falcor/Scene/Lights/FinalizeIntegration.cs.slang b/Source/Falcor/Scene/Lights/FinalizeIntegration.cs.slang index 01b232ff1..cf6ff4531 100644 --- a/Source/Falcor/Scene/Lights/FinalizeIntegration.cs.slang +++ b/Source/Falcor/Scene/Lights/FinalizeIntegration.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-21, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Falcor/Scene/Lights/ILightCollection.h b/Source/Falcor/Scene/Lights/ILightCollection.h new file mode 100644 index 000000000..cfd6c3028 --- /dev/null +++ b/Source/Falcor/Scene/Lights/ILightCollection.h @@ -0,0 +1,171 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "MeshLightData.slang" +#include "Core/Object.h" +#include "Utils/Math/VectorTypes.h" + +#include + +#include +#include + +namespace Falcor +{ + class Device; + class Scene; + class RenderContext; + struct ShaderVar; + + /** Class that holds a collection of mesh lights for a scene. + + Each mesh light is represented by a mesh instance with an emissive material. + + This class has utility functions for updating and pre-processing the mesh lights. + The LightCollection can be used standalone, but more commonly it will be wrapped + by an emissive light sampler. + */ + class FALCOR_API ILightCollection : public Object + { + public: + enum class UpdateFlags : uint32_t + { + None = 0u, ///< Nothing was changed. + MatrixChanged = 1u, ///< Mesh instance transform changed. + LayoutChanged = 2u, ///< MeshLightData layouts have changed. + }; + + struct UpdateStatus + { + std::vector lightsUpdateInfo; + }; + + struct MeshLightStats + { + // Stats before pre-processing (input data). + uint32_t meshLightCount = 0; ///< Number of mesh lights. + uint32_t triangleCount = 0; ///< Number of mesh light triangles (total). + uint32_t meshesTextured = 0; ///< Number of mesh lights with textured emissive. + uint32_t trianglesTextured = 0; ///< Number of mesh light triangles with textured emissive. + + // Stats after pre-processing (what's used during rendering). + uint32_t trianglesCulled = 0; ///< Number of triangles culled (due to zero radiance and/or area). + uint32_t trianglesActive = 0; ///< Number of active (non-culled) triangles. + uint32_t trianglesActiveUniform = 0; ///< Number of active triangles with const radiance. + uint32_t trianglesActiveTextured = 0; ///< Number of active triangles with textured radiance. + }; + + /** Represents one mesh light triangle vertex. + */ + struct MeshLightVertex + { + float3 pos; ///< World-space position. + float2 uv; ///< Texture coordinates in emissive texture (if textured). + }; + + /** Represents one mesh light triangle. + */ + struct MeshLightTriangle + { + // TODO: Perf of indexed vs non-indexed on GPU. We avoid level of indirection, but have more bandwidth non-indexed. + MeshLightVertex vtx[3]; ///< Vertices. These are non-indexed for now. + uint32_t lightIdx = MeshLightData::kInvalidIndex; ///< Per-triangle index into mesh lights array. + + // Pre-computed quantities. + float3 normal = float3(0); ///< Triangle's face normal in world space. + float3 averageRadiance = float3(0); ///< Average radiance emitted over triangle. For textured emissive the radiance varies over the surface. + float flux = 0.f; ///< Pre-integrated flux emitted by the triangle. Note that emitters are single-sided. + float area = 0.f; ///< Triangle area in world space units. + + /** Returns the center of the triangle in world space. + */ + float3 getCenter() const + { + return (vtx[0].pos + vtx[1].pos + vtx[2].pos) / 3.0f; + } + }; + + using UpdateFlagsSignal = sigs::Signal; + + virtual ~ILightCollection() = default; + + virtual const ref& getDevice() const = 0; + + /** Updates the light collection to the current state of the scene. + \param[in] pRenderContext The render context. + \param[out] pUpdateStatus Stores information about which type of updates were performed for each mesh light. This is an optional output parameter. + \return True if the lighting in the scene has changed since the last frame. + */ + virtual bool update(RenderContext* renderContext, UpdateStatus* pUpdateStatus = nullptr) = 0; + + /** Bind the light collection data to a given shader var + \param[in] var The shader variable to set the data into. + */ + virtual void bindShaderData(const ShaderVar& var) const = 0; + + /** Returns the total number of active (non-culled) triangle lights. + */ + uint32_t getActiveLightCount(RenderContext* pRenderContext) const { return getStats(pRenderContext).trianglesActive; } + + /** Returns the total number of triangle lights (may include culled triangles). + */ + virtual uint32_t getTotalLightCount() const = 0; + + /** Returns stats. + */ + virtual const MeshLightStats& getStats(RenderContext* pRenderContext) const = 0; + + /** Returns a CPU buffer with all emissive triangles in world space. + Note that update() must have been called before for the data to be valid. + Call prepareSyncCPUData() ahead of time to avoid stalling the GPU. + */ + virtual const std::vector& getMeshLightTriangles(RenderContext* pRenderContext) const = 0; + + /** Returns a CPU buffer with all mesh lights. + Note that update() must have been called before for the data to be valid. + */ + virtual const std::vector& getMeshLights() const = 0; + + /** Prepare for syncing the CPU data. + If the mesh light triangles will be accessed with getMeshLightTriangles() + performance can be improved by calling this function ahead of time. + This function schedules the copies so that it can be read back without delay later. + */ + virtual void prepareSyncCPUData(RenderContext* pRenderContext) const = 0; + + /** Get the total GPU memory usage in bytes. + */ + virtual uint64_t getMemoryUsageInBytes() const = 0; + + /** Gets a signal interface that is signaled when the LightCollection is updated. + */ + virtual UpdateFlagsSignal::Interface getUpdateFlagsSignal() = 0; + }; + + FALCOR_ENUM_CLASS_OPERATORS(ILightCollection::UpdateFlags); +} diff --git a/Source/Falcor/Scene/Lights/LightCollection.cpp b/Source/Falcor/Scene/Lights/LightCollection.cpp index aac81b3a4..72f77ea8a 100644 --- a/Source/Falcor/Scene/Lights/LightCollection.cpp +++ b/Source/Falcor/Scene/Lights/LightCollection.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -107,6 +107,7 @@ namespace Falcor if (!updatedLights.empty()) { updateTrianglePositions(pRenderContext, *mpScene, updatedLights); + mUpdateFlagsSignal(UpdateFlags::MatrixChanged); return true; } @@ -142,7 +143,7 @@ namespace Falcor // Set state. mIntegrator.pState->setProgram(mIntegrator.pProgram); - mIntegrator.pState->setVao(Vao::create(Vao::Topology::TriangleList)); + mIntegrator.pState->setVao(Vao::create(Vao::Topology::PointList)); // Set viewport. Note we don't bind any render targets so the size just determines the dispatch limits. const uint32_t vpDim = 16384; // 16K x 16K @@ -255,6 +256,8 @@ namespace Falcor timeReport.measure("LightCollection::build finalize"); timeReport.printToLog(); } + + mUpdateFlagsSignal(UpdateFlags::LayoutChanged); } void LightCollection::prepareTriangleData(RenderContext* pRenderContext, const Scene& scene) @@ -343,7 +346,7 @@ namespace Falcor // Execute. mIntegrator.pProgram->addDefine("INTEGRATOR_PASS", "1"); - pRenderContext->draw(mIntegrator.pState.get(), mIntegrator.pVars.get(), mTriangleCount * 3, 0); + pRenderContext->draw(mIntegrator.pState.get(), mIntegrator.pVars.get(), mTriangleCount, 0); } // 2nd pass: Rasterize emissive triangles in texture space to sum up their texels. @@ -366,7 +369,7 @@ namespace Falcor // Execute. mIntegrator.pProgram->addDefine("INTEGRATOR_PASS", "2"); - pRenderContext->draw(mIntegrator.pState.get(), mIntegrator.pVars.get(), mTriangleCount * 3, 0); + pRenderContext->draw(mIntegrator.pState.get(), mIntegrator.pVars.get(), mTriangleCount, 0); } // 3rd pass: Finalize the per-triangle flux values. diff --git a/Source/Falcor/Scene/Lights/LightCollection.h b/Source/Falcor/Scene/Lights/LightCollection.h index 6f558f940..2cc9471ce 100644 --- a/Source/Falcor/Scene/Lights/LightCollection.h +++ b/Source/Falcor/Scene/Lights/LightCollection.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,6 +27,7 @@ **************************************************************************/ #pragma once #include "MeshLightData.slang" +#include "ILightCollection.h" #include "Core/Macros.h" #include "Core/Object.h" #include "Core/API/Buffer.h" @@ -54,65 +55,10 @@ namespace Falcor The LightCollection can be used standalone, but more commonly it will be wrapped by an emissive light sampler. */ - class FALCOR_API LightCollection : public Object + class FALCOR_API LightCollection : public ILightCollection { FALCOR_OBJECT(LightCollection) public: - enum class UpdateFlags : uint32_t - { - None = 0u, ///< Nothing was changed. - MatrixChanged = 1u, ///< Mesh instance transform changed. - }; - - struct UpdateStatus - { - std::vector lightsUpdateInfo; - }; - - struct MeshLightStats - { - // Stats before pre-processing (input data). - uint32_t meshLightCount = 0; ///< Number of mesh lights. - uint32_t triangleCount = 0; ///< Number of mesh light triangles (total). - uint32_t meshesTextured = 0; ///< Number of mesh lights with textured emissive. - uint32_t trianglesTextured = 0; ///< Number of mesh light triangles with textured emissive. - - // Stats after pre-processing (what's used during rendering). - uint32_t trianglesCulled = 0; ///< Number of triangles culled (due to zero radiance and/or area). - uint32_t trianglesActive = 0; ///< Number of active (non-culled) triangles. - uint32_t trianglesActiveUniform = 0; ///< Number of active triangles with const radiance. - uint32_t trianglesActiveTextured = 0; ///< Number of active triangles with textured radiance. - }; - - /** Represents one mesh light triangle vertex. - */ - struct MeshLightVertex - { - float3 pos; ///< World-space position. - float2 uv; ///< Texture coordinates in emissive texture (if textured). - }; - - /** Represents one mesh light triangle. - */ - struct MeshLightTriangle - { - // TODO: Perf of indexed vs non-indexed on GPU. We avoid level of indirection, but have more bandwidth non-indexed. - MeshLightVertex vtx[3]; ///< Vertices. These are non-indexed for now. - uint32_t lightIdx = MeshLightData::kInvalidIndex; ///< Per-triangle index into mesh lights array. - - // Pre-computed quantities. - float3 normal = float3(0); ///< Triangle's face normal in world space. - float3 averageRadiance = float3(0); ///< Average radiance emitted over triangle. For textured emissive the radiance varies over the surface. - float flux = 0.f; ///< Pre-integrated flux emitted by the triangle. Note that emitters are single-sided. - float area = 0.f; ///< Triangle area in world space units. - - /** Returns the center of the triangle in world space. - */ - float3 getCenter() const - { - return (vtx[0].pos + vtx[1].pos + vtx[2].pos) / 3.0f; - } - }; /** Creates a light collection for the given scene. Note that update() must be called before the collection is ready to use. @@ -129,51 +75,49 @@ namespace Falcor LightCollection(ref pDevice, RenderContext* pRenderContext, Scene* pScene); ~LightCollection() = default; + const ref& getDevice() const override { return mpDevice; } + /** Updates the light collection to the current state of the scene. \param[in] pRenderContext The render context. \param[out] pUpdateStatus Stores information about which type of updates were performed for each mesh light. This is an optional output parameter. \return True if the lighting in the scene has changed since the last frame. */ - bool update(RenderContext* pRenderContext, UpdateStatus* pUpdateStatus = nullptr); + bool update(RenderContext* pRenderContext, UpdateStatus* pUpdateStatus = nullptr) override; /** Bind the light collection data to a given shader var \param[in] var The shader variable to set the data into. */ - void bindShaderData(const ShaderVar& var) const; - - /** Returns the total number of active (non-culled) triangle lights. - */ - uint32_t getActiveLightCount(RenderContext* pRenderContext) const { return getStats(pRenderContext).trianglesActive; } + void bindShaderData(const ShaderVar& var) const override; /** Returns the total number of triangle lights (may include culled triangles). */ - uint32_t getTotalLightCount() const { return mTriangleCount; } + uint32_t getTotalLightCount() const override { return mTriangleCount; } /** Returns stats. */ - const MeshLightStats& getStats(RenderContext* pRenderContext) const { computeStats(pRenderContext); return mMeshLightStats; } + const MeshLightStats& getStats(RenderContext* pRenderContext) const override { computeStats(pRenderContext); return mMeshLightStats; } /** Returns a CPU buffer with all emissive triangles in world space. Note that update() must have been called before for the data to be valid. Call prepareSyncCPUData() ahead of time to avoid stalling the GPU. */ - const std::vector& getMeshLightTriangles(RenderContext* pRenderContext) const { syncCPUData(pRenderContext); return mMeshLightTriangles; } + const std::vector& getMeshLightTriangles(RenderContext* pRenderContext) const override { syncCPUData(pRenderContext); return mMeshLightTriangles; } /** Returns a CPU buffer with all mesh lights. Note that update() must have been called before for the data to be valid. */ - const std::vector& getMeshLights() const { return mMeshLights; } + const std::vector& getMeshLights() const override { return mMeshLights; } /** Prepare for syncing the CPU data. If the mesh light triangles will be accessed with getMeshLightTriangles() performance can be improved by calling this function ahead of time. This function schedules the copies so that it can be read back without delay later. */ - void prepareSyncCPUData(RenderContext* pRenderContext) const { copyDataToStagingBuffer(pRenderContext); } + void prepareSyncCPUData(RenderContext* pRenderContext) const override { copyDataToStagingBuffer(pRenderContext); } /** Get the total GPU memory usage in bytes. */ - uint64_t getMemoryUsageInBytes() const; + uint64_t getMemoryUsageInBytes() const override; // Internal update flags. This only public for FALCOR_ENUM_CLASS_OPERATORS() to work. enum class CPUOutOfDateFlags : uint32_t @@ -185,6 +129,10 @@ namespace Falcor All = TriangleData | FluxData }; + /** Gets a signal interface that is signaled when the LightCollection is updated. + */ + UpdateFlagsSignal::Interface getUpdateFlagsSignal() override { return mUpdateFlagsSignal.getInterface(); } + protected: void initIntegrator(RenderContext* pRenderContext, const Scene& scene); void setupMeshLights(const Scene& scene); @@ -243,8 +191,9 @@ namespace Falcor mutable CPUOutOfDateFlags mCPUInvalidData = CPUOutOfDateFlags::None; ///< Flags indicating which CPU data is valid. mutable bool mStagingBufferValid = true; ///< Flag to indicate if the contents of the staging buffer is up-to-date. + + UpdateFlagsSignal mUpdateFlagsSignal; }; FALCOR_ENUM_CLASS_OPERATORS(LightCollection::CPUOutOfDateFlags); - FALCOR_ENUM_CLASS_OPERATORS(LightCollection::UpdateFlags); } diff --git a/Source/Falcor/Scene/Lights/LightCollection.slang b/Source/Falcor/Scene/Lights/LightCollection.slang index 8dea1527d..2cdfebaa6 100644 --- a/Source/Falcor/Scene/Lights/LightCollection.slang +++ b/Source/Falcor/Scene/Lights/LightCollection.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -53,6 +53,7 @@ struct LightCollection StructuredBuffer meshData; ///< Per-mesh data for emissive meshes. StructuredBuffer perMeshInstanceOffset; ///< Per-mesh instance offset into emissive triangles array, or kInvalidIndex if mesh has no emissive triangles. + static const uint kInvalidIndex = 0xffffffff; /** Returns the total number of emissive triangles. diff --git a/Source/Falcor/Scene/Lights/LightProfile.cpp b/Source/Falcor/Scene/Lights/LightProfile.cpp index 14afa155a..26a64dab4 100644 --- a/Source/Falcor/Scene/Lights/LightProfile.cpp +++ b/Source/Falcor/Scene/Lights/LightProfile.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -255,7 +255,7 @@ namespace Falcor var["sampler"] = mpSampler; } - void LightProfile::renderUI(Gui::Widgets& widget) + void LightProfile::renderUI(Gui::Widgets& widget) const { widget.text("Light Profile: " + mName); if (mpTexture) diff --git a/Source/Falcor/Scene/Lights/LightProfile.h b/Source/Falcor/Scene/Lights/LightProfile.h index 63895fcc7..6af6a5454 100644 --- a/Source/Falcor/Scene/Lights/LightProfile.h +++ b/Source/Falcor/Scene/Lights/LightProfile.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -55,7 +55,7 @@ namespace Falcor /** Render the UI. */ - void renderUI(Gui::Widgets& widget); + void renderUI(Gui::Widgets& widget) const; private: LightProfile(ref pDevice, const std::string& name, const std::vector& rawData); diff --git a/Source/Falcor/Scene/Lights/MeshLightData.slang b/Source/Falcor/Scene/Lights/MeshLightData.slang index c883746e6..555ac8ea9 100644 --- a/Source/Falcor/Scene/Lights/MeshLightData.slang +++ b/Source/Falcor/Scene/Lights/MeshLightData.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -46,4 +46,5 @@ struct MeshLightData static constexpr uint kInvalidIndex = 0xffffffff; }; + END_NAMESPACE_FALCOR diff --git a/Source/Falcor/Scene/Lights/UpdateTriangleVertices.cs.slang b/Source/Falcor/Scene/Lights/UpdateTriangleVertices.cs.slang index 7764c0899..85a781b38 100644 --- a/Source/Falcor/Scene/Lights/UpdateTriangleVertices.cs.slang +++ b/Source/Falcor/Scene/Lights/UpdateTriangleVertices.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-21, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -52,14 +52,14 @@ void updateTriangleVertices(uint3 DTid : SV_DispatchThreadID) uint lightIdx = gTriangleData[triIdx].lightIdx; const MeshLightData meshData = gMeshData[lightIdx]; - GeometryInstanceID instanceID = { meshData.instanceID }; uint triangleIndex = triIdx - meshData.triangleOffset; // Local triangle index in the mesh // Load triangle data. EmissiveTriangle tri = gTriangleData[triIdx].unpack(); - // Update triangle data. + GeometryInstanceID instanceID = { meshData.instanceID }; gScene.getVertexPositionsW(instanceID, triangleIndex, tri.posW); tri.normal = gScene.computeFaceNormalAndAreaW(instanceID, tri.posW, tri.area); + gTriangleData[triIdx].pack(tri); } diff --git a/Source/Falcor/Scene/Material/Material.cpp b/Source/Falcor/Scene/Material/Material.cpp index 4a28c7b31..2ef909191 100644 --- a/Source/Falcor/Scene/Material/Material.cpp +++ b/Source/Falcor/Scene/Material/Material.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -404,6 +404,7 @@ namespace Falcor material.def("loadTexture", loadTexture, "slot"_a, "path"_a, "useSrgb"_a = true); // PYTHONDEPRECATED material.def("load_texture", loadTexture, "slot"_a, "path"_a, "use_srgb"_a = true); // PYTHONDEPRECATED material.def("clearTexture", &Material::clearTexture, "slot"_a); + material.def("setRoughnessMollification", &Material::setRoughnessMollification, "value"_a); auto getMaterialParamLayoutDict = [&](MaterialType type) -> pybind11::dict { MaterialParamLayout layout = getMaterialParamLayout(type); diff --git a/Source/Falcor/Scene/Material/Material.h b/Source/Falcor/Scene/Material/Material.h index 719d8ed20..f30fc1971 100644 --- a/Source/Falcor/Scene/Material/Material.h +++ b/Source/Falcor/Scene/Material/Material.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -343,6 +343,12 @@ namespace Falcor virtual SerializedMaterialParams serializeParams() const { FALCOR_THROW("Material does not support serializing parameters."); } virtual void deserializeParams(const SerializedMaterialParams& params) { FALCOR_THROW("Material does not support deserializing parameters."); } + /** Set roughness mollification factor. + * Mollification smooths over highly glossy lobes in the distribution. + * The factor range from 0 (no mollification) to 1 (maximum mollification). + */ + virtual void setRoughnessMollification( float factor ) {}; + protected: Material(ref pDevice, const std::string& name, MaterialType type); diff --git a/Source/Falcor/Scene/Material/MaterialFactory.slang b/Source/Falcor/Scene/Material/MaterialFactory.slang index 9abc4791f..4fa115b74 100644 --- a/Source/Falcor/Scene/Material/MaterialFactory.slang +++ b/Source/Falcor/Scene/Material/MaterialFactory.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -73,10 +73,9 @@ extension MaterialSystem \param[in] v Interpolated vertex data. \param[in] materialID Material ID at the hit. \param[in] viewDir View direction. - \param[in] lod Method for computing texture level-of-detail, must implement the `ITextureSampler` interface. \return Shading data struct. */ - ShadingData prepareShadingData(const VertexData v, const uint materialID, const float3 viewDir, L lod) + ShadingData prepareShadingData(const VertexData v, const uint materialID, const float3 viewDir) { ShadingData sd = {}; diff --git a/Source/Falcor/Scene/Material/MaterialParamLayout.h b/Source/Falcor/Scene/Material/MaterialParamLayout.h index 33cd0d692..03111515c 100644 --- a/Source/Falcor/Scene/Material/MaterialParamLayout.h +++ b/Source/Falcor/Scene/Material/MaterialParamLayout.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,6 +27,7 @@ **************************************************************************/ #pragma once #include +#include namespace Falcor { diff --git a/Source/Falcor/Scene/Material/MaterialSystem.cpp b/Source/Falcor/Scene/Material/MaterialSystem.cpp index d6e8a58c2..4e0d1f24a 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.cpp +++ b/Source/Falcor/Scene/Material/MaterialSystem.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -31,6 +31,7 @@ #include "Utils/Logger.h" #include "Utils/StringUtils.h" #include "MaterialTypeRegistry.h" +#include "Scene/Lights/LightProfile.h" #include namespace Falcor @@ -43,6 +44,7 @@ namespace Falcor const std::string kMaterialTexturesName = "materialTextures"; const std::string kMaterialBuffersName = "materialBuffers"; const std::string kMaterialTextures3DName = "materialTextures3D"; + const std::string kLightProfile = "lightProfile"; const size_t kMaxSamplerCount = 1ull << MaterialHeader::kSamplerIDBits; const size_t kMaxTextureCount = 1ull << TextureHandle::kTextureIDBits; @@ -77,6 +79,18 @@ namespace Falcor void MaterialSystem::renderUI(Gui::Widgets& widget) { + widget.text("Material types:"); + auto materialTypes = getMaterialTypes(); + for (auto materialType : materialTypes) + { + uint32_t count = getMaterialCountByType(materialType); + std::string name = to_string(materialType); + widget.text(fmt::format(" {}: {}", name, count)); + } + + widget.text(""); + widget.text("Materials:"); + auto showMaterial = [&](uint32_t materialID, const std::string& label) { const auto& pMaterial = mMaterials[materialID]; if (auto materialGroup = widget.group(label)) @@ -433,6 +447,13 @@ namespace Falcor Material::UpdateFlags MaterialSystem::update(bool forceUpdate) { + // Some materials only determine the required number of textures during their update. + // If the number of required textures is greated than getMaxTextureCount() reported, + // we could run out of texture slots. This is why metadata is reupdated again, + // after the material's update() has been called. (A typical example is UDIM, the number + // of actually used texture slots depends on the number of files on drive) + bool reupdateMetadata = false; + // If materials were added/removed since last update, we update all metadata // and trigger re-creation of the parameter block and update of all materials. if (forceUpdate || mMaterialsChanged) @@ -443,6 +464,7 @@ namespace Falcor mpMaterialsBlock = nullptr; mMaterialsChanged = false; forceUpdate = true; + reupdateMetadata = true; } // Update all materials. @@ -478,6 +500,18 @@ namespace Falcor updateMaterial(materialID); } + if (reupdateMetadata) + { + updateMetadata(); + reupdateMetadata = false; + } + + if (mpLightProfile && !mLightProfileBaked) + { + mpLightProfile->bake(mpDevice->getRenderContext()); + mLightProfileBaked = true; + } + // Include updates recorded since last update. // After this point no more material changes are expected. updateFlags |= mMaterialUpdates; @@ -639,6 +673,13 @@ namespace Falcor return s; } + void MaterialSystem::loadLightProfile(const std::filesystem::path& absoluteFilename, bool normalize) + { + FALCOR_ASSERT(absoluteFilename.is_absolute()); + mpLightProfile = LightProfile::createFromIesProfile(mpDevice, absoluteFilename, normalize); + mLightProfileBaked = false; + } + void MaterialSystem::getDefines(DefineList& defines) const { FALCOR_CHECK(!mMaterialsChanged, "Materials have changed. Call update() first."); @@ -653,6 +694,7 @@ namespace Falcor defines.add("MATERIAL_SYSTEM_TEXTURE_3D_DESC_COUNT", std::to_string(mTexture3DDescCount)); defines.add("MATERIAL_SYSTEM_UDIM_INDIRECTION_ENABLED", mpTextureManager->getUdimIndirectionCount() > 0 ? "1" : "0"); defines.add("MATERIAL_SYSTEM_HAS_SPEC_GLOSS_MATERIALS", mHasSpecGlossStandardMaterial ? "1" : "0"); + defines.add("MATERIAL_SYSTEM_USE_LIGHT_PROFILE", mpLightProfile != nullptr ? "1" : "0"); defines.add("FALCOR_MATERIAL_INSTANCE_SIZE", std::to_string(materialInstanceByteSize)); // Add defines specified by the materials. @@ -749,6 +791,11 @@ namespace Falcor // Bind resources to parameter block. blockVar[kMaterialDataName] = !mMaterials.empty() ? mpMaterialDataBuffer : nullptr; blockVar["materialCount"] = getMaterialCount(); + if (mpLightProfile) + { + FALCOR_ASSERT(mLightProfileBaked); + mpLightProfile->bindShaderData(blockVar[kLightProfile]); + } } void MaterialSystem::uploadMaterial(const uint32_t materialID) diff --git a/Source/Falcor/Scene/Material/MaterialSystem.h b/Source/Falcor/Scene/Material/MaterialSystem.h index af2955382..852e07689 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.h +++ b/Source/Falcor/Scene/Material/MaterialSystem.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,6 +42,8 @@ namespace Falcor { + class LightProfile; + /** This class represents a material system. It holds a collection of materials and their associated resources. @@ -251,6 +253,9 @@ namespace Falcor TextureManager& getTextureManager() { return *mpTextureManager; } + void loadLightProfile(const std::filesystem::path& absoluteFilename, bool normalize); + + const LightProfile* getLightProfile() const { return mpLightProfile.get(); } private: void updateMetadata(); void updateUI(); @@ -264,6 +269,8 @@ namespace Falcor std::unique_ptr mpTextureManager; ///< Texture manager holding all material textures. ProgramDesc::ShaderModuleList mShaderModules; ///< Shader modules for all materials in use. std::map mTypeConformances; ///< Type conformances for each material type in use. + ref mpLightProfile; ///< Global light profile. + bool mLightProfileBaked = true; // Metadata diff --git a/Source/Falcor/Scene/Material/MaterialSystem.slang b/Source/Falcor/Scene/Material/MaterialSystem.slang index 28d7f32b5..59cc62c79 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.slang +++ b/Source/Falcor/Scene/Material/MaterialSystem.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -28,6 +28,7 @@ import Scene.Material.TextureHandle; import Scene.Material.TextureSampler; import Scene.Material.AlphaTest; +import Scene.Lights.LightProfile; import Rendering.Volumes.PhaseFunction; import Utils.SlangUtils; __exported import Scene.SceneTypes; @@ -57,6 +58,11 @@ struct MaterialSystem Texture3D materialTextures3D[ArrayMax<1, MATERIAL_SYSTEM_TEXTURE_3D_DESC_COUNT>.value]; + /// True when any emissive material is using IES + static bool kUseLightProfile = MATERIAL_SYSTEM_USE_LIGHT_PROFILE; + /// IES light profile to use with materials `hasLightProfile(id)` == true + LightProfile lightProfile; + /** * When UDIMs are used, this array contains indirection from -1001 to materialTextures. @@ -387,6 +393,29 @@ struct MaterialSystem ExplicitLodTextureSampler explicitLOD = { lod }; return alphaTest(v, materialID, explicitLOD); } + + bool lightProfilesEnabled() + { + return kUseLightProfile; + } + + /// Returns true when the material uses LightProfile + bool hasLightProfile(const uint materialID) + { + if (!lightProfilesEnabled()) + return false; + return getMaterialHeader(materialID).isLightProfileEnabled(); + } + + /// Evaluates the light profile. + /// Assumes that `hasLightProfile` has been checked. + /// TODO: Extend to take const uint materialID once materials can have different profiles + float evalLightProfile(const float cosTheta) + { + if (!lightProfilesEnabled()) + return 1.f; + return lightProfile.eval(cosTheta); + } }; #ifdef MATERIAL_SYSTEM_PARAMETER_BLOCK diff --git a/Source/Falcor/Scene/MeshIO.cs.slang b/Source/Falcor/Scene/MeshIO.cs.slang index 4d451790a..347b6064f 100644 --- a/Source/Falcor/Scene/MeshIO.cs.slang +++ b/Source/Falcor/Scene/MeshIO.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -70,7 +70,7 @@ struct MeshUpdater StructuredBuffer texcrds; // Output - RWStructuredBuffer vertexData; + RWSplitVertexBuffer vertexData; void setMeshVertexData(uint vertexId) { diff --git a/Source/Falcor/Scene/Raster.slang b/Source/Falcor/Scene/Raster.slang index ca3ef9fd0..2da15d3d0 100644 --- a/Source/Falcor/Scene/Raster.slang +++ b/Source/Falcor/Scene/Raster.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -124,14 +124,13 @@ VertexData prepareVertexData(VSOut vsOut, float3 faceNormalW) \param[in] vsOut Interpolated vertex attributes. \param[in] triangleIndex Triangle index. \param[in] viewDir View direction. Points from the shading point towards the viewer. - \param[in] lod Method for computing texture level-of-detail, must implement the `ITextureSampler` interface. \return Shading data struct. */ -ShadingData prepareShadingData(VSOut vsOut, uint triangleIndex, float3 viewDir, L lod) +ShadingData prepareShadingData(VSOut vsOut, uint triangleIndex, float3 viewDir) { float3 faceNormal = gScene.getFaceNormalW(vsOut.instanceID, triangleIndex); VertexData v = prepareVertexData(vsOut, faceNormal); - return gScene.materials.prepareShadingData(v, vsOut.materialID, viewDir, lod); + return gScene.materials.prepareShadingData(v, vsOut.materialID, viewDir); } /** Helper function to evaluate alpha testing based on VSOut. diff --git a/Source/Falcor/Scene/Raytracing.slang b/Source/Falcor/Scene/Raytracing.slang index 25d23da22..bab253740 100644 --- a/Source/Falcor/Scene/Raytracing.slang +++ b/Source/Falcor/Scene/Raytracing.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-21, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -111,3 +111,5 @@ float3 getPrevPosW(GeometryInstanceID instanceID, uint triangleIndex, BuiltInTri float3 barycentrics = float3(1.0 - attribs.barycentrics.x - attribs.barycentrics.y, attribs.barycentrics.x, attribs.barycentrics.y); return gScene.getPrevPosW(instanceID, triangleIndex, barycentrics); } + +uint getRayTypeCount() { return rayTypeCount; } diff --git a/Source/Falcor/Scene/RaytracingInline.slang b/Source/Falcor/Scene/RaytracingInline.slang index b13180d13..5d5611959 100644 --- a/Source/Falcor/Scene/RaytracingInline.slang +++ b/Source/Falcor/Scene/RaytracingInline.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -28,7 +28,7 @@ /** Utilities for inline ray tracing using DXR 1.1. - Import this module in your shader and call Scene::setRaytracingShaderData() + Import this module in your shader and call Scene::bindShaderDataForRaytracing() on the host to bind the necessary resources. */ #include "Scene/SceneDefines.slangh" @@ -38,6 +38,7 @@ import Scene.SDFs.SDFGridHitData; __exported import Scene.Shading; __exported import Scene.SceneRayQueryInterface; + /** Return the geometry type for a committed hit. */ GeometryType getCommittedGeometryType(RayQuery rayQuery) @@ -82,7 +83,7 @@ TriangleHit getCandidateTriangleHit(RayQuery rayQuery) // TODO: Pass UseAlphaTest as a template argument. -bool traceSceneRayImpl(const bool useAlphaTest, inout RayQuery rayQuery, const Ray ray, out HitInfo hit, out float hitT, uint rayFlags, uint instanceInclusionMask) +HitInfo traceSceneRayImpl(const bool useAlphaTest, inout RayQuery rayQuery, const Ray ray, out float hitT, uint rayFlags, uint instanceInclusionMask) { rayQuery.TraceRayInline(gScene.rtAccel, rayFlags, instanceInclusionMask, ray.toRayDesc()); @@ -160,14 +161,14 @@ bool traceSceneRayImpl(const bool useAlphaTest, inout RayQuery< #endif } - hit = {}; + HitInfo hit = makeInvalidHit(); hitT = 0.f; if (rayQuery.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { hit = HitInfo(getCommittedTriangleHit(rayQuery)); hitT = rayQuery.CommittedRayT(); - return true; + return hit; } #if SCENE_HAS_PROCEDURAL_GEOMETRY() @@ -188,7 +189,7 @@ bool traceSceneRayImpl(const bool useAlphaTest, inout RayQuery< displacedTriangleHit.displacement = displacedTriangleMeshCommittedAttribs.displacement; hit = HitInfo(displacedTriangleHit); hitT = rayQuery.CommittedRayT(); - return true; + return hit; } #endif #if SCENE_HAS_GEOMETRY_TYPE(GEOMETRY_TYPE_CURVE) @@ -200,7 +201,7 @@ bool traceSceneRayImpl(const bool useAlphaTest, inout RayQuery< curveHit.barycentrics = curveCommittedAttribs.barycentrics; hit = HitInfo(curveHit); hitT = rayQuery.CommittedRayT(); - return true; + return hit; } #endif @@ -211,20 +212,20 @@ bool traceSceneRayImpl(const bool useAlphaTest, inout RayQuery< sdfGridHit.hitData = sdfGridCommittedHitData; hit = HitInfo(sdfGridHit); hitT = rayQuery.CommittedRayT(); - return true; + return hit; #endif default: // Should not happen. - return false; + return hit; } } #endif - return false; + return hit; } [__NoSideEffect] // Required for the performance of reverse-mode auto-diff. -bool traceSceneRay(const Ray ray, out HitInfo hit, out float hitT, uint rayFlags, uint instanceInclusionMask) +HitInfo traceSceneRay(const Ray ray, out float hitT, uint rayFlags, uint instanceInclusionMask) { // TODO: Use a constant expression to derive static ray flags. @@ -235,7 +236,7 @@ bool traceSceneRay(const Ray ray, out HitInfo hit, out f #else RayQuery rayQuery; #endif - return traceSceneRayImpl(true, rayQuery, ray, hit, hitT, rayFlags, instanceInclusionMask); + return traceSceneRayImpl(true, rayQuery, ray, hitT, rayFlags, instanceInclusionMask); } else { @@ -244,7 +245,7 @@ bool traceSceneRay(const Ray ray, out HitInfo hit, out f #else RayQuery rayQuery; #endif - return traceSceneRayImpl(false, rayQuery, ray, hit, hitT, rayFlags, instanceInclusionMask); + return traceSceneRayImpl(false, rayQuery, ray, hitT, rayFlags, instanceInclusionMask); } } @@ -349,12 +350,12 @@ bool traceSceneVisibilityRay(const Ray ray, uint rayFlag */ struct SceneRayQuery : ISceneRayQuery { - bool traceRay(const Ray ray, out HitInfo hit, out float hitT, uint rayFlags, uint instanceInclusionMask) + HitInfo traceRay(const Ray ray, out float hitT, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff) { - return traceSceneRay(ray, hit, hitT, rayFlags, instanceInclusionMask); + return traceSceneRay(ray, hitT, rayFlags, instanceInclusionMask); } - bool traceVisibilityRay(const Ray ray, uint rayFlags, uint instanceInclusionMask) + bool traceVisibilityRay(const Ray ray, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff) { return traceSceneVisibilityRay(ray, rayFlags, instanceInclusionMask); } diff --git a/Source/Falcor/Scene/SDFs/SDF3DPrimitive.slang b/Source/Falcor/Scene/SDFs/SDF3DPrimitive.slang index 1b080fa3b..c851bf174 100644 --- a/Source/Falcor/Scene/SDFs/SDF3DPrimitive.slang +++ b/Source/Falcor/Scene/SDFs/SDF3DPrimitive.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -57,9 +57,9 @@ extension SDF3DPrimitive static float evalShape(float3 p, SDF3DShapeType shapeType, float3 shapeData, float blobbing, float3 translation, float3x3 invRotationScale) { - // OLD CODE: invRotationScale was tranpose w.r.t. CPU: p = mul(invRotationScale, p - translation); + // OLD CODE: invRotationScale was transpose w.r.t. CPU: p = mul(invRotationScale, p - translation); // The old code did M.v, where M was actually transposed w.r.t. what was on the CPU because of the col-major CPU and row-major GPU. - // Matrices are now row-major on both, meaning there is no more any hidden tranpose. + // Matrices are now row-major on both, meaning there is no more any hidden transpose. // However, to keep the exact same semantic as before, we explicitly transpose the invRotationScale here. // We choose to transpose rather than do v.M to make the weirdness even more obvious. // tdavidovic: I think this is a bug and the transpose shouldn't be there, but don't have a test case with a ground truth to verify @@ -82,9 +82,9 @@ extension SDF3DPrimitive static float2 evalIntervalShape(float3 pCenter, float3 pHalfExtent, SDF3DShapeType shapeType, float3 shapeData, float blobbing, float3 translation, float3x3 invRotationScale) { - // OLD CODE: invRotationScale was tranpose w.r.t. CPU: pCenter = mul(invRotationScale, pCenter - translation); + // OLD CODE: invRotationScale was transpose w.r.t. CPU: pCenter = mul(invRotationScale, pCenter - translation); // The old code did M.v, where M was actually transposed w.r.t. what was on the CPU because of the col-major CPU and row-major GPU. - // Matrices are now row-major on both, meaning there is no more any hidden tranpose. + // Matrices are now row-major on both, meaning there is no more any hidden transpose. // However, to keep the exact same semantic as before, we explicitly transpose the invRotationScale here. // We choose to transpose rather than do v.M to make the weirdness even more obvious. // tdavidovic: I think this is a bug and the transpose shouldn't be there, but don't have a test case with a ground truth to verify diff --git a/Source/Falcor/Scene/SDFs/SDFGrid.cpp b/Source/Falcor/Scene/SDFs/SDFGrid.cpp index 02cea748c..2d7561a7d 100644 --- a/Source/Falcor/Scene/SDFs/SDFGrid.cpp +++ b/Source/Falcor/Scene/SDFs/SDFGrid.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -37,6 +37,8 @@ #include "Utils/Logger.h" #include "Utils/Math/Common.h" #include "Utils/Math/Matrix.h" +#include "Utils/Math/MatrixJson.h" +#include "Utils/Math/VectorJson.h" #include "Utils/Scripting/ScriptBindings.h" #include "GlobalState.h" #include @@ -45,37 +47,6 @@ using json = nlohmann::json; -namespace Falcor::math -{ - void to_json(json& j, const float3& v) - { - j = { v.x, v.y, v.z }; - } - - void from_json(const json& j, float3& v) - { - j[0].get_to(v.x); - j[1].get_to(v.y); - j[2].get_to(v.z); - } - - void to_json(json& j, const float3x3& m) - { - for (uint32_t i = 0; i < 9; ++i) - { - j[i] = m[i / 3][i % 3]; - } - } - - void from_json(const json& j, float3x3& m) - { - for (uint32_t i = 0; i < 9; ++i) - { - j[i].get_to(m[i / 3][i % 3]); - } - } -} - namespace Falcor { namespace diff --git a/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.cpp b/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.cpp index 06871842f..0adb46845 100644 --- a/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.cpp +++ b/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -103,7 +103,14 @@ namespace Falcor if (!mpVoxelBuffer || mpVoxelBuffer->getElementCount() < mVoxelCount) { - mpVoxelBuffer = mpDevice->createStructuredBuffer(sizeof(SDFSVSVoxel), mVoxelCount); + mpVoxelBuffer = mpDevice->createStructuredBuffer( + sizeof(SDFSVSVoxel), + mVoxelCount, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + nullptr, + true + ); } } diff --git a/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.slang b/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.slang index 9e93f3e22..b1af63180 100644 --- a/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.slang +++ b/Source/Falcor/Scene/SDFs/SparseVoxelSet/SDFSVS.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -114,6 +114,9 @@ struct SDFSVS : SDFGridBase, ISDFGrid bool intersectSDF(const float3 rayOrigin, const float3 rayDir, const float tMin, const float tMax, const uint primitiveID, out float t, out SDFGridHitData hitData) { + t = {}; + hitData = {}; + // Normalize ray direction. float dirLength = length(rayDir); float inverseDirLength = 1.0f / dirLength; diff --git a/Source/Falcor/Scene/Scene.cpp b/Source/Falcor/Scene/Scene.cpp index f0e0c81bd..da5d4f2bc 100644 --- a/Source/Falcor/Scene/Scene.cpp +++ b/Source/Falcor/Scene/Scene.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -93,10 +93,8 @@ namespace Falcor const std::string kCameraSpeed = "cameraSpeed"; const std::string kSetCameraBounds = "setCameraBounds"; const std::string kLights = "lights"; - const std::string kLightProfile = "lightProfile"; const std::string kAnimated = "animated"; const std::string kRenderSettings = "renderSettings"; - const std::string kUpdateCallback = "updateCallback"; const std::string kEnvMap = "envMap"; const std::string kMaterials = "materials"; const std::string kGridVolumes = "gridVolumes"; @@ -166,7 +164,8 @@ namespace Falcor : mpDevice(pDevice) { // Copy/move scene data to member variables. - mPath = sceneData.path; + mImportPaths = sceneData.importPaths; + mImportDicts = sceneData.importDicts; mRenderSettings = sceneData.renderSettings; mCameras = std::move(sceneData.cameras); mSelectedCamera = sceneData.selectedCamera; @@ -177,7 +176,6 @@ namespace Falcor mGridVolumes = std::move(sceneData.gridVolumes); mGrids = std::move(sceneData.grids); mpEnvMap = sceneData.pEnvMap; - mpLightProfile = sceneData.pLightProfile; mSceneGraph = std::move(sceneData.sceneGraph); mMetadata = std::move(sceneData.metadata); @@ -209,6 +207,14 @@ namespace Falcor mCustomPrimitiveDesc = std::move(sceneData.customPrimitiveDesc); mCustomPrimitiveAABBs = std::move(sceneData.customPrimitiveAABBs); + mMeshIndexData = std::move(sceneData.meshIndexData); + mMeshStaticData = std::move(sceneData.meshStaticData); + + mMeshIndexData.setBufferCountDefinePrefix("SCENE_INDEX"); + mMeshIndexData.createGpuBuffers(mpDevice, ResourceBindFlags::Index | ResourceBindFlags::ShaderResource); + mMeshStaticData.setBufferCountDefinePrefix("SCENE_VERTEX"); + mMeshStaticData.createGpuBuffers(mpDevice, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess | ResourceBindFlags::Vertex); + // Setup additional resources. mFrontClockwiseRS[RasterizerState::CullMode::None] = RasterizerState::create(RasterizerState::Desc().setFrontCounterCW(false).setCullMode(RasterizerState::CullMode::None)); mFrontClockwiseRS[RasterizerState::CullMode::Back] = RasterizerState::create(RasterizerState::Desc().setFrontCounterCW(false).setCullMode(RasterizerState::CullMode::Back)); @@ -224,12 +230,12 @@ namespace Falcor setSDFGridConfig(); // Create vertex array objects for meshes and curves. - createMeshVao(sceneData.meshDrawCount, sceneData.meshIndexData, sceneData.meshStaticData, sceneData.meshSkinningData); + createMeshVao(sceneData.meshDrawCount, sceneData.meshSkinningData); createCurveVao(mCurveIndexData, mCurveStaticData); - createMeshUVTiles(mMeshDesc, sceneData.meshIndexData, sceneData.meshStaticData); + createMeshUVTiles(mMeshDesc); // Create animation controller. - mpAnimationController = std::make_unique(mpDevice, this, sceneData.meshStaticData, sceneData.meshSkinningData, sceneData.prevVertexCount, sceneData.animations); + mpAnimationController = std::make_unique(mpDevice, this, sceneData.meshSkinningData, sceneData.prevVertexCount, sceneData.animations); // Some runtime mesh data validation. These are essentially asserts, but large scenes are mostly opened in Release for (const auto& mesh : mMeshDesc) @@ -257,7 +263,7 @@ namespace Falcor } // Must be placed after curve data/AABB creation. - mpAnimationController->addAnimatedVertexCaches(std::move(sceneData.cachedCurves), std::move(sceneData.cachedMeshes), sceneData.meshStaticData); + mpAnimationController->addAnimatedVertexCaches(std::move(sceneData.cachedCurves), std::move(sceneData.cachedMeshes)); // Finalize scene. finalize(); @@ -282,7 +288,8 @@ namespace Falcor defines.add("SCENE_HAS_INDEXED_VERTICES", hasIndexBuffer() ? "1" : "0"); defines.add("SCENE_HAS_16BIT_INDICES", mHas16BitIndices ? "1" : "0"); defines.add("SCENE_HAS_32BIT_INDICES", mHas32BitIndices ? "1" : "0"); - defines.add("SCENE_USE_LIGHT_PROFILE", mpLightProfile != nullptr ? "1" : "0"); + mMeshIndexData.getShaderDefines(defines); + mMeshStaticData.getShaderDefines(defines); defines.add(mHitInfo.getDefines()); defines.add(getSceneSDFGridDefines()); @@ -416,7 +423,7 @@ namespace Falcor } uint32_t rayTypeCount = pVars->getRayTypeCount(); - setRaytracingShaderData(pRenderContext, pVars->getRootVar(), rayTypeCount); + bindShaderDataForRaytracing(pRenderContext, pVars->getRootVar()[kParameterBlockName], rayTypeCount); // Set ray type constant. pVars->getRootVar()["DxrPerFrame"]["rayTypeCount"] = rayTypeCount; @@ -424,38 +431,20 @@ namespace Falcor pRenderContext->raytrace(pProgram, pVars.get(), dispatchDims.x, dispatchDims.y, dispatchDims.z); } - void Scene::createMeshVao(uint32_t drawCount, const std::vector& indexData, const std::vector& staticData, const std::vector& skinningData) + void Scene::createMeshVao(uint32_t drawCount, const std::vector& skinningData) { if (drawCount == 0) return; - - // Create the index buffer. - size_t ibSize = sizeof(uint32_t) * indexData.size(); - if (ibSize > std::numeric_limits::max()) + if (mMeshIndexData.getBufferCount() > 1 || mMeshStaticData.getBufferCount() > 1) { - FALCOR_THROW("Index buffer size exceeds 4GB"); + logWarning("MeshVao cannot be created, rasterization will not be available."); + return; } ref pIB; - if (ibSize > 0) - { - ResourceBindFlags ibBindFlags = ResourceBindFlags::Index | ResourceBindFlags::ShaderResource; - pIB = mpDevice->createBuffer(ibSize, ibBindFlags, MemoryType::DeviceLocal, indexData.data()); - } - - // Create the vertex data structured buffer. - const size_t vertexCount = (uint32_t)staticData.size(); - size_t staticVbSize = sizeof(PackedStaticVertexData) * vertexCount; - if (staticVbSize > std::numeric_limits::max()) - { - FALCOR_THROW("Vertex buffer size exceeds 4GB"); - } + if (!mMeshIndexData.empty()) + pIB = mMeshIndexData.getGpuBuffer(0); - ref pStaticBuffer; - if (vertexCount > 0) - { - ResourceBindFlags vbBindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess | ResourceBindFlags::Vertex; - pStaticBuffer = mpDevice->createStructuredBuffer(sizeof(PackedStaticVertexData), (uint32_t)vertexCount, vbBindFlags, MemoryType::DeviceLocal, nullptr, false); - } + ref pStaticBuffer = mMeshStaticData.getGpuBuffer(0); Vao::BufferVec pVBs(kVertexBufferCount); pVBs[kStaticDataBufferIndex] = pStaticBuffer; @@ -556,9 +545,8 @@ namespace Falcor mpCurveVao = Vao::create(Vao::Topology::LineStrip, pLayout, pVBs, pIB, ResourceFormat::R32Uint); } - void Scene::createMeshUVTiles(const std::vector& meshDescs, const std::vector& indexData, const std::vector& staticData) + void Scene::createMeshUVTiles(const std::vector& meshDescs) { - const uint8_t* indexData8 = reinterpret_cast(indexData.data()); mMeshUVTiles.resize(meshDescs.size()); auto processMeshTile = [&](size_t meshIndex) @@ -568,6 +556,10 @@ namespace Falcor Rectangle largeTriangleTile; std::map tiles; + const uint8_t* meshIndexData8 = nullptr; + if (desc.useVertexIndices()) + meshIndexData8 = reinterpret_cast(&mMeshIndexData[desc.ibOffset]); + const uint tcount = desc.getTriangleCount(); for (uint tidx = 0; tidx < tcount; ++tidx) { @@ -575,21 +567,20 @@ namespace Falcor uint32_t vidx[3] = {}; if (desc.useVertexIndices()) { - FALCOR_ASSERT(indexData8 != nullptr); - uint baseIndex = desc.ibOffset * 4; + FALCOR_ASSERT(meshIndexData8 != nullptr); if (desc.use16BitIndices()) { - baseIndex += tidx * 3 * sizeof(uint16_t); - vidx[0] = reinterpret_cast(indexData8 + baseIndex)[0]; - vidx[1] = reinterpret_cast(indexData8 + baseIndex)[1]; - vidx[2] = reinterpret_cast(indexData8 + baseIndex)[2]; + uint byteOffset = tidx * 3 * sizeof(uint16_t); + vidx[0] = reinterpret_cast(meshIndexData8 + byteOffset)[0]; + vidx[1] = reinterpret_cast(meshIndexData8 + byteOffset)[1]; + vidx[2] = reinterpret_cast(meshIndexData8 + byteOffset)[2]; } else { - baseIndex += tidx * 3 * sizeof(uint32_t); - vidx[0] = reinterpret_cast(indexData8 + baseIndex)[0]; - vidx[1] = reinterpret_cast(indexData8 + baseIndex)[1]; - vidx[2] = reinterpret_cast(indexData8 + baseIndex)[2]; + uint byteOffset = tidx * 3 * sizeof(uint32_t); + vidx[0] = reinterpret_cast(meshIndexData8 + byteOffset)[0]; + vidx[1] = reinterpret_cast(meshIndexData8 + byteOffset)[1]; + vidx[2] = reinterpret_cast(meshIndexData8 + byteOffset)[2]; } } else @@ -605,11 +596,10 @@ namespace Falcor // Load vertices from global vertex buffer. // Note that the mesh local vbOffset is added to address into the global vertex buffer. - FALCOR_ASSERT((size_t)desc.vbOffset + desc.vertexCount <= staticData.size()); StaticVertexData vertices[3]; - vertices[0] = staticData[(size_t)desc.vbOffset + vidx[0]].unpack(); - vertices[1] = staticData[(size_t)desc.vbOffset + vidx[1]].unpack(); - vertices[2] = staticData[(size_t)desc.vbOffset + vidx[2]].unpack(); + vertices[0] = mMeshStaticData[(size_t)desc.vbOffset + vidx[0]].unpack(); + vertices[1] = mMeshStaticData[(size_t)desc.vbOffset + vidx[1]].unpack(); + vertices[2] = mMeshStaticData[(size_t)desc.vbOffset + vidx[2]].unpack(); int2 v0 = int2(std::floor(vertices[0].texCrd[0]), std::floor(vertices[0].texCrd[1])); int2 v1 = int2(std::floor(vertices[1].texCrd[0]), std::floor(vertices[1].texCrd[1])); @@ -833,12 +823,10 @@ namespace Falcor FALCOR_ASSERT(mpAnimationController); mpAnimationController->bindBuffers(); - if (mpMeshVao != nullptr) - { - if (hasIndexBuffer()) var[kIndexBufferName] = mpMeshVao->getIndexBuffer(); - var[kVertexBufferName] = mpMeshVao->getVertexBuffer(Scene::kStaticDataBufferIndex); - var[kPrevVertexBufferName] = mpAnimationController->getPrevVertexData(); // Can be nullptr - } + if (hasIndexBuffer()) + mMeshIndexData.bindShaderData(var[kIndexBufferName]); + mMeshStaticData.bindShaderData(var[kVertexBufferName]); + var[kPrevVertexBufferName] = mpAnimationController->getPrevVertexData(); if (mpCurveVao != nullptr) { @@ -953,11 +941,11 @@ namespace Falcor } } - Scene::UpdateFlags Scene::updateRaytracingAABBData(bool forceUpdate) + IScene::UpdateFlags Scene::updateRaytracingAABBData(bool forceUpdate) { // This function updates the global list of AABBs for all procedural primitives. // TODO: Move this code to the GPU. Then the CPU copies of some buffers won't be needed anymore. - Scene::UpdateFlags flags = Scene::UpdateFlags::None; + IScene::UpdateFlags flags = IScene::UpdateFlags::None; size_t curveAABBCount = 0; for (const auto& curve : mCurveDesc) curveAABBCount += curve.indexCount; @@ -1010,7 +998,7 @@ namespace Falcor mRtAABBRaw[index++] = static_cast(curveSegBB); } - flags |= Scene::UpdateFlags::CurvesMoved; + flags |= IScene::UpdateFlags::CurvesMoved; } FALCOR_ASSERT(index == curveAABBCount); } @@ -1029,7 +1017,7 @@ namespace Falcor mRtAABBRaw[index++] = static_cast(aabb); } FALCOR_ASSERT(index == totalAABBCount); - flags |= Scene::UpdateFlags::CustomPrimitivesMoved; + flags |= IScene::UpdateFlags::CustomPrimitivesMoved; } // Create/update GPU buffer. This is used in BLAS creation and also bound to the scene for lookup in shaders. @@ -1057,9 +1045,9 @@ namespace Falcor return flags; } - Scene::UpdateFlags Scene::updateDisplacement(RenderContext* pRenderContext, bool forceUpdate) + IScene::UpdateFlags Scene::updateDisplacement(RenderContext* pRenderContext, bool forceUpdate) { - if (!hasGeometryType(GeometryType::DisplacedTriangleMesh)) return UpdateFlags::None; + if (!hasGeometryType(GeometryType::DisplacedTriangleMesh)) return IScene::UpdateFlags::None; // For now we assume that displaced meshes are static. // Create AABB and AABB update task buffers. @@ -1101,7 +1089,7 @@ namespace Falcor FALCOR_ASSERT(!mDisplacement.updateTasks.empty()); // We cannot access the scene parameter block until its finalized. - if (!mFinalized) return UpdateFlags::None; + if (!mFinalized) return IScene::UpdateFlags::None; // Update the AABB data. if (!mDisplacement.pUpdatePass) @@ -1127,15 +1115,15 @@ namespace Falcor mCustomPrimitivesChanged = true; // Trigger a BVH update. mDisplacement.needsUpdate = false; - return UpdateFlags::DisplacementChanged; + return IScene::UpdateFlags::DisplacementChanged; } - return UpdateFlags::None; + return IScene::UpdateFlags::None; } - Scene::UpdateFlags Scene::updateSDFGrids(RenderContext* pRenderContext) + IScene::UpdateFlags Scene::updateSDFGrids(RenderContext* pRenderContext) { - UpdateFlags updateFlags = UpdateFlags::None; + IScene::UpdateFlags updateFlags = IScene::UpdateFlags::None; if (!is_set(mGeometryTypes, GeometryTypeFlags::SDFGrid)) return updateFlags; auto sdfGridsVar = mpSceneBlock->getRootVar()[kSDFGridsArrayName]; @@ -1154,25 +1142,25 @@ namespace Falcor // Clear any previous BLAS data. This will trigger a full BLAS/TLAS rebuild. // TODO: Support partial rebuild of just the procedural primitives. mBlasDataValid = false; - updateFlags |= Scene::UpdateFlags::SDFGeometryChanged; + updateFlags |= IScene::UpdateFlags::SDFGeometryChanged; } if (is_set(sdfGridUpdateFlags, SDFGrid::UpdateFlags::BuffersReallocated)) { updateGeometryStats(); pSDFGrid->bindShaderData(sdfGridsVar[sdfGridID]); - updateFlags |= Scene::UpdateFlags::SDFGeometryChanged; + updateFlags |= IScene::UpdateFlags::SDFGeometryChanged; } } return updateFlags; } - Scene::UpdateFlags Scene::updateProceduralPrimitives(bool forceUpdate) + IScene::UpdateFlags Scene::updateProceduralPrimitives(bool forceUpdate) { // Update the AABB buffer. // The bounds are updated if any primitive has moved or been added/removed. - Scene::UpdateFlags flags = updateRaytracingAABBData(forceUpdate); + IScene::UpdateFlags flags = updateRaytracingAABBData(forceUpdate); // Update the procedural primitives metadata. if (forceUpdate || mCustomPrimitivesChanged) @@ -1207,7 +1195,7 @@ namespace Falcor var["customPrimitiveInstanceCount"] = customPrimitiveInstanceCount; var["customPrimitiveAABBOffset"] = mCustomPrimitiveAABBOffset; - flags |= Scene::UpdateFlags::GeometryChanged; + flags |= IScene::UpdateFlags::GeometryChanged; } return flags; @@ -1243,12 +1231,6 @@ namespace Falcor { RenderContext* pRenderContext = mpDevice->getRenderContext(); - // DEMO21: Setup light profile. - if (mpLightProfile) - { - mpLightProfile->bake(mpDevice->getRenderContext()); - } - // Perform setup that affects the scene defines. initSDFGrids(); mHitInfo.init(*this, mUseCompressedHitInfo); @@ -1400,14 +1382,12 @@ namespace Falcor s.geometryMemoryInBytes = 0; s.animationMemoryInBytes = 0; + s.indexMemoryInBytes += mMeshIndexData.getByteSize(); + s.vertexMemoryInBytes += mMeshStaticData.getByteSize(); + if (mpMeshVao) { - const auto& pIB = mpMeshVao->getIndexBuffer(); - const auto& pVB = mpMeshVao->getVertexBuffer(kStaticDataBufferIndex); const auto& pDrawID = mpMeshVao->getVertexBuffer(kDrawIdBufferIndex); - - s.indexMemoryInBytes += pIB ? pIB->getSize() : 0; - s.vertexMemoryInBytes += pVB ? pVB->getSize() : 0; s.geometryMemoryInBytes += pDrawID ? pDrawID->getSize() : 0; } @@ -1582,7 +1562,7 @@ namespace Falcor return false; } - Scene::UpdateFlags Scene::updateSelectedCamera(bool forceUpdate) + IScene::UpdateFlags Scene::updateSelectedCamera(bool forceUpdate) { auto camera = mCameras[mSelectedCamera]; @@ -1595,20 +1575,20 @@ namespace Falcor mpCamCtrl->update(); } - UpdateFlags flags = UpdateFlags::None; + IScene::UpdateFlags flags = IScene::UpdateFlags::None; auto cameraChanges = camera->beginFrame(); if (mCameraSwitched || cameraChanges != Camera::Changes::None) { bindSelectedCamera(); - if (is_set(cameraChanges, Camera::Changes::Movement)) flags |= UpdateFlags::CameraMoved; - if ((cameraChanges & (~Camera::Changes::Movement)) != Camera::Changes::None) flags |= UpdateFlags::CameraPropertiesChanged; - if (mCameraSwitched) flags |= UpdateFlags::CameraSwitched; + if (is_set(cameraChanges, Camera::Changes::Movement)) flags |= IScene::UpdateFlags::CameraMoved; + if ((cameraChanges & (~Camera::Changes::Movement)) != Camera::Changes::None) flags |= IScene::UpdateFlags::CameraPropertiesChanged; + if (mCameraSwitched) flags |= IScene::UpdateFlags::CameraSwitched; } mCameraSwitched = false; return flags; } - Scene::UpdateFlags Scene::updateLights(bool forceUpdate) + IScene::UpdateFlags Scene::updateLights(bool forceUpdate) { Light::Changes combinedChanges = Light::Changes::None; @@ -1651,13 +1631,13 @@ namespace Falcor } // Compute update flags. - UpdateFlags flags = UpdateFlags::None; - if (is_set(combinedChanges, Light::Changes::Intensity)) flags |= UpdateFlags::LightIntensityChanged; - if (is_set(combinedChanges, Light::Changes::Position)) flags |= UpdateFlags::LightsMoved; - if (is_set(combinedChanges, Light::Changes::Direction)) flags |= UpdateFlags::LightsMoved; - if (is_set(combinedChanges, Light::Changes::Active)) flags |= UpdateFlags::LightCountChanged; + IScene::UpdateFlags flags = IScene::UpdateFlags::None; + if (is_set(combinedChanges, Light::Changes::Intensity)) flags |= IScene::UpdateFlags::LightIntensityChanged; + if (is_set(combinedChanges, Light::Changes::Position)) flags |= IScene::UpdateFlags::LightsMoved; + if (is_set(combinedChanges, Light::Changes::Direction)) flags |= IScene::UpdateFlags::LightsMoved; + if (is_set(combinedChanges, Light::Changes::Active)) flags |= IScene::UpdateFlags::LightCountChanged; const Light::Changes otherChanges = ~(Light::Changes::Intensity | Light::Changes::Position | Light::Changes::Direction | Light::Changes::Active); - if ((combinedChanges & otherChanges) != Light::Changes::None) flags |= UpdateFlags::LightPropertiesChanged; + if ((combinedChanges & otherChanges) != Light::Changes::None) flags |= IScene::UpdateFlags::LightPropertiesChanged; return flags; } @@ -1671,13 +1651,11 @@ namespace Falcor if (mpLightCollection) mpLightCollection->bindShaderData(var["lightCollection"]); - if (mpLightProfile) - mpLightProfile->bindShaderData(var[kLightProfile]); if (mpEnvMap) mpEnvMap->bindShaderData(var[kEnvMap]); } - Scene::UpdateFlags Scene::updateGridVolumes(bool forceUpdate) + IScene::UpdateFlags Scene::updateGridVolumes(bool forceUpdate) { GridVolume::UpdateFlags combinedUpdates = GridVolume::UpdateFlags::None; @@ -1691,7 +1669,7 @@ namespace Falcor } // Early out if no volumes have changed. - if (!forceUpdate && combinedUpdates == GridVolume::UpdateFlags::None) return UpdateFlags::None; + if (!forceUpdate && combinedUpdates == GridVolume::UpdateFlags::None) return IScene::UpdateFlags::None; // Upload grids. if (forceUpdate) @@ -1722,11 +1700,11 @@ namespace Falcor volumeIndex++; } - UpdateFlags flags = UpdateFlags::None; - if (is_set(combinedUpdates, GridVolume::UpdateFlags::TransformChanged)) flags |= UpdateFlags::GridVolumesMoved; - if (is_set(combinedUpdates, GridVolume::UpdateFlags::PropertiesChanged)) flags |= UpdateFlags::GridVolumePropertiesChanged; - if (is_set(combinedUpdates, GridVolume::UpdateFlags::GridsChanged)) flags |= UpdateFlags::GridVolumeGridsChanged; - if (is_set(combinedUpdates, GridVolume::UpdateFlags::BoundsChanged)) flags |= UpdateFlags::GridVolumeBoundsChanged; + IScene::UpdateFlags flags = IScene::UpdateFlags::None; + if (is_set(combinedUpdates, GridVolume::UpdateFlags::TransformChanged)) flags |= IScene::UpdateFlags::GridVolumesMoved; + if (is_set(combinedUpdates, GridVolume::UpdateFlags::PropertiesChanged)) flags |= IScene::UpdateFlags::GridVolumePropertiesChanged; + if (is_set(combinedUpdates, GridVolume::UpdateFlags::GridsChanged)) flags |= IScene::UpdateFlags::GridVolumeGridsChanged; + if (is_set(combinedUpdates, GridVolume::UpdateFlags::BoundsChanged)) flags |= IScene::UpdateFlags::GridVolumeBoundsChanged; return flags; } @@ -1744,10 +1722,9 @@ namespace Falcor } } - Scene::UpdateFlags Scene::updateEnvMap(bool forceUpdate) + IScene::UpdateFlags Scene::updateEnvMap(bool forceUpdate) { - UpdateFlags flags = UpdateFlags::None; - + IScene::UpdateFlags flags = IScene::UpdateFlags::None; if (mpEnvMap) { if (mpEnvMap->mpDevice != mpDevice) @@ -1755,7 +1732,7 @@ namespace Falcor auto envMapChanges = mpEnvMap->beginFrame(); if (envMapChanges != EnvMap::Changes::None || mEnvMapChanged || forceUpdate) { - if (envMapChanges != EnvMap::Changes::None) flags |= UpdateFlags::EnvMapPropertiesChanged; + if (envMapChanges != EnvMap::Changes::None) flags |= IScene::UpdateFlags::EnvMapPropertiesChanged; mpEnvMap->bindShaderData(mpSceneBlock->getRootVar()[kEnvMap]); } } @@ -1763,23 +1740,23 @@ namespace Falcor if (mEnvMapChanged) { - flags |= UpdateFlags::EnvMapChanged; + flags |= IScene::UpdateFlags::EnvMapChanged; mEnvMapChanged = false; } return flags; } - Scene::UpdateFlags Scene::updateMaterials(bool forceUpdate) + IScene::UpdateFlags Scene::updateMaterials(bool forceUpdate) { // Update material system. FALCOR_ASSERT(mpMaterials); Material::UpdateFlags materialUpdates = mpMaterials->update(forceUpdate); - UpdateFlags flags = UpdateFlags::None; + IScene::UpdateFlags flags = IScene::UpdateFlags::None; if (forceUpdate || materialUpdates != Material::UpdateFlags::None) { - flags |= UpdateFlags::MaterialsChanged; + flags |= IScene::UpdateFlags::MaterialsChanged; // Bind materials parameter block to scene. if (mpSceneBlock) @@ -1796,7 +1773,7 @@ namespace Falcor // Check if emissive materials have changed. if (is_set(materialUpdates, Material::UpdateFlags::EmissiveChanged)) { - flags |= UpdateFlags::EmissiveMaterialsChanged; + flags |= IScene::UpdateFlags::EmissiveMaterialsChanged; } // Update type conformances. @@ -1804,13 +1781,13 @@ namespace Falcor mTypeConformances = mpMaterials->getTypeConformances(); if (mTypeConformances != prevTypeConformances) { - flags |= UpdateFlags::TypeConformancesChanged; + flags |= IScene::UpdateFlags::TypeConformancesChanged; } // Pass on update flag indicating shader code changes. if (is_set(materialUpdates, Material::UpdateFlags::CodeChanged)) { - flags |= UpdateFlags::ShaderCodeChanged; + flags |= IScene::UpdateFlags::ShaderCodeChanged; } // Update material stats upon any material changes for now. @@ -1821,9 +1798,9 @@ namespace Falcor return flags; } - Scene::UpdateFlags Scene::updateGeometry(RenderContext* pRenderContext, bool forceUpdate) + IScene::UpdateFlags Scene::updateGeometry(RenderContext* pRenderContext, bool forceUpdate) { - UpdateFlags flags = updateProceduralPrimitives(forceUpdate); + IScene::UpdateFlags flags = updateProceduralPrimitives(forceUpdate); flags |= updateDisplacement(pRenderContext, forceUpdate); if (forceUpdate || mCustomPrimitivesChanged) @@ -1842,28 +1819,27 @@ namespace Falcor void Scene::updateForInverseRendering(RenderContext* pRenderContext, bool isMaterialChanged, bool isMeshChanged) { - mUpdates = UpdateFlags::None; + mUpdates = IScene::UpdateFlags::None; if (isMaterialChanged) mUpdates |= updateMaterials(false); - if (isMeshChanged) mUpdates |= UpdateFlags::MeshesChanged; + if (isMeshChanged) mUpdates |= IScene::UpdateFlags::MeshesChanged; pRenderContext->submit(); - bool blasUpdateRequired = is_set(mUpdates, UpdateFlags::MeshesChanged); + bool blasUpdateRequired = is_set(mUpdates, IScene::UpdateFlags::MeshesChanged); if (mBlasDataValid && blasUpdateRequired) { invalidateTlasCache(); buildBlas(pRenderContext); } + mUpdateFlagsSignal(mUpdates); + // TODO: Update light collection if we allow changing area lights. } - Scene::UpdateFlags Scene::update(RenderContext* pRenderContext, double currentTime) + IScene::UpdateFlags Scene::update(RenderContext* pRenderContext, double currentTime) { - // Run scene update callback. - if (mUpdateCallback) mUpdateCallback(ref(this), currentTime); - - mUpdates = UpdateFlags::None; + mUpdates = IScene::UpdateFlags::None; // Perform updates that may affect the scene defines. updateGeometryTypes(); @@ -1874,7 +1850,7 @@ namespace Falcor updateSceneDefines(); if (mSceneDefines != mPrevSceneDefines) { - mUpdates |= UpdateFlags::SceneDefinesChanged; + mUpdates |= IScene::UpdateFlags::SceneDefinesChanged; mPrevSceneDefines = mSceneDefines; mpSceneBlock = nullptr; } @@ -1890,20 +1866,20 @@ namespace Falcor if (mpAnimationController->animate(pRenderContext, currentTime)) { - mUpdates |= UpdateFlags::SceneGraphChanged; - if (mpAnimationController->hasSkinnedMeshes()) mUpdates |= UpdateFlags::MeshesChanged; + mUpdates |= IScene::UpdateFlags::SceneGraphChanged; + if (mpAnimationController->hasSkinnedMeshes()) mUpdates |= IScene::UpdateFlags::MeshesChanged; for (const auto& inst : mGeometryInstanceData) { if (mpAnimationController->isMatrixChanged(NodeID{ inst.globalMatrixID })) { - mUpdates |= UpdateFlags::GeometryMoved; + mUpdates |= IScene::UpdateFlags::GeometryMoved; } } // We might end up setting the flag even if curves haven't changed (if looping is disabled for example). - if (mpAnimationController->hasAnimatedCurveCaches()) mUpdates |= UpdateFlags::CurvesMoved; - if (mpAnimationController->hasAnimatedMeshCaches()) mUpdates |= UpdateFlags::MeshesChanged; + if (mpAnimationController->hasAnimatedCurveCaches()) mUpdates |= IScene::UpdateFlags::CurvesMoved; + if (mpAnimationController->hasAnimatedMeshCaches()) mUpdates |= IScene::UpdateFlags::MeshesChanged; } for (const auto& pGridVolume : mGridVolumes) @@ -1919,15 +1895,15 @@ namespace Falcor mUpdates |= updateSDFGrids(pRenderContext); pRenderContext->submit(); - if (is_set(mUpdates, UpdateFlags::GeometryMoved)) + if (is_set(mUpdates, IScene::UpdateFlags::GeometryMoved)) { invalidateTlasCache(); updateGeometryInstances(false); } // Update existing BLASes if skinned animation and/or procedural primitives moved. - bool updateProcedural = is_set(mUpdates, UpdateFlags::CurvesMoved) || is_set(mUpdates, UpdateFlags::CustomPrimitivesMoved); - bool blasUpdateRequired = is_set(mUpdates, UpdateFlags::MeshesChanged) || updateProcedural; + bool updateProcedural = is_set(mUpdates, IScene::UpdateFlags::CurvesMoved) || is_set(mUpdates, IScene::UpdateFlags::CustomPrimitivesMoved); + bool blasUpdateRequired = is_set(mUpdates, IScene::UpdateFlags::MeshesChanged) || updateProcedural; if (mBlasDataValid && blasUpdateRequired) { @@ -1940,16 +1916,16 @@ namespace Falcor { // If emissive material properties changed we recreate the light collection. // This can be expensive and should be optimized by letting the light collection internally update its data structures. - if (is_set(mUpdates, UpdateFlags::EmissiveMaterialsChanged)) + if (is_set(mUpdates, IScene::UpdateFlags::EmissiveMaterialsChanged)) { mpLightCollection = nullptr; getLightCollection(pRenderContext); - mUpdates |= UpdateFlags::LightCollectionChanged; + mUpdates |= IScene::UpdateFlags::LightCollectionChanged; } else { if (mpLightCollection->update(pRenderContext)) - mUpdates |= UpdateFlags::LightCollectionChanged; + mUpdates |= IScene::UpdateFlags::LightCollectionChanged; mSceneStats.emissiveMemoryInBytes = mpLightCollection->getMemoryUsageInBytes(); } } @@ -1960,13 +1936,13 @@ namespace Falcor if (mRenderSettings != mPrevRenderSettings) { - mUpdates |= UpdateFlags::RenderSettingsChanged; + mUpdates |= IScene::UpdateFlags::RenderSettingsChanged; mPrevRenderSettings = mRenderSettings; } if (mSDFGridConfig != mPrevSDFGridConfig) { - mUpdates |= UpdateFlags::SDFGridConfigChanged; + mUpdates |= IScene::UpdateFlags::SDFGridConfigChanged; mPrevSDFGridConfig = mSDFGridConfig; } @@ -1974,6 +1950,7 @@ namespace Falcor updateSceneDefines(); FALCOR_CHECK(mSceneDefines == mPrevSceneDefines, "Scene defines changed unexpectedly"); + mUpdateFlagsSignal(mUpdates); return mUpdates; } @@ -2137,11 +2114,11 @@ namespace Falcor } } - if (mpLightProfile) + if (mpMaterials->getLightProfile()) { if (auto lightProfileGroup = widget.group("Light Profile")) { - mpLightProfile->renderUI(lightProfileGroup); + mpMaterials->getLightProfile()->renderUI(lightProfileGroup); } } @@ -2171,7 +2148,7 @@ namespace Falcor const double channelsPerTexel = (double)s.materials.textureTexelChannelCount / s.materials.textureTexelCount; std::ostringstream oss; - oss << "Path: " << mPath << std::endl; + oss << "Path: " << (mImportPaths.empty() ? "" : mImportPaths.back()) << std::endl; oss << "Bounds: (" << mSceneBB.minPoint.x << "," << mSceneBB.minPoint.y << "," << mSceneBB.minPoint.z << ")-(" << mSceneBB.maxPoint.x << "," << mSceneBB.maxPoint.y << "," << mSceneBB.maxPoint.z << ")" << std::endl; oss << "Total scene memory: " << formatByteSize(s.getTotalMemory()) << std::endl; @@ -2346,7 +2323,7 @@ namespace Falcor } } - const ref& Scene::getCamera() + const ref& Scene::getCamera() const { FALCOR_CHECK(mSelectedCamera < mCameras.size(), "Selected camera index {} is invalid.", mSelectedCamera); return mCameras[mSelectedCamera]; @@ -2714,6 +2691,9 @@ namespace Falcor void Scene::createDrawList() { + if (!mpMeshVao) + return; + // This function creates argument buffers for draw indirect calls to rasterize the scene. // The updateGeometryInstances() function must have been called before so that the flags are accurate. // @@ -2812,10 +2792,6 @@ namespace Falcor if (!mMeshGroups.empty()) { - FALCOR_ASSERT(mpMeshVao); - const ref& pVbLayout = mpMeshVao->getVertexLayout()->getBufferLayout(kStaticDataBufferIndex); - const ref& pVb = mpMeshVao->getVertexBuffer(kStaticDataBufferIndex); - const ref& pIb = mpMeshVao->getIndexBuffer(); const auto& globalMatrices = mpAnimationController->getGlobalMatrices(); // Normally static geometry is already pre-transformed to world space by the SceneBuilder, @@ -2897,18 +2873,18 @@ namespace Falcor desc.flags = pMaterial->isOpaque() ? RtGeometryFlags::Opaque : RtGeometryFlags::None; // Set the position data - desc.content.triangles.vertexData = pVb->getGpuAddress() + (mesh.vbOffset * pVbLayout->getStride()); - desc.content.triangles.vertexStride = pVbLayout->getStride(); + desc.content.triangles.vertexData = mMeshStaticData.getGpuAddress(mesh.vbOffset); + desc.content.triangles.vertexStride = sizeof(PackedStaticVertexData); desc.content.triangles.vertexCount = mesh.vertexCount; - desc.content.triangles.vertexFormat = pVbLayout->getElementFormat(0); + desc.content.triangles.vertexFormat = ResourceFormat::RGB32Float; // Set index data - if (pIb) + if (!mMeshIndexData.empty()) { // The global index data is stored in a dword array. // Each mesh specifies whether its indices are in 16-bit or 32-bit format. ResourceFormat ibFormat = mesh.use16BitIndices() ? ResourceFormat::R16Uint : ResourceFormat::R32Uint; - desc.content.triangles.indexData = pIb->getGpuAddress() + mesh.ibOffset * sizeof(uint32_t); + desc.content.triangles.indexData = mMeshIndexData.getGpuAddress(mesh.ibOffset); desc.content.triangles.indexCount = mesh.indexCount; desc.content.triangles.indexFormat = ibFormat; } @@ -3209,12 +3185,18 @@ namespace Falcor } // Add barriers for the VB and IB which will be accessed by the build. - if (mpMeshVao) + for (size_t i = 0; i < mMeshStaticData.getBufferCount(); ++i) { - const ref& pVb = mpMeshVao->getVertexBuffer(kStaticDataBufferIndex); - const ref& pIb = mpMeshVao->getIndexBuffer(); - pRenderContext->resourceBarrier(pVb.get(), Resource::State::NonPixelShader); - if (pIb) pRenderContext->resourceBarrier(pIb.get(), Resource::State::NonPixelShader); + ref pVb = mMeshStaticData.getGpuBuffer(i); + if (pVb) + pRenderContext->resourceBarrier(pVb.get(), Resource::State::NonPixelShader); + } + + for (size_t i = 0; i < mMeshIndexData.getBufferCount(); ++i) + { + ref pIb = mMeshIndexData.getGpuBuffer(i); + if (pIb) + pRenderContext->resourceBarrier(pIb.get(), Resource::State::NonPixelShader); } if (mpCurveVao) @@ -3375,6 +3357,7 @@ namespace Falcor } pRenderContext->buildAccelerationStructure(asDesc, 1, &postbuildInfoDesc); + pRenderContext->submit(true); } // Read back the calculated final size requirements for each BLAS. @@ -3463,7 +3446,7 @@ namespace Falcor // - Update or rebuild in-place the ones that are animated. FALCOR_ASSERT(!mRebuildBlas); - bool updateProcedural = is_set(mUpdates, UpdateFlags::CurvesMoved) || is_set(mUpdates, UpdateFlags::CustomPrimitivesMoved); + bool updateProcedural = is_set(mUpdates, IScene::UpdateFlags::CurvesMoved) || is_set(mUpdates, IScene::UpdateFlags::CustomPrimitivesMoved); for (const auto& group : mBlasGroups) { @@ -3728,6 +3711,7 @@ namespace Falcor { tlas.second.pTlasObject = nullptr; } + mTlasLastBuiltRayCount = 0; } void Scene::buildTlas(RenderContext* pRenderContext, uint32_t rayTypeCount, bool perMeshHitEntry) @@ -3825,9 +3809,10 @@ namespace Falcor mTlasCache[rayTypeCount] = tlas; updateRaytracingTLASStats(); + mTlasLastBuiltRayCount = rayTypeCount; } - void Scene::setRaytracingShaderData(RenderContext* pRenderContext, const ShaderVar& var, uint32_t rayTypeCount) + void Scene::bindShaderDataForRaytracing(RenderContext* pRenderContext, const ShaderVar& sceneVar, uint32_t rayTypeCount) { // On first execution or if BLASes need to be rebuilt, create BLASes for all geometries. if (!mBlasDataValid) @@ -3836,6 +3821,12 @@ namespace Falcor buildBlas(pRenderContext); } + // Find any valid TLAS, if none found, create it for rayTypeCount == 1 + if (rayTypeCount == 0) + rayTypeCount = mTlasLastBuiltRayCount; + if (rayTypeCount == 0) + rayTypeCount = 1; + // On first execution, when meshes have moved, when there's a new ray type count, or when a BLAS has changed, create/update the TLAS // // The raytracing shader table has one hit record per ray type and geometry. We need to know the ray type count in order to setup the indexing properly. @@ -3858,7 +3849,7 @@ namespace Falcor // Bind Scene parameter block. getCamera()->bindShaderData(mpSceneBlock->getRootVar()[kCamera]); // TODO REMOVE: Shouldn't be needed anymore? - var[kParameterBlockName] = mpSceneBlock; + sceneVar = mpSceneBlock; } std::vector Scene::getMeshBlasIDs() const @@ -4096,8 +4087,8 @@ namespace Falcor // Bind variables. auto var = mpUpdateMeshPass->getRootVar()["meshUpdater"]; var["vertexCount"] = meshDesc.vertexCount; - var["vbOffset"] = meshDesc.vbOffset; - var["vertexData"] = getMeshVao()->getVertexBuffer(kStaticDataBufferIndex); + var["vbOffset"].setBlob(meshDesc.vbOffset); + mMeshStaticData.bindShaderData(var["vertexData"]); for (const auto& name : kMeshUpdaterRequiredBufferNames) { FALCOR_CHECK(buffers.find(name) != buffers.end(), "Mesh data buffer '{}' is missing.", name); @@ -4202,7 +4193,7 @@ namespace Falcor inline void getMaterialParamsPython(Scene& scene, ref materialIDsBuffer, ref paramsBuffer) { // Get material IDs from buffer. - FALCOR_CHECK(materialIDsBuffer->getElementSize() == sizeof(uint32_t), "Material IDs buffer must contain uint32_t elements."); + FALCOR_CHECK(materialIDsBuffer->getStructSize() == sizeof(uint32_t), "Material IDs buffer must contain uint32_t elements."); std::vector materialIDs = materialIDsBuffer->getElements(); // Fetch material parameters. @@ -4229,7 +4220,7 @@ namespace Falcor inline void setMaterialParamsPython(Scene& scene, ref materialIDsBuffer, ref paramsBuffer) { // Get material IDs buffer. - FALCOR_CHECK(materialIDsBuffer->getElementSize() == sizeof(uint32_t), "Material IDs buffer must contain uint32_t elements."); + FALCOR_CHECK(materialIDsBuffer->getStructSize() == sizeof(uint32_t), "Material IDs buffer must contain uint32_t elements."); std::vector materialIDs = materialIDsBuffer->getElements(); // Get material parameters from buffer. @@ -4339,7 +4330,6 @@ namespace Falcor scene.def_property(kAnimated.c_str(), &Scene::isAnimated, &Scene::setIsAnimated); scene.def_property(kLoopAnimations.c_str(), &Scene::isLooped, &Scene::setIsLooped); scene.def_property(kRenderSettings.c_str(), pybind11::overload_cast<>(&Scene::getRenderSettings, pybind11::const_), &Scene::setRenderSettings); - scene.def_property(kUpdateCallback.c_str(), &Scene::getUpdateCallback, &Scene::setUpdateCallback); scene.def(kSetEnvMap.c_str(), &Scene::loadEnvMap, "path"_a); scene.def(kGetLight.c_str(), &Scene::getLight, "index"_a); @@ -4366,7 +4356,7 @@ namespace Falcor return scene->getGeometryIDs(pMaterial.get()); }, "material"_a); scene.def("replace_material", [](const Scene* pScene, uint32_t index, ref replacementMaterial) { - pScene->getMaterialSystem().replaceMaterial(MaterialID{ index }, replacementMaterial); }, "index"_a, "replacement_material"_a); + pScene->replaceMaterial(MaterialID{ index }, replacementMaterial); }, "index"_a, "replacement_material"_a); scene.def("get_material_params", getMaterialParamsPython); scene.def("set_material_params", setMaterialParamsPython); diff --git a/Source/Falcor/Scene/Scene.h b/Source/Falcor/Scene/Scene.h index f738010f0..e52b94307 100644 --- a/Source/Falcor/Scene/Scene.h +++ b/Source/Falcor/Scene/Scene.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,6 +29,7 @@ #include "SceneIDs.h" #include "SceneTypes.slang" #include "HitInfo.h" +#include "IScene.h" #include "Animation/Animation.h" #include "Animation/AnimationController.h" #include "Displacement/DisplacementUpdateTask.slang" @@ -53,6 +54,9 @@ #include "Utils/Math/Matrix.h" #include "Utils/UI/Gui.h" #include "Utils/Settings/Settings.h" +#include "Utils/SplitBuffer.h" + +#include #include #include @@ -61,6 +65,7 @@ #include #include #include +#include namespace Falcor { @@ -106,7 +111,7 @@ namespace Falcor - "InstanceID() + GeometryIndex()" is used for indexing into GeometryInstanceData. - This is wrapped in getGeometryInstanceID() in Raytracing.slang. */ - class FALCOR_API Scene : public Object + class FALCOR_API Scene : public IScene { FALCOR_OBJECT(Scene) public: @@ -115,54 +120,12 @@ namespace Falcor using UpDirection = CameraController::UpDirection; - using UpdateCallback = std::function& pScene, double currentTime)>; + using SplitVertexBuffer = SplitBuffer; + using SplitIndexBuffer = SplitBuffer; static constexpr uint32_t kMaxBonesPerVertex = 4; static constexpr uint32_t kInvalidAttributeIndex = -1; - /** Flags indicating if and what was updated in the scene. - */ - enum class UpdateFlags - { - None = 0x0, ///< Nothing happened. - GeometryMoved = 0x1, ///< Geometry moved. - CameraMoved = 0x2, ///< The camera moved. - CameraPropertiesChanged = 0x4, ///< Some camera properties changed, excluding position. - CameraSwitched = 0x8, ///< Selected a different camera. - LightsMoved = 0x10, ///< Lights were moved. - LightIntensityChanged = 0x20, ///< Light intensity changed. - LightPropertiesChanged = 0x40, ///< Other light changes not included in LightIntensityChanged and LightsMoved. - SceneGraphChanged = 0x80, ///< Any transform in the scene graph changed. - LightCollectionChanged = 0x100, ///< Light collection changed (mesh lights). - MaterialsChanged = 0x200, ///< Materials changed. - EnvMapChanged = 0x400, ///< Environment map changed. - EnvMapPropertiesChanged = 0x800, ///< Environment map properties changed (check EnvMap::getChanges() for more specific information). - LightCountChanged = 0x1000, ///< Number of active lights changed. - RenderSettingsChanged = 0x2000, ///< Render settings changed. - GridVolumesMoved = 0x4000, ///< Grid volumes were moved. - GridVolumePropertiesChanged = 0x8000, ///< Grid volume properties changed. - GridVolumeGridsChanged = 0x10000, ///< Grid volume grids changed. - GridVolumeBoundsChanged = 0x20000, ///< Grid volume bounds changed. - CurvesMoved = 0x40000, ///< Curves moved. - CustomPrimitivesMoved = 0x80000, ///< Custom primitives moved. - GeometryChanged = 0x100000, ///< Scene geometry changed (added/removed). - DisplacementChanged = 0x200000, ///< Displacement mapping parameters changed. - SDFGridConfigChanged = 0x400000, ///< SDF grid config changed. - SDFGeometryChanged = 0x800000, ///< SDF grid geometry changed. - MeshesChanged = 0x1000000, ///< Mesh data changed (skinning or vertex animations). - SceneDefinesChanged = 0x2000000, ///< Scene defines changed. All programs that access the scene must be updated! - TypeConformancesChanged = 0x4000000, ///< Type conformances changed. All programs that access the scene must be updated! - ShaderCodeChanged = 0x8000000, ///< Shader code changed. All programs that access the scene must be updated! - EmissiveMaterialsChanged = 0x10000000, ///< Emissive materials changed. - - /// Flags indicating that programs that access the scene need to be recompiled. - /// This is needed if defines, type conformances, and/or the shader code has changed. - /// The goal is to minimize changes that require recompilation, as it can be costly. - RecompileNeeded = SceneDefinesChanged | TypeConformancesChanged | ShaderCodeChanged, - - All = -1 - }; - /** Settings for how the scene ray tracing acceleration structures are updated. */ enum class UpdateMode @@ -230,32 +193,6 @@ namespace Falcor bool operator!=(const SDFGridConfig& other) const { return !(*this == other); } }; - /** Render settings determining how the scene is rendered. - This is used primarily by the path tracer renderers. - */ - struct RenderSettings - { - bool useEnvLight = true; ///< Enable lighting from environment map. - bool useAnalyticLights = true; ///< Enable lighting from analytic lights. - bool useEmissiveLights = true; ///< Enable lighting from emissive lights. - bool useGridVolumes = true; ///< Enable rendering of grid volumes. - - // DEMO21 - float diffuseAlbedoMultiplier = 1.f; ///< Fixed multiplier applied to material diffuse albedo. - - bool operator==(const RenderSettings& other) const - { - return (useEnvLight == other.useEnvLight) && - (useAnalyticLights == other.useAnalyticLights) && - (useEmissiveLights == other.useEmissiveLights) && - (useGridVolumes == other.useGridVolumes); - } - - bool operator!=(const RenderSettings& other) const { return !(*this == other); } - }; - - static_assert(std::is_trivially_copyable() , "RenderSettings needs to be trivially copyable"); - /** Optional importer-provided rendering metadata */ struct Metadata @@ -305,7 +242,9 @@ namespace Falcor */ struct SceneData { - std::filesystem::path path; ///< Path of the asset file the scene was loaded from. + using ImportDict = std::map; + std::vector importPaths; ///< Paths of the asset files the scene was loaded from. + std::vector importDicts; ///< Dictionaries used to load each asset in importPaths. RenderSettings renderSettings; ///< Render settings. std::vector> cameras; ///< List of cameras. uint32_t selectedCamera = 0; ///< Index of selected camera. @@ -315,7 +254,6 @@ namespace Falcor std::vector> gridVolumes; ///< List of grid volumes. std::vector> grids; ///< List of grids. ref pEnvMap; ///< Environment map. - ref pLightProfile; ///< DEMO21: Global light profile. std::vector sceneGraph; ///< Scene graph nodes. std::vector> animations; ///< List of animations. Metadata metadata; ///< Scene meadata. @@ -335,9 +273,12 @@ namespace Falcor bool has32BitIndices = false; ///< True if 32-bit mesh indices are used. uint32_t meshDrawCount = 0; ///< Number of meshes to draw. - std::vector meshIndexData; ///< Vertex indices for all meshes in either 32-bit or 16-bit format packed tightly, decided per mesh. - std::vector meshStaticData; ///< Vertex attributes for all meshes in packed format. - std::vector meshSkinningData; ///< Additional vertex attributes for skinned meshes. + /// Vertex indices for all meshes in either 32-bit or 16-bit format packed tightly, decided per mesh. + SplitIndexBuffer meshIndexData; + /// Vertex attributes for all meshes in packed format. + SplitVertexBuffer meshStaticData; + /// Additional vertex attributes for skinned meshes. + std::vector meshSkinningData; // Curve data std::vector curveDesc; ///< List of curve descriptors. @@ -467,13 +408,13 @@ namespace Falcor /** Return the associated GPU device. */ - const ref& getDevice() const { return mpDevice; } + const ref& getDevice() const override { return mpDevice; } /** Bind the scene to a given shader var. Note that the scene may change between calls to update(). The caller should rebind the scene data before executing any program that accesses the scene. */ - void bindShaderData(const ShaderVar& var) const { var = mpSceneBlock; } + void bindShaderData(const ShaderVar& sceneVar) const override { sceneVar = mpSceneBlock; } /** Get scene defines. These defines must be set on all programs that access the scene. @@ -504,7 +445,7 @@ namespace Falcor /** Get the render settings. */ - const RenderSettings& getRenderSettings() const { return mRenderSettings; } + const RenderSettings& getRenderSettings() const override { return mRenderSettings; } /** Get the render settings. */ @@ -520,7 +461,7 @@ namespace Falcor /** Returns true if environment map is available and should be used as a distant light. */ - bool useEnvLight() const; + bool useEnvLight() const override; /** Returns true if there are active analytic lights and they should be used for lighting. */ @@ -528,7 +469,7 @@ namespace Falcor /** Returns true if there are active emissive lights and they should be used for lighting. */ - bool useEmissiveLights() const; + bool useEmissiveLights() const override; /** Returns true if there are active grid volumes and they should be rendererd. */ @@ -538,17 +479,9 @@ namespace Falcor */ const Metadata& getMetadata() { return mMetadata; } - /** Get the scene update callback. - */ - UpdateCallback getUpdateCallback() const { return mUpdateCallback; } - - /** Set the scene update callback. - */ - void setUpdateCallback(UpdateCallback updateCallback) { mUpdateCallback = updateCallback; } - /** Access the scene's currently selected camera to change properties or to use elsewhere. */ - const ref& getCamera(); + const ref& getCamera() const override; /** Get the camera bounds */ @@ -846,7 +779,17 @@ namespace Falcor /** Get the material system. */ - MaterialSystem& getMaterialSystem() const { return *mpMaterials; } + const MaterialSystem& getMaterialSystem() const override { return *mpMaterials; } + + void replaceMaterial(const MaterialID materialID, const ref& pReplacement) const + { + mpMaterials->replaceMaterial(materialID, pReplacement); + } + + void setDefaultTextureSampler(const ref& pSampler) override + { + mpMaterials->setDefaultTextureSampler(pSampler); + } /** Get a list of all materials in the scene. */ @@ -892,7 +835,7 @@ namespace Falcor /** Get the scene bounds in world space. */ - const AABB& getSceneBounds() const { return mSceneBB; } + const AABB& getSceneBounds() const override { return mSceneBB; } /** Get a mesh's bounds in object space. */ @@ -920,15 +863,7 @@ namespace Falcor /** Get a list of all active lights in the scene. */ - const std::vector>& getActiveLights() const { return mActiveLights; } - - /** Get the number of active lights in the scene. - */ - uint32_t getActiveLightCount() const { return (uint32_t)mActiveLights.size(); } - - /** Get an active light. - */ - const ref& getActiveLight(uint32_t lightID) const { return mActiveLights[lightID]; } + const std::vector>& getActiveAnalyticLights() const { return mActiveLights; } /** Get the light collection representing all the mesh lights in the scene. The light collection is created lazily on the first call. It needs a render context. @@ -940,7 +875,7 @@ namespace Falcor /** Get the environment map or nullptr if it doesn't exist. */ - const ref& getEnvMap() const { return mpEnvMap; } + const ref& getEnvMap() const override { return mpEnvMap; } /** Set how the scene's TLASes are updated when raytracing. TLASes are REBUILT by default. @@ -965,13 +900,13 @@ namespace Falcor \param[in] currentTime The current time in seconds. \return Flags indicating what changes happened in the update. */ - UpdateFlags update(RenderContext* pRenderContext, double currentTime); + IScene::UpdateFlags update(RenderContext* pRenderContext, double currentTime); /** Get the changes that happened during the last update. The flags only change during an `update()` call, if something changed between calling `update()` and `getUpdates()`, the returned result will not reflect it. \return Flags indicating what changes happened in the last update. */ - UpdateFlags getUpdates() const { return mUpdates; } + IScene::UpdateFlags getUpdates() const { return mUpdates; } /** Update material and geometry for inverse rendering applications. This is a subset of the update() function. @@ -1056,9 +991,17 @@ namespace Falcor */ bool onGamepadState(const GamepadState& gamepadState); - /** Get the path that the scene was loaded from. + /** Get the last path that was loaded to create the scene. + */ + std::filesystem::path getPath() const { return mImportPaths.empty() ? std::filesystem::path() : mImportPaths.back(); } + + /** Get all of the paths that were loaded to create the scene. */ - const std::filesystem::path& getPath() const { return mPath; } + std::vector getImportPaths() const { return mImportPaths; } + + /** Get all of the dictionaries that were loaded to create the scene. + */ + std::vector> getImportDicts() const { return mImportDicts; } /** Get the animation controller. */ @@ -1095,10 +1038,10 @@ namespace Falcor /** Set the scene ray tracing resources into a shader var. The acceleration structure is created lazily, which requires the render context. \param[in] pRenderContext Render context. - \param[in] var Shader variable to set data into, usually the root var. + \param[in] sceneVar Shader variable to set data into, usually the root var. \param[in] rayTypeCount Number of ray types in raygen program. Not needed for DXR 1.1. */ - void setRaytracingShaderData(RenderContext* pRenderContext, const ShaderVar& var, uint32_t rayTypeCount = 1); + void bindShaderDataForRaytracing(RenderContext* pRenderContext, const ShaderVar& sceneVar, uint32_t rayTypeCount = 0); /** Get the name of the mesh with the given ID. */ @@ -1121,6 +1064,40 @@ namespace Falcor uint64_t getMemoryUsageInBytes() const { return getSceneStats().getTotalMemory(); } + /** Allows connecting to signal that signals IScene::UpdateFlags when they are changed. + */ + UpdateFlagsSignal::Interface getUpdateFlagsSignal() override { return mUpdateFlagsSignal.getInterface(); } + + public: /// IScene specific methods + void getShaderDefines(DefineList& defines) const override + { + defines.add(getSceneDefines()); + } + + void getTypeConformances(TypeConformanceList& conformances, TypeConformancesKind kind = TypeConformancesKind::All) const override + { + /// We only have material conformances + if (is_set(kind, TypeConformancesKind::Material)) + conformances.add(getTypeConformances()); + } + + void getShaderModules(ProgramDesc::ShaderModuleList& shaderModuleList) const override + { + auto localModuleList = getShaderModules(); + shaderModuleList.insert(shaderModuleList.end(), localModuleList.begin(), localModuleList.end()); + } + + ref getILightCollection(RenderContext* renderContext) override + { + return getLightCollection(renderContext); + } + + RtPipelineFlags getRtPipelineFlags() const override + { + if (!hasProceduralGeometry()) + return RtPipelineFlags::SkipProceduralPrimitives; + return RtPipelineFlags::None; + } private: friend class AnimationController; friend class AnimatedVertexCache; @@ -1129,9 +1106,9 @@ namespace Falcor static constexpr uint32_t kDrawIdBufferIndex = kStaticDataBufferIndex + 1; static constexpr uint32_t kVertexBufferCount = kDrawIdBufferIndex + 1; - void createMeshVao(uint32_t drawCount, const std::vector& indexData, const std::vector& staticData, const std::vector& skinningData); + void createMeshVao(uint32_t drawCount, const std::vector& skinningData); void createCurveVao(const std::vector& indexData, const std::vector& staticData); - void createMeshUVTiles(const std::vector& meshDesc, const std::vector& indexData, const std::vector& staticData); + void createMeshUVTiles(const std::vector& meshDesc); void updateSceneDefines(); DefineList getSceneSDFGridDefines() const; @@ -1204,7 +1181,7 @@ namespace Falcor /** Check whether scene has an index buffer. */ - bool hasIndexBuffer() const { return mpMeshVao && mpMeshVao->getIndexBuffer() != nullptr; } + bool hasIndexBuffer() const { return !mMeshIndexData.empty(); } /** Initialize all cameras in the scene through the animation controller using their corresponding scene graph nodes. */ @@ -1218,16 +1195,16 @@ namespace Falcor */ bool updateAnimatable(Animatable& animatable, const AnimationController& controller, bool force = false); - UpdateFlags updateSelectedCamera(bool forceUpdate); - UpdateFlags updateLights(bool forceUpdate); - UpdateFlags updateGridVolumes(bool forceUpdate); - UpdateFlags updateEnvMap(bool forceUpdate); - UpdateFlags updateMaterials(bool forceUpdate); - UpdateFlags updateGeometry(RenderContext* pRenderContext, bool forceUpdate); - UpdateFlags updateProceduralPrimitives(bool forceUpdate); - UpdateFlags updateRaytracingAABBData(bool forceUpdate); - UpdateFlags updateDisplacement(RenderContext* pRenderContext, bool forceUpdate); - UpdateFlags updateSDFGrids(RenderContext* pRenderContext); + IScene::UpdateFlags updateSelectedCamera(bool forceUpdate); + IScene::UpdateFlags updateLights(bool forceUpdate); + IScene::UpdateFlags updateGridVolumes(bool forceUpdate); + IScene::UpdateFlags updateEnvMap(bool forceUpdate); + IScene::UpdateFlags updateMaterials(bool forceUpdate); + IScene::UpdateFlags updateGeometry(RenderContext* pRenderContext, bool forceUpdate); + IScene::UpdateFlags updateProceduralPrimitives(bool forceUpdate); + IScene::UpdateFlags updateRaytracingAABBData(bool forceUpdate); + IScene::UpdateFlags updateDisplacement(RenderContext* pRenderContext, bool forceUpdate); + IScene::UpdateFlags updateSDFGrids(RenderContext* pRenderContext); void updateGeometryStats(); void updateMaterialStats(); @@ -1266,8 +1243,8 @@ namespace Falcor bool mHas16BitIndices = false; ///< True if any meshes use 16-bit indices. bool mHas32BitIndices = false; ///< True if any meshes use 32-bit indices. - ref mpMeshVao; ///< Vertex array object for the global mesh vertex/index buffers. - ref mpMeshVao16Bit; ///< VAO for drawing meshes with 16-bit vertex indices. + ref mpMeshVao; ///< Vertex array object for the global mesh vertex/index buffers. + ref mpMeshVao16Bit; ///< VAO for drawing meshes with 16-bit vertex indices. ref mpCurveVao; ///< Vertex array object for the global curve vertex/index buffers. std::vector mDrawArgs; ///< List of draw arguments for rasterizing the meshes in the scene. @@ -1329,7 +1306,6 @@ namespace Falcor ref mpLightCollection; ///< Class for managing emissive geometry. This is created lazily upon first use. ref mpEnvMap; ///< Environment map or nullptr if not loaded. bool mEnvMapChanged = false; ///< Flag indicating that the environment map has changed since last frame. - ref mpLightProfile; ///< DEMO21: Global light profile. // Scene metadata (CPU only) std::vector mMeshBBs; ///< Bounding boxes for meshes (not instances) in object space. @@ -1346,8 +1322,6 @@ namespace Falcor DefineList mPrevSceneDefines; ///< List of defines for the previous frame. TypeConformanceList mTypeConformances; ///< Current list of type conformances that need to be set on any program accessing the scene. - UpdateCallback mUpdateCallback; ///< Scene update callback. - // Scene block resources ref mpGeometryInstancesBuffer; ref mpMeshesBuffer; @@ -1384,7 +1358,7 @@ namespace Falcor // Rendering std::map> mFrontClockwiseRS; std::map> mFrontCounterClockwiseRS; - UpdateFlags mUpdates = UpdateFlags::All; + IScene::UpdateFlags mUpdates = IScene::UpdateFlags::All; std::unique_ptr mpAnimationController; // Raytracing data @@ -1404,6 +1378,7 @@ namespace Falcor ///< Number of ray types in program affects Shader Table indexing. ref mpTlasScratch; ///< Scratch buffer used for TLAS builds. Can be shared as long as instance desc count is the same, which for now it is. RtAccelerationStructurePrebuildInfo mTlasPrebuildInfo; ///< This can be reused as long as the number of instance descs doesn't change. + uint32_t mTlasLastBuiltRayCount = 0; ///< RayTypeCount of the last built TLAS, zero if there is none /** Describes one BLAS. */ @@ -1457,9 +1432,24 @@ namespace Falcor bool mBlasDataValid = false; ///< Flag to indicate if the BLAS data is valid. This will be reset when geometry is changed. bool mRebuildBlas = true; ///< Flag to indicate BLASes need to be rebuilt. - std::filesystem::path mPath; + std::vector mImportPaths; ///< Vector of paths to assets loaded to create scene. + std::vector mImportDicts; ///< Vector of dictionaries associated with each asset loaded to create scene. bool mFinalized = false; ///< True if scene is ready to be bound to the GPU. - }; - FALCOR_ENUM_CLASS_OPERATORS(Scene::UpdateFlags); + /// Used for very large scenes + SplitIndexBuffer mMeshIndexData; + SplitVertexBuffer mMeshStaticData; + + UpdateFlagsSignal mUpdateFlagsSignal; + public: + SplitVertexBuffer& getMeshStaticData() + { + return mMeshStaticData; + } + + const SplitVertexBuffer& getMeshStaticData() const + { + return mMeshStaticData; + } + }; } diff --git a/Source/Falcor/Scene/Scene.slang b/Source/Falcor/Scene/Scene.slang index dde8238a1..bb0f4fa16 100644 --- a/Source/Falcor/Scene/Scene.slang +++ b/Source/Falcor/Scene/Scene.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,7 +25,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -#ifndef FALCOR_USE_SCENE2 #include "SceneDefines.slangh" __exported import Scene.HitInfo; @@ -58,9 +57,6 @@ import Rendering.Materials.TexLODHelpers; */ struct Scene { - // DEMO21 - static bool kUseLightProfile = SCENE_USE_LIGHT_PROFILE; - // Raytracing [root] RaytracingAccelerationStructure rtAccel; @@ -76,10 +72,13 @@ struct Scene // Triangle meshes StructuredBuffer meshes; - [root] StructuredBuffer vertices; ///< Vertex data for this frame. + /// Vertex data for this frame. + SplitVertexBuffer vertices; + StructuredBuffer prevVertices; ///< Vertex data for the previous frame, for dynamic meshes only. #if SCENE_HAS_INDEXED_VERTICES - [root] ByteAddressBuffer indexData; ///< Vertex indices, three indices per triangle packed tightly. The format is specified per mesh. + /// Vertex indices, three indices per triangle packed tightly. The format is specified per mesh. + SplitIndexBuffer indexData; #endif // Curves @@ -108,7 +107,6 @@ struct Scene uint lightCount; StructuredBuffer lights; LightCollection lightCollection; - LightProfile lightProfile; EnvMap envMap; Camera camera; @@ -294,13 +292,11 @@ struct Scene uint3 vtxIndices; if (read16Bit) { - baseIndex += triangleIndex * 6; - vtxIndices = (uint3)indexData.Load(baseIndex); + vtxIndices = (uint3)indexData.Load16b(ibOffset, triangleIndex); } else { - baseIndex += triangleIndex * 12; - vtxIndices = indexData.Load3(baseIndex); + vtxIndices = indexData.Load32b(ibOffset, triangleIndex); } #else // !SCENE_HAS_INDEXED_VERTICES uint baseIndex = triangleIndex * 3; @@ -978,5 +974,3 @@ struct Scene }; ParameterBlock gScene; -#else -#endif diff --git a/Source/Falcor/Scene/SceneBuilder.cpp b/Source/Falcor/Scene/SceneBuilder.cpp index faf823c5e..d904ef6d4 100644 --- a/Source/Falcor/Scene/SceneBuilder.cpp +++ b/Source/Falcor/Scene/SceneBuilder.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -280,7 +280,9 @@ namespace Falcor throw ImporterError(path, "Can't find scene file '{}'.", path); } - mSceneData.path = resolvedPath; + mSceneData.importPaths.push_back(resolvedPath); + mSceneData.importDicts.push_back(materialToShortName); + if (auto importer = Importer::create(getExtensionFromPath(resolvedPath))) { importer->importScene(resolvedPath, *this, materialToShortName); @@ -296,7 +298,9 @@ namespace Falcor logInfo("Importing scene from memory"); std::map materialToShortName = convertDictToMap(dict); - mSceneData.path = ""; + mSceneData.importPaths.push_back(""); + mSceneData.importDicts.push_back(materialToShortName); + if (auto importer = Importer::create(extension)) { importer->importSceneFromMemory(buffer, byteSize, extension, *this, materialToShortName); @@ -997,7 +1001,7 @@ namespace Falcor void SceneBuilder::loadLightProfile(const std::string& filename, bool normalize) { - mSceneData.pLightProfile = LightProfile::createFromIesProfile(mpDevice, mAssetResolver.resolvePath(std::filesystem::path(filename)), normalize); + mSceneData.pMaterials->loadLightProfile(mAssetResolver.resolvePath(std::filesystem::path(filename)), normalize); } // Cameras @@ -1164,6 +1168,11 @@ namespace Falcor } } + MaterialTextureLoader& SceneBuilder::getMaterialTextureLoader() + { + return *mpMaterialTextureLoader; + } + // Internal void SceneBuilder::updateLinkedObjects(NodeID nodeID, NodeID newNodeID) @@ -2270,7 +2279,8 @@ namespace Falcor // Early out if splitting is not needed or possible. size_t triangleCount = 0; - if (!needsSplit(meshGroup, triangleCount)) return MeshGroupList{ std::move(meshGroup) }; + if (!needsSplit(meshGroup, triangleCount)) + return MeshGroupList{ std::move(meshGroup) }; // Find the midpoint along the largest axis. AABB bb = calculateBoundingBox(meshGroup); @@ -2283,12 +2293,33 @@ namespace Falcor for (auto meshID : meshGroup.meshList) { auto result = splitMesh(meshID, axis, pos); - if (auto leftMeshID = result.first) leftMeshes.push_back(*leftMeshID); - if (auto rightMeshID = result.second) rightMeshes.push_back(*rightMeshID); + if (auto leftMeshID = result.first) + leftMeshes.push_back(*leftMeshID); + if (auto rightMeshID = result.second) + rightMeshes.push_back(*rightMeshID); + } + + // If either side contains all meshes, we just sort by their centroid and split in half + if (leftMeshes.empty() || rightMeshes.empty()) + { + std::sort(meshGroup.meshList.begin(), + meshGroup.meshList.end(), + [&](MeshID lhs, MeshID rhs) + { + return mMeshes[lhs.get()].boundingBox.center()[axis] < mMeshes[rhs.get()].boundingBox.center()[axis]; + }); + + size_t totalMeshCount = meshGroup.meshList.size(); + if (totalMeshCount > 1) + { + size_t halfCount = totalMeshCount / 2; + leftMeshes.assign(meshGroup.meshList.begin(), meshGroup.meshList.begin() + halfCount); + rightMeshes.assign(meshGroup.meshList.begin() + halfCount, meshGroup.meshList.end()); + } } - // If either side contains all meshes, do not split further. - if (leftMeshes.empty() || rightMeshes.empty()) return MeshGroupList{ std::move(meshGroup) }; + if (leftMeshes.empty() || rightMeshes.empty()) + return MeshGroupList{ meshGroup }; // Recursively split the left and right mesh groups. MeshGroup leftGroup{ std::move(leftMeshes), meshGroup.isStatic }; @@ -2392,45 +2423,37 @@ namespace Falcor const bool isIndexed = !is_set(mFlags, Flags::NonIndexedVertices); // Count total number of vertex and index data elements. - size_t totalIndexDataCount = 0; - size_t totalStaticVertexCount = 0; size_t totalSkinningVertexCount = 0; - - for (const auto& mesh : mMeshes) + for (auto& mesh : mMeshes) { - totalIndexDataCount += mesh.indexData.size(); - totalStaticVertexCount += mesh.staticData.size(); totalSkinningVertexCount += mesh.skinningData.size(); mSceneData.prevVertexCount += mesh.prevVertexCount; } - // Check the range. We currently use 32-bit offsets. - if (totalIndexDataCount > std::numeric_limits::max() || - totalStaticVertexCount > std::numeric_limits::max() || - totalSkinningVertexCount > std::numeric_limits::max()) + // Check the range. + if (totalSkinningVertexCount > std::numeric_limits::max()) { FALCOR_THROW("Trying to build a scene that exceeds supported mesh data size."); } - mSceneData.meshIndexData.reserve(totalIndexDataCount); - mSceneData.meshStaticData.reserve(totalStaticVertexCount); + mSceneData.meshIndexData.setName("mMeshIndexData"); + mSceneData.meshStaticData.setName("meshStaticData"); + mSceneData.meshSkinningData.reserve(totalSkinningVertexCount); // Copy all vertex and index data into the global buffers. for (auto& mesh : mMeshes) { - mesh.staticVertexOffset = (uint32_t)mSceneData.meshStaticData.size(); mesh.skinningVertexOffset = (uint32_t)mSceneData.meshSkinningData.size(); mesh.prevVertexOffset = mesh.skinningVertexOffset; // Insert the static vertex data in the global array. // The vertices are automatically converted to their packed format in this step. - mSceneData.meshStaticData.insert(mSceneData.meshStaticData.end(), mesh.staticData.begin(), mesh.staticData.end()); + mesh.staticVertexOffset = mSceneData.meshStaticData.insert(mesh.staticData.begin(), mesh.staticData.end()); if (isIndexed) { - mesh.indexOffset = (uint32_t)mSceneData.meshIndexData.size(); - mSceneData.meshIndexData.insert(mSceneData.meshIndexData.end(), mesh.indexData.begin(), mesh.indexData.end()); + mesh.indexOffset = mSceneData.meshIndexData.insert(mesh.indexData.begin(), mesh.indexData.end()); } if (mesh.isSkinned()) @@ -2986,6 +3009,6 @@ namespace Falcor sceneBuilder.def("addCustomPrimitive", &SceneBuilder::addCustomPrimitive); sceneBuilder.def("getSettings", static_cast(&SceneBuilder::getSettings), pybind11::return_value_policy::reference); - sceneBuilder.def_property_readonly("assetResolver", &SceneBuilder::getAssetResolver, pybind11::return_value_policy::reference); + sceneBuilder.def_property_readonly("assetResolver", pybind11::overload_cast<>(&SceneBuilder::getAssetResolver), pybind11::return_value_policy::reference); } } diff --git a/Source/Falcor/Scene/SceneBuilder.h b/Source/Falcor/Scene/SceneBuilder.h index 0855e42c8..ffa9f6d1d 100644 --- a/Source/Falcor/Scene/SceneBuilder.h +++ b/Source/Falcor/Scene/SceneBuilder.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -360,6 +360,7 @@ namespace Falcor /// Access the current asset resolver (on top of the stack). AssetResolver& getAssetResolver() { return mAssetResolver; } + const AssetResolver& getAssetResolver() const { return mAssetResolver; } /// Push the state of the asset resolver to the stack. void pushAssetResolver(); @@ -667,6 +668,9 @@ namespace Falcor */ void setNodeInterpolationMode(NodeID nodeID, Animation::InterpolationMode interpolationMode, bool enableWarping); + /** Returns texture manager used by the material system. + */ + MaterialTextureLoader& getMaterialTextureLoader(); private: struct InternalNode : Node { diff --git a/Source/Falcor/Scene/SceneBuilderDump.cpp b/Source/Falcor/Scene/SceneBuilderDump.cpp index e47478092..5e1d6e7bf 100644 --- a/Source/Falcor/Scene/SceneBuilderDump.cpp +++ b/Source/Falcor/Scene/SceneBuilderDump.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,7 +29,7 @@ #include "Scene/SceneBuilder.h" #include "Utils/Math/FNVHash.h" #include -#include +#include /// SceneBuilder printing is split off to its own file to avoid polluting the SceneBuilder.cpp with debug prints diff --git a/Source/Falcor/Scene/SceneCache.cpp b/Source/Falcor/Scene/SceneCache.cpp index 47c41e741..51e7f9d2f 100644 --- a/Source/Falcor/Scene/SceneCache.cpp +++ b/Source/Falcor/Scene/SceneCache.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -117,6 +117,17 @@ namespace Falcor if (hasValue) write(opt.value()); } + template + void write(const std::map& map) + { + write((uint32_t)map.size()); + for (const auto& e : map) + { + write(e.first); + write(e.second); + } + } + private: std::ostream& mStream; }; @@ -188,6 +199,18 @@ namespace Falcor } } + template + void read(std::map& map) + { + uint32_t count = read(); + for (uint32_t i = 0; i < count; ++i) + { + K k = read(); + V v = read(); + map.emplace(k, v); + } + } + private: std::istream& mStream; }; @@ -265,8 +288,13 @@ namespace Falcor void SceneCache::writeSceneData(OutputStream& stream, const Scene::SceneData& sceneData) { - writeMarker(stream, "Path"); - stream.write(sceneData.path); + writeMarker(stream, "Paths"); + stream.write((uint32_t)sceneData.importPaths.size()); + for (const auto& pPath: sceneData.importPaths) stream.write(pPath); + + writeMarker(stream, "Dicts"); + stream.write((uint32_t)sceneData.importDicts.size()); + for (const auto& pDict: sceneData.importDicts) stream.write(pDict); writeMarker(stream, "RenderSettings"); stream.write(sceneData.renderSettings); @@ -347,8 +375,8 @@ namespace Falcor stream.write(sceneData.has16BitIndices); stream.write(sceneData.has32BitIndices); stream.write(sceneData.meshDrawCount); - stream.write(sceneData.meshIndexData); - stream.write(sceneData.meshStaticData); + writeSplitBuffer(stream, sceneData.meshIndexData); + writeSplitBuffer(stream, sceneData.meshStaticData); stream.write(sceneData.meshSkinningData); writeMarker(stream, "Curves"); @@ -381,8 +409,13 @@ namespace Falcor Scene::SceneData sceneData; sceneData.pMaterials = std::make_unique(pDevice); - readMarker(stream, "Path"); - stream.read(sceneData.path); + readMarker(stream, "Paths"); + sceneData.importPaths.resize(stream.read()); + for (auto& pPath : sceneData.importPaths) stream.read(pPath); + + readMarker(stream, "Dicts"); + sceneData.importDicts.resize(stream.read()); + for (auto& pDict : sceneData.importDicts) stream.read(pDict); readMarker(stream, "RenderSettings"); stream.read(sceneData.renderSettings); @@ -468,8 +501,8 @@ namespace Falcor stream.read(sceneData.has16BitIndices); stream.read(sceneData.has32BitIndices); stream.read(sceneData.meshDrawCount); - stream.read(sceneData.meshIndexData); - stream.read(sceneData.meshStaticData); + readSplitBuffer(stream, sceneData.meshIndexData); + readSplitBuffer(stream, sceneData.meshStaticData); stream.read(sceneData.meshSkinningData); readMarker(stream, "Curves"); @@ -947,4 +980,22 @@ namespace Falcor auto str = stream.read(); if (id != str) FALCOR_THROW("Found invalid marker"); } + + // SplitBuffer + template + void SceneCache::writeSplitBuffer(OutputStream& stream, const SplitBuffer& buffer) + { + stream.write(buffer.mBufferName); + stream.write(buffer.mBufferCountDefinePrefix); + stream.write(buffer.mCpuBuffers); + } + + template + void SceneCache::readSplitBuffer(InputStream& stream, SplitBuffer& buffer) + { + stream.read(buffer.mBufferName); + stream.read(buffer.mBufferCountDefinePrefix); + stream.read(buffer.mCpuBuffers); + } + } diff --git a/Source/Falcor/Scene/SceneCache.h b/Source/Falcor/Scene/SceneCache.h index 51e56c6b0..d2bff6fcd 100644 --- a/Source/Falcor/Scene/SceneCache.h +++ b/Source/Falcor/Scene/SceneCache.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -47,6 +47,9 @@ namespace Falcor { + template + class SplitBuffer; + /** Helper class for reading and writing scene cache files. The scene cache is used to heavily reduce load times of more complex assets. The cache stores a binary representation of `Scene::SceneData` which contains everything to re-create a `Scene`. @@ -120,5 +123,10 @@ namespace Falcor static void writeMarker(OutputStream& stream, const std::string& id); static void readMarker(InputStream& stream, const std::string& id); + + template + static void writeSplitBuffer(OutputStream& stream, const SplitBuffer& buffer); + template + static void readSplitBuffer(InputStream& stream, SplitBuffer& buffer); }; } diff --git a/Source/Falcor/Scene/SceneRayQueryInterface.slang b/Source/Falcor/Scene/SceneRayQueryInterface.slang index 7746bb15c..700f4cb1e 100644 --- a/Source/Falcor/Scene/SceneRayQueryInterface.slang +++ b/Source/Falcor/Scene/SceneRayQueryInterface.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-21, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -38,13 +38,12 @@ interface ISceneRayQuery { /** Trace a ray against the scene and return the closest hit point. \param[in] ray Ray. - \param[out] hit Hit info (only valid if function returns true). \param[out] hitT Hit distance (only valid if function returns true). \param[in] rayFlags Optional ray flags OR'ed with queries flags. \param[in] instanceInclusionMask Includes/rejects geometry based on instance mask. - \return Returns true if the ray does intersect the scene. + \return Returns Hit info, only valid when there was a hit. */ - bool traceRay(const Ray ray, out HitInfo hit, out float hitT, uint rayFlags, uint instanceInclusionMask); + HitInfo traceRay(const Ray ray, out float hitT, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff); /** Trace a visibility ray against the scene. \param[in] ray Ray. @@ -52,5 +51,5 @@ interface ISceneRayQuery \param[in] instanceInclusionMask Includes/rejects geometry based on instance mask. \return Returns true if the ray endpoints are mutually visible (i.e. the ray does NOT intersect the scene). */ - bool traceVisibilityRay(const Ray ray, uint rayFlags, uint instanceInclusionMask); + bool traceVisibilityRay(const Ray ray, uint rayFlags = RAY_FLAG_NONE, uint instanceInclusionMask = 0xff); }; diff --git a/Source/Falcor/Scene/SceneTypes.slang b/Source/Falcor/Scene/SceneTypes.slang index 731ae9b08..27f608de3 100644 --- a/Source/Falcor/Scene/SceneTypes.slang +++ b/Source/Falcor/Scene/SceneTypes.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -31,8 +31,12 @@ #ifdef HOST_CODE #include "Utils/Math/PackedFormats.h" +#include "VertexData.slang" #else import Utils.Math.PackedFormats; +import Utils.SlangUtils; +import Utils.Attributes; +__exported import Scene.VertexData; #endif BEGIN_NAMESPACE_FALCOR @@ -109,11 +113,11 @@ struct GeometryInstanceData uint materialID; uint geometryID; - uint vbOffset; ///< Offset into vertex buffer. - uint ibOffset; ///< Offset into index buffer, or zero if non-indexed. + uint vbOffset; ///< Offset into vertex buffer. + uint ibOffset; ///< Offset into index buffer, or zero if non-indexed. - uint instanceIndex; ///< InstanceIndex in TLAS. - uint geometryIndex; ///< GeometryIndex in BLAS. + uint instanceIndex; ///< InstanceIndex in TLAS. + uint geometryIndex; ///< GeometryIndex in BLAS. #ifdef HOST_CODE GeometryInstanceData() = default; @@ -149,7 +153,7 @@ enum class MeshFlags : uint32_t IsAnimated = 0x10, ///< Mesh is affected by vertex-animations. }; -/** Mesh data stored in 64B. +/** Mesh data stored in 32B. */ struct MeshDesc { @@ -299,27 +303,11 @@ struct SkinningVertexData { uint4 boneID; float4 boneWeight; - uint staticIndex; ///< The index in the static vertex buffer. + uint staticIndex; ///< The index in the static vertex buffer. uint bindMatrixID; uint skeletonMatrixID; }; -/** Struct representing interpolated vertex attributes in world space. - Note the tangent is not guaranteed to be orthogonal to the normal. - The bitangent should be computed: cross(normal, tangent.xyz) * tangent.w. - The tangent space is orthogonalized in prepareShadingData(). -*/ -struct VertexData -{ - float3 posW; ///< Position in world space. - float3 normalW; ///< Shading normal in world space (normalized). - float4 tangentW; ///< Shading tangent in world space (normalized). The last component is guaranteed to be +-1.0 or zero if tangents are missing. - float2 texC; ///< Texture coordinate. - float3 faceNormalW; ///< Face normal in world space (normalized). - float curveRadius; ///< Curve cross-sectional radius. Valid only for geometry generated from curves. - float coneTexLODValue; ///< Texture LOD data for cone tracing. This is zero, unless getVertexDataRayCones() is used. -}; - struct CurveDesc { uint vbOffset; ///< Offset into global curve vertex buffer. @@ -356,4 +344,148 @@ struct CustomPrimitiveDesc uint aabbOffset; ///< Offset into list of procedural primitive AABBs. }; +#ifndef HOST_CODE + +#ifndef SCENE_INDEX_BUFFER_COUNT +// #error "Define SCENE_INDEX_BUFFER_COUNT, SCENE_INDEX_BUFFER_INDEX_BITS" +#define SCENE_INDEX_BUFFER_COUNT 1 // here for the benefit of the IntelliSense +#define SCENE_INDEX_BUFFER_INDEX_BITS 1 +#endif // SCENE_INDEX_BUFFER_COUNT + +/** + * GPU representation for SplitBuffer. + * Unlike the SplitVertexBuffer, this wraps a ByteAddressBuffer access rather than + * StructuredBuffer, but since we do not have templates, Load has to be + * explicitly instantiated. + * + * Functions as an adaptor when we need larger-than-4GB buffers. + * In such case, the top bits of the index are used to select which buffers + * is the item in, while the lower bits address the item inside the buffer. + * Host is only used for static_asserts. + */ +struct SplitIndexBuffer +{ + typedef uint ElementType; + static constexpr uint kBufferIndexBits = SCENE_INDEX_BUFFER_INDEX_BITS; // log2(sizeof(ElementType)) + static constexpr uint kBufferIndexOffset = 32 - kBufferIndexBits; + static constexpr uint kElementIndexMask = (1u << kBufferIndexOffset) - 1; + static constexpr uint kBufferCount = SCENE_INDEX_BUFFER_COUNT; + +#if SCENE_INDEX_BUFFER_COUNT > 0 +#if SCENE_INDEX_BUFFER_COUNT > 1 + /// TODO: Once the [root] signature issue has been solved, this should be the only version + ByteAddressBuffer data[kBufferCount]; +#else + [root] ByteAddressBuffer data[kBufferCount]; +#endif + + /// baseOffset - offset to the start of the mesh + /// byteIndex - bytes to read from offset + /// Returns bytes starting baseOffset*4 + byteIndex, handling the uint overflow + /// for large buffers. + /// expected usage is LoadUint16_t3(baseOffset, baseOffset*4 + index) + uint16_t3 Load16b(uint baseOffset, uint triangleIndex) + { + if (kBufferCount == 1) + return data[0].Load(baseOffset * 4 + triangleIndex * 6); + uint bufferIndex = baseOffset >> kBufferIndexOffset; + uint byteOffset = (baseOffset & kElementIndexMask) * 4; + return data[bufferIndex].Load(byteOffset + triangleIndex * 6); + } + + uint3 Load32b(uint baseOffset, uint triangleIndex) + { + if (kBufferCount == 1) + return data[0].Load3(baseOffset * 4 + triangleIndex * 12); + uint bufferIndex = baseOffset >> kBufferIndexOffset; + uint byteOffset = (baseOffset & kElementIndexMask) * 4; + return data[bufferIndex].Load3(byteOffset + triangleIndex * 12); + } +#endif // SCENE_INDEX_BUFFER_COUNT > 0 +}; + +#ifndef SCENE_VERTEX_BUFFER_COUNT +// #error "Define SCENE_VERTEX_BUFFER_COUNT, SCENE_VERTEX_BUFFER_INDEX_BITS" +#define SCENE_VERTEX_BUFFER_COUNT 1 // here for the benefit of the IntelliSense +#define SCENE_VERTEX_BUFFER_INDEX_BITS 1 +#endif // SCENE_VERTEX_BUFFER_COUNT + +/** + * GPU representation for SplitBuffer. + * All comments apply to RWSplitVertexBuffer below as well. + * + * Functions as an adaptor when we need larger-than-4GB buffers. + * In such case, the top bits of the index are used to select which buffers + * is the item in, while the lower bits address the item inside the buffer. + * Host is only used for static_asserts. + */ +struct SplitVertexBuffer +{ + typedef PackedStaticVertexData ElementType; + static constexpr uint kBufferIndexBits = SCENE_VERTEX_BUFFER_INDEX_BITS; + static constexpr uint kBufferIndexOffset = 32 - kBufferIndexBits; + static constexpr uint kElementIndexMask = (1u << kBufferIndexOffset) - 1; + static constexpr uint kBufferCount = SCENE_VERTEX_BUFFER_COUNT; + +#if SCENE_VERTEX_BUFFER_COUNT > 1 + /// TODO: Once the [root] signature issue has been solved, this should be the only version + StructuredBuffer data[ArrayMax<1, kBufferCount>.value]; +#else + [root] StructuredBuffer data[1]; +#endif + + __subscript(uint index)->ElementType + { + get { + if (kBufferCount == 1) + return data[0][index]; + uint bufferIndex = index >> kBufferIndexOffset; + uint elementIndex = index & kElementIndexMask; + return data[bufferIndex][elementIndex]; + } + } +}; + +struct RWSplitVertexBuffer +{ + typedef PackedStaticVertexData ElementType; + static constexpr uint kBufferIndexBits = SCENE_VERTEX_BUFFER_INDEX_BITS; + static constexpr uint kBufferIndexOffset = 32 - kBufferIndexBits; + static constexpr uint kElementIndexMask = (1u << kBufferIndexOffset) - 1; + static constexpr uint kBufferCount = SCENE_VERTEX_BUFFER_COUNT; + +#if SCENE_VERTEX_BUFFER_COUNT > 1 + /// TODO: Once the [root] signature issue has been solved, this should be the only version + RWStructuredBuffer data[ArrayMax<1, kBufferCount>.value]; +#else + [root] RWStructuredBuffer data[1]; +#endif + + __subscript(uint index)->ElementType + { + get { + if (kBufferCount == 1) + return data[0][index]; + uint bufferIndex = index >> kBufferIndexOffset; + uint elementIndex = index & kElementIndexMask; + return data[bufferIndex][elementIndex]; + } + + set { + if (kBufferCount == 1) + { + data[0][index] = newValue; + } + else + { + uint bufferIndex = index >> kBufferIndexOffset; + uint elementIndex = index & kElementIndexMask; + data[bufferIndex][elementIndex] = newValue; + } + } + } +}; + +#endif /// HOST_CODE + END_NAMESPACE_FALCOR diff --git a/Source/Falcor/Scene/ShadingData.slang b/Source/Falcor/Scene/ShadingData.slang index a0b9a5cd9..1c8ee0864 100644 --- a/Source/Falcor/Scene/ShadingData.slang +++ b/Source/Falcor/Scene/ShadingData.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -26,7 +26,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ import Scene.Material.MaterialData; -import Scene.HitInfo; import Utils.Geometry.GeometryHelpers; import Utils.Math.ShadingFrame; @@ -34,9 +33,9 @@ import Utils.Math.ShadingFrame; This includes: - Geometric properties of the surface. + - View direction. - Texture coordinates. - Material ID and header. - - Opacity value for alpha testing. - Index of refraction of the surrounding medium. Based on a ShadingData struct, the material system can be queried diff --git a/Source/Falcor/Scene/VertexData.slang b/Source/Falcor/Scene/VertexData.slang new file mode 100644 index 000000000..0e1949b1c --- /dev/null +++ b/Source/Falcor/Scene/VertexData.slang @@ -0,0 +1,49 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Utils/HostDeviceShared.slangh" + +BEGIN_NAMESPACE_FALCOR + +/** Struct representing interpolated vertex attributes in world space. + Note the tangent is not guaranteed to be orthogonal to the normal. + The bitangent should be computed: cross(normal, tangent.xyz) * tangent.w. + The tangent space is orthogonalized in prepareShadingData(). +*/ +struct VertexData +{ + float3 posW; ///< Position in world space. + float3 normalW; ///< Shading normal in world space (normalized). + float4 tangentW; ///< Shading tangent in world space (normalized). The last component is guaranteed to be +-1.0 or zero if tangents are missing. + float2 texC; ///< Texture coordinate. + float3 faceNormalW; ///< Face normal in world space (normalized). + float curveRadius; ///< Curve cross-sectional radius. Valid only for geometry generated from curves. + float coneTexLODValue; ///< Texture LOD data for cone tracing. This is zero, unless getVertexDataRayCones() is used. +}; + +END_NAMESPACE_FALCOR diff --git a/Source/Falcor/Scene/Volume/Grid.h b/Source/Falcor/Scene/Volume/Grid.h index b70b6d086..0afa900dc 100644 --- a/Source/Falcor/Scene/Volume/Grid.h +++ b/Source/Falcor/Scene/Volume/Grid.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -67,7 +67,7 @@ namespace Falcor \param[in] blendRange Range in voxels to blend from 0 to 1 (starting at surface inwards). \return A new grid. */ - static ref createSphere(ref pDevice, float radius, float voxelSize, float blendRange = 2.f); + static ref createSphere(ref pDevice, float radius, float voxelSize, float blendRange = 3.f); /** Create a box voxel grid. \param[in] pDevice GPU device. @@ -78,7 +78,7 @@ namespace Falcor \param[in] blendRange Range in voxels to blend from 0 to 1 (starting at surface inwards). \return A new grid. */ - static ref createBox(ref pDevice, float width, float height, float depth, float voxelSize, float blendRange = 2.f); + static ref createBox(ref pDevice, float width, float height, float depth, float voxelSize, float blendRange = 3.f); /** Create a grid from a file. Currently only OpenVDB and NanoVDB grids of type float are supported. diff --git a/Source/Falcor/Scene/Volume/GridConverter.h b/Source/Falcor/Scene/Volume/GridConverter.h index 631c55680..3a8e88775 100644 --- a/Source/Falcor/Scene/Volume/GridConverter.h +++ b/Source/Falcor/Scene/Volume/GridConverter.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -28,6 +28,7 @@ #pragma once #include "BrickedGrid.h" #include "BC4Encode.h" +#include "Core/API/Device.h" #include "Core/API/Formats.h" #include "Utils/Logger.h" #include "Utils/HostDeviceShared.slangh" diff --git a/Source/Falcor/Testing/UnitTest.cpp b/Source/Falcor/Testing/UnitTest.cpp index 5adfac8ec..2d7059c97 100644 --- a/Source/Falcor/Testing/UnitTest.cpp +++ b/Source/Falcor/Testing/UnitTest.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,7 +42,7 @@ #include #include #include -#include +#include #include #include @@ -218,21 +218,31 @@ inline TestResult runTest(const Test& test, DevicePool& devicePool) TestResult result{TestResult::Status::Passed}; - ref pDevice; - if (test.gpuFunc) - pDevice = devicePool.acquireDevice(test.deviceType); - - CPUUnitTestContext cpuCtx; - GPUUnitTestContext gpuCtx(pDevice); - auto startTime = std::chrono::steady_clock::now(); try { if (test.cpuFunc) + { + CPUUnitTestContext cpuCtx; test.cpuFunc(cpuCtx); - else - test.gpuFunc(gpuCtx); + result.messages = cpuCtx.getFailureMessages(); + } + else if (test.gpuFunc) + { + ref pDevice; + pDevice = devicePool.acquireDevice(test.deviceType); + + { + GPUUnitTestContext gpuCtx(pDevice); + test.gpuFunc(gpuCtx); + result.messages = gpuCtx.getFailureMessages(); + } + + pDevice->endFrame(); + pDevice->wait(); + devicePool.releaseDevice(std::move(pDevice)); + } } catch (const SkippingTestException& e) { @@ -260,8 +270,6 @@ inline TestResult runTest(const Test& test, DevicePool& devicePool) result.extraMessage = e.what(); } - result.messages = test.cpuFunc ? cpuCtx.getFailureMessages() : gpuCtx.getFailureMessages(); - if (!result.messages.empty()) result.status = TestResult::Status::Failed; @@ -271,14 +279,6 @@ inline TestResult runTest(const Test& test, DevicePool& devicePool) auto endTime = std::chrono::steady_clock::now(); result.elapsedMS = std::chrono::duration_cast(endTime - startTime).count(); - // Release GPU resources. - if (pDevice) - { - pDevice->endFrame(); - pDevice->wait(); - devicePool.releaseDevice(std::move(pDevice)); - } - return result; } diff --git a/Source/Falcor/Utils/Algorithm/UnionFind.h b/Source/Falcor/Utils/Algorithm/UnionFind.h index f2686eed1..ea9ed0735 100644 --- a/Source/Falcor/Utils/Algorithm/UnionFind.h +++ b/Source/Falcor/Utils/Algorithm/UnionFind.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,6 +27,7 @@ **************************************************************************/ #pragma once #include +#include namespace Falcor { diff --git a/Source/Falcor/Utils/Color/ColorHelpers.slang b/Source/Falcor/Utils/Color/ColorHelpers.slang index eb1a6fb80..08f2ce04e 100644 --- a/Source/Falcor/Utils/Color/ColorHelpers.slang +++ b/Source/Falcor/Utils/Color/ColorHelpers.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -198,6 +198,9 @@ inline float computeMichelsonContrast(float iMin, float iMax) static const float3 kD65ReferenceIlluminant = float3(0.950428545, 1.000000000, 1.088900371); static const float3 kInvD65ReferenceIlluminant = float3(1.052156925, 1.000000000, 0.918357670); +#ifndef HOST_CODE +[BackwardDifferentiable] +#endif inline float3 linearRGBToXYZ(float3 linColor) { // Source: https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz @@ -220,6 +223,9 @@ inline float3 linearRGBToXYZ(float3 linColor) return xyzColor; } +#ifndef HOST_CODE +[BackwardDifferentiable] +#endif inline float3 XYZToLinearRGB(float3 xyzColor) { // Return values in linear RGB, assuming D65 standard illuminant. @@ -241,6 +247,103 @@ inline float3 XYZToLinearRGB(float3 xyzColor) return linColor; } +#ifndef HOST_CODE + +[BackwardDifferentiable] +float actualPow(float x, float y) +{ + if (x >= 0.f) + return pow(x, y); + else + return -pow(-x, y); +} + +[BackwardDifferentiable] +float3 actualPow(float3 in, float y) +{ + return float3(actualPow(in.x, y), actualPow(in.y, y), actualPow(in.z, y)); +} + +[BackwardDifferentiable] +inline float3 XYZToOklab(float3 xyzColor) +{ + static const float3x3 M1 = { + 0.8189330101f, + 0.3618667424f, + -0.1288597137f, + 0.0329845436f, + 0.9293118715f, + 0.0361456387f, + 0.0482003018f, + 0.2643662691f, + 0.6338517070f + }; + float3 lms = mul(M1, xyzColor); + lms = actualPow(lms, 1.f / 3.f); + static const float3x3 M2 = { + 0.2104542553f, + 0.7936177850f, + -0.0040720468f, + 1.9779984951f, + -2.4285922050f, + 0.4505937099f, + 0.0259040371f, + 0.7827717662f, + -0.8086757660f + }; + return mul(M2, lms); +} + +[BackwardDifferentiable] +inline float3 OklabToXYZ(float3 oklabColor) +{ + float3x3 M2inv = + float3x3(1.00000000f, 0.39633779f, 0.21580376f, 1.00000001f, -0.10556134f, -0.06385417f, 1.00000005f, -0.08948418f, -1.29148554f); + float3 lms = mul(M2inv, oklabColor); + lms = lms * lms * lms; + float3x3 M1inv = + float3x3(1.22701385f, -0.55779998f, 0.28125615f, -0.04058018f, 1.11225687f, -0.07167668f, -0.07638128f, -0.42148198f, 1.58616322f); + return mul(M1inv, lms); +} + +[BackwardDifferentiable] +inline float3 OklabToHCL(float3 oklabColor) +{ + float L = oklabColor.x; + float a = oklabColor.y; + float b = oklabColor.z; + + float H = atan2(b, a); + float C = sqrt(a * a + b * b); + return float3(H, C, L); +} + +[BackwardDifferentiable] +inline float3 HCLToOklab(float3 hclColor) +{ + float H = hclColor.x; + float C = hclColor.y; + float L = hclColor.z; + + float a = C * cos(H); + float b = C * sin(H); + return float3(L, a, b); +} + +[BackwardDifferentiable] +inline float3 RGBToOklab(float3 lColor) +{ + return XYZToOklab(linearRGBToXYZ(lColor)); +} + +[BackwardDifferentiable] +inline float3 OklabToRGB(float3 lab) +{ + return XYZToLinearRGB(OklabToXYZ(lab)); +} + +#endif + inline float3 XYZToCIELab(float3 xyzColor, const float3 invReferenceIlluminant = kInvD65ReferenceIlluminant) { // The default illuminant is D65. diff --git a/Source/Falcor/Utils/CudaUtils.cpp b/Source/Falcor/Utils/CudaUtils.cpp index 17dd5598d..d5edb2313 100644 --- a/Source/Falcor/Utils/CudaUtils.cpp +++ b/Source/Falcor/Utils/CudaUtils.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -99,18 +99,24 @@ cudaExternalMemory_t importExternalMemory(const Buffer* buffer) cudaExternalMemoryHandleDesc desc = {}; switch (buffer->getDevice()->getType()) { +#if FALCOR_WINDOWS case Device::Type::D3D12: desc.type = cudaExternalMemoryHandleTypeD3D12Resource; + desc.handle.win32.handle = sharedHandle; break; -#if FALCOR_WINDOWS case Device::Type::Vulkan: desc.type = cudaExternalMemoryHandleTypeOpaqueWin32; + desc.handle.win32.handle = sharedHandle; + break; +#elif FALCOR_LINUX + case Device::Type::Vulkan: + desc.type = cudaExternalMemoryHandleTypeOpaqueFd; + desc.handle.fd = (int)reinterpret_cast(sharedHandle); break; #endif default: FALCOR_THROW("Unsupported device type '{}'.", buffer->getDevice()->getType()); } - desc.handle.win32.handle = sharedHandle; desc.size = buffer->getSize(); desc.flags = cudaExternalMemoryDedicated; @@ -145,13 +151,24 @@ cudaExternalSemaphore_t importExternalSemaphore(const Fence* fence) cudaExternalSemaphoreHandleDesc desc = {}; switch (fence->getDevice()->getType()) { +#if FALCOR_WINDOWS case Device::Type::D3D12: desc.type = cudaExternalSemaphoreHandleTypeD3D12Fence; + desc.handle.win32.handle = sharedHandle; + break; + case Device::Type::Vulkan: + desc.type = cudaExternalSemaphoreHandleTypeTimelineSemaphoreWin32; + desc.handle.win32.handle = sharedHandle; break; +#elif FALCOR_LINUX + case Device::Type::Vulkan: + desc.type = cudaExternalSemaphoreHandleTypeTimelineSemaphoreFd; + desc.handle.fd = (int)reinterpret_cast(sharedHandle); + break; +#endif default: FALCOR_THROW("Unsupported device type '{}'.", fence->getDevice()->getType()); } - desc.handle.win32.handle = (void*)sharedHandle; cudaExternalSemaphore_t extSem; FALCOR_CUDA_CHECK(cudaImportExternalSemaphore(&extSem, &desc)); @@ -177,22 +194,39 @@ void waitExternalSemaphore(cudaExternalSemaphore_t extSem, uint64_t value, cudaS FALCOR_CUDA_CHECK(cudaWaitExternalSemaphoresAsync(&extSem, ¶ms, 1, stream)); } -void* getSharedDevicePtr(SharedResourceApiHandle sharedHandle, uint32_t bytes) +void* getSharedDevicePtr(Device::Type deviceType, SharedResourceApiHandle sharedHandle, uint32_t bytes) { // No handle? No pointer! FALCOR_CHECK(sharedHandle, "Texture shared handle creation failed"); - // Create the descriptor of our shared memory buffer - cudaExternalMemoryHandleDesc externalMemoryHandleDesc; - memset(&externalMemoryHandleDesc, 0, sizeof(externalMemoryHandleDesc)); - externalMemoryHandleDesc.type = cudaExternalMemoryHandleTypeD3D12Resource; - externalMemoryHandleDesc.handle.win32.handle = sharedHandle; - externalMemoryHandleDesc.size = bytes; - externalMemoryHandleDesc.flags = cudaExternalMemoryDedicated; + cudaExternalMemoryHandleDesc externalMemoryDesc = {}; + switch (deviceType) + { +#if FALCOR_WINDOWS + case Device::Type::D3D12: + externalMemoryDesc.type = cudaExternalMemoryHandleTypeD3D12Resource; + externalMemoryDesc.handle.win32.handle = sharedHandle; + break; + case Device::Type::Vulkan: + externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueWin32; + externalMemoryDesc.handle.win32.handle = sharedHandle; + break; +#elif FALCOR_LINUX + case Device::Type::Vulkan: + externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd; + externalMemoryDesc.handle.fd = (int)reinterpret_cast(sharedHandle); + break; +#endif + default: + FALCOR_THROW("Unsupported device type '{}'.", deviceType); + } + + externalMemoryDesc.size = bytes; + externalMemoryDesc.flags = cudaExternalMemoryDedicated; // Get a handle to that memory cudaExternalMemory_t externalMemory; - FALCOR_CUDA_CHECK(cudaImportExternalMemory(&externalMemory, &externalMemoryHandleDesc)); + FALCOR_CUDA_CHECK(cudaImportExternalMemory(&externalMemory, &externalMemoryDesc)); // Create a descriptor for our shared buffer pointer cudaExternalMemoryBufferDesc bufDesc; diff --git a/Source/Falcor/Utils/CudaUtils.h b/Source/Falcor/Utils/CudaUtils.h index 289ebf213..be1cd0f65 100644 --- a/Source/Falcor/Utils/CudaUtils.h +++ b/Source/Falcor/Utils/CudaUtils.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -100,7 +100,7 @@ FALCOR_API void waitExternalSemaphore(cudaExternalSemaphore_t extSem, uint64_t v * cudaExternalMemoryGetMappedBuffer(), so should follow its rules (e.g., the docs claim * you are responsible for calling cudaFree() on this pointer). */ -FALCOR_API void* getSharedDevicePtr(SharedResourceApiHandle sharedHandle, uint32_t bytes); +FALCOR_API void* getSharedDevicePtr(Device::Type deviceType, SharedResourceApiHandle sharedHandle, uint32_t bytes); /** * Calls cudaFree() on the provided pointer. @@ -251,8 +251,8 @@ inline InteropBuffer createInteropBuffer(ref pDevice, size_t byteSize) // Create a new DX <-> CUDA shared buffer using the Falcor API to create, then find its CUDA pointer. interop.buffer = pDevice->createBuffer(byteSize, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess | ResourceBindFlags::Shared); - interop.devicePtr = - (CUdeviceptr)cuda_utils::getSharedDevicePtr(interop.buffer->getSharedApiHandle(), (uint32_t)interop.buffer->getSize()); + interop.devicePtr = (CUdeviceptr + )cuda_utils::getSharedDevicePtr(pDevice->getType(), interop.buffer->getSharedApiHandle(), (uint32_t)interop.buffer->getSize()); FALCOR_CHECK(interop.devicePtr != (CUdeviceptr)0, "Failed to create CUDA device ptr for buffer"); return interop; diff --git a/Source/Falcor/Utils/HostDeviceShared.slangh b/Source/Falcor/Utils/HostDeviceShared.slangh index 8b0fba430..402615602 100644 --- a/Source/Falcor/Utils/HostDeviceShared.slangh +++ b/Source/Falcor/Utils/HostDeviceShared.slangh @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -43,6 +43,8 @@ ((((value) & ((1 << (bits)) - 1)) << (offset)) | ((flags) & (~(((1 << (bits)) - 1) << (offset))))) #define PACK_BITS_UNSAFE(bits, offset, flags, value) (((value) << (offset)) | ((flags) & (~(((1 << (bits)) - 1) << (offset))))) +#define FALCOR_GPU_CACHE_SIZE 128 + #ifdef HOST_CODE /******************************************************************* CPU declarations @@ -53,6 +55,9 @@ #define END_NAMESPACE } #define BEGIN_NAMESPACE_FALCOR BEGIN_NAMESPACE(Falcor) #define END_NAMESPACE_FALCOR END_NAMESPACE +/// Check if the type is either a multiple of ALIGNMENT, or multiples of it fit exactly into ALIGNMENT +#define FALCOR_ASSERT_ALIGNMENT_FOR(T, ALIGNMENT) \ + static_assert((sizeof(T) >= ALIGNMENT) ? ((sizeof(T) % ALIGNMENT) == 0) : ((ALIGNMENT % sizeof(T)) == 0)) #define SETTER_DECL #define CONST_FUNCTION const @@ -90,6 +95,7 @@ namespace Falcor #define SETTER_DECL [mutating] #define CONST_FUNCTION #define STD_NAMESPACE +#define FALCOR_ASSERT_ALIGNMENT_FOR(T, ALIGNMENT) #define FALCOR_ENUM_INFO(T, ...) #define FALCOR_ENUM_REGISTER(T) diff --git a/Source/Falcor/Utils/Image/Bitmap.cpp b/Source/Falcor/Utils/Image/Bitmap.cpp index 7559fc56d..b47bd02a2 100644 --- a/Source/Falcor/Utils/Image/Bitmap.cpp +++ b/Source/Falcor/Utils/Image/Bitmap.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -34,6 +34,11 @@ #include "Utils/Logger.h" #include "Utils/StringUtils.h" +#include +#include +#include +#include + #if FALCOR_WINDOWS #ifndef WINDOWS_LEAN_AND_MEAN #define WINDOWS_LEAN_AND_MEAN @@ -44,6 +49,52 @@ namespace Falcor { +namespace +{ + +/// Wraps MemoryMappedFile in an OpenEXR interface +class OpenExrStream : public Imf::IStream +{ +public: + OpenExrStream(const MemoryMappedFile& file) : Imf::IStream(""), mFile(file) + { + mFileData = reinterpret_cast(mFile.getData()); + } + + virtual bool read(char c[/*n*/], int n) + { + if (mOffset + size_t(n) > mFile.getSize()) + return false; + memcpy(c, mFileData + mOffset, n); + mOffset += n; + return true; + } + + virtual uint64_t tellg() { return mOffset; } + + virtual void seekg(uint64_t pos) { mOffset = pos; } + + virtual void clear() {} + +private: + const MemoryMappedFile& mFile; + const uint8_t* mFileData; + size_t mOffset = 0; +}; + +bool isFloat16Exr(const MemoryMappedFile& inputFile) +{ + OpenExrStream stream(inputFile); + Imf::InputFile imfFile(stream); + const Imf::ChannelList& channels = imfFile.header().channels(); + for (auto it = channels.begin(); it != channels.end(); ++it) + if (it.channel().type != Imf::HALF) + return false; + return true; +} + +} // namespace + static bool isRGB32fSupported() { return false; // FIX THIS @@ -274,6 +325,13 @@ Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, genWarning("Can't open image file {}", path); return nullptr; } + + if (fifFormat == FIF_EXR) + { + if (isFloat16Exr(file)) + importFlags |= ImportFlags::ConvertToFloat16; + } + FIMEMORY* memory = FreeImage_OpenMemory((BYTE*)file.getData(), file.getSize()); FIBITMAP* pDib = FreeImage_LoadFromMemory(fifFormat, memory); FreeImage_CloseMemory(memory); @@ -308,6 +366,8 @@ Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, genWarning("Failed to convert palettized image to RGBA format", path); return nullptr; } + + colorType = FreeImage_GetColorType(pDib); } // Identify resource format based on bit depth. @@ -322,8 +382,19 @@ Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, format = isRGB32fSupported() ? ResourceFormat::RGB32Float : ResourceFormat::RGBA32Float; // 3xfloat32 HDR format break; case 64: - format = ResourceFormat::RGBA16Float; // 4xfloat16 HDR format + FALCOR_CHECK(colorType == FIC_RGBALPHA, "Only expect 16b RGBA with 64 bits per pixel"); + format = ResourceFormat::RGBA16Unorm; break; + case 48: + { + FALCOR_CHECK(colorType == FIC_RGB, "Only expect 16b RGB with 48 bits per pixel"); + format = ResourceFormat::RGBA16Unorm; + auto pNew = FreeImage_ConvertToRGBA16(pDib); + FreeImage_Unload(pDib); + pDib = pNew; + bpp = FreeImage_GetBPP(pDib); + } + break; case 32: format = ResourceFormat::BGRA8Unorm; break; @@ -384,11 +455,11 @@ Bitmap::Bitmap(uint32_t width, uint32_t height, ResourceFormat format) { uint32_t blockSizeY = getFormatHeightCompressionRatio(format); FALCOR_ASSERT(height % blockSizeY == 0); // Should divide evenly - mSize = mRowPitch * (height / blockSizeY); + mSize = size_t(mRowPitch) * (height / blockSizeY); } else { - mSize = height * mRowPitch; + mSize = height * size_t(mRowPitch); } mpData = std::unique_ptr(new uint8_t[mSize]); @@ -531,6 +602,9 @@ void Bitmap::saveImage( FALCOR_CHECK(fileFormat != FileFormat::DdsFile, "Cannot save DDS files. Use ImageIO instead."); if (is_set(exportFlags, ExportFlags::Uncompressed) && is_set(exportFlags, ExportFlags::Lossy)) FALCOR_THROW("Incompatible flags: lossy cannot be combined with uncompressed."); + if (is_set(exportFlags, ExportFlags::ExrFloat16) && + (!is_set(exportFlags, ExportFlags::Uncompressed) || fileFormat != FileFormat::ExrFile)) + FALCOR_THROW("Incompatible flags: EXR float16 can only be set for uncompressed EXR files."); int flags = 0; FIBITMAP* pImage = nullptr; @@ -610,7 +684,9 @@ void Bitmap::saveImage( flags = 0; if (is_set(exportFlags, ExportFlags::Uncompressed)) { - flags |= EXR_NONE | EXR_FLOAT; + flags |= EXR_NONE; + if (!is_set(exportFlags, ExportFlags::ExrFloat16)) + flags |= EXR_FLOAT; } else if (is_set(exportFlags, ExportFlags::Lossy)) { diff --git a/Source/Falcor/Utils/Image/Bitmap.h b/Source/Falcor/Utils/Image/Bitmap.h index 9ae2e1109..88d593a66 100644 --- a/Source/Falcor/Utils/Image/Bitmap.h +++ b/Source/Falcor/Utils/Image/Bitmap.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -48,6 +48,7 @@ class FALCOR_API Bitmap ExportAlpha = 1u << 0, //< Save alpha channel as well Lossy = 1u << 1, //< Try to store in a lossy format Uncompressed = 1u << 2, //< Prefer faster load to a more compact file size + ExrFloat16 = 1u << 3, //< Use half-float instead of float when writing EXRs }; enum class ImportFlags : uint32_t @@ -136,7 +137,7 @@ class FALCOR_API Bitmap uint32_t getRowPitch() const { return mRowPitch; } /// Get the data size in bytes - uint32_t getSize() const { return mSize; } + size_t getSize() const { return mSize; } /** * Get the file dialog filter vec for images. @@ -165,7 +166,7 @@ class FALCOR_API Bitmap uint32_t mWidth = 0; ///< Width in pixels. uint32_t mHeight = 0; ///< Height in pixels. uint32_t mRowPitch = 0; ///< Row pitch in bytes. - uint32_t mSize = 0; ///< Total size in bytes. + size_t mSize = 0; ///< Total size in bytes. ResourceFormat mFormat = ResourceFormat::Unknown; }; diff --git a/Source/Falcor/Utils/Math/AABB.h b/Source/Falcor/Utils/Math/AABB.h index 22c5cb46c..acb2331ae 100644 --- a/Source/Falcor/Utils/Math/AABB.h +++ b/Source/Falcor/Utils/Math/AABB.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -54,6 +54,9 @@ struct AABB /// Construct bounding box initialized to min/max point. AABB(const float3& pmin, const float3& pmax) : minPoint(pmin), maxPoint(pmax) {} + /// Construct bounding box initialized to min/max point. + explicit AABB(const RtAABB& aabb) : minPoint(aabb.min), maxPoint(aabb.max) {} + /// Set box to single point. void set(const float3& p) { minPoint = maxPoint = p; } @@ -155,7 +158,7 @@ struct AABB * @param[in] mat Transform matrix * @return Bounding box after transformation. */ - AABB transform(const float4x4& mat) const + [[nodiscard]] AABB transform(const float4x4& mat) const { if (!valid()) return {}; diff --git a/Source/Falcor/Utils/Math/FNVHash.h b/Source/Falcor/Utils/Math/FNVHash.h index 01ddd468b..a35a7538b 100644 --- a/Source/Falcor/Utils/Math/FNVHash.h +++ b/Source/Falcor/Utils/Math/FNVHash.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -37,7 +37,7 @@ namespace Falcor namespace detail { -template +template struct FNVHashConstants {}; @@ -61,14 +61,14 @@ struct FNVHashConstants * To hash multiple items, create one Hash and insert all the items into it if at all possible. * This is superior to hashing the items individually and combining the hashes. * - * @tparam T - type of the storage for the hash, either 32 or 64 unsigned integer + * @tparam TUInt - type of the storage for the hash, either 32 or 64 unsigned integer */ -template +template class FNVHash { public: - static constexpr T kOffsetBasis = detail::FNVHashConstants::kOffsetBasis; - static constexpr T kPrime = detail::FNVHashConstants::kPrime; + static constexpr TUInt kOffsetBasis = detail::FNVHashConstants::kOffsetBasis; + static constexpr TUInt kPrime = detail::FNVHashConstants::kPrime; /** * Inserts all data between [begin,end) into the hash. @@ -98,7 +98,13 @@ class FNVHash insert(srcData8, srcData8 + size); } - T get() const { return mHash; } + template + void insert(const T& data) + { + insert(&data, sizeof(T)); + } + + TUInt get() const { return mHash; } constexpr bool operator==(const FNVHash& rhs) { return get() == rhs.get(); } @@ -113,7 +119,7 @@ class FNVHash constexpr bool operator>(const FNVHash& rhs) { return get() > rhs.get(); } private: - T mHash = kOffsetBasis; + TUInt mHash = kOffsetBasis; }; using FNVHash64 = FNVHash; diff --git a/Source/Falcor/Utils/Math/Float16.h b/Source/Falcor/Utils/Math/Float16.h index c5acfddc7..d7c913d7b 100644 --- a/Source/Falcor/Utils/Math/Float16.h +++ b/Source/Falcor/Utils/Math/Float16.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -106,6 +106,9 @@ struct float16_t #elif FALCOR_CLANG #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wuser-defined-literals" +#elif FALCOR_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wliteral-suffix" #endif /// h suffix for "half float" literals. @@ -118,6 +121,8 @@ inline float16_t operator""h(long double value) #pragma warning(pop) #elif FALCOR_CLANG #pragma clang diagnostic pop +#elif FALCOR_GCC +#pragma GCC diagnostic pop #endif } // namespace math diff --git a/Source/Falcor/Utils/Math/MathConstants.slangh b/Source/Falcor/Utils/Math/MathConstants.slangh index 7bb10bca4..50b6cf885 100644 --- a/Source/Falcor/Utils/Math/MathConstants.slangh +++ b/Source/Falcor/Utils/Math/MathConstants.slangh @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -63,6 +63,7 @@ #define M_PI_4 0.785398163397448309616 // pi/4 #define M_1_PI 0.318309886183790671538 // 1/pi #define M_2_PI 0.636619772367581343076 // 2/pi +#define M_1_SQRTPI 0.564189583547756279280 // 1/sqrt(pi) #define M_2_SQRTPI 1.12837916709551257390 // 2/sqrt(pi) #define M_SQRT2 1.41421356237309504880 // sqrt(2) #define M_SQRT1_2 0.707106781186547524401 // 1/sqrt(2) diff --git a/Source/Falcor/Utils/Math/MathHelpers.slang b/Source/Falcor/Utils/Math/MathHelpers.slang index 5040931d0..f95df44ad 100644 --- a/Source/Falcor/Utils/Math/MathHelpers.slang +++ b/Source/Falcor/Utils/Math/MathHelpers.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -719,6 +719,7 @@ void orthogonalizeVectors(const float3 v1, inout float3 v2) * @param[in] angle Angle in radians. * @return Transformed vector. */ +[BackwardDifferentiable] float3 rotate_x(const float3 v, const float angle) { float c, s; @@ -733,6 +734,7 @@ float3 rotate_x(const float3 v, const float angle) * @param[in] angle Angle in radians. * @return Transformed vector. */ +[BackwardDifferentiable] float3 rotate_y(const float3 v, const float angle) { float c, s; @@ -747,6 +749,7 @@ float3 rotate_y(const float3 v, const float angle) * @param[in] angle Angle in radians. * @return Transformed vector. */ +[BackwardDifferentiable] float3 rotate_z(const float3 v, const float angle) { float c, s; diff --git a/Source/Falcor/Utils/Math/MatrixJson.h b/Source/Falcor/Utils/Math/MatrixJson.h new file mode 100644 index 000000000..fe4e7824d --- /dev/null +++ b/Source/Falcor/Utils/Math/MatrixJson.h @@ -0,0 +1,66 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ + +/** + * Most of this code is derived from the GLM library at https://github.com/g-truc/glm + * + * License: https://github.com/g-truc/glm/blob/master/copying.txt + */ + +#pragma once + +#include "MatrixTypes.h" +#include "ScalarJson.h" + +#include "Core/Error.h" + +#include + +namespace Falcor +{ +namespace math +{ + +template +void to_json(nlohmann::json& j, const matrix& v) +{ + j = nlohmann::json::array(); + for (int i = 0; i < RowCount * ColCount; ++i) + j.push_back(v.data()[i]); +} + +template +void from_json(const nlohmann::json& j, matrix& v) +{ + FALCOR_ASSERT(j.is_array() && j.size() == RowCount * ColCount); + for (int i = 0; i < RowCount * ColCount; ++i) + j[i].get_to(v.data()[i]); +} + +} // namespace math +} // namespace Falcor diff --git a/Source/Falcor/Utils/Math/MatrixMath.h b/Source/Falcor/Utils/Math/MatrixMath.h index 280bb2c12..cf19d6f00 100644 --- a/Source/Falcor/Utils/Math/MatrixMath.h +++ b/Source/Falcor/Utils/Math/MatrixMath.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -132,7 +132,7 @@ template // Functions // ---------------------------------------------------------------------------- -/// Tranpose a matrix. +/// Transpose a matrix. template matrix transpose(const matrix& m) { diff --git a/Source/Falcor/Utils/Math/MatrixTypes.h b/Source/Falcor/Utils/Math/MatrixTypes.h index 59bad6eb1..0c4e698a4 100644 --- a/Source/Falcor/Utils/Math/MatrixTypes.h +++ b/Source/Falcor/Utils/Math/MatrixTypes.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -76,6 +76,7 @@ class matrix template matrix(std::initializer_list v) { + FALCOR_ASSERT(v.size() == RowCount * ColCount); T* f = &mRows[0][0]; for (auto it = v.begin(); it != v.end(); ++it, ++f) *f = static_cast(*it); diff --git a/Source/Falcor/Utils/Math/Quaternion.slang b/Source/Falcor/Utils/Math/Quaternion.slang index 12e8c7fb3..3ce2b4695 100644 --- a/Source/Falcor/Utils/Math/Quaternion.slang +++ b/Source/Falcor/Utils/Math/Quaternion.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -35,6 +35,7 @@ // Quaternion multiplication // http://mathworld.wolfram.com/Quaternion.html +[BackwardDifferentiable] float4 qmul(float4 q1, float4 q2) { return float4(q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz), q1.w * q2.w - dot(q1.xyz, q2.xyz)); @@ -42,6 +43,7 @@ float4 qmul(float4 q1, float4 q2) // Vector rotation with a quaternion // http://mathworld.wolfram.com/Quaternion.html +[BackwardDifferentiable] float3 rotate_vector(float3 v, float4 r) { float4 r_c = r * float4(-1, -1, -1, 1); diff --git a/Source/Falcor/Utils/Math/ScalarJson.h b/Source/Falcor/Utils/Math/ScalarJson.h new file mode 100644 index 000000000..4e684dd23 --- /dev/null +++ b/Source/Falcor/Utils/Math/ScalarJson.h @@ -0,0 +1,56 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once + +#include "Float16.h" + +#include "Core/Error.h" + +#include + +namespace Falcor +{ +namespace math +{ + +inline void to_json(nlohmann::json& j, const float16_t& v) +{ + j = double(v); +} + +inline void from_json(const nlohmann::json& j, float16_t& v) +{ + FALCOR_ASSERT(j.is_number()); + double d; + j.get_to(d); + v = float16_t(d); +} + +} // namespace math + +} // namespace Falcor diff --git a/Source/Falcor/Utils/Math/SphericalHarmonics.slang b/Source/Falcor/Utils/Math/SphericalHarmonics.slang index bf9fc55be..3a69a754b 100644 --- a/Source/Falcor/Utils/Math/SphericalHarmonics.slang +++ b/Source/Falcor/Utils/Math/SphericalHarmonics.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,6 +25,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ +#include "Utils/Math/MathConstants.slangh" + import Utils.Math.MathHelpers; /** @@ -42,6 +44,7 @@ uint get_SH_index(int l, int m) * @param[in] p Cartesian coordinate p=(x,y,z) on the unit sphere. * @return Evaluated SH basis function. */ +[BackwardDifferentiable] float eval_SH(uint idx, float3 p) { // Standard real SH basis. See https://en.wikipedia.org/wiki/Table_of_spherical_harmonics @@ -52,25 +55,25 @@ float eval_SH(uint idx, float3 p) switch (idx) { // l = 0 - case 0: return 1.f / (2.f * M_SQRT_PIf); // m = 0 + case 0: return 1.f / (2.f * M_1_SQRTPI); // m = 0 // l = 1 - case 1: return p.y * sqrt(3.f) / (2.f * M_SQRT_PIf); // m =-1 - case 2: return p.z * sqrt(3.f) / (2.f * M_SQRT_PIf); // m = 0 - case 3: return p.x * sqrt(3.f) / (2.f * M_SQRT_PIf); // m = 1 + case 1: return p.y * sqrt(3.f) / (2.f * M_1_SQRTPI); // m =-1 + case 2: return p.z * sqrt(3.f) / (2.f * M_1_SQRTPI); // m = 0 + case 3: return p.x * sqrt(3.f) / (2.f * M_1_SQRTPI); // m = 1 // l = 2 - case 4: return p.x * p.y * sqrt(15.f) / (2.f * M_SQRT_PIf); // m =-2 - case 5: return p.y * p.z * sqrt(15.f) / (2.f * M_SQRT_PIf); // m =-1 - case 6: return (3.f * p.z * p.z - 1.f) * sqrt(5.f) / (4.f * M_SQRT_PIf); // m = 0 - case 7: return p.x * p.z * sqrt(15.f) / (2.f * M_SQRT_PIf); // m = 1 - case 8: return (p.x * p.x - p.y * p.y) * sqrt(15.f) / (4.f * M_SQRT_PIf); // m = 2 + case 4: return p.x * p.y * sqrt(15.f) / (2.f * M_1_SQRTPI); // m =-2 + case 5: return p.y * p.z * sqrt(15.f) / (2.f * M_1_SQRTPI); // m =-1 + case 6: return (3.f * p.z * p.z - 1.f) * sqrt(5.f) / (4.f * M_1_SQRTPI); // m = 0 + case 7: return p.x * p.z * sqrt(15.f) / (2.f * M_1_SQRTPI); // m = 1 + case 8: return (p.x * p.x - p.y * p.y) * sqrt(15.f) / (4.f * M_1_SQRTPI); // m = 2 // l = 3 - case 9: return p.y * (3.f * p.x * p.x - p.y * p.y) * sqrt(70.f) / (8.f * M_SQRT_PIf); // m =-3 - case 10: return p.x * p.y * p.z * sqrt(105.f) / (2.f * M_SQRT_PIf); // m =-2 - case 11: return p.y * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_SQRT_PIf); // m =-1 - case 12: return p.z * (5.f * p.z * p.z - 3.f) * sqrt(7.f) / (4.f * M_SQRT_PIf); // m = 0 - case 13: return p.x * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_SQRT_PIf); // m = 1 - case 14: return p.z * (p.x * p.x - p.y * p.y) * sqrt(105.f) / (4.f * M_SQRT_PIf); // m = 2 - case 15: return p.x * (p.x * p.x - 3.f * p.y * p.y) * sqrt(70.f) / (8.f * M_SQRT_PIf); // m = 3 + case 9: return p.y * (3.f * p.x * p.x - p.y * p.y) * sqrt(70.f) / (8.f * M_1_SQRTPI); // m =-3 + case 10: return p.x * p.y * p.z * sqrt(105.f) / (2.f * M_1_SQRTPI); // m =-2 + case 11: return p.y * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_1_SQRTPI); // m =-1 + case 12: return p.z * (5.f * p.z * p.z - 3.f) * sqrt(7.f) / (4.f * M_1_SQRTPI); // m = 0 + case 13: return p.x * (5.f * p.z * p.z - 1.f) * sqrt(42.f) / (8.f * M_1_SQRTPI); // m = 1 + case 14: return p.z * (p.x * p.x - p.y * p.y) * sqrt(105.f) / (4.f * M_1_SQRTPI); // m = 2 + case 15: return p.x * (p.x * p.x - 3.f * p.y * p.y) * sqrt(70.f) / (8.f * M_1_SQRTPI); // m = 3 } // clang-format on return 0.f; diff --git a/Source/Falcor/Utils/Math/VectorJson.h b/Source/Falcor/Utils/Math/VectorJson.h new file mode 100644 index 000000000..1fd46a6e7 --- /dev/null +++ b/Source/Falcor/Utils/Math/VectorJson.h @@ -0,0 +1,60 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once + +#include "VectorTypes.h" +#include "ScalarJson.h" + +#include "Core/Error.h" + +#include + +namespace Falcor +{ +namespace math +{ + +template +void to_json(nlohmann::json& j, const vector& v) +{ + j = nlohmann::json::array(); + for (int i = 0; i < N; ++i) + j.push_back(v[i]); +} + +template +void from_json(const nlohmann::json& j, vector& v) +{ + FALCOR_ASSERT(j.is_array() && j.size() == N); + for (int i = 0; i < N; ++i) + j[i].get_to(v[i]); +} + +} // namespace math + +} // namespace Falcor diff --git a/Source/Falcor/Utils/ObjectID.h b/Source/Falcor/Utils/ObjectID.h index 858fecaa1..f41e6932c 100644 --- a/Source/Falcor/Utils/ObjectID.h +++ b/Source/Falcor/Utils/ObjectID.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -68,8 +68,8 @@ class ObjectID * * @param[in] id Integer ID to initialize from. */ - template - explicit ObjectID(const T& id, std::enable_if_t, bool> = true) : mID(IntType(id)) + template, bool> = true> + explicit ObjectID(const T& id) : mID(IntType(id)) { // First we make sure it is positive FALCOR_ASSERT_GE(id, T(0)); diff --git a/Source/Falcor/Utils/Scripting/Scripting.cpp b/Source/Falcor/Utils/Scripting/Scripting.cpp index a7e154b61..58dedad39 100644 --- a/Source/Falcor/Utils/Scripting/Scripting.cpp +++ b/Source/Falcor/Utils/Scripting/Scripting.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -45,7 +45,11 @@ void Scripting::start() sRunning = true; #ifdef FALCOR_PYTHON_EXECUTABLE +#if FALCOR_WINDOWS static std::filesystem::path pythonHome{std::filesystem::path{FALCOR_PYTHON_EXECUTABLE}.parent_path()}; +#else + static std::filesystem::path pythonHome{std::filesystem::path{FALCOR_PYTHON_EXECUTABLE}.parent_path().parent_path()}; +#endif #else static std::filesystem::path pythonHome{getRuntimeDirectory() / "pythondist"}; #endif @@ -117,7 +121,7 @@ class RedirectStream pybind11::object mBuffer; }; -static Scripting::RunResult runScript(const std::string& script, pybind11::dict& globals, bool captureOutput) +static Scripting::RunResult runScript(std::string_view script, pybind11::dict& globals, bool captureOutput) { Scripting::RunResult result; @@ -125,19 +129,19 @@ static Scripting::RunResult runScript(const std::string& script, pybind11::dict& { RedirectStream rstdout("stdout"); RedirectStream rstderr("stderr"); - pybind11::exec(script.c_str(), globals); + pybind11::exec(script, globals); result.out = rstdout; result.err = rstderr; } else { - pybind11::exec(script.c_str(), globals); + pybind11::exec(script, globals); } return result; } -Scripting::RunResult Scripting::runScript(const std::string& script, Context& context, bool captureOutput) +Scripting::RunResult Scripting::runScript(std::string_view script, Context& context, bool captureOutput) { return Falcor::runScript(script, context.mGlobals, captureOutput); } diff --git a/Source/Falcor/Utils/Scripting/Scripting.h b/Source/Falcor/Utils/Scripting/Scripting.h index bea738616..568a4421d 100644 --- a/Source/Falcor/Utils/Scripting/Scripting.h +++ b/Source/Falcor/Utils/Scripting/Scripting.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -150,7 +150,7 @@ class FALCOR_API Scripting * @param[in] captureOutput Enable capturing stdout/stderr and returning it in RunResult. * @return Returns the captured output if enabled. */ - static RunResult runScript(const std::string& script, Context& context = getDefaultContext(), bool captureOutput = false); + static RunResult runScript(std::string_view script, Context& context = getDefaultContext(), bool captureOutput = false); /** * Run a script from a file. diff --git a/Source/Falcor/Utils/Settings/AttributeFilters.h b/Source/Falcor/Utils/Settings/AttributeFilters.h index d24736ebd..fdd43740a 100644 --- a/Source/Falcor/Utils/Settings/AttributeFilters.h +++ b/Source/Falcor/Utils/Settings/AttributeFilters.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -40,7 +40,6 @@ #include - namespace pybind11 { class dict; diff --git a/Source/Falcor/Utils/Settings/Attributes.h b/Source/Falcor/Utils/Settings/Attributes.h index fa61aa8e6..a235d087a 100644 --- a/Source/Falcor/Utils/Settings/Attributes.h +++ b/Source/Falcor/Utils/Settings/Attributes.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -48,6 +48,8 @@ class Attributes Attributes() = default; Attributes(nlohmann::json jsonDict) : mJsonDict(jsonDict) {} + void overrideWith(const Attributes& other) { addDict(other.mJsonDict); } + template std::optional get(std::string_view attrName) const { @@ -60,7 +62,7 @@ class Attributes if (attribute.is_null()) return {}; - if(!detail::TypeChecker::validType(attribute)) + if (!detail::TypeChecker::validType(attribute)) throw detail::TypeError("Attribute's type does not match the requested type."); // Handle return value of bool, if the actual is convertible to bool (from int, usually) @@ -100,10 +102,7 @@ class Attributes mJsonDict[it.key()] = it.value(); } - void clear() - { - mJsonDict = nlohmann::json::object(); - } + void clear() { mJsonDict = nlohmann::json::object(); } void removePrefix(std::string_view prefix) { @@ -116,15 +115,10 @@ class Attributes mJsonDict = std::move(filtered); } - void removeExact(std::string_view name) - { - mJsonDict.erase(name); - } + void removeExact(std::string_view name) { mJsonDict.erase(name); } + + std::string to_string() const { return mJsonDict.dump(); } - std::string to_string() const - { - return mJsonDict.dump(); - } private: nlohmann::json mJsonDict; }; diff --git a/Source/Falcor/Utils/Settings/Settings.cpp b/Source/Falcor/Utils/Settings/Settings.cpp index 4b5012336..faf5857c4 100644 --- a/Source/Falcor/Utils/Settings/Settings.cpp +++ b/Source/Falcor/Utils/Settings/Settings.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -98,7 +98,6 @@ void Settings::addOptions(const pybind11::list& options) addOptions(settings::detail::flattenDictionary(pyjson::to_json(options))); } - bool Settings::addOptions(const std::filesystem::path& path) { if (path.extension() != ".json") @@ -109,7 +108,9 @@ bool Settings::addOptions(const std::filesystem::path& path) std::ifstream ifs(path); if (!ifs) return false; - nlohmann::json jf = settings::detail::flattenDictionary(nlohmann::json::parse(ifs, nullptr /*callback*/, true /*allow exceptions*/, true /*ignore comments*/)); + nlohmann::json jf = settings::detail::flattenDictionary( + nlohmann::json::parse(ifs, nullptr /*callback*/, true /*allow exceptions*/, true /*ignore comments*/) + ); for (auto& it : jf.items()) getActive().mOptions.removePrefix(it.key()); getActive().mOptions.addDict(jf); @@ -133,6 +134,23 @@ void Settings::addFilteredAttributes(const nlohmann::json& attributes) getActive().mAttributeFilters.add(attributes); } +bool Settings::addFilteredAttributes(const std::filesystem::path& path) +{ + if (path.extension() != ".json") + return false; + + if (!std::filesystem::exists(path)) + return false; + std::ifstream ifs(path); + if (!ifs) + return false; + + nlohmann::json jf = nlohmann::json::parse(ifs, nullptr /*callback*/, true /*allow exceptions*/, true /*ignore comments*/); + + getActive().mAttributeFilters.add(jf); + return true; +} + void Settings::clearFilteredAttributes() { getActive().mAttributeFilters.clear(); diff --git a/Source/Falcor/Utils/Settings/Settings.h b/Source/Falcor/Utils/Settings/Settings.h index 2f73ab26a..6d3cae6fa 100644 --- a/Source/Falcor/Utils/Settings/Settings.h +++ b/Source/Falcor/Utils/Settings/Settings.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -41,7 +41,7 @@ namespace pybind11 { class dict; class list; -} +} // namespace pybind11 namespace Falcor { @@ -52,6 +52,7 @@ class FALCOR_API Settings using Options = settings::Attributes; using Attributes = settings::Attributes; using TypeError = settings::detail::TypeError; + public: using SearchDirectories = std::vector; @@ -60,10 +61,7 @@ class FALCOR_API Settings Settings() : mData(1) {} - const Options& getOptions() const - { - return getActive().mOptions; - } + const Options& getOptions() const { return getActive().mOptions; } template std::optional getOption(std::string_view optionName) const @@ -106,6 +104,8 @@ class FALCOR_API Settings return getActive().mAttributeFilters.getAttribute(shapeName, attributeName, def); } + Attributes getAttributes(std::string_view shapeName) const { return getActive().mAttributeFilters.getAttributes(shapeName); } + /** * @brief Adds filtered attributes with the following syntax. * @@ -155,6 +155,7 @@ class FALCOR_API Settings void addFilteredAttributes(const nlohmann::json& attributes); void addFilteredAttributes(const pybind11::dict& attributes); void addFilteredAttributes(const pybind11::list& attributes); + bool addFilteredAttributes(const std::filesystem::path& path); // Clears all the attributes to default void clearFilteredAttributes(); diff --git a/Source/Falcor/Utils/Settings/SettingsUtils.h b/Source/Falcor/Utils/Settings/SettingsUtils.h index 8951cda74..13dd6f974 100644 --- a/Source/Falcor/Utils/Settings/SettingsUtils.h +++ b/Source/Falcor/Utils/Settings/SettingsUtils.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -60,7 +60,7 @@ void flattenDictionary(const nlohmann::json& dict, const std::string& prefix, nl flattenDictionary(it.value(), name, flattened); } } -} +} // namespace /// Flattens nested dictionaries into colon separated name, /// e.g. {"foo":{"bar":4}} becomes {"foo:bar":4} @@ -101,20 +101,14 @@ inline bool isType(const nlohmann::json& json) template struct TypeChecker { - static bool validType(const nlohmann::json& json) - { - return isType(json); - } + static bool validType(const nlohmann::json& json) { return isType(json); } }; template struct TypeChecker, gccfix> { using ArrayType = std::array; - static bool validType(const nlohmann::json& json) - { - return isType(json); - } + static bool validType(const nlohmann::json& json) { return isType(json); } }; class TypeError : public std::runtime_error diff --git a/Source/Falcor/Utils/SplitBuffer.h b/Source/Falcor/Utils/SplitBuffer.h new file mode 100644 index 000000000..4f2e0d1ab --- /dev/null +++ b/Source/Falcor/Utils/SplitBuffer.h @@ -0,0 +1,298 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Core/API/Buffer.h" +#include "Core/API/Device.h" +#include "Core/Program/DefineList.h" +#include "Core/Program/ShaderVar.h" +#include "Core/Error.h" + +#include +#include + +namespace Falcor +{ + +/** + * @brief Represents a cpu/gpu buffer, that handles the GPU limit on 4GB buffers, up to 4 billion items. + * + * GPU's currently handle at most 4GB buffers. If we want to store more data, it has to be split into multiple buffers. + * This class facilitates this for objects with the power-of-2 byte size, by using the upper bits of the 32b index + * to select which 4GB buffer. The number of buffers is equal to the size of the stored object, i.e., + * for uint32_t, we can have up to 2^30 in a single 4GB buffer, leaving only 2 bits for buffer selection. + * This naturally limites the size to the total of 2^32 items. + * + * @tparam T - Type of the object stored in the buffer. + */ +template +class SplitBuffer +{ + /// We are encoding the buffer index in the top bits of the index, + /// so we can only have as many buffers as it the sizeof(T) rounded down + /// to the nearest power of two + static_assert((sizeof(T) & (sizeof(T) - 1)) == 0, "Size has to be power of 2"); + +public: + using ElementType = T; + +public: + SplitBuffer() : mCpuBuffers(1) {} + + /// Returns the maximum number of buffers supported for the given split buffer. + size_t getMaxBufferCount() const { return kMaxBufferCount; } + + /// Forces the number of buffers to be at least bufferCount. + /// Used for debugging, as SplitBuffer will balance content of all existing buffers + /// before creating new ones, so setting this to MaxBufferCount will create balanced + /// buffers even for small scenes. + void setBufferCount(uint32_t bufferCount) + { + FALCOR_ASSERT(mGpuBuffers.empty(), "Cannot change buffer count after creating GPU buffers."); + FALCOR_CHECK(bufferCount >= getBufferCount(), "Cannot reduce number of existing buffers ({}).", getBufferCount()); + FALCOR_CHECK(bufferCount <= kMaxBufferCount, "Cannot exceed the max number of buffers ({}).", kMaxBufferCount); + mCpuBuffers.resize(bufferCount); + } + + /// Sets name used for CPU reporting, and for GPU buffer names + void setName(std::string_view name) { mBufferName = name; } + + /// Sets the name of the define that governs the number of buffers in the GPU code. + /// This is less than ideal, but there is no way to automatically detect which + /// type/define it is on the GPU. + void setBufferCountDefinePrefix(std::string bufferCountDefinePrefix) { mBufferCountDefinePrefix = bufferCountDefinePrefix; } + + /// Insert range of items into the split buffer, returning an index at which it starts. + /// All items in the range will be in the same GPU buffer. + /// Not possible after the GPU buffers have been created. + template + uint32_t insert(Iter first, Iter last) + { + FALCOR_ASSERT(mGpuBuffers.empty(), "Cannot insert after creating GPU buffers."); + if (first == last) + return 0; + const size_t itemCount = std::distance(first, last); + + // Find the buffer with the fewest items. + auto it = std::min_element( + mCpuBuffers.begin(), + mCpuBuffers.end(), + [](const std::vector& lhs, const std::vector& rhs) { return lhs.size() < rhs.size(); } + ); + uint32_t bufferIndex = std::distance(mCpuBuffers.begin(), it); + + // If new items wouldn't fit into the buffer with fewest items, create a new buffer, + // throw if new buffer cannot be created. + if ((mCpuBuffers[bufferIndex].size() + itemCount) * sizeof(T) > kBufferSizeLimit) + { + bufferIndex = mCpuBuffers.size(); + if (bufferIndex >= kMaxBufferCount) + FALCOR_THROW("Buffers {} cannot accomodate all the date within the buffer limit.", mBufferName); + mCpuBuffers.push_back({}); + } + + const uint32_t elementIndex = mCpuBuffers[bufferIndex].size(); + FALCOR_ASSERT((((1 << kBufferIndexOffset) - 1) & elementIndex) == elementIndex, "Element index overflows into buffer index"); + mCpuBuffers[bufferIndex].insert(mCpuBuffers[bufferIndex].end(), first, last); + return ((bufferIndex << kBufferIndexOffset) | elementIndex); + } + + /// Inserts an empty range, mostly for testing and debugging purposes + uint32_t insertEmpty(size_t itemCount) + { + FALCOR_ASSERT(mGpuBuffers.empty(), "Cannot insert after creating GPU buffers."); + if (itemCount == 0) + return 0; + + // Find the buffer with the fewest items. + auto it = std::min_element( + mCpuBuffers.begin(), + mCpuBuffers.end(), + [](const std::vector& lhs, const std::vector& rhs) { return lhs.size() < rhs.size(); } + ); + uint32_t bufferIndex = std::distance(mCpuBuffers.begin(), it); + + // If new items wouldn't fit into the buffer with fewest items, create a new buffer, + // throw if new buffer cannot be created. + if ((mCpuBuffers[bufferIndex].size() + itemCount) * sizeof(T) > kBufferSizeLimit) + { + bufferIndex = mCpuBuffers.size(); + if (bufferIndex >= kMaxBufferCount) + FALCOR_THROW("Buffers {} cannot accomodate all the date within the buffer limit.", mBufferName); + mCpuBuffers.push_back({}); + } + + const uint32_t elementIndex = mCpuBuffers[bufferIndex].size(); + FALCOR_ASSERT((((1 << kBufferIndexOffset) - 1) & elementIndex) == elementIndex, "Element index overflows into buffer index"); + mCpuBuffers[bufferIndex].resize(mCpuBuffers[bufferIndex].size() + itemCount); + return ((bufferIndex << kBufferIndexOffset) | elementIndex); + } + + /// Creates the GPU buffers, locking further inserts. + /// Will clear any existing GPU buffers. + void createGpuBuffers(const ref& mpDevice, ResourceBindFlags bindFlags) + { + mGpuBuffers.clear(); + mGpuBuffers.reserve(mCpuBuffers.size()); + for (size_t i = 0; i < mCpuBuffers.size(); ++i) + { + if (mCpuBuffers[i].empty()) + { + mGpuBuffers.push_back({}); + continue; + } + + ref buffer = mpDevice->createStructuredBuffer( + sizeof(T), mCpuBuffers[i].size(), bindFlags, MemoryType::DeviceLocal, mCpuBuffers[i].data(), false + ); + buffer->setName(fmt::format("SplitBuffer:{}:[{}]", mBufferName, i)); + mGpuBuffers.push_back(std::move(buffer)); + } + }; + + /// Return true when the SplitBuffer empty. + bool empty() const + { + // We check if all CPU buffers are empty. If so, we also check GPU buffers, as the CPU buffers + // maybe have been dropped. + for (auto& it : mCpuBuffers) + if (!it.empty()) + return false; + for (auto& it : mGpuBuffers) + if (it) + return false; + return true; + } + + /// Returns the number of buffers. + size_t getBufferCount() const + { + // We check both CPU and GPU buffers, to get correct answer even before `createGpuBuffers` + // and after `dropCpuBuffers` + return std::max(mCpuBuffers.size(), mGpuBuffers.size()); + } + + /// Total number of bytes used by the buffers (mostly for statistics) + size_t getByteSize() const + { + size_t result = 0; + if (!mCpuBuffers.empty()) + { + for (auto& it : mCpuBuffers) + result += it.size() * sizeof(T); + } + else + { + for (auto& it : mGpuBuffers) + result += it->getSize(); + } + return result; + } + + uint32_t getBufferIndex(uint32_t index) const { return (index >> kBufferIndexOffset); } + + uint32_t getElementIndex(uint32_t index) const { return index & kElementIndexMask; } + + /// Access to the CPU data via index returned from `insert` + const T& operator[](uint32_t index) const + { + FALCOR_ASSERT(!mCpuBuffers.empty()); + const uint32_t bufferIndex = getBufferIndex(index); + const uint32_t elementIndex = getElementIndex(index); + return mCpuBuffers[bufferIndex][elementIndex]; + } + + /// Access to the CPU data via index returned from `insert` + T& operator[](uint32_t index) + { + FALCOR_ASSERT(!mCpuBuffers.empty()); + const uint32_t bufferIndex = getBufferIndex(index); + const uint32_t elementIndex = getElementIndex(index); + return mCpuBuffers[bufferIndex][elementIndex]; + } + + /// Removes all CPU data, to conserve memory. + void dropCpuData() { mCpuBuffers.clear(); } + + /// True when there is any CPU buffer present. + bool hasCpuData() const { return !mCpuBuffers.empty(); } + + /// Return a GPU buffer, indexed by buffer index. + ref getGpuBuffer(uint32_t bufferIndex) const { return mGpuBuffers[bufferIndex]; } + + const std::vector& getCpuBuffer(uint32_t bufferIndex) const { return mCpuBuffers[bufferIndex]; } + + /// Gets GPU address of the index returned from `insert` + uint64_t getGpuAddress(uint32_t index) const + { + const uint32_t bufferIndex = getBufferIndex(index); + const uint32_t elementIndex = getElementIndex(index); + return getGpuBuffer(bufferIndex)->getGpuAddress() + size_t(elementIndex) * sizeof(T); + } + + /// Sets the define that governs how many buffers will the corresponding GPU type have. + void getShaderDefines(DefineList& defines) const + { + FALCOR_ASSERT(!mBufferCountDefinePrefix.empty()); + defines.add(mBufferCountDefinePrefix + "_BUFFER_COUNT", std::to_string(mGpuBuffers.size())); + defines.add(mBufferCountDefinePrefix + "_BUFFER_INDEX_BITS", std::to_string(kBufferIndexBits)); + } + + /// Binds the SplitBuffer to the corresponding GPU type + void bindShaderData(const ShaderVar& var) const + { + static const std::string kDataStr = "data"; + for (size_t i = 0; i < mGpuBuffers.size(); ++i) + var[kDataStr][i] = mGpuBuffers[i]; + } + +private: + /// Min number of bits needed to store the number + static constexpr uint32_t bitCount(uint32_t number) { return number < 2 ? number : (bitCount(number / 2) + 1); } + +private: + static constexpr size_t k4GBSizeLimit = (UINT64_C(1) << UINT64_C(32)) - UINT64_C(1024); + static constexpr size_t k2GBSizeLimit = (UINT64_C(1) << UINT64_C(31)); + static constexpr size_t kBufferSizeLimit = (sizeof(T) < 16 || TByteBuffer) ? k2GBSizeLimit : k4GBSizeLimit; + static constexpr size_t kMaxElementCount = (kBufferSizeLimit + 1 - sizeof(T)) / sizeof(T); + static_assert(kMaxElementCount * sizeof(T) <= kBufferSizeLimit); + static constexpr size_t kElementIndexBits = bitCount(kMaxElementCount - 1); + static constexpr size_t kBufferIndexBits = 32 - kElementIndexBits; + static constexpr size_t kMaxBufferCount = (1u << kBufferIndexBits); + + static constexpr uint32_t kBufferIndexOffset = 32 - kBufferIndexBits; + static constexpr size_t kElementIndexMask = ((1 << kBufferIndexOffset) - 1); + + std::string mBufferName; + std::string mBufferCountDefinePrefix; + std::vector> mCpuBuffers; + std::vector> mGpuBuffers; + + friend class SceneCache; +}; + +} // namespace Falcor diff --git a/Source/Falcor/Utils/TaskManager.h b/Source/Falcor/Utils/TaskManager.h index 6aecff674..aad9f254f 100644 --- a/Source/Falcor/Utils/TaskManager.h +++ b/Source/Falcor/Utils/TaskManager.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -29,7 +29,7 @@ #include "Core/Macros.h" -#include +#include #include #include diff --git a/Source/Falcor/Utils/Timing/Profiler.cpp b/Source/Falcor/Utils/Timing/Profiler.cpp index 31b902711..7903f5e77 100644 --- a/Source/Falcor/Utils/Timing/Profiler.cpp +++ b/Source/Falcor/Utils/Timing/Profiler.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -123,7 +123,7 @@ Profiler::Stats Profiler::Stats::compute(const float* data, size_t len) double mean = sum / len; double mean2 = sum2 / len; - double variance = mean2 - mean * mean; + double variance = len > 1 ? std::max(mean2 - mean * mean, 0.0) : 0.0; double stdDev = std::sqrt(variance); return {min, max, (float)mean, (float)stdDev}; @@ -231,6 +231,13 @@ void Profiler::Event::endFrame(uint32_t frameIndex) mTriggered = 0; } +void Profiler::Event::resetStats() +{ + FALCOR_ASSERT(mTriggered == 0); + mHistoryWriteIndex = 0; + mHistorySize = 0; +} + // Profiler::Capture std::string Profiler::Capture::toJsonString() const @@ -394,6 +401,18 @@ void Profiler::endFrame(RenderContext* pRenderContext) mLastFrameEvents = std::move(mCurrentFrameEvents); ++mFrameIndex; + + if (mPendingReset) + { + for (auto e : mLastFrameEvents) + e->resetStats(); + mPendingReset = false; + } +} + +void Profiler::resetStats() +{ + mPendingReset = true; } void Profiler::startCapture(size_t reservedFrames) @@ -481,6 +500,8 @@ FALCOR_SCRIPT_BINDING(Profiler) profiler.def_property_readonly("events", [](const Profiler& profiler) { return toPython(profiler.getEvents()); }); profiler.def("start_capture", &Profiler::startCapture, "reserved_frames"_a = 1000); profiler.def("end_capture", endCapture); + profiler.def("end_frame", [](Profiler& self) { self.endFrame(self.getDevice()->getRenderContext()); }); + profiler.def("reset_stats", &Profiler::resetStats); pybind11::class_(m, "ProfilerEvent") .def(pybind11::init()) diff --git a/Source/Falcor/Utils/Timing/Profiler.h b/Source/Falcor/Utils/Timing/Profiler.h index f8dd01da4..8d2bb04b7 100644 --- a/Source/Falcor/Utils/Timing/Profiler.h +++ b/Source/Falcor/Utils/Timing/Profiler.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -83,6 +83,8 @@ class FALCOR_API Profiler Stats computeCpuTimeStats() const; Stats computeGpuTimeStats() const; + void resetStats(); + private: Event(const std::string& name); @@ -237,6 +239,11 @@ class FALCOR_API Profiler */ const std::vector& getEvents() const { return mLastFrameEvents; } + /** + * Reset profiler stats at the next call to endFrame(). + */ + void resetStats(); + void breakStrongReferenceToDevice(); private: @@ -265,6 +272,7 @@ class FALCOR_API Profiler std::string mCurrentEventName; ///< Current nested event name. uint32_t mCurrentLevel = 0; ///< Current nesting level. uint32_t mFrameIndex = 0; ///< Current frame index. + bool mPendingReset = false; ///< Reset profiler stats at the next call to endFrame(). std::shared_ptr mpCapture; ///< Currently active capture. diff --git a/Source/Falcor/Utils/UI/Gui.cpp b/Source/Falcor/Utils/UI/Gui.cpp index 8f7be1c68..43aef444f 100644 --- a/Source/Falcor/Utils/UI/Gui.cpp +++ b/Source/Falcor/Utils/UI/Gui.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -137,6 +137,7 @@ class GuiImpl bool addDirectionWidget(const char label[], float3& direction); bool addCheckbox(const char label[], bool& var, bool sameLine = false); bool addCheckbox(const char label[], int& var, bool sameLine = false); + bool addCheckbox(const char label[], uint32_t& var, bool sameLine = false); template bool addBoolVecVar(const char label[], T& var, bool sameLine = false); bool addDragDropSource(const char label[], const char dataLabel[], const std::string& payloadString); @@ -715,6 +716,14 @@ bool GuiImpl::addCheckbox(const char label[], int& var, bool sameLine) return modified; } +bool GuiImpl::addCheckbox(const char label[], uint32_t& var, bool sameLine) +{ + bool value = (var != 0); + bool modified = addCheckbox(label, value, sameLine); + var = (value ? 1 : 0); + return modified; +} + template bool GuiImpl::addBoolVecVar(const char label[], T& var, bool sameLine) { @@ -1484,6 +1493,12 @@ FALCOR_API bool Gui::Widgets::checkbox(const char label[], int& var, bool s return mpGui ? mpGui->mpWrapper->addCheckbox(label, var, sameLine) : false; } +template<> +FALCOR_API bool Gui::Widgets::checkbox(const char label[], uint32_t& var, bool sameLine) +{ + return mpGui ? mpGui->mpWrapper->addCheckbox(label, var, sameLine) : false; +} + template bool Gui::Widgets::checkbox(const char label[], T& var, bool sameLine) { diff --git a/Source/Falcor/Utils/UI/ImGuiConfig.h b/Source/Falcor/Utils/UI/ImGuiConfig.h new file mode 100644 index 000000000..2d40ea50e --- /dev/null +++ b/Source/Falcor/Utils/UI/ImGuiConfig.h @@ -0,0 +1,48 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once + +// ImGui configuration file. See imconfig.h for more details. +// We use this to provide implicit conversion between ImGui's vector types +// and Falcor's vector types. + +#include "Utils/Math/VectorTypes.h" + +// clang-format off +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(const ::Falcor::float2& f) : x(f.x), y(f.y) {} \ + operator ::Falcor::float2() const { return ::Falcor::float2(x, y); } + +#define IM_VEC3_CLASS_EXTRA \ + constexpr ImVec3(const ::Falcor::float3& f) : x(f.x), y(f.y), z(f.z) {} \ + operator ::Falcor::float3() const { return ::Falcor::float3(x, y, z); } + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(const ::Falcor::float4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ + operator ::Falcor::float4() const { return ::Falcor::float4(x, y, z, w); } +// clang-format on diff --git a/Source/Falcor/Utils/UI/PythonUI.cpp b/Source/Falcor/Utils/UI/PythonUI.cpp index 3f1312a39..8770212fe 100644 --- a/Source/Falcor/Utils/UI/PythonUI.cpp +++ b/Source/Falcor/Utils/UI/PythonUI.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -176,6 +176,8 @@ class Text : public Widget virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); ImGui::TextUnformatted(m_text.c_str()); } @@ -196,6 +198,8 @@ class ProgressBar : public Widget virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); ImGui::ProgressBar(m_fraction); } @@ -221,6 +225,8 @@ class Button : public Widget virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); if (ImGui::Button(m_label.c_str())) { @@ -269,6 +275,8 @@ class Checkbox : public Property virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); if (ImGui::Checkbox(m_label.c_str(), &m_value)) { @@ -304,6 +312,8 @@ class Combobox : public Property virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); if (ImGui::Combo( m_label.c_str(), @@ -401,6 +411,8 @@ class Drag : public Property virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); bool changed = false; if constexpr (is_float == true) @@ -494,6 +506,8 @@ class Slider : public Property virtual void render() override { ScopedID id(this); + if (!m_visible) + return; ScopedDisable disable(!m_enabled); bool changed = false; if constexpr (is_float == true) diff --git a/Source/Modules/USDUtils/CMakeLists.txt b/Source/Modules/USDUtils/CMakeLists.txt index 68a28c534..fc8f5abaf 100644 --- a/Source/Modules/USDUtils/CMakeLists.txt +++ b/Source/Modules/USDUtils/CMakeLists.txt @@ -13,6 +13,10 @@ target_sources(USDUtils PRIVATE Tessellator/Tessellation.h Tessellator/UsdIndexedVector.h + ConvertedInput.h + ConvertedInput.cpp + ConvertedMaterialCache.h + ConvertedMaterialCache.cpp USDHelpers.h USDScene1Utils.cpp USDScene1Utils.h diff --git a/Source/Modules/USDUtils/ConvertedInput.cpp b/Source/Modules/USDUtils/ConvertedInput.cpp new file mode 100644 index 000000000..da24e91b4 --- /dev/null +++ b/Source/Modules/USDUtils/ConvertedInput.cpp @@ -0,0 +1,429 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "ConvertedInput.h" +#include + +using namespace pxr; +namespace Falcor +{ +namespace +{ + +static const TfToken gStToken("st"); +static const TfToken gTransform2dToken("UsdTransform2d"); +static const TfToken gScaleToken("scale"); +static const TfToken gRotationToken("rotation"); +static const TfToken gTranslationToken("translation"); +static const TfToken gFloat2PrimvarReaderToken("UsdPrimvarReader_float2"); +static const TfToken gFallbackToken("fallback"); +static const TfToken gFileToken("file"); +static const TfToken gSRGBToken("sRGB"); +static const TfToken gRawToken("raw"); +static const TfToken gSourceColorSpaceToken("sourceColorSpace"); +static const TfToken gWrapSToken("wrapS"); +static const TfToken gWrapTToken("wrapT"); +static const TfToken gBiasToken("bias"); +static const TfToken gUVTextureToken("UsdUVTexture"); +static const TfToken gRToken("r"); +static const TfToken gGToken("g"); +static const TfToken gBToken("b"); +static const TfToken gAToken("a"); +static const TfToken gRGToken("rg"); +static const TfToken gRGBToken("rgb"); +static const TfToken gInputsFileToken("inputs:file"); + +bool getFloat2Value(const UsdShadeInput& input, float2& val) +{ + SdfValueTypeName typeName(input.GetTypeName()); + if (typeName == SdfValueTypeNames->Float2) + { + GfVec2f value; + input.Get(&value, UsdTimeCode::EarliestTime()); + val = float2(value[0], value[1]); + return true; + } + else if (typeName == SdfValueTypeNames->Float) + { + float value; + input.Get(&value, UsdTimeCode::EarliestTime()); + val = float2(value, value); + return true; + } + return false; +} + +bool getFloat4Value(const UsdShadeInput& input, float4& val) +{ + SdfValueTypeName typeName(input.GetTypeName()); + if (typeName == SdfValueTypeNames->Float4) + { + GfVec4f value; + input.Get(&value, UsdTimeCode::EarliestTime()); + val = float4(value[0], value[1], value[2], value[3]); + return true; + } + else if (typeName == SdfValueTypeNames->Float3) + { + GfVec3f value; + input.Get(&value, UsdTimeCode::EarliestTime()); + val = float4(value[0], value[1], value[2], 1.f); + return true; + } + else if (typeName == SdfValueTypeNames->Float) + { + float value; + input.Get(&value, UsdTimeCode::EarliestTime()); + val = float4(value, value, value, value); + return true; + } + return false; +} +} // namespace + +bool ConvertedInput::convertTextureCoords(const UsdShadeShader& shader, Falcor::Transform& xform) +{ + UsdShadeConnectableAPI source; + TfToken sourceName; + UsdShadeAttributeType sourceType; + UsdShadeInput stInput(getSourceInput(shader.GetInput(gStToken), source, sourceName, sourceType)); + if (!stInput) + { + return false; + } + + TfToken id = getAttribute(source.GetPrim().GetAttribute(UsdShadeTokens->infoId), TfToken()); + if (id == gTransform2dToken) + { + if (UsdShadeInput scaleInput = source.GetInput(gScaleToken); scaleInput) + { + float2 scaleVec; + if (getFloat2Value(scaleInput, scaleVec)) + { + xform.setScaling(float3(scaleVec.x, scaleVec.y, 1.f)); + } + } + + if (UsdShadeInput rotateInput = source.GetInput(gRotationToken); rotateInput) + { + float degrees; + if (rotateInput.Get(°rees, UsdTimeCode::EarliestTime())) + { + xform.setRotationEulerDeg(float3(0.f, 0.f, degrees)); + } + } + + if (UsdShadeInput translateInput = source.GetInput(gTranslationToken); translateInput) + { + float2 translateVec; + if (getFloat2Value(translateInput, translateVec)) + { + xform.setTranslation(float3(translateVec.x, translateVec.y, 0.f)); + } + } + xform.setCompositionOrder(Transform::CompositionOrder::ScaleRotateTranslate); + } + else if (id != gFloat2PrimvarReaderToken) + { + logWarning( + "UsdUVTexture node '{}' defines an st primvar reader '{}' of an unexpected type: '{}'.", + shader.GetPrim().GetPath().GetString(), + source.GetPrim().GetPath().GetString(), + id.GetString() + ); + } + return true; +} + +ConvertedInput ConvertedInput::convertTexture( + const UsdShadeInput& input, + const TfToken& outputName, + const TextureEncoding& texEncoding, + ConvertedTexTransform& texTransform +) +{ + ConvertedInput ret; + + ret.shadeInput = input; + + // Note that the wrapS, wrapT, scale, and bias inputs are unsupported, and are ignored. + + UsdPrim prim(input.GetPrim()); + if (!prim.IsA()) + { + logWarning("Expected UsdUVTexture node '{}' is not a UsdShadeShader.", prim.GetPath().GetString()); + return ret; + } + + UsdShadeShader shader(prim); + + TfToken id = getAttribute(prim.GetAttribute(UsdShadeTokens->infoId), TfToken()); + if (id != gUVTextureToken) + { + logWarning("Expected UsdUVTexture node '{}' is not a UsdUVTexture.", prim.GetPath().GetString()); + return ret; + } + + if (shader.GetInput(gWrapSToken) || shader.GetInput(gWrapTToken)) + { + // Issue a low-priorty message, under the assumption that wrap modes most often don't matter. + logInfo("UsdUvTexture node '{}' specifies a wrap mode, which is not supported.", prim.GetPath().GetString()); + } + + if (UsdShadeInput biasInput = shader.GetInput(gBiasToken); biasInput) + { + float4 value; + if (!getFloat4Value(biasInput, value)) + { + logWarning( + "UsdUvTexture node '{}' specifies value scale of an unsupported type: '{}'.", + prim.GetPath().GetString(), + biasInput.GetTypeName().GetAsToken().GetString() + ); + } + else if (texEncoding == TextureEncoding::Normal) + { + if (any(value.xy() != float2(-1.f, -1.f))) + { + logWarning( + "UsdUvTexture normalMap node '{}' specifies a bias other than (-1, -1), which is not supported.", + prim.GetPath().GetString() + ); + } + } + else if (any(value != float4(0.f, 0.f, 0.f, 0.f))) + { + logWarning("UsdUvTexture node '{}' specifies a non-zero bias, which is not supported.", prim.GetPath().GetString()); + } + } + + if (UsdShadeInput scaleInput = shader.GetInput(gScaleToken); scaleInput) + { + if (!getFloat4Value(scaleInput, ret.textureScale)) + { + logWarning( + "UsdUvTexture node '{}' specifies value scale of an unsupported type: '{}'.", + prim.GetPath().GetString(), + scaleInput.GetTypeName().GetAsToken().GetString() + ); + } + else if (texEncoding == TextureEncoding::Normal) + { + if (any(ret.textureScale.xy() != float2(2.f, 2.f))) + { + logWarning( + "UsdUvTexture normallMap '{}' has scale other than (2.0, 2.0), which is not supported.", prim.GetPath().GetString() + ); + } + } + else if (any(ret.textureScale != float4(1.f, 1.f, 1.f, 1.f))) + { + logWarning("UsdUvTexture node '{}' specifies a value scale, which is not supported.", prim.GetPath().GetString()); + } + } + + if (convertTextureCoords(shader, ret.texTransform)) + { + texTransform.update(ret); + } + else + { + // Issue a lower priority message if there is simply no st input defined, under the assumption that the default + // behavior is expected. + logInfo("UsdUVTexture node '{}' does not define an st input.", prim.GetPath().GetString()); + } + + // Initialize the uniform converted value using the fallback value, if any. + GfVec4f fallbackValue = getAttribute(prim.GetAttribute(gFallbackToken), GfVec4f(0.f, 0.f, 0.f, 1.f)); + ret.uniformValue = float4(fallbackValue[0], fallbackValue[1], fallbackValue[2], fallbackValue[3]); + + // Color space may be specified in USD either as an attribute of the input connection, which we check here, or + // directly on the the asset, which we check below, and which takes precedence. + ret.loadSRGB = texEncoding == TextureEncoding::Srgb; + TfToken colorSpace = getAttribute(prim.GetAttribute(gSourceColorSpaceToken), TfToken()); + if (colorSpace == gSRGBToken) + ret.loadSRGB = true; + else if (colorSpace == gRawToken) + ret.loadSRGB = false; + + // Convert output specification to texture channel flag. + ret.channels = TextureChannelFlags::None; + if (outputName == gRToken) + { + ret.channels |= TextureChannelFlags::Red; + } + if (outputName == gGToken) + { + ret.channels |= TextureChannelFlags::Green; + } + if (outputName == gBToken) + { + ret.channels |= TextureChannelFlags::Blue; + } + if (outputName == gAToken) + { + ret.channels |= TextureChannelFlags::Alpha; + } + if (outputName == gRGToken) + { + ret.channels |= TextureChannelFlags::Red; + ret.channels |= TextureChannelFlags::Green; + } + if (outputName == gRGBToken) + { + ret.channels |= TextureChannelFlags::Red; + ret.channels |= TextureChannelFlags::Green; + ret.channels |= TextureChannelFlags::Blue; + } + + if (ret.channels == TextureChannelFlags::None) + { + logWarning("No valid output specified by UsdUVTexture '{}'.", prim.GetName().GetString()); + return ret; + } + + UsdShadeConnectableAPI source; + TfToken sourceName; + UsdShadeAttributeType sourceType; + UsdShadeInput fileInput(getSourceInput(shader.GetInput(gFileToken), source, sourceName, sourceType)); + if (fileInput) + { + SdfAssetPath path; + UsdPrim filePrim(fileInput.GetPrim()); + UsdAttribute fileAttrib = filePrim.GetAttribute(gInputsFileToken); + + TfToken fileColorSpace = fileAttrib.GetColorSpace(); + if (fileColorSpace == gSRGBToken) + ret.loadSRGB = true; + else if (fileColorSpace == gRawToken) + ret.loadSRGB = false; + + fileInput.Get(&path, UsdTimeCode::EarliestTime()); + ret.texturePath = path.GetResolvedPath().empty() ? path.GetAssetPath() : path.GetResolvedPath(); + + // If the filename contains , the asset refers to a UDIM texture set, which Falcor does not support. + // Replace with 1001 in an attempt to at least load one texture from the set. + auto udimPos = ret.texturePath.find(""); + std::string replacedFilename = ret.texturePath; + if (udimPos != std::string::npos) + { + ret.texturePath.replace(udimPos, 6, "1001"); + // This hoop-jumping is required because we need to resolve the given path relative to the + // layer in which it is defined, to ensure that e.g., "../../Textures/foo.dds" resolves properly. + SdfLayerHandle layerHandle(getLayerHandle(fileAttrib, UsdTimeCode::EarliestTime())); + ret.texturePath = SdfComputeAssetPathRelativeToLayer(layerHandle, ret.texturePath); + if (ret.texturePath.empty()) + { + ret.texturePath = path.GetAssetPath(); + } + } + } + else + { + logWarning("UsdUVTexture '{}' does not specify a file input.", prim.GetName().GetString()); + } + return ret; +} + +ConvertedInput ConvertedInput::convertFloat(const UsdShadeInput& input, const TfToken& sourceName, ConvertedTexTransform& texTransform) +{ + // Get the source attribute + ConvertedInput ret; + ret.shadeInput = input; + + SdfValueTypeName typeName(input.GetTypeName()); + if (typeName == SdfValueTypeNames->Asset) + { + ret = convertTexture(input, sourceName, TextureEncoding::Linear, texTransform); + } + else if (typeName == SdfValueTypeNames->Float) + { + input.Get(&ret.uniformValue.r, UsdTimeCode::EarliestTime()); + } + else + { + logWarning("Unexpected value type when converting float input: '{}'.", typeName.GetAsToken().GetString()); + } + return ret; +} + +ConvertedInput ConvertedInput::convertColor( + const UsdShadeInput& input, + const TfToken& sourceName, + const TextureEncoding& texEncoding, + ConvertedTexTransform& texTransform +) +{ + ConvertedInput ret; + ret.shadeInput = input; + + SdfValueTypeName typeName(input.GetTypeName()); + ret.uniformValue = float4(0.f, 0.f, 0.f, 1.f); + if (typeName == SdfValueTypeNames->Color3f || typeName == SdfValueTypeNames->Float3) + { + GfVec3f v; + if (input.Get(&v, UsdTimeCode::EarliestTime())) + { + ret.uniformValue = float4(v[0], v[1], v[2], 1.f); + } + } + else if (typeName == SdfValueTypeNames->Asset) + { + ret = convertTexture(input, sourceName, texEncoding, texTransform); + } + else + { + logWarning( + "Unexpected value type when converting color input: '{}' for '{}'.", + typeName.GetAsToken().GetString(), + input.GetFullName().GetString() + ); + } + return ret; +} + +void ConvertedTexTransform::update(const ConvertedInput& input) +{ + if (!input.isTextured()) + return; + const Falcor::Transform& newTransform = input.texTransform; + if (newTransform.getMatrix() != Falcor::float4x4::identity() && transform.getMatrix() == Falcor::float4x4::identity()) + { + transform = newTransform; + return; + } + if (newTransform.getMatrix() != transform.getMatrix()) + { + Falcor::logWarning( + "Shader input '{}' specifies a texture transform that differs from that used on another texture, which is not supported. " + "Applying the first " + "encountered non-idenity transform to all textures.", + input.shadeInput.GetPrim().GetPath().GetString() + ); + } +} +} // namespace Falcor diff --git a/Source/Modules/USDUtils/ConvertedInput.h b/Source/Modules/USDUtils/ConvertedInput.h new file mode 100644 index 000000000..9386e28d9 --- /dev/null +++ b/Source/Modules/USDUtils/ConvertedInput.h @@ -0,0 +1,157 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once + +#include "Utils/Math/Vector.h" +#include "Scene/Transform.h" +#include "USDUtils/USDHelpers.h" +#include "USDUtils/USDUtils.h" +#include "Core/API/Formats.h" +#include +#include +BEGIN_DISABLE_USD_WARNINGS +#include +#include +#include +#include +END_DISABLE_USD_WARNINGS + +namespace Falcor +{ + +struct ConvertedInput; + +struct ConvertedTexTransform +{ + Transform transform; + + /** + * Update transform based on TextureTransform associated with the given ConvertedInput. + * + * USD generally supports specifying a unique transform per texture, whereas Falcor assumes a single transform is + * applied to all textures in a material. This function causes a warning to be emitted if the USD is not compatible + * with Falcor. + */ + void update(const ConvertedInput& input); +}; + +struct ConvertedInput +{ + enum class TextureEncoding : uint8_t + { + Unknown = 0, + Linear, + Srgb, + Normal + }; + + ConvertedInput() : uniformValue(float4(0.f, 0.f, 0.f, 1.f)) {} + ConvertedInput(float v) : uniformValue(float4(v, 0.f, 0.f, 1.f)) {} + ConvertedInput(float4 v) : uniformValue(v) {} + + static ConvertedInput convertTexture( + const pxr::UsdShadeInput& input, + const pxr::TfToken& outputName, + const TextureEncoding& texEncoding, + ConvertedTexTransform& texTransform + ); + + static ConvertedInput convertFloat(const pxr::UsdShadeInput& input, const pxr::TfToken& outputName) + { + ConvertedTexTransform texTransform; // throwaway + return convertFloat(input, outputName, texTransform); + } + + static ConvertedInput convertFloat( + const pxr::UsdShadeInput& input, + const pxr::TfToken& outputName, + ConvertedTexTransform& texTransform + ); + + static ConvertedInput convertColor(const pxr::UsdShadeInput& input, const pxr::TfToken& outputName) + { + ConvertedTexTransform texTransform; // throwaway + return convertColor(input, outputName, TextureEncoding::Linear, texTransform); + } + + static ConvertedInput convertColor( + const pxr::UsdShadeInput& input, + const pxr::TfToken& outputName, + const TextureEncoding& texEncoding, + ConvertedTexTransform& texTransform + ); + static bool convertTextureCoords(const pxr::UsdShadeShader& shader, Falcor::Transform& xform); + + bool isTextured() const { return !texturePath.empty(); } + + bool operator==(const ConvertedInput& o) const + { + return texturePath == o.texturePath && texTransform == o.texTransform && all(textureScale == o.textureScale) && + channels == o.channels && all(uniformValue == o.uniformValue) && loadSRGB == o.loadSRGB; + } + + float4 uniformValue = float4(0.f, 0.f, 0.f, 0.f); ///< Uniform value, may only hold a single valid component for + ///< a float, and so on. + std::string texturePath; ///< Path to texture file. + Falcor::Transform texTransform; ///< 2D transformation applied to texture coordinates + float4 textureScale = float4(1.f, 1.f, 1.f, 1.f); ///< Texture value scale; valid only for emissive component. + ///< Alpha may be specified, but is ignored. + TextureChannelFlags channels = TextureChannelFlags::None; ///< Texture channels that hold the data. + bool loadSRGB = false; ///< If true, texture should be assumed to hold sRGB data. + UsdShadeInput shadeInput; ///< Input associated with value (for warning/error messages, not in operator==) +}; + +} // namespace Falcor + +namespace std +{ + +template<> +struct hash +{ + size_t operator()(const Falcor::Transform& t) const + { + size_t hash = 0; + Falcor::hash_combine(hash, t.getTranslation(), t.getScaling(), t.getRotation(), static_cast(t.getCompositionOrder())); + return hash; + } +}; + +template<> +struct hash +{ + size_t operator()(const Falcor::ConvertedInput& i) const + { + size_t hash = 0; + Falcor::hash_combine( + hash, i.uniformValue, i.texturePath, i.texTransform, i.textureScale, static_cast(i.channels), i.loadSRGB + ); + return hash; + } +}; +} // namespace std diff --git a/Source/Modules/USDUtils/ConvertedMaterialCache.cpp b/Source/Modules/USDUtils/ConvertedMaterialCache.cpp new file mode 100644 index 000000000..b2d324393 --- /dev/null +++ b/Source/Modules/USDUtils/ConvertedMaterialCache.cpp @@ -0,0 +1,28 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "ConvertedMaterialCache.h" diff --git a/Source/Modules/USDUtils/ConvertedMaterialCache.h b/Source/Modules/USDUtils/ConvertedMaterialCache.h new file mode 100644 index 000000000..b0cfbecd8 --- /dev/null +++ b/Source/Modules/USDUtils/ConvertedMaterialCache.h @@ -0,0 +1,176 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Scene/Material/Material.h" + +#include "USDUtils/USDUtils.h" +#include "USDUtils/USDHelpers.h" + +BEGIN_DISABLE_USD_WARNINGS +#include +#include +END_DISABLE_USD_WARNINGS + +#include +#include +#include +#include + +namespace Falcor +{ + +/** Cache of materials converted from a UsdShadeShader. + Minimizes creation of duplicate materials by caching converted materials by UsdShadeShader + (to trivially reuse created materials), and by a material spec (i.e., parameters used to create a + material of a given type), which avoids creating identical materials from different UsdShadeShaders. + Insertion and retrieval are thread-safe. + + The class is templated by: + + T: Material type (e.g., Falcor::StandardMaterial) + S: Material spec (e.g., Falcor::StandardMaterialSpec) + H: Hash class over S +*/ +template +class ConvertedMaterialCache +{ +public: + ref get(const pxr::UsdShadeShader& shader); + ref get(const S& spec, const std::string& shaderName); + + void add(const UsdShadeShader& shader, const S& spec, ref pMaterial); + +private: + //< Spec creation can be expensive, so we cache both by shader (to avoid unnecessary spec creation) + //< and by spec. + + ///< Map from UsdShadeShader to Falcor material instance. An entry with a null instance + ///< indicates in-progress conversion. + std::unordered_map, UsdObjHash> mPrimMaterialCache; + + ///< Map from material spec S to Falcor material instance. An entry with a null instance indicates in-progress + ///< conversion. + std::unordered_map, H> mSpecMaterialCache; + + std::mutex mCacheMutex; ///< Mutex controlling access to the unordered_maps. + std::condition_variable mPrimCacheUpdated; ///< Condition variable for threads waiting on by-prim cache update. + std::condition_variable mSpecCacheUpdated; ///< Condition variable for threads waiting on by-spec cache update. +}; + +template +ref ConvertedMaterialCache::get(const UsdShadeShader& shader) +{ + std::unique_lock lock(mCacheMutex); + + std::string shaderName = shader.GetPath().GetString(); + + while (true) + { + auto iter = mPrimMaterialCache.find(shader.GetPrim()); + if (iter == mPrimMaterialCache.end()) + { + // No entry for this shader. Add one, with a nullptr Falcor material instance, to indicate that work is + // underway. + logDebug("No cached material prim for shader '{}'; starting conversion.", shaderName); + mPrimMaterialCache.insert(std::make_pair(shader.GetPrim(), nullptr)); + return nullptr; + } + + if (iter->second != nullptr) + { + // Found a valid entry + logDebug("Found a cached material prim for shader '{}'.", shaderName); + return iter->second; + } + /** There is an cache entry, but it is null, indicating that another thread is current performing conversion. + Wait until we are signaled that a new entry has been added to the cache, and loop again to check for + existence in the cache. Note that this mechanism requires that conversion never fail to create an entry in + the cache after calling this function. + */ + logDebug("In-progress cached entry for shader '{}' detected. Waiting.", shaderName); + mPrimCacheUpdated.wait(lock); + } + FALCOR_UNREACHABLE(); + return nullptr; +} + +template +ref ConvertedMaterialCache::get(const S& spec, const std::string& shaderName) +{ + std::unique_lock lock(mCacheMutex); + + while (true) + { + auto iter = mSpecMaterialCache.find(spec); + if (iter == mSpecMaterialCache.end()) + { + // No entry for this shader. Add one, with a nullptr Falcor material instance, to indicate that work is + // underway. + logDebug("No cached material for spec '{}'; starting conversion.", shaderName); + mSpecMaterialCache.insert(std::make_pair(spec, nullptr)); + return nullptr; + } + if (iter->second != nullptr) + { + // Found a valid entry + logDebug("Found a cached material spec for surface '{}'.", shaderName); + return iter->second; + } + /** There is an cache entry, but it is null, indicating that another thread is current performing conversion. + Wait until we are signaled that a new entry has been added to the cache, and loop again to check for + existence in the cache. Note that this mechanism requires that conversion never fail to create an entry in + the cache after calling this function. + */ + logDebug("In-progress cached entry for spec '{}' detected. Waiting.", shaderName); + mSpecCacheUpdated.wait(lock); + } + FALCOR_UNREACHABLE(); + return nullptr; +} + +template +void ConvertedMaterialCache::add(const UsdShadeShader& shader, const S& spec, ref pMaterial) +{ + { + std::unique_lock lock(mCacheMutex); + if (mPrimMaterialCache.find(shader.GetPrim()) == mPrimMaterialCache.end()) + { + FALCOR_THROW("Expected converted material cache entry for '{}' not found.", shader.GetPath().GetString()); + } + mPrimMaterialCache[shader.GetPrim()] = pMaterial; + + if (mSpecMaterialCache.find(spec) == mSpecMaterialCache.end()) + { + FALCOR_THROW("Expected converted material spec cache entry for '{}' not found.", shader.GetPath().GetString()); + } + mSpecMaterialCache[spec] = pMaterial; + } + mPrimCacheUpdated.notify_all(); + mSpecCacheUpdated.notify_all(); +} +} // namespace Falcor diff --git a/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.cpp b/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.cpp index 0b5f43f0f..f6a0baf20 100644 --- a/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.cpp +++ b/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -73,450 +73,8 @@ inline int32_t getChannelIndex(TextureChannelFlags flags) } return -1; } - -// Return the ultimate source of the given input. -UsdShadeInput getSourceInput(UsdShadeInput input, UsdShadeConnectableAPI& source, TfToken& sourceName, UsdShadeAttributeType& sourceType) -{ - if (input && input.HasConnectedSource()) - { - if (input.GetConnectedSource(&source, &sourceName, &sourceType)) - { - auto inputs = source.GetInputs(); - if (!inputs.empty()) - { - // If there's a connected source of type asset, return it. - for (uint32_t i = 0; i < inputs.size(); ++i) - { - logDebug("Input '{}' has source '{}'.", input.GetBaseName().GetString(), inputs[i].GetBaseName().GetString()); - SdfValueTypeName typeName(inputs[i].GetTypeName()); - if (typeName == SdfValueTypeNames->Asset) - { - return inputs[i]; - } - } - // Otherwise, if there's no input of type asset, use the first connected source. - return inputs[0]; - } - } - } - return input; -} - -// Get the layer in which the given authored attribute is defined, if any. -SdfLayerHandle getLayerHandle(const UsdAttribute& attr, const UsdTimeCode& time) -{ - for (auto& spec : attr.GetPropertyStack(time)) - { - if (spec->HasDefaultValue() || spec->GetLayer()->GetNumTimeSamplesForPath(spec->GetPath()) > 0) - { - return spec->GetLayer(); - } - } - return SdfLayerHandle(); -} } // namespace -ref PreviewSurfaceConverter::getCachedMaterial(const UsdShadeShader& shader) -{ - std::unique_lock lock(mCacheMutex); - - std::string shaderName = shader.GetPath().GetString(); - - while (true) - { - auto iter = mPrimMaterialCache.find(shader.GetPrim()); - if (iter == mPrimMaterialCache.end()) - { - // No entry for this shader. Add one, with a nullptr Falcor material instance, to indicate that work is - // underway. - logDebug("No cached material prim for UsdPreviewSurface '{}'; starting conversion.", shaderName); - mPrimMaterialCache.insert(std::make_pair(shader.GetPrim(), nullptr)); - return nullptr; - } - if (iter->second != nullptr) - { - // Found a valid entry - logDebug("Found a cached material prim for UsdPreviewSurface '{}'.", shaderName); - return iter->second; - } - // There is an cache entry, but it is null, indicating that another thread is current performing conversion. - // Wait until we are signaled that a new entry has been added to the cache, and loop again to check for - // existence in the cache. Note that this mechanism requires that conversion never fail to create an entry in - // the cache after calling this function. - logDebug("In-progress cached entry for UsdPreviewSurface '{}' detected. Waiting.", shaderName); - mPrimCacheUpdated.wait(lock); - } - FALCOR_UNREACHABLE(); - return nullptr; -} - -ref PreviewSurfaceConverter::getCachedMaterial(const StandardMaterialSpec& spec) -{ - std::unique_lock lock(mCacheMutex); - - const std::string shaderName(spec.name); - - while (true) - { - auto iter = mSpecMaterialCache.find(spec); - if (iter == mSpecMaterialCache.end()) - { - // No entry for this shader. Add one, with a nullptr Falcor material instance, to indicate that work is - // underway. - logDebug("No cached material for spec '{}'; starting conversion.", shaderName); - mSpecMaterialCache.insert(std::make_pair(spec, nullptr)); - return nullptr; - } - if (iter->second != nullptr) - { - // Found a valid entry - logDebug("Found a cached material spec for UsdPreviewSurface '{}'.", shaderName); - return iter->second; - } - // There is an cache entry, but it is null, indicating that another thread is current performing conversion. - // Wait until we are signaled that a new entry has been added to the cache, and loop again to check for - // existence in the cache. Note that this mechanism requires that conversion never fail to create an entry in - // the cache after calling this function. - logDebug("In-progress cached entry for spec '{}' detected. Waiting.", shaderName); - mSpecCacheUpdated.wait(lock); - } - FALCOR_UNREACHABLE(); - return nullptr; -} - -void PreviewSurfaceConverter::cacheMaterial(const UsdShadeShader& shader, ref pMaterial) -{ - { - std::unique_lock lock(mCacheMutex); - if (mPrimMaterialCache.find(shader.GetPrim()) == mPrimMaterialCache.end()) - { - FALCOR_THROW("Expected PreviewSurfaceConverter cache entry for '{}' not found.", shader.GetPath().GetString()); - } - mPrimMaterialCache[shader.GetPrim()] = pMaterial; - } - mPrimCacheUpdated.notify_all(); -} - -void PreviewSurfaceConverter::cacheMaterial(const StandardMaterialSpec& spec, ref pMaterial) -{ - { - std::unique_lock lock(mCacheMutex); - if (mSpecMaterialCache.find(spec) == mSpecMaterialCache.end()) - { - FALCOR_THROW("Expected PreviewSurfaceConverter spec cache entry for '{}' not found.", spec.name); - } - mSpecMaterialCache[spec] = pMaterial; - } - mSpecCacheUpdated.notify_all(); -} - -bool getFloat2Value(const UsdShadeInput& input, float2& val) -{ - SdfValueTypeName typeName(input.GetTypeName()); - if (typeName == SdfValueTypeNames->Float2) - { - GfVec2f value; - input.Get(&value, UsdTimeCode::EarliestTime()); - val = float2(value[0], value[1]); - return true; - } - else if (typeName == SdfValueTypeNames->Float) - { - float value; - input.Get(&value, UsdTimeCode::EarliestTime()); - val = float2(value, value); - return true; - } - return false; -} - -bool getFloat4Value(const UsdShadeInput& input, float4& val) -{ - SdfValueTypeName typeName(input.GetTypeName()); - if (typeName == SdfValueTypeNames->Float4) - { - GfVec4f value; - input.Get(&value, UsdTimeCode::EarliestTime()); - val = float4(value[0], value[1], value[2], value[3]); - return true; - } - else if (typeName == SdfValueTypeNames->Float3) - { - GfVec3f value; - input.Get(&value, UsdTimeCode::EarliestTime()); - val = float4(value[0], value[1], value[2], 1.f); - return true; - } - else if (typeName == SdfValueTypeNames->Float) - { - float value; - input.Get(&value, UsdTimeCode::EarliestTime()); - val = float4(value, value, value, value); - return true; - } - return false; -} - -StandardMaterialSpec::ConvertedInput PreviewSurfaceConverter::convertTexture( - const UsdShadeInput& input, - const TfToken& outputName, - bool assumeSrgb, - bool scaleSupported -) const -{ - const TfToken fallbackToken("fallback"); - const TfToken fileToken("file"); - const TfToken float2PrimvarReaderToken("UsdPrimvarReader_float2"); - const TfToken transform2dToken("UsdTransform2d"); - const TfToken sRGBToken("sRGB"); - const TfToken rawToken("raw"); - const TfToken sourceColorSpaceToken("sourceColorSpace"); - const TfToken stToken("st"); - const TfToken wrapSToken("wrapS"); - const TfToken wrapTToken("wrapT"); - const TfToken biasToken("bias"); - const TfToken scaleToken("scale"); - const TfToken rotationToken("rotation"); - const TfToken translationToken("translation"); - - StandardMaterialSpec::ConvertedInput ret; - - // Note that the wrapS, wrapT, scale, and bias inputs are unsupported, and are ignored. - - UsdPrim prim(input.GetPrim()); - if (!prim.IsA()) - { - logWarning("Expected UsdUVTexture node '{}' is not a UsdShadeShader.", prim.GetPath().GetString()); - return ret; - } - - UsdShadeShader shader(prim); - - TfToken id = getAttribute(prim.GetAttribute(UsdShadeTokens->infoId), TfToken()); - if (id != TfToken("UsdUVTexture")) - { - logWarning("Expected UsdUVTexture node '{}' is not a UsdUVTexture.", prim.GetPath().GetString()); - return ret; - } - - if (shader.GetInput(wrapSToken) || shader.GetInput(wrapTToken)) - { - // Issue a low-priorty message, under the assumption that wrap modes most often don't matter. - logInfo("UsdUvTexture node '{}' specifies a wrap mode, which is not supported.", prim.GetPath().GetString()); - } - - if (shader.GetInput(biasToken)) - { - float4 value; - if (!getFloat4Value(input, value) || any(value != float4(0.f, 0.f, 0.f, 0.f))) - { - logWarning("UsdUvTexture node '{}' specifies a non-zero bias, which is not supported.", prim.GetPath().GetString()); - } - } - - if (UsdShadeInput scaleInput = shader.GetInput(scaleToken); scaleInput) - { - if (!getFloat4Value(scaleInput, ret.textureScale)) - { - logWarning("UsdUvTexture node '{}' specifies value scale of an unsupported type.", prim.GetPath().GetString()); - } - else if (!scaleSupported && any(ret.textureScale != float4(1.f, 1.f, 1.f, 1.f))) - { - logWarning("UsdUvTexture node '{}' specifies a value scale, which is not supported.", prim.GetPath().GetString()); - } - } - - UsdShadeConnectableAPI source; - TfToken sourceName; - UsdShadeAttributeType sourceType; - UsdShadeInput stInput(getSourceInput(shader.GetInput(stToken), source, sourceName, sourceType)); - if (stInput) - { - id = getAttribute(source.GetPrim().GetAttribute(UsdShadeTokens->infoId), TfToken()); - if (id == transform2dToken) - { - if (UsdShadeInput scaleInput = source.GetInput(scaleToken); scaleInput) - { - float2 scaleVec; - if (getFloat2Value(scaleInput, scaleVec)) - { - ret.texTransform.setScaling(float3(scaleVec.x, scaleVec.y, 1.f)); - } - } - - if (UsdShadeInput rotateInput = source.GetInput(rotationToken); rotateInput) - { - float degrees; - if (rotateInput.Get(°rees, UsdTimeCode::EarliestTime())) - { - ret.texTransform.setRotationEulerDeg(float3(0.f, 0.f, degrees)); - } - } - - if (UsdShadeInput translateInput = source.GetInput(translationToken); translateInput) - { - float2 translateVec; - if (getFloat2Value(translateInput, translateVec)) - { - ret.texTransform.setTranslation(float3(translateVec.x, translateVec.y, 0.f)); - } - } - ret.texTransform.setCompositionOrder(Transform::CompositionOrder::ScaleRotateTranslate); - } - else if (id != float2PrimvarReaderToken) - { - logWarning( - "UsdUVTexture node '{}' defines an st primvar reader '{}' of an unexpected type: '{}'.", - prim.GetPath().GetString(), - source.GetPrim().GetPath().GetString(), - id.GetString() - ); - } - } - else - { - // Issue a lower priority message if there is simply no st input defined, under the assumption that the default - // behavior is expected. - logInfo("UsdUVTexture node '{}' does not define an st input.", prim.GetPath().GetString()); - } - - // Initialize the uniform converted value using the fallback value, if any. - GfVec4f fallbackValue = getAttribute(prim.GetAttribute(fallbackToken), GfVec4f(0.f, 0.f, 0.f, 1.f)); - ret.uniformValue = float4(fallbackValue[0], fallbackValue[1], fallbackValue[2], fallbackValue[3]); - - // Color space may be specified in USD either as an attribute of the input connection, which we check here, or - // directly on the the asset, which we check below, and which takes precedence. - ret.loadSRGB = assumeSrgb; - TfToken colorSpace = getAttribute(prim.GetAttribute(sourceColorSpaceToken), TfToken()); - if (colorSpace == sRGBToken) - ret.loadSRGB = true; - else if (colorSpace == rawToken) - ret.loadSRGB = false; - - // Convert output specification to texture channel flag. - ret.channels = TextureChannelFlags::None; - if (outputName == TfToken("r")) - { - ret.channels |= TextureChannelFlags::Red; - } - if (outputName == TfToken("g")) - { - ret.channels |= TextureChannelFlags::Green; - } - if (outputName == TfToken("b")) - { - ret.channels |= TextureChannelFlags::Blue; - } - if (outputName == TfToken("a")) - { - ret.channels |= TextureChannelFlags::Alpha; - } - if (outputName == TfToken("rg")) - { - // Note: not legal per the UsdPreviewSurface spec - ret.channels |= TextureChannelFlags::Red; - ret.channels |= TextureChannelFlags::Green; - } - if (outputName == TfToken("rgb")) - { - ret.channels |= TextureChannelFlags::Red; - ret.channels |= TextureChannelFlags::Green; - ret.channels |= TextureChannelFlags::Blue; - } - - if (ret.channels == TextureChannelFlags::None) - { - logWarning("No valid output specified by UsdUVTexture '{}'.", prim.GetName().GetString()); - return ret; - } - - UsdShadeInput fileInput(getSourceInput(shader.GetInput(fileToken), source, sourceName, sourceType)); - if (fileInput) - { - SdfAssetPath path; - UsdPrim filePrim(fileInput.GetPrim()); - UsdAttribute fileAttrib = filePrim.GetAttribute(TfToken("inputs:file")); - - TfToken fileColorSpace = fileAttrib.GetColorSpace(); - if (fileColorSpace == sRGBToken) - ret.loadSRGB = true; - else if (fileColorSpace == rawToken) - ret.loadSRGB = false; - - fileInput.Get(&path, UsdTimeCode::EarliestTime()); - ret.texturePath = path.GetResolvedPath().empty() ? path.GetAssetPath() : path.GetResolvedPath(); - - // If the filename contains , the asset refers to a UDIM texture set, which Falcor does not support. - // Replace with 1001 in an attempt to at least load one texture from the set. - auto udimPos = ret.texturePath.find(""); - std::string replacedFilename = ret.texturePath; - if (udimPos != std::string::npos) - { - ret.texturePath.replace(udimPos, 6, "1001"); - // This hoop-jumping is required because we need to resolve the given path relative to the - // layer in which it is defined, to ensure that e.g., "../../Textures/foo.dds" resolves properly. - SdfLayerHandle layerHandle(getLayerHandle(fileAttrib, UsdTimeCode::EarliestTime())); - ret.texturePath = SdfComputeAssetPathRelativeToLayer(layerHandle, ret.texturePath); - if (ret.texturePath.empty()) - { - ret.texturePath = path.GetAssetPath(); - } - } - } - else - { - logWarning("UsdUVTexture '{}' does not specify a file input.", prim.GetName().GetString()); - } - return ret; -} - -StandardMaterialSpec::ConvertedInput PreviewSurfaceConverter::convertFloat(const UsdShadeInput& input, const TfToken& sourceName) const -{ - StandardMaterialSpec::ConvertedInput ret; - SdfValueTypeName typeName(input.GetTypeName()); - if (typeName == SdfValueTypeNames->Asset) - { - ret = convertTexture(input, sourceName); - } - else if (typeName == SdfValueTypeNames->Float) - { - input.Get(&ret.uniformValue.r, UsdTimeCode::EarliestTime()); - } - else - { - logWarning("Unexpected value type when converting float input: '{}'.", typeName.GetAsToken().GetString()); - } - return ret; -} - -StandardMaterialSpec::ConvertedInput PreviewSurfaceConverter::convertColor( - const UsdShadeInput& input, - const TfToken& sourceName, - bool assumeSrgb, - bool scaleSupported -) const -{ - StandardMaterialSpec::ConvertedInput ret; - - SdfValueTypeName typeName(input.GetTypeName()); - ret.uniformValue = float4(0.f, 0.f, 0.f, 1.f); - if (typeName == SdfValueTypeNames->Color3f) - { - GfVec3f v; - input.Get(&v, UsdTimeCode::EarliestTime()); - ret.uniformValue = float4(v[0], v[1], v[2], 1.f); - } - else if (typeName == SdfValueTypeNames->Asset) - { - ret = convertTexture(input, sourceName, assumeSrgb, scaleSupported); - } - else - { - logWarning("Unexpected value type when converting color input: '{}'.", typeName.GetAsToken().GetString()); - } - return ret; -} - PreviewSurfaceConverter::PreviewSurfaceConverter(ref pDevice) : mpDevice(pDevice) { mpSpecTransPass = ComputePass::create(mpDevice, kSpecTransShaderFile, kSpecTransShaderEntry); @@ -534,7 +92,7 @@ PreviewSurfaceConverter::PreviewSurfaceConverter(ref pDevice) : mpDevice // Convert textured opacity to textured specular transparency. ref PreviewSurfaceConverter::createSpecularTransmissionTexture( - StandardMaterialSpec::ConvertedInput& opacity, + ConvertedInput& opacity, ref opacityTexture, RenderContext* pRenderContext ) @@ -581,9 +139,9 @@ ref PreviewSurfaceConverter::createSpecularTransmissionTexture( * If both are textured, they may be of different resolutions. */ ref PreviewSurfaceConverter::packBaseColorAlpha( - StandardMaterialSpec::ConvertedInput& baseColor, + ConvertedInput& baseColor, ref baseColorTexture, - StandardMaterialSpec::ConvertedInput& opacity, + ConvertedInput& opacity, ref opacityTexture, RenderContext* pRenderContext ) @@ -650,9 +208,9 @@ ref PreviewSurfaceConverter::packBaseColorAlpha( // Combine roughness and metallic parameters, one or both of which are textured, into a specular/ORM texture. // If both are textured, they may be of different resolutions. ref PreviewSurfaceConverter::createSpecularTexture( - StandardMaterialSpec::ConvertedInput& roughness, + ConvertedInput& roughness, ref roughnessTexture, - StandardMaterialSpec::ConvertedInput& metallic, + ConvertedInput& metallic, ref metallicTexture, RenderContext* pRenderContext ) @@ -705,7 +263,7 @@ ref PreviewSurfaceConverter::createSpecularTexture( return pTexture; } -ref PreviewSurfaceConverter::loadTexture(const StandardMaterialSpec::ConvertedInput& ci) +ref PreviewSurfaceConverter::loadTexture(const ConvertedInput& ci) { if (ci.texturePath.empty()) { @@ -723,7 +281,7 @@ ref PreviewSurfaceConverter::loadTexture(const StandardMaterialSpec::Co { // Create the texture by first reading the image (which is relatively slow) outside of the mutex, // and then creating the texture itself inside it. - Bitmap::UniqueConstPtr pBitmap(Bitmap::createFromFile(ci.texturePath, false)); + Bitmap::UniqueConstPtr pBitmap(Bitmap::createFromFile(ci.texturePath, true /* topDown */)); if (pBitmap) { ResourceFormat format = pBitmap->getFormat(); @@ -748,7 +306,13 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name { StandardMaterialSpec spec(name); - for (auto& curInput : shader.GetInputs()) + std::vector shadeInputs(shader.GetInputs()); + + bool useNormal2 = + std::find_if(shadeInputs.begin(), shadeInputs.end(), [](const UsdShadeInput& input) { return input.GetBaseName() == "normal2"; }) != + shadeInputs.end(); + + for (auto& curInput : shadeInputs) { logDebug("UsdPreviewSurface '{}' has input: {}", shader.GetPath().GetString(), curInput.GetBaseName().GetString()); @@ -770,23 +334,24 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name if (inputName == "diffusecolor") { // Get diffuse color value(s), assume that textures are sRGB by default. - spec.baseColor = convertColor(input, sourceName, true); - spec.updateTexTransform(spec.baseColor); + spec.baseColor = ConvertedInput::convertColor(input, sourceName, ConvertedInput::TextureEncoding::Srgb, spec.texTransform); } else if (inputName == "emissivecolor") { // Get color value(s), assume that textures are sRGB by default. - spec.emission = convertColor(input, sourceName, true); + spec.emission = ConvertedInput::convertColor(input, sourceName); } else if (inputName == "usespecularworkflow") { if (typeName == SdfValueTypeNames->Int) { int specularWorkflow; - input.Get(&specularWorkflow, UsdTimeCode::EarliestTime()); - if (specularWorkflow) + if (input.Get(&specularWorkflow, UsdTimeCode::EarliestTime())) { - logWarning("Specular workflow is not supported."); + if (specularWorkflow) + { + logWarning("Specular workflow is not supported."); + } } } else @@ -796,8 +361,7 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name } else if (inputName == "metallic") { - spec.metallic = convertFloat(input, sourceName); - spec.updateTexTransform(spec.metallic); + spec.metallic = ConvertedInput::convertFloat(input, sourceName, spec.texTransform); } else if (inputName == "specularcolor") { @@ -816,8 +380,7 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name } else if (inputName == "roughness") { - spec.roughness = convertFloat(input, sourceName); - spec.updateTexTransform(spec.roughness); + spec.roughness = ConvertedInput::convertFloat(input, sourceName, spec.texTransform); } else if (inputName == "clearcoat") { @@ -829,8 +392,7 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name } else if (inputName == "opacity") { - spec.opacity = convertFloat(input, sourceName); - spec.updateTexTransform(spec.opacity); + spec.opacity = ConvertedInput::convertFloat(input, sourceName, spec.texTransform); } else if (inputName == "opacitythreshold") { @@ -862,38 +424,42 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name logWarning("Unsupported ior value type: '{}'.", typeName.GetAsToken().GetString()); } } - else if (inputName == "normal") + else if (inputName == "normal" || inputName == "normal2") { - if (typeName == SdfValueTypeNames->Normal3f) + // The extra logic here allows us to suppress the 'unsupported' warning message below. + if ((inputName == "normal" && !useNormal2) || (inputName == "normal2" && useNormal2)) { - logWarning("Falcor's standard material does not support uniform normal values."); - } - else if (typeName == SdfValueTypeNames->Asset) - { - spec.normal = convertTexture(input, sourceName); - spec.updateTexTransform(spec.normal); - } - else - { - logWarning("Unsupported normal value type: '{}'.", typeName.GetAsToken().GetString()); + if (typeName == SdfValueTypeNames->Asset) + { + spec.normal = + ConvertedInput::convertTexture(input, sourceName, ConvertedInput::TextureEncoding::Normal, spec.texTransform); + } + else + { + logWarning("Falcor's standard material does not support uniform normal values."); + } } } else if (inputName == "displacement") { - spec.disp = convertFloat(input, sourceName); + spec.disp = ConvertedInput::convertFloat(input, sourceName, spec.texTransform); if (!spec.disp.isTextured()) { logWarning("Falcor's standard material does not support uniform displacement."); } - else - { - spec.updateTexTransform(spec.disp); - } } else if (inputName == "occlusion") { logWarning("Falcor's standard material does not support an occlusion parameter."); } + else if (inputName == "volumeabsorption") + { + spec.volumeAbsorption = ConvertedInput::convertColor(input, sourceName); + } + else if (inputName == "volumescattering") + { + spec.volumeScattering = ConvertedInput::convertColor(input, sourceName); + } else { logWarning("Unsupported UsdPreviewSurface input '{}'.", inputName); @@ -902,53 +468,29 @@ StandardMaterialSpec PreviewSurfaceConverter::createSpec(const std::string& name return spec; } -ref PreviewSurfaceConverter::convert(const UsdShadeMaterial& material, const std::string& primName, RenderContext* pRenderContext) +ref PreviewSurfaceConverter::convert(const UsdShadeMaterial& material, RenderContext* pRenderContext) { - TfToken renderContext(""); // Blank implies universal context. Use e.g. "falcor" for a renderer- or - // material-specific context. - UsdShadeOutput surface(material.GetSurfaceOutput(renderContext)); - - if (!surface.IsDefined()) - { - logDebug("Material '{}' does not define a universal surface output.", primName); - return nullptr; - } + UsdShadeShader shader = material.ComputeSurfaceSource(); + std::string materialName = material.GetPath().GetString(); - // Get the UsdShadeShader that provides the surface output - UsdShadeShader shader; - UsdPrim surfacePrim(surface.GetPrim()); - if (surfacePrim.IsDefined()) - { - UsdShadeConnectableAPI source; - TfToken sourceName; - UsdShadeAttributeType sourceType; - if (UsdShadeConnectableAPI::GetConnectedSource(surface, &source, &sourceName, &sourceType)) - { - UsdPrim prim(source.GetPrim()); - if (prim.IsA()) - { - shader = UsdShadeShader(prim); - } - } - } if (!shader) { - logDebug("Material '{}' surface output is not a UsdShadeShader.", primName); + logDebug("Material '{}': could not find surface output UsdShadeShader.", materialName); return nullptr; } - TfToken id = getAttribute(shader.GetPrim().GetAttribute(UsdShadeTokens->infoId), TfToken()); - if (id != TfToken("UsdPreviewSurface")) + TfToken surfaceId = getAttribute(shader.GetPrim().GetAttribute(UsdShadeTokens->infoId), TfToken()); + if (surfaceId != TfToken("UsdPreviewSurface")) { - logDebug("Material '{}' has a surface output node of type '{}', not UsdPreviewSurface.", primName, id.GetString()); + logDebug("Material '{}' has a surface output node of type '{}', not UsdPreviewSurface.", materialName, surfaceId.GetString()); return nullptr; } - logDebug("Material '{}' has UsdPreviewSurface output '{}'.", primName, shader.GetPath().GetString()); + logDebug("Material '{}' has output '{}'.", materialName, shader.GetPath().GetString()); // Is there a valid cached instance for this material prim? If so, simply return it. // Note that this call will block if another thread is concurrently converting the same material. - ref pMaterial = getCachedMaterial(shader); + ref pMaterial = mMaterialCache.get(shader); if (pMaterial) { return pMaterial; @@ -957,19 +499,17 @@ ref PreviewSurfaceConverter::convert(const UsdShadeMaterial& material, // Due to the fact that threads may be waiting for material conversion to complete, the following code must always // create non-null by-prim and by-spec material cache entries. - std::string materialName(material.GetPrim().GetPath().GetString()); - // Construct a StandardMaterialSpec before creating the StandardMaterial itself from it in order to // avoid creating duplicate material instances. StandardMaterialSpec spec = createSpec(materialName, shader); // Does there already exist a material matching this spec? If so, return it. - pMaterial = getCachedMaterial(spec); + pMaterial = mMaterialCache.get(spec, spec.name); if (pMaterial) { // There is an entry for this material in the by-spec cache. Ensure that there is also an entry in the by-prim // cache. - cacheMaterial(shader, pMaterial); + mMaterialCache.add(shader, spec, pMaterial); return pMaterial; } @@ -1003,7 +543,7 @@ ref PreviewSurfaceConverter::convert(const UsdShadeMaterial& material, if (spec.opacity.uniformValue.r < 1.f || opacityTexture) { - // Handle non-unit opacity + pMaterial->setAlphaThreshold(spec.opacityThreshold); if (spec.opacityThreshold > 0.f) { // Opacity encodes cutout values @@ -1016,7 +556,6 @@ ref PreviewSurfaceConverter::convert(const UsdShadeMaterial& material, { spec.baseColor.uniformValue = float4(spec.baseColor.uniformValue.xyz(), spec.opacity.uniformValue.r); } - pMaterial->setAlphaThreshold(spec.opacityThreshold); } else if (opacityTexture) { @@ -1085,25 +624,43 @@ ref PreviewSurfaceConverter::convert(const UsdShadeMaterial& material, pMaterial->setDisplacementMap(displacementTexture); } - if (spec.texTransform.getMatrix() != float4x4::identity()) + // Here, we apply a y-flip transformation to the texture, along with any USD-specified transform. + // We perform a flip because textures are stored in top-down order, but we address them assuming + // bottom-up order, per the USD spec. + Transform texTrans; + if (spec.texTransform.transform.getMatrix() != float4x4::identity()) { // Compute the inverse of the texture transform, as Falcor assumes // the transform applies to the texture, rather than the texture coordinates, // as UsdPreviewSurface does. - FALCOR_ASSERT(spec.texTransform.getCompositionOrder() == Transform::CompositionOrder::ScaleRotateTranslate); - Transform inv; - inv.setTranslation(-spec.texTransform.getTranslation()); - float3 scale = spec.texTransform.getScaling(); - inv.setScaling(float3(1.f / scale.x, 1.f / scale.y, 1.f)); - float3 rot = spec.texTransform.getRotationEuler(); - inv.setRotationEuler(-rot); - inv.setCompositionOrder(Transform::CompositionOrder::TranslateRotateScale); - pMaterial->setTextureTransform(inv); + FALCOR_ASSERT(spec.texTransform.transform.getCompositionOrder() == Transform::CompositionOrder::ScaleRotateTranslate); + texTrans.setTranslation(-spec.texTransform.transform.getTranslation()); + float3 scale = spec.texTransform.transform.getScaling(); + float3 rot = spec.texTransform.transform.getRotationEuler(); + texTrans.setRotationEuler(-rot); + // Negate y scale to perform y-flip + texTrans.setScaling(float3(1.f / scale.x, -1.f / scale.y, 1.f)); + texTrans.setCompositionOrder(Transform::CompositionOrder::TranslateRotateScale); + } + else + { + // Apply y-flip + texTrans.setScaling(float3(1.f, -1.f, 1.f)); + } + pMaterial->setTextureTransform(texTrans); + + if (any(spec.volumeAbsorption.uniformValue > 0.f)) + { + pMaterial->setVolumeAbsorption(spec.volumeAbsorption.uniformValue.xyz()); + } + + if (any(spec.volumeScattering.uniformValue > 0.f)) + { + pMaterial->setVolumeScattering(spec.volumeScattering.uniformValue.xyz()); } - // Cache the result of the conversion, both by shader node and by spec. - cacheMaterial(shader, pMaterial); - cacheMaterial(spec, pMaterial); + // Cache the result of the conversion. + mMaterialCache.add(shader, spec, pMaterial); return pMaterial; } diff --git a/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.h b/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.h index 58f7df26a..12019849d 100644 --- a/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.h +++ b/Source/Modules/USDUtils/PreviewSurfaceConverter/PreviewSurfaceConverter.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -35,21 +35,25 @@ #include "USDUtils/USDUtils.h" #include "USDUtils/USDHelpers.h" +#include "USDUtils/ConvertedMaterialCache.h" BEGIN_DISABLE_USD_WARNINGS #include #include END_DISABLE_USD_WARNINGS -#include #include #include -#include namespace Falcor { -/** Class to create Falcor::Material instances from UsdPreviewSurfaces. +/** + * Class to create Falcor::Material instances from UsdPreviewSurface. + * We extend UsdPreviewSurface by supporting additional inputs: + * - uniform float3 volumeAbsorption: Volume absorption coefficients + * - uniform float3 volumeScattering; Volume scattering coefficients + * - float2 normal2: 2-channel normal map */ class PreviewSurfaceConverter { @@ -62,55 +66,30 @@ class PreviewSurfaceConverter /** * Create a Falcor material from a USD material containing a UsdPreviewSurface shader. * \param material UsdShadeMaterial that contains the UsdPreviewSurface to be converted - * \param primName Name of the primitive to which the material is bound (for warning message reporting only). * \return A Falcor material instance representing the UsdPreviewSurface specified in the given material, if any, nullptr otherwise. */ - ref convert(const pxr::UsdShadeMaterial& material, const std::string& primName, RenderContext* pRenderContext); + ref convert(const pxr::UsdShadeMaterial& material, RenderContext* pRenderContext); private: StandardMaterialSpec createSpec(const std::string& name, const UsdShadeShader& shader) const; - ref loadTexture(const StandardMaterialSpec::ConvertedInput& ci); + ref loadTexture(const ConvertedInput& ci); - ref createSpecularTransmissionTexture( - StandardMaterialSpec::ConvertedInput& opacity, - ref opacityTexture, - RenderContext* pRenderContext - ); + ref createSpecularTransmissionTexture(ConvertedInput& opacity, ref opacityTexture, RenderContext* pRenderContext); ref packBaseColorAlpha( - StandardMaterialSpec::ConvertedInput& baseColor, + ConvertedInput& baseColor, ref baseColorTexture, - StandardMaterialSpec::ConvertedInput& opacity, + ConvertedInput& opacity, ref opacityTexture, RenderContext* pRenderContext ); ref createSpecularTexture( - StandardMaterialSpec::ConvertedInput& roughness, + ConvertedInput& roughness, ref roughnessTexture, - StandardMaterialSpec::ConvertedInput& metallic, + ConvertedInput& metallic, ref metallicTexture, RenderContext* pRenderContext ); - StandardMaterialSpec::ConvertedInput convertTexture( - const pxr::UsdShadeInput& input, - const TfToken& outputName, - bool assumeSrgb = false, - bool scaleSupported = false - ) const; - StandardMaterialSpec::ConvertedInput convertFloat(const pxr::UsdShadeInput& input, const TfToken& outputName) const; - StandardMaterialSpec::ConvertedInput convertColor( - const pxr::UsdShadeInput& input, - const TfToken& outputName, - bool assumeSrgb = false, - bool scaleSupported = false - ) const; - - ref getCachedMaterial(const pxr::UsdShadeShader& shader); - ref getCachedMaterial(const StandardMaterialSpec& spec); - - void cacheMaterial(const UsdShadeShader& shader, ref pMaterial); - void cacheMaterial(const StandardMaterialSpec& spec, ref pMaterial); - ref mpDevice; ref mpSpecTransPass; ///< Pass to convert opacity to transparency @@ -119,18 +98,9 @@ class PreviewSurfaceConverter ref mpSampler; ///< Bilinear clamp sampler - ///< Map from UsdPreviewSurface-defining UsdShadeShader to Falcor material instance. An entry with a null instance - ///< indicates in-progress conversion. - std::unordered_map, UsdObjHash> mPrimMaterialCache; - - ///< Map from StandardMaterialSpec to Falcor material instance. An entry with a null instance indicates in-progress - ///< conversion. - std::unordered_map, SpecHash> mSpecMaterialCache; + ConvertedMaterialCache mMaterialCache; - std::mutex mMutex; ///< Mutex to ensure serial invocation of calls that are not thread safe (e.g., texture creation - ///< and compute program execution). - std::mutex mCacheMutex; ///< Mutex controlling access to the material caches. - std::condition_variable mPrimCacheUpdated; ///< Condition variable for threads waiting on by-prim cache update. - std::condition_variable mSpecCacheUpdated; ///< Condition variable for threads waiting on by-spec cache update. + std::mutex mMutex; ///< Mutex to ensure serial invocation of calls that are not thread safe (e.g., texture creation + ///< and compute program execution). }; } // namespace Falcor diff --git a/Source/Modules/USDUtils/PreviewSurfaceConverter/StandardMaterialSpec.h b/Source/Modules/USDUtils/PreviewSurfaceConverter/StandardMaterialSpec.h index ec0a5ac30..b8dce1015 100644 --- a/Source/Modules/USDUtils/PreviewSurfaceConverter/StandardMaterialSpec.h +++ b/Source/Modules/USDUtils/PreviewSurfaceConverter/StandardMaterialSpec.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -31,28 +31,13 @@ #include "Scene/Material/Material.h" #include "Scene/Material/StandardMaterial.h" #include "Utils/Logger.h" +#include "USDUtils/USDUtils.h" +#include "USDUtils/ConvertedInput.h" #include namespace Falcor { -/** - * We avoid creating duplicate material instances by maintaing a cache that maps from StandardMaterialSpec to - * Falcor::StandardMaterial. Doing so requires that we compute a hash for a StandardMaterialSpec. The code here, and - * below in the std namespace, allows us to do so. - */ - -// Recursion bottom-out -inline void hash_combine(std::size_t& seed) {} - -// Varadic helper function to combine hash values. Adapted from boost::hash_combine -template -inline void hash_combine(std::size_t& seed, const T& v, Args... args) -{ - seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - hash_combine(seed, args...); -} - /** * Falcor::StandardMaterial specification. * Used to hold parameters needed to construct a StandardMaterial instance prior to actually doing so. @@ -60,87 +45,19 @@ inline void hash_combine(std::size_t& seed, const T& v, Args... args) */ struct StandardMaterialSpec { - struct TextureTransform - { - float2 scale = float2(1.f, 1.f); - float2 translate = float2(0.f, 0.f); - float rotate = 0.f; - - bool isIdentity() const { return rotate == 0.f && scale.x == 1.f && scale.y && 1.f && translate.x == 0.f && translate.y == 0.f; } - - bool operator==(const TextureTransform& other) const - { - return all(scale == other.scale) && all(translate == other.translate) && rotate == other.rotate; - } - - bool operator!=(const TextureTransform& other) const - { - return any(scale != other.scale) || any(translate != other.translate) || rotate != other.rotate; - } - }; - - struct ConvertedInput - { - ConvertedInput() : uniformValue(float4(0.f, 0.f, 0.f, 1.f)) {} - ConvertedInput(float v) : uniformValue(float4(v, 0.f, 0.f, 1.f)) {} - ConvertedInput(float4 v) : uniformValue(v) {} - - bool isTextured() const { return !texturePath.empty(); } - - bool operator==(const ConvertedInput& o) const - { - return texturePath == o.texturePath && texTransform == o.texTransform && all(textureScale == o.textureScale) && - channels == o.channels && all(uniformValue == o.uniformValue) && loadSRGB == o.loadSRGB; - } - - float4 uniformValue = float4(0.f, 0.f, 0.f, 0.f); ///< Uniform value, may only hold a single valid component for - ///< a float, and so on. - std::string texturePath; ///< Path to texture file. - Falcor::Transform texTransform; ///< 2D transformation applied to texture coordinates - float4 textureScale = float4(1.f, 1.f, 1.f, 1.f); ///< Texture value scale; valid only for emissive component. - ///< Alpha may be specified, but is ignored. - TextureChannelFlags channels = TextureChannelFlags::None; ///< Texture channels that hold the data. - bool loadSRGB = false; ///< If true, texture should be assumed to hold sRGB data. - }; - StandardMaterialSpec() {} StandardMaterialSpec(const std::string& name) : name(name) {} - /** - * Update texTransform based on TextureTransform associated with the given ConvertedInput. - * - * USD generally supports specifying a unique transform per texture, whereas Falcor assumes a single transform is - * applied to all textures in a material. This function causes a warning to be emitted if the USD is not compatible - * with Falcor. - */ - void updateTexTransform(StandardMaterialSpec::ConvertedInput& input) - { - if (!input.isTextured()) - return; - const Falcor::Transform& newTransform = input.texTransform; - if (newTransform.getMatrix() != Falcor::float4x4::identity() && texTransform.getMatrix() == Falcor::float4x4::identity()) - { - texTransform = newTransform; - } - else if (newTransform.getMatrix() != texTransform.getMatrix()) - { - Falcor::logWarning( - "Material '{}' uses more than one unique texture transform, which is not supported. Applying the first " - "encountered non-idenity transform to all textures.", - name - ); - } - }; - bool operator==(const StandardMaterialSpec& o) const { - return texTransform == o.texTransform && baseColor == o.baseColor && normal == o.normal && metallic == o.metallic && - roughness == o.roughness && opacity == o.opacity && emission == o.emission && disp == o.disp && - opacityThreshold == o.opacityThreshold && ior == o.ior; + return texTransform.transform == o.texTransform.transform && baseColor == o.baseColor && normal == o.normal && + metallic == o.metallic && roughness == o.roughness && opacity == o.opacity && emission == o.emission && disp == o.disp && + volumeAbsorption == o.volumeAbsorption && volumeScattering == o.volumeScattering && opacityThreshold == o.opacityThreshold && + ior == o.ior; } std::string name; - Transform texTransform; + ConvertedTexTransform texTransform; ConvertedInput baseColor; ConvertedInput normal; ConvertedInput metallic; @@ -148,6 +65,8 @@ struct StandardMaterialSpec ConvertedInput opacity = {1.f}; ConvertedInput emission; ConvertedInput disp; + ConvertedInput volumeAbsorption; + ConvertedInput volumeScattering; float opacityThreshold = 0.f; float ior = 1.5f; }; @@ -155,56 +74,28 @@ struct StandardMaterialSpec /** * Hash object for use by hashed containers. */ -class SpecHash +class StandardMaterialSpecHash { public: size_t operator()(const StandardMaterialSpec& o) const { size_t hash = 0; hash_combine( - hash, o.texTransform, o.baseColor, o.normal, o.metallic, o.roughness, o.opacity, o.emission, o.disp, o.opacityThreshold, o.ior + hash, + o.texTransform.transform, + o.baseColor, + o.normal, + o.metallic, + o.roughness, + o.opacity, + o.emission, + o.disp, + o.volumeAbsorption, + o.volumeScattering, + o.opacityThreshold, + o.ior ); return hash; } }; } // namespace Falcor - -namespace std -{ -// Hash objects for types comprising StandardMaterialSpec - -template<> -struct hash -{ - size_t operator()(const Falcor::StandardMaterialSpec::TextureTransform& t) const - { - size_t hash = 0; - Falcor::hash_combine(hash, t.scale, t.translate, t.rotate); - return hash; - } -}; - -template<> -struct hash -{ - size_t operator()(const Falcor::Transform& t) const - { - size_t hash = 0; - Falcor::hash_combine(hash, t.getTranslation(), t.getScaling(), t.getRotation(), static_cast(t.getCompositionOrder())); - return hash; - } -}; - -template<> -struct hash -{ - size_t operator()(const Falcor::StandardMaterialSpec::ConvertedInput& i) const - { - size_t hash = 0; - Falcor::hash_combine( - hash, i.uniformValue, i.texturePath, i.texTransform, i.textureScale, static_cast(i.channels), i.loadSRGB - ); - return hash; - } -}; -} // namespace std diff --git a/Source/Modules/USDUtils/Tessellator/Tessellation.cpp b/Source/Modules/USDUtils/Tessellator/Tessellation.cpp index 245be8a60..7933b9cd4 100644 --- a/Source/Modules/USDUtils/Tessellator/Tessellation.cpp +++ b/Source/Modules/USDUtils/Tessellator/Tessellation.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -399,6 +399,7 @@ UsdMeshData tessellate( Far::TopologyDescriptor::FVarChannel channels; Far::TopologyDescriptor desc = {}; + desc.isLeftHanded = (baseMesh.topology.orient == UsdGeomTokens->leftHanded); desc.numVertices = baseMesh.topology.faceIndices.size(); desc.numFaces = baseMesh.topology.getNumFaces(); desc.numVertsPerFace = (const int*)baseMesh.topology.faceCounts.data(); @@ -448,8 +449,6 @@ UsdMeshData tessellate( // Facet size 3 => triangulate tessOptions.SetFacetSize(3); - bool leftHanded = baseMesh.topology.orient == UsdGeomTokens->leftHanded; - // Note that normals on refined meshes are always per-vertex, and generated as part of // the subdivision process, as per the USD spec. As such, any authored normals are ignored. // Further, we do not need to handle e.g., face-varying or uniform normals here. @@ -538,8 +537,6 @@ UsdMeshData tessellate( ); // Use the partials to construct a normal vector. float3 normal = normalize(cross(du, dv)); - if (leftHanded) - normal = -normal; outNormals[j + 0] = normal.x; outNormals[j + 1] = normal.y; outNormals[j + 2] = normal.z; diff --git a/Source/Modules/USDUtils/USDUtils.cpp b/Source/Modules/USDUtils/USDUtils.cpp index 8c1a694a2..42d6fb19f 100644 --- a/Source/Modules/USDUtils/USDUtils.cpp +++ b/Source/Modules/USDUtils/USDUtils.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/Modules/USDUtils/USDUtils.h b/Source/Modules/USDUtils/USDUtils.h index a7f53cfd7..ac2093e96 100644 --- a/Source/Modules/USDUtils/USDUtils.h +++ b/Source/Modules/USDUtils/USDUtils.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -45,6 +45,8 @@ BEGIN_DISABLE_USD_WARNINGS #include #include #include +#include +#include END_DISABLE_USD_WARNINGS #include @@ -208,6 +210,61 @@ inline bool isTimeSampled(const UsdGeomPointBased& geomPointBased) return geomPointBased.GetPointsAttr().GetNumTimeSamples() > 1; } +// Return the ultimate source of the given input. +inline UsdShadeInput getSourceInput( + UsdShadeInput input, + UsdShadeConnectableAPI& source, + TfToken& sourceName, + UsdShadeAttributeType& sourceType +) +{ + if (input && input.HasConnectedSource()) + { + if (input.GetConnectedSource(&source, &sourceName, &sourceType)) + { + auto inputs = source.GetInputs(); + if (!inputs.empty()) + { + // If there's a connected source of type asset, return it. + for (uint32_t i = 0; i < inputs.size(); ++i) + { + SdfValueTypeName typeName(inputs[i].GetTypeName()); + logDebug( + "Input '{}' has source '{}', of type '{}'.", + input.GetBaseName().GetString(), + inputs[i].GetBaseName().GetString(), + typeName.GetAsToken().GetString() + ); + if (typeName == SdfValueTypeNames->Asset) + { + return inputs[i]; + } + } + // Otherwise, if there's no input of type asset, use the first connected source. + return inputs[0]; + } + } + else + { + logError("Failed to get connected source on {}", source.GetPath().GetString()); + } + } + return input; +} + +// Get the layer in which the given authored attribute is defined, if any. +inline SdfLayerHandle getLayerHandle(const UsdAttribute& attr, const UsdTimeCode& time) +{ + for (auto& spec : attr.GetPropertyStack(time)) + { + if (spec->HasDefaultValue() || spec->GetLayer()->GetNumTimeSamplesForPath(spec->GetPath()) > 0) + { + return spec->GetLayer(); + } + } + return SdfLayerHandle(); +} + // Route USD messages through Falcor's logging system class DiagDelegate : public TfDiagnosticMgr::Delegate { @@ -237,4 +294,16 @@ class ScopeGuard private: std::function m_func; }; + +// Recursion bottom-out +inline void hash_combine(std::size_t& seed) {} + +// Varadic helper function to combine hash values. Adapted from boost::hash_combine +template +inline void hash_combine(std::size_t& seed, const T& v, Args... args) +{ + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + hash_combine(seed, args...); +} + } // namespace Falcor diff --git a/Source/Mogwai/Mogwai.cpp b/Source/Mogwai/Mogwai.cpp index 3849a71b6..f9d687dd4 100644 --- a/Source/Mogwai/Mogwai.cpp +++ b/Source/Mogwai/Mogwai.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -587,13 +587,13 @@ namespace Mogwai desc.setMaxAnisotropy(8); mpSampler = getDevice()->createSampler(desc); } - mpScene->getMaterialSystem().setDefaultTextureSampler(mpSampler); + mpScene->setDefaultTextureSampler(mpSampler); } for (auto& g : mGraphs) { g.pGraph->setScene(mpScene); - g.sceneUpdates = Scene::UpdateFlags::None; + g.sceneUpdates = IScene::UpdateFlags::None; } getGlobalClock().setTime(0); } @@ -646,10 +646,10 @@ namespace Mogwai auto& pGraph = data.pGraph; // Notify active graph of any scene updates. - if (data.sceneUpdates != Scene::UpdateFlags::None) + if (data.sceneUpdates != IScene::UpdateFlags::None) { pGraph->onSceneUpdates(pRenderContext, data.sceneUpdates); - data.sceneUpdates = Scene::UpdateFlags::None; + data.sceneUpdates = IScene::UpdateFlags::None; } // Execute graph. @@ -694,6 +694,9 @@ namespace Mogwai { auto& pGraph = mGraphs[mActiveGraph].pGraph; + if (mSceneUpdateCallback) + mSceneUpdateCallback(mpScene, getGlobalClock().getTime()); + // Update scene and camera. if (mpScene) { @@ -847,6 +850,8 @@ int runMain(int argc, char** argv) args::Flag generateShaderDebugInfoFlag(parser, "", "Generate shader debug info.", {"debug-shaders"}); args::Flag enableDebugLayerFlag(parser, "", "Enable debug layer (enabled by default in Debug build).", {"enable-debug-layer"}); args::Flag preciseProgramFlag(parser, "", "Force all slang programs to run in precise mode", { "precise" }); + args::ValueFlag attributesFlag(parser, "path", "JSON attributes file.", { 'a', "attributes" }); + args::Flag rayTracingValidationFlag(parser, "", "Enable ray tracing validation (requires env-var NV_ALLOW_RAYTRACING_VALIDATION=1)", {"enable-raytracing-validation"}); args::CompletionFlag completionFlag(parser, {"complete"}); @@ -893,6 +898,16 @@ int runMain(int argc, char** argv) Logger::setLogFilePath(logfile); } + if (attributesFlag) + { + std::filesystem::path attributesPath(args::get(attributesFlag)); + if (!Settings::getGlobalSettings().addFilteredAttributes(attributesPath)) + { + std::cerr << "Failed to load attributes file '" << attributesPath.string() << "'." << std::endl; + return 1; + } + } + SampleAppConfig config; if (deviceTypeFlag) { @@ -925,6 +940,8 @@ int runMain(int argc, char** argv) config.generateShaderDebugInfo = true; if (preciseProgramFlag) config.shaderPreciseFloat = true; + if (rayTracingValidationFlag) + config.deviceDesc.enableRaytracingValidation = true; config.windowDesc.title = "Mogwai"; if (widthFlag) diff --git a/Source/Mogwai/Mogwai.h b/Source/Mogwai/Mogwai.h index ff0de8453..ee2d2df8a 100644 --- a/Source/Mogwai/Mogwai.h +++ b/Source/Mogwai/Mogwai.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -90,6 +90,7 @@ namespace Mogwai }; using KeyCallback = std::function; + using SceneUpdateCallback = std::function& pScene, double currentTime)>; Renderer(const SampleAppConfig& config, const Options& options); ~Renderer(); @@ -128,6 +129,9 @@ namespace Mogwai KeyCallback getKeyCallback() const { return mKeyCallback; } void setKeyCallback(KeyCallback keyCallback) { mKeyCallback = keyCallback; } + SceneUpdateCallback getSceneUpdateCallback() const { return mSceneUpdateCallback; } + void setSceneUpdateCallback(SceneUpdateCallback sceneUpdateCallback) { mSceneUpdateCallback = sceneUpdateCallback; } + // private: // MOGWAI friend class Extension; @@ -150,7 +154,7 @@ namespace Mogwai std::vector originalOutputs; std::vector debugWindows; std::unordered_map graphOutputRefs; - Scene::UpdateFlags sceneUpdates = Scene::UpdateFlags::None; + IScene::UpdateFlags sceneUpdates = IScene::UpdateFlags::None; }; ref mpScene; @@ -202,6 +206,7 @@ namespace Mogwai std::string mEditorScript; KeyCallback mKeyCallback; + SceneUpdateCallback mSceneUpdateCallback; FILE* mPipedOutput = nullptr; // Scripting diff --git a/Source/Mogwai/MogwaiScripting.cpp b/Source/Mogwai/MogwaiScripting.cpp index 1acfc7304..799e116b1 100644 --- a/Source/Mogwai/MogwaiScripting.cpp +++ b/Source/Mogwai/MogwaiScripting.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -55,6 +55,7 @@ namespace Mogwai const std::string kScene = "scene"; const std::string kClock = "clock"; const std::string kProfiler = "profiler"; + const std::string kSceneUpdateCallback = "sceneUpdateCallback"; const std::string kRendererVar = "m"; @@ -140,6 +141,7 @@ namespace Mogwai renderer.def(kRemoveGraph.c_str(), pybind11::overload_cast(&Renderer::removeGraph), "name"_a); renderer.def(kRemoveGraph.c_str(), pybind11::overload_cast&>(&Renderer::removeGraph), "graph"_a); renderer.def(kGetGraph.c_str(), &Renderer::getGraph, "name"_a); + renderer.def_property(kSceneUpdateCallback.c_str(), &Renderer::getSceneUpdateCallback, &Renderer::setSceneUpdateCallback); auto resizeFrameBuffer = [](Renderer* pRenderer, uint32_t width, uint32_t height) { pRenderer->resizeFrameBuffer(width, height); }; renderer.def(kResizeFrameBuffer.c_str(), resizeFrameBuffer); diff --git a/Source/Mogwai/MogwaiSettings.cpp b/Source/Mogwai/MogwaiSettings.cpp index a10698651..ba2fcd77a 100644 --- a/Source/Mogwai/MogwaiSettings.cpp +++ b/Source/Mogwai/MogwaiSettings.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -43,6 +43,7 @@ namespace Mogwai "F1 - Show the help message\n" "F9 - Show/hide the time\n" "F6 - Show/hide the graph UI\n" + "F7 - Enable/disable the overlay UI\n" "F10 - Show/hide the FPS\n" "F11 - Enable/disable main menu auto-hiding\n" "\n"; @@ -257,6 +258,9 @@ namespace Mogwai // Graph UI auto pActiveGraph = mpRenderer->mGraphs[mpRenderer->mActiveGraph].pGraph; pActiveGraph->renderUI(mpRenderer->getRenderContext(), w); + + if (mShowOverlayUI) + pActiveGraph->renderOverlayUI(mpRenderer->getRenderContext()); } void MogwaiSettings::renderMainMenu(Gui* pGui) @@ -303,6 +307,7 @@ namespace Mogwai { auto view = m.dropdown("View"); view.item("Graph UI", mShowGraphUI, "F6"); + view.item("Overlay UI", mShowOverlayUI, "F7"); view.item("Auto Hide", mAutoHideMenu, "F11"); view.item("FPS", mShowFps, "F10"); view.item("Time", mShowTime, "F9"); @@ -364,6 +369,9 @@ namespace Mogwai case Input::Key::F11: mAutoHideMenu = !mAutoHideMenu; break; + case Input::Key::F7: + mShowOverlayUI = !mShowOverlayUI; + break; case Input::Key::F6: mShowGraphUI = !mShowGraphUI; break; diff --git a/Source/Mogwai/MogwaiSettings.h b/Source/Mogwai/MogwaiSettings.h index 6fc1349cc..0ab1f23df 100644 --- a/Source/Mogwai/MogwaiSettings.h +++ b/Source/Mogwai/MogwaiSettings.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -48,6 +48,7 @@ namespace Mogwai MogwaiSettings(Renderer* pRenderer) : Extension(pRenderer, "Settings") {} void renderMainMenu(Gui* pGui); + void renderOverlay(Gui* pGui); void renderGraphs(Gui* pGui); void renderTimeSettings(Gui* pGui); void renderWindowSettings(Gui* pGui); @@ -56,6 +57,7 @@ namespace Mogwai bool mAutoHideMenu = false; bool mShowFps = true; + bool mShowOverlayUI = true; bool mShowGraphUI = true; bool mShowConsole = false; bool mShowTime = false; diff --git a/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp b/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp index fac6c2192..34f569d63 100644 --- a/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp +++ b/Source/RenderPasses/AccumulatePass/AccumulatePass.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -141,11 +141,11 @@ void AccumulatePass::execute(RenderContext* pRenderContext, const RenderData& re if (mpScene) { auto sceneUpdates = mpScene->getUpdates(); - if ((sceneUpdates & ~Scene::UpdateFlags::CameraPropertiesChanged) != Scene::UpdateFlags::None) + if ((sceneUpdates & ~IScene::UpdateFlags::CameraPropertiesChanged) != IScene::UpdateFlags::None) { reset(); } - if (is_set(sceneUpdates, Scene::UpdateFlags::CameraPropertiesChanged)) + if (is_set(sceneUpdates, IScene::UpdateFlags::CameraPropertiesChanged)) { auto excluded = Camera::Changes::Jitter | Camera::Changes::History; auto cameraChanges = mpScene->getCamera()->getChanges(); diff --git a/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp b/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp index f2356afc7..e79914b4f 100644 --- a/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp +++ b/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -256,7 +256,7 @@ void BSDFOptimizer::executeViewerPass(RenderContext* pRenderContext, const Rende void BSDFOptimizer::execute(RenderContext* pRenderContext, const RenderData& renderData) { - if (mpScene && is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded)) + if (mpScene && is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded)) { FALCOR_THROW("This render pass does not support scene changes that require shader recompilation."); } diff --git a/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cs.slang b/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cs.slang index af40893c1..719512731 100644 --- a/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cs.slang +++ b/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -47,12 +47,12 @@ struct SurfaceData : IDifferentiable }; [Differentiable] -SurfaceData prepareShadingData(const VertexData v, const float3 viewDir, const uint materialID, const ITextureSampler lod) +SurfaceData prepareShadingData(const VertexData v, const float3 viewDir, const uint materialID) { SurfaceData data = {}; // Setup Falcor's ShadingData based on the selected scene material and lobes. - data.sd = no_diff gScene.materials.prepareShadingData(v, materialID, viewDir, lod); + data.sd = no_diff gScene.materials.prepareShadingData(v, materialID, viewDir); return data; } @@ -69,7 +69,6 @@ IMaterialInstance getDiffMaterialInstance(out DiffMaterialData diffData, const S float3 evalBSDFSlice(bool isRef, const uint2 pixel) { float2 uv = no_diff getViewportCoord(pixel, float2(0.f), params.viewPortScale); - let lod = ExplicitLodTextureSampler(0.f); SampleGenerator sg = SampleGenerator(pixel, params.frameCount); // Calculate geometry and incident/outgoing directions. @@ -78,7 +77,7 @@ float3 evalBSDFSlice(bool isRef, const uint2 pixel) float3 lightDir = no_diff calculateSliceGeometry(uv, v, viewDir); // Setup shading data. - SurfaceData data = prepareShadingData(v, viewDir, isRef ? params.refMaterialID : params.initMaterialID, lod); + SurfaceData data = prepareShadingData(v, viewDir, isRef ? params.refMaterialID : params.initMaterialID); data.wo = detach(lightDir); // Set offset for writing gradients. diff --git a/Source/RenderPasses/BSDFOptimizer/BSDFViewer.cs.slang b/Source/RenderPasses/BSDFOptimizer/BSDFViewer.cs.slang index b484db6bf..00fadd31e 100644 --- a/Source/RenderPasses/BSDFOptimizer/BSDFViewer.cs.slang +++ b/Source/RenderPasses/BSDFOptimizer/BSDFViewer.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -47,12 +47,12 @@ struct BSDFViewer float3 wo; }; - SurfaceData prepareShadingData(const VertexData v, const float3 viewDir, const uint materialID, const ITextureSampler lod) + SurfaceData prepareShadingData(const VertexData v, const float3 viewDir, const uint materialID) { SurfaceData data = {}; // Setup Falcor's ShadingData based on the selected scene material and lobes. - data.sd = gScene.materials.prepareShadingData(v, materialID, viewDir, lod); + data.sd = gScene.materials.prepareShadingData(v, materialID, viewDir); return data; } @@ -65,7 +65,7 @@ struct BSDFViewer float3 lightDir = calculateSliceGeometry(uv, v, viewDir); // Setup shading data. - SurfaceData data = prepareShadingData(v, viewDir, materialID, lod); + SurfaceData data = prepareShadingData(v, viewDir, materialID); data.wo = lightDir; // Create BSDF instance. diff --git a/Source/RenderPasses/BSDFViewer/BSDFViewer.cpp b/Source/RenderPasses/BSDFViewer/BSDFViewer.cpp index cfb1522d6..c75fdd75e 100644 --- a/Source/RenderPasses/BSDFViewer/BSDFViewer.cpp +++ b/Source/RenderPasses/BSDFViewer/BSDFViewer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -176,7 +176,7 @@ void BSDFViewer::execute(RenderContext* pRenderContext, const RenderData& render return; } - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded)) { FALCOR_THROW("This render pass does not support scene changes that require shader recompilation."); } diff --git a/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang b/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang index d1dc24b8b..7bdfda38c 100644 --- a/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang +++ b/Source/RenderPasses/BSDFViewer/BSDFViewer.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -171,12 +171,12 @@ struct BSDFViewer * Prepare SurfaceData struct with shading data. * All unused fields are initialized to their default values. */ - SurfaceData prepareShadingData(const VertexData v, const float3 viewDir, const ITextureSampler lod) + SurfaceData prepareShadingData(const VertexData v, const float3 viewDir) { SurfaceData data = {}; // Setup Falcor's ShadingData based on the selected scene material and lobes. - data.sd = gScene.materials.prepareShadingData(v, params.materialID, viewDir, lod); + data.sd = gScene.materials.prepareShadingData(v, params.materialID, viewDir); data.sd.mtl.setActiveLobes(getActiveLobes()); return data; @@ -243,7 +243,7 @@ struct BSDFViewer float3 lightDir = calculateSliceGeometry(uv, v, viewDir); // Setup shading data. - data = prepareShadingData(v, viewDir, lod); + data = prepareShadingData(v, viewDir); data.wo = lightDir; // Create BSDF instance. @@ -319,7 +319,7 @@ struct BSDFViewer return evalBackground(uv, rayDir); // Setup shading data. - data = prepareShadingData(v, -rayDir, lod); + data = prepareShadingData(v, -rayDir); // Create BSDF instance. uint hints = !params.useNormalMapping ? (uint)MaterialInstanceHints::DisableNormalMapping : 0; diff --git a/Source/RenderPasses/CMakeLists.txt b/Source/RenderPasses/CMakeLists.txt index 96a8fb406..8d31a4f56 100644 --- a/Source/RenderPasses/CMakeLists.txt +++ b/Source/RenderPasses/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(MinimalPathTracer) add_subdirectory(ModulateIllumination) add_subdirectory(NRDPass) add_subdirectory(OptixDenoiser) +add_subdirectory(OverlaySamplePass) add_subdirectory(PathTracer) add_subdirectory(PixelInspectorPass) add_subdirectory(RenderPassTemplate) diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp index ea7f6f871..a76150410 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -127,9 +127,9 @@ void GBufferRT::execute(RenderContext* pRenderContext, const RenderData& renderD } // Check for scene changes. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::SDFGridConfigChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::GeometryChanged) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::SDFGridConfigChanged)) { recreatePrograms(); } @@ -307,7 +307,7 @@ void GBufferRT::executeCompute(RenderContext* pRenderContext, const RenderData& // Bind static resources ShaderVar var = mpComputePass->getRootVar(); - mpScene->setRaytracingShaderData(pRenderContext, var); + mpScene->bindShaderDataForRaytracing(pRenderContext, var["gScene"]); mpSampleGenerator->bindShaderData(var); } diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cs.slang b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cs.slang index 098ebf41e..7a1372805 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cs.slang +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -43,9 +43,9 @@ void main(uint3 dispatchThreadId: SV_DispatchThreadID) const Ray ray = gGBufferRT.generateRay(pixel); SceneRayQuery sceneRayQuery; - HitInfo hit; float hitT; - if (sceneRayQuery.traceRay(ray, hit, hitT, GBufferRT::kRayFlags, 0xff)) + const HitInfo hit = sceneRayQuery.traceRay(ray, hitT, GBufferRT::kRayFlags); + if (hit.isValid()) { gGBufferRT.writeHit(pixel, ray.origin, ray.dir, hit, hitT); } diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.slang b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.slang index eece7c03d..268741730 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.slang +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRT.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -255,7 +255,7 @@ struct GBufferRT } // Prepare shading data. - ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); bool modifyNormal = evalModifyNormal(hit.getType()); uint hints = !modifyNormal ? (uint)MaterialInstanceHints::DisableNormalMapping : 0; @@ -359,7 +359,7 @@ struct GBufferRT // Encode hit information. if (is_valid(gVBuffer)) { - gVBuffer[pixel] = hit.getData(); + gVBuffer[pixel] = hit.pack(); } } diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.3d.slang b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.3d.slang index 0bb338090..7194b2e6e 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.3d.slang +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.3d.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -93,7 +93,7 @@ GBufferPSOut psMain(VSOut vsOut, uint triangleIndex: SV_PrimitiveID, float3 bary discard; #endif const float3 viewDir = normalize(gScene.camera.getPosition() - v.posW); - ShadingData sd = gScene.materials.prepareShadingData(v, vsOut.materialID, viewDir, lod); + ShadingData sd = gScene.materials.prepareShadingData(v, vsOut.materialID, viewDir); uint hints = 0; #if ADJUST_SHADING_NORMALS diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp index ef021ab83..1b8a89e86 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -128,7 +128,7 @@ void GBufferRaster::recreatePrograms() mGBufferPass.pVars = nullptr; } -void GBufferRaster::onSceneUpdates(RenderContext* pRenderContext, Scene::UpdateFlags sceneUpdates) {} +void GBufferRaster::onSceneUpdates(RenderContext* pRenderContext, IScene::UpdateFlags sceneUpdates) {} void GBufferRaster::execute(RenderContext* pRenderContext, const RenderData& renderData) { @@ -162,7 +162,7 @@ void GBufferRaster::execute(RenderContext* pRenderContext, const RenderData& ren const RasterizerState::CullMode cullMode = mForceCullMode ? mCullMode : kDefaultCullMode; // Check for scene changes. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded)) { recreatePrograms(); } diff --git a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.h b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.h index 69539d9a4..27beae4ca 100644 --- a/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.h +++ b/Source/RenderPasses/GBuffer/GBuffer/GBufferRaster.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -48,7 +48,7 @@ class GBufferRaster : public GBuffer RenderPassReflection reflect(const CompileData& compileData) override; void execute(RenderContext* pRenderContext, const RenderData& renderData) override; void setScene(RenderContext* pRenderContext, const ref& pScene) override; - void onSceneUpdates(RenderContext* pRenderContext, Scene::UpdateFlags sceneUpdates) override; + void onSceneUpdates(RenderContext* pRenderContext, IScene::UpdateFlags sceneUpdates) override; virtual void compile(RenderContext* pRenderContext, const CompileData& compileData) override; private: diff --git a/Source/RenderPasses/GBuffer/GBufferBase.cpp b/Source/RenderPasses/GBuffer/GBufferBase.cpp index 4c39dee3c..97e8f885e 100644 --- a/Source/RenderPasses/GBuffer/GBufferBase.cpp +++ b/Source/RenderPasses/GBuffer/GBufferBase.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -176,12 +176,17 @@ void GBufferBase::execute(RenderContext* pRenderContext, const RenderData& rende void GBufferBase::setScene(RenderContext* pRenderContext, const ref& pScene) { + mUpdateFlagsConnection = {}; + mUpdateFlags = IScene::UpdateFlags::None; + mpScene = pScene; mFrameCount = 0; updateSamplePattern(); if (pScene) { + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); + // Trigger graph recompilation if we need to change the V-buffer format. ResourceFormat format = pScene->getHitInfo().getFormat(); if (format != mVBufferFormat) @@ -192,6 +197,7 @@ void GBufferBase::setScene(RenderContext* pRenderContext, const ref& pSce } } + static ref createSamplePattern(GBufferBase::SamplePattern type, uint32_t sampleCount) { switch (type) diff --git a/Source/RenderPasses/GBuffer/GBufferBase.h b/Source/RenderPasses/GBuffer/GBufferBase.h index c53e920cf..126565ea4 100644 --- a/Source/RenderPasses/GBuffer/GBufferBase.h +++ b/Source/RenderPasses/GBuffer/GBufferBase.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -72,6 +72,10 @@ class GBufferBase : public RenderPass // Internal state ref mpScene; + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + /// IScene::UpdateFlags accumulated since last `beginFrame()` + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + /// Sample generator for camera jitter. ref mpSampleGenerator; diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp index d25b3db94..72f53bb68 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -98,33 +98,33 @@ void VBufferRT::execute(RenderContext* pRenderContext, const RenderData& renderD FALCOR_ASSERT(pOutput); updateFrameDim(uint2(pOutput->getWidth(), pOutput->getHeight())); - // If there is no scene, clear the output and return. - if (mpScene == nullptr) + if (mpScene) { - pRenderContext->clearUAV(pOutput->getUAV().get(), uint4(0)); - clearRenderPassChannels(pRenderContext, kVBufferExtraChannels, renderData); - return; - } + // Check for scene changes. + if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged) || + is_set(mUpdateFlags, IScene::UpdateFlags::SDFGridConfigChanged)) + { + recreatePrograms(); + } - // Check for scene changes. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::SDFGridConfigChanged)) - { - recreatePrograms(); - } + // Configure depth-of-field. + // When DOF is enabled, two PRNG dimensions are used. Pass this info to subsequent passes via the dictionary. + mComputeDOF = mUseDOF && mpScene->getCamera()->getApertureRadius() > 0.f; + if (mUseDOF) + { + renderData.getDictionary()[Falcor::kRenderPassPRNGDimension] = mComputeDOF ? 2u : 0u; + } - // Configure depth-of-field. - // When DOF is enabled, two PRNG dimensions are used. Pass this info to subsequent passes via the dictionary. - mComputeDOF = mUseDOF && mpScene->getCamera()->getApertureRadius() > 0.f; - if (mUseDOF) + mUseTraceRayInline ? executeCompute(pRenderContext, renderData) : executeRaytrace(pRenderContext, renderData); + mUpdateFlags = IScene::UpdateFlags::None; + mFrameCount++; + } + else // If there is no scene, clear the output and return. { - renderData.getDictionary()[Falcor::kRenderPassPRNGDimension] = mComputeDOF ? 2u : 0u; + pRenderContext->clearUAV(pOutput->getUAV().get(), uint4(0)); + clearRenderPassChannels(pRenderContext, kVBufferExtraChannels, renderData); + return; } - - mUseTraceRayInline ? executeCompute(pRenderContext, renderData) : executeRaytrace(pRenderContext, renderData); - - mFrameCount++; } void VBufferRT::renderUI(Gui::Widgets& widget) @@ -156,6 +156,7 @@ Properties VBufferRT::getProperties() const return props; } + void VBufferRT::setScene(RenderContext* pRenderContext, const ref& pScene) { GBufferBase::setScene(pRenderContext, pScene); @@ -269,7 +270,7 @@ void VBufferRT::executeCompute(RenderContext* pRenderContext, const RenderData& // Bind static resources ShaderVar var = mpComputePass->getRootVar(); - mpScene->setRaytracingShaderData(pRenderContext, var); + mpScene->bindShaderDataForRaytracing(pRenderContext, var["gScene"]); mpSampleGenerator->bindShaderData(var); } @@ -281,6 +282,7 @@ void VBufferRT::executeCompute(RenderContext* pRenderContext, const RenderData& mpComputePass->execute(pRenderContext, uint3(mFrameDim, 1)); } + DefineList VBufferRT::getShaderDefines(const RenderData& renderData) const { DefineList defines; diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cs.slang b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cs.slang index 146007d57..f4d61f901 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cs.slang +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -43,9 +43,9 @@ void main(uint3 dispatchThreadId: SV_DispatchThreadID) const Ray ray = gVBufferRT.generateRay(pixel); SceneRayQuery sceneRayQuery; - HitInfo hit; float hitT; - if (sceneRayQuery.traceRay(ray, hit, hitT, VBufferRT::kRayFlags, 0xff)) + const HitInfo hit = sceneRayQuery.traceRay(ray, hitT, VBufferRT::kRayFlags); + if (hit.isValid()) { gVBufferRT.writeHit(pixel, ray.origin, ray.dir, hit); } diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.h b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.h index a2cafe67f..146c52ad1 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.h +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.rt.slang b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.rt.slang index ea59d01dd..8140095f3 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.rt.slang +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.rt.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -26,7 +26,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ #include "Scene/SceneDefines.slangh" - import Scene.Raytracing; import Scene.Intersection; import Scene.SDFs.SDFGridHitData; @@ -40,6 +39,7 @@ struct RayData int dummy; }; + // // Shader entry point for miss shader. // @@ -52,6 +52,7 @@ void miss(inout RayData rayData) gVBufferRT.writeMiss(launchIndex, WorldRayOrigin(), WorldRayDirection()); } + // // Shader entry points for TriangleMesh hit groups. // @@ -195,7 +196,7 @@ void rayGen() VBufferRT::kRayFlags, 0xff /* instanceInclusionMask */, 0 /* hitIdx */, - rayTypeCount, + getRayTypeCount(), 0 /* missIdx */, ray.toRayDesc(), rayData diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang index b12989381..e7ef80ad9 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRT.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,11 +25,11 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -__exported import Scene.Shading; __exported import Utils.Timing.GpuTimer; __exported import Utils.Math.Ray; import Utils.Math.MathHelpers; import Utils.Sampling.SampleGenerator; +__exported import Scene.Shading; RWTexture2D gVBuffer; RWTexture2D gDepth; @@ -68,7 +68,7 @@ struct VBufferRT void writeHit(uint2 pixel, float3 rayOrigin, float3 rayDir, const HitInfo hit) { - gVBuffer[pixel] = hit.getData(); + gVBuffer[pixel] = hit.pack(); VertexData v; float depth = 1.f; diff --git a/Source/RenderPasses/GBuffer/VBuffer/VBufferRaster.cpp b/Source/RenderPasses/GBuffer/VBuffer/VBufferRaster.cpp index 788e8b24f..563e4631e 100644 --- a/Source/RenderPasses/GBuffer/VBuffer/VBufferRaster.cpp +++ b/Source/RenderPasses/GBuffer/VBuffer/VBufferRaster.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -137,7 +137,7 @@ void VBufferRaster::execute(RenderContext* pRenderContext, const RenderData& ren } // Check for scene changes. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded)) { recreatePrograms(); } diff --git a/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.cpp b/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.cpp index 5ac801426..aa20fec94 100644 --- a/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.cpp +++ b/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -130,8 +130,8 @@ void MinimalPathTracer::execute(RenderContext* pRenderContext, const RenderData& return; } - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::GeometryChanged)) { FALCOR_THROW("This render pass does not support scene changes that require shader recompilation."); } diff --git a/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang b/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang index a32209df6..df2b53f24 100644 --- a/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang +++ b/Source/RenderPasses/MinimalPathTracer/MinimalPathTracer.rt.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -131,10 +131,9 @@ struct ScatterRayData * @param[in] hit Hit information. * @param[in] rayOrigin Ray origin. * @param[in] rayDir Normalized ray direction. - * @param[in] lod Method for computing texture level-of-detail. * @return ShadingData struct. */ -ShadingData loadShadingData(const HitInfo hit, const float3 rayOrigin, const float3 rayDir, const ITextureSampler lod) +ShadingData loadShadingData(const HitInfo hit, const float3 rayOrigin, const float3 rayDir) { VertexData v = {}; uint materialID = {}; @@ -172,7 +171,7 @@ ShadingData loadShadingData(const HitInfo hit, const float3 rayOrigin, const flo } #endif - ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); return sd; } @@ -325,12 +324,12 @@ bool generateScatterRay(const ShadingData sd, const IMaterialInstance mi, bool i void handleHit(const HitInfo hit, inout ScatterRayData rayData) { const bool isCurveHit = hit.getType() == HitType::Curve; - let lod = ExplicitLodTextureSampler(0.f); // Load shading data. - ShadingData sd = loadShadingData(hit, rayData.origin, rayData.direction, lod); + ShadingData sd = loadShadingData(hit, rayData.origin, rayData.direction); // Create material instance. + let lod = ExplicitLodTextureSampler(0.f); let mi = gScene.materials.getMaterialInstance(sd, lod); // Add emitted light. @@ -403,12 +402,12 @@ float3 tracePath(const uint2 pixel, const uint2 frameDim) // Pixel represents a valid primary hit. Compute its contribution. const bool isCurveHit = hit.getType() == HitType::Curve; - let lod = ExplicitLodTextureSampler(0.f); // Load shading data. - ShadingData sd = loadShadingData(hit, primaryRayOrigin, primaryRayDir, lod); + ShadingData sd = loadShadingData(hit, primaryRayOrigin, primaryRayDir); // Create material instance at shading point. + let lod = ExplicitLodTextureSampler(0.f); let mi = gScene.materials.getMaterialInstance(sd, lod); // Create sample generator. diff --git a/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp b/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp index 5dd2a86a8..7436e88bc 100644 --- a/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp +++ b/Source/RenderPasses/OptixDenoiser/OptixDenoiser.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -458,7 +458,7 @@ void* OptixDenoiser_::exportBufferToCudaDevice(ref& buf) { if (buf == nullptr) return nullptr; - return cuda_utils::getSharedDevicePtr(buf->getSharedApiHandle(), (uint32_t)buf->getSize()); + return cuda_utils::getSharedDevicePtr(buf->getDevice()->getType(), buf->getSharedApiHandle(), (uint32_t)buf->getSize()); } void OptixDenoiser_::setupDenoiser() diff --git a/Source/RenderPasses/OverlaySamplePass/CMakeLists.txt b/Source/RenderPasses/OverlaySamplePass/CMakeLists.txt new file mode 100644 index 000000000..0fef875e7 --- /dev/null +++ b/Source/RenderPasses/OverlaySamplePass/CMakeLists.txt @@ -0,0 +1,10 @@ +add_plugin(OverlaySamplePass) + +target_sources(OverlaySamplePass PRIVATE + OverlaySamplePass.cpp + OverlaySamplePass.h +) + +target_copy_shaders(OverlaySamplePass RenderPasses/OverlaySamplePass) + +target_source_group(OverlaySamplePass "RenderPasses") diff --git a/Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.cpp b/Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.cpp new file mode 100644 index 000000000..db0f6cdef --- /dev/null +++ b/Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.cpp @@ -0,0 +1,276 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "OverlaySamplePass.h" +#include "RenderGraph/RenderPassStandardFlags.h" + +// We'll be directly using ImGui to draw the overlay UI. +#include "imgui.h" + +extern "C" FALCOR_API_EXPORT void registerPlugin(Falcor::PluginRegistry& registry) +{ + registry.registerClass(); + ScriptBindings::registerBinding(OverlaySamplePass::registerBindings); +} + +namespace +{ +const ChannelList kInputChannels = { + {"input", "", "Input buffer", true, ResourceFormat::RGBA32Float}, +}; + +const ChannelList kOutputChannels = { + {"output", "", "Output buffer of the solution", false, ResourceFormat::RGBA32Float}, +}; +} // namespace + +OverlaySamplePass::OverlaySamplePass(ref pDevice, const Properties& props) : RenderPass(pDevice) {} + +Properties OverlaySamplePass::getProperties() const +{ + Properties props; + return props; +} + +RenderPassReflection OverlaySamplePass::reflect(const CompileData& compileData) +{ + RenderPassReflection reflector; + + // Define our input/output channels. + addRenderPassInputs(reflector, kInputChannels); + addRenderPassOutputs(reflector, kOutputChannels); + + return reflector; +} + +void OverlaySamplePass::execute(RenderContext* pRenderContext, const RenderData& renderData) +{ + // Just copy the input to the output. + auto src = renderData.getTexture(kInputChannels[0].name); + auto dst = renderData.getTexture(kOutputChannels[0].name); + mFrameDim.x = dst->getWidth(); + mFrameDim.y = dst->getHeight(); + if (src) + { + pRenderContext->blit(src->getSRV(), dst->getRTV()); + } + mFrameCount++; +} + +void OverlaySamplePass::renderOverlayUI(RenderContext* pRenderContext) +{ + // This callback occurs after the "renderUI" callback, and will be triggered even when the dropdown is closed. + // Rather than drawing to a widget, we work directly with the ImGui draw list. + + float margin = 50.f; + + float2 frameMin = float2(0.0) + margin; + float2 frameMax = float2(mFrameDim) - margin; + float2 frameSize = frameMax - frameMin; + + // Get the background draw list + ImDrawList* drawList = ImGui::GetBackgroundDrawList(); + + // Create a rectangular frame with a red border. + drawList->AddRect(frameMin, frameMax, ImColor(float4(1.0, 0.0, 0.0, 0.0))); + + int primType = 0; + + // Inside that retangle, create a 5x3 grid of rectangles. + for (int j = 0; j < 3; j++) + { + for (int i = 0; i < 5; i++) + { + float2 rectMin = frameMin + frameSize * float2(float(i + 0) / 5.0f, float(j + 0) / 3.0f); + float2 rectMax = frameMin + frameSize * float2(float(i + 1) / 5.0f, float(j + 1) / 3.0f); + rectMin += margin; + rectMax -= margin; + drawList->AddRect(rectMin, rectMax, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + + // Draw one of the primitive types in this rectangle. + + // draw a line + if (primType == 0) + { + float2 p0 = rectMin; + float2 p1 = rectMax; + drawList->AddLine(p0 + margin, p1 - margin, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // draw a filled rectangle + if (primType == 1) + { + drawList->AddRect(rectMin + margin, rectMax - margin, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a filled multicolor rectangle + if (primType == 2) + { + drawList->AddRectFilledMultiColor( + rectMin + margin, + rectMax - margin, + ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f)), + ImColor(float4(0.0f, 0.0f, 1.0f, 1.0f)), + ImColor(float4(0.0f, 1.0f, 0.0f, 1.0f)), + ImColor(float4(1.0f, 0.0f, 0.0f, 1.0f)) + ); + } + + // Draw a diamond using the quad function + if (primType == 3) + { + float2 center = (rectMin + rectMax) / 2.0f; + float2 size = ((rectMax - rectMin) / 2.0f) - 2.f * float2(margin, margin); + float2 p1 = center + float2(0.0f, size.y); + float2 p2 = center + float2(size.x, 0.0f); + float2 p3 = center + float2(0.0f, -size.y); + float2 p4 = center + float2(-size.x, 0.0f); + drawList->AddQuad(p1, p2, p3, p4, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a filled diamond using the quad function + if (primType == 4) + { + float2 center = (rectMin + rectMax) / 2.0f; + float2 size = ((rectMax - rectMin) / 2.0f) - 2.f * float2(margin, margin); + float2 p1 = center + float2(0.0f, size.y); + float2 p2 = center + float2(size.x, 0.0f); + float2 p3 = center + float2(0.0f, -size.y); + float2 p4 = center + float2(-size.x, 0.0f); + drawList->AddQuadFilled(p1, p2, p3, p4, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a triangle within the rectangle + if (primType == 5) + { + // the three tri points between 0 and 1 + float2 p1 = (rectMin + margin) + float2(0.5f, 0.0f) * ((rectMax - margin) - (rectMin + margin)); + float2 p2 = (rectMin + margin) + float2(1.0f, 1.0f) * ((rectMax - margin) - (rectMin + margin)); + float2 p3 = (rectMin + margin) + float2(0.0f, 1.0f) * ((rectMax - margin) - (rectMin + margin)); + drawList->AddTriangle(p1, p2, p3, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a filled triangle within the rectangle + if (primType == 6) + { + float2 p1 = (rectMin + margin) + float2(0.5f, 0.0f) * ((rectMax - margin) - (rectMin + margin)); + float2 p2 = (rectMin + margin) + float2(1.0f, 1.0f) * ((rectMax - margin) - (rectMin + margin)); + float2 p3 = (rectMin + margin) + float2(0.0f, 1.0f) * ((rectMax - margin) - (rectMin + margin)); + drawList->AddTriangleFilled(p1, p2, p3, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a circle within the rectangle + if (primType == 7) + { + float2 center = ((rectMin + margin) + (rectMax - margin)) / 2.0f; + float2 size = (rectMax - margin) - (rectMin + margin); + float radius = std::min(size.x, size.y) / 2.0f; + drawList->AddCircle(center, radius, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a filled circle within the rectangle + if (primType == 8) + { + float2 center = ((rectMin + margin) + (rectMax - margin)) / 2.0f; + float2 size = (rectMax - margin) - (rectMin + margin); + float radius = std::min(size.x, size.y) / 2.0f; + drawList->AddCircleFilled(center, radius, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw an ngon + if (primType == 9) + { + float2 center = ((rectMin + margin) + (rectMax - margin)) / 2.0f; + float2 size = (rectMax - margin) - (rectMin + margin); + float radius = std::min(size.x, size.y) / 2.0f; + drawList->AddNgon(center, radius, 5, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a filled ngon + if (primType == 10) + { + float2 center = ((rectMin + margin) + (rectMax - margin)) / 2.0f; + float2 size = (rectMax - margin) - (rectMin + margin); + float radius = std::min(size.x, size.y) / 2.0f; + drawList->AddNgonFilled(center, radius, 5, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Draw a text string + if (primType == 11) + { + std::string text = "Hello, world!"; + drawList->AddText(rectMin + margin, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f)), text.c_str()); + } + + // Draw an example polyline inside the area of the rectangle in the form of a star + if (primType == 12) + { + std::vector points; + float2 center = ((rectMin + margin) + (rectMax - margin)) / 2.0f; + float2 size = (rectMax - margin) - (rectMin + margin); + float radius = std::min(size.x, size.y) / 2.0f; + for (int k = 0; k < 12; k++) + { + float angle = 2.0f * 3.14159f * float(k) / 12.0f; + float2 point = center + radius * (((k % 2) == 0) ? .5f : 1.f) * float2(std::cos(angle), std::sin(angle)); + points.push_back(point); + } + drawList->AddPolyline(points.data(), points.size(), ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f)), ImDrawFlags_None, 1.0f); + } + + // Same as above, but makes a filled convex polygon + if (primType == 13) + { + std::vector points; + float2 center = ((rectMin + margin) + (rectMax - margin)) / 2.0f; + float2 size = (rectMax - margin) - (rectMin + margin); + float radius = std::min(size.x, size.y) / 2.0f; + for (int k = 0; k < 12; k++) + { + float angle = 2.0f * 3.14159f * float(k) / 12.0f; + float2 point = center + radius * (((k % 2) == 0) ? .5f : 1.f) * float2(std::cos(angle), std::sin(angle)); + points.push_back(point); + } + drawList->AddConvexPolyFilled(points.data(), points.size(), ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f))); + } + + // Now draw a bezier cubic curve in the next rectangle + if (primType == 14) + { + float2 p0 = rectMin + margin; + float2 p1 = rectMin + float2(0.25f, 0.75f) * (rectMax - rectMin); + float2 p2 = rectMin + float2(0.75f, 0.25f) * (rectMax - rectMin); + float2 p3 = rectMax - margin; + drawList->AddBezierCubic(p0, p1, p2, p3, ImColor(float4(1.0f, 1.0f, 1.0f, 1.0f)), 1.0f); + } + + primType++; + } + } +} + +void OverlaySamplePass::registerBindings(pybind11::module& m) {} diff --git a/Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.h b/Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.h new file mode 100644 index 000000000..29315f092 --- /dev/null +++ b/Source/RenderPasses/OverlaySamplePass/OverlaySamplePass.h @@ -0,0 +1,62 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Falcor.h" +#include "RenderGraph/RenderPass.h" +#include "RenderGraph/RenderPassHelpers.h" + +using namespace Falcor; + +class OverlaySamplePass : public RenderPass +{ +public: + FALCOR_PLUGIN_CLASS( + OverlaySamplePass, + "OverlaySamplePass", + "Demonstrates how to use the renderOverlayUI callback in a renderpass to draw simple shapes to the screen." + ); + + static ref create(ref pDevice, const Properties& props) + { + return make_ref(pDevice, props); + } + + OverlaySamplePass(ref pDevice, const Properties& props); + + virtual Properties getProperties() const override; + virtual RenderPassReflection reflect(const CompileData& compileData) override; + virtual void execute(RenderContext* pRenderContext, const RenderData& renderData) override; + virtual void renderOverlayUI(RenderContext* pRenderContext) override; + static void registerBindings(pybind11::module& m); + +private: + void setFrameDim(const uint2 frameDim); + + uint2 mFrameDim; + uint32_t mFrameCount = 0; +}; diff --git a/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang b/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang index 907126007..a5d6fdb18 100644 --- a/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang +++ b/Source/RenderPasses/PathTracer/GeneratePaths.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,7 +27,6 @@ **************************************************************************/ import Utils.Attributes; import Utils.Color.ColorHelpers; -import Rendering.Lights.EnvMapSampler; import Rendering.RTXDI.RTXDI; import RenderPasses.Shared.Denoising.NRDBuffers; import RenderPasses.Shared.Denoising.NRDConstants; @@ -117,7 +116,7 @@ struct PathGenerator if (kUseViewDir) cameraRay.dir = -viewDir[pixel]; // Load the primary hit from the V-buffer. - const HitInfo hit = HitInfo(vbuffer[pixel]); + const HitInfo hit = unpackHitInfo(vbuffer[pixel]); hitSurface = hit.isValid(); // Prepare per-pixel surface data for RTXDI. @@ -126,10 +125,10 @@ struct PathGenerator bool validSurface = false; if (hitSurface) { - let lod = ExplicitLodTextureSampler(0.f); - ShadingData sd = loadShadingData(hit, cameraRay.origin, cameraRay.dir, lod); + ShadingData sd = loadShadingData(hit, cameraRay.origin, cameraRay.dir); // Create material instance and query its properties. + let lod = ExplicitLodTextureSampler(0.f); let hints = getMaterialInstanceHints(hit, true /* primary hit */); let mi = gScene.materials.getMaterialInstance(sd, lod, hints); let bsdfProperties = mi.getProperties(sd); diff --git a/Source/RenderPasses/PathTracer/LoadShadingData.slang b/Source/RenderPasses/PathTracer/LoadShadingData.slang index f2ba3f615..90592ff6f 100644 --- a/Source/RenderPasses/PathTracer/LoadShadingData.slang +++ b/Source/RenderPasses/PathTracer/LoadShadingData.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -127,14 +127,13 @@ uint getMaterialInstanceHints(const HitInfo hit, const bool isPrimary) \param[in] hit Hit information. \param[in] rayOrigin Ray origin. \param[in] rayDir Normalized ray direction. - \param[in] lod Method for computing texture level-of-detail \return ShadingData struct. */ -ShadingData loadShadingData(const HitInfo hit, const float3 rayOrigin, const float3 rayDir, const ITextureSampler lod) +ShadingData loadShadingData(const HitInfo hit, const float3 rayOrigin, const float3 rayDir) { uint materialID = {}; VertexData v = loadVertexData(hit, rayOrigin, rayDir, materialID); - ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); return sd; } diff --git a/Source/RenderPasses/PathTracer/PathState.slang b/Source/RenderPasses/PathTracer/PathState.slang index f6fb5d85d..678c93ec8 100644 --- a/Source/RenderPasses/PathTracer/PathState.slang +++ b/Source/RenderPasses/PathTracer/PathState.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/RenderPasses/PathTracer/PathTracer.cpp b/Source/RenderPasses/PathTracer/PathTracer.cpp index 094327635..221166264 100644 --- a/Source/RenderPasses/PathTracer/PathTracer.cpp +++ b/Source/RenderPasses/PathTracer/PathTracer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -30,6 +30,7 @@ #include "RenderGraph/RenderPassStandardFlags.h" #include "Rendering/Lights/EmissiveUniformSampler.h" + namespace { const std::string kGeneratePathsFilename = "RenderPasses/PathTracer/GeneratePaths.cs.slang"; @@ -415,6 +416,9 @@ void PathTracer::setFrameDim(const uint2 frameDim) void PathTracer::setScene(RenderContext* pRenderContext, const ref& pScene) { + mUpdateFlagsConnection = {}; + mUpdateFlags = IScene::UpdateFlags::None; + mpScene = pScene; mParams.frameCount = 0; mParams.frameDim = {}; @@ -428,6 +432,8 @@ void PathTracer::setScene(RenderContext* pRenderContext, const ref& pScen if (mpScene) { + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); + if (pScene->hasGeometryType(Scene::GeometryType::Custom)) { logWarning("PathTracer: This render pass does not support custom primitives."); @@ -437,6 +443,7 @@ void PathTracer::setScene(RenderContext* pRenderContext, const ref& pScen } } + void PathTracer::execute(RenderContext* pRenderContext, const RenderData& renderData) { if (!beginFrame(pRenderContext, renderData)) return; @@ -757,6 +764,7 @@ PathTracer::TracePass::TracePass(ref pDevice, const std::string& name, c pProgram = Program::create(pDevice, desc, defines); } + void PathTracer::TracePass::prepareProgram(ref pDevice, const DefineList& defines) { FALCOR_ASSERT(pProgram != nullptr && pBindingTable != nullptr); @@ -788,11 +796,12 @@ void PathTracer::updatePrograms() // When only defines have changed, it is sufficient to update the existing programs and recreate the program vars. auto defines = mStaticParams.getDefines(*this); - auto globalTypeConformances = mpScene->getTypeConformances(); + TypeConformanceList globalTypeConformances; + mpScene->getTypeConformances(globalTypeConformances); // Create trace pass. if (!mpTracePass) - mpTracePass = std::make_unique(mpDevice, "tracePass", "", mpScene, defines, globalTypeConformances); + mpTracePass = TracePass::create(mpDevice, "tracePass", "", mpScene, defines, globalTypeConformances); mpTracePass->prepareProgram(mpDevice, defines); @@ -800,9 +809,9 @@ void PathTracer::updatePrograms() if (mOutputNRDAdditionalData) { if (!mpTraceDeltaReflectionPass) - mpTraceDeltaReflectionPass = std::make_unique(mpDevice, "traceDeltaReflectionPass", "DELTA_REFLECTION_PASS", mpScene, defines, globalTypeConformances); + mpTraceDeltaReflectionPass = TracePass::create(mpDevice, "traceDeltaReflectionPass", "DELTA_REFLECTION_PASS", mpScene, defines, globalTypeConformances); if (!mpTraceDeltaTransmissionPass) - mpTraceDeltaTransmissionPass = std::make_unique(mpDevice, "traceDeltaTransmissionPass", "DELTA_TRANSMISSION_PASS", mpScene, defines, globalTypeConformances); + mpTraceDeltaTransmissionPass = TracePass::create(mpDevice, "traceDeltaTransmissionPass", "DELTA_TRANSMISSION_PASS", mpScene, defines, globalTypeConformances); mpTraceDeltaReflectionPass->prepareProgram(mpDevice, defines); mpTraceDeltaTransmissionPass->prepareProgram(mpDevice, defines); @@ -810,7 +819,7 @@ void PathTracer::updatePrograms() // Create compute passes. ProgramDesc baseDesc; - baseDesc.addShaderModules(mpScene->getShaderModules()); + mpScene->getShaderModules(baseDesc.shaderModules); baseDesc.addTypeConformances(globalTypeConformances); if (!mpGeneratePaths) @@ -932,8 +941,8 @@ void PathTracer::prepareMaterials(RenderContext* pRenderContext) // Whenever materials or geometry is added/removed to the scene, we reset the shader programs to trigger // recompilation with the correct defines, type conformances, shader modules, and binding table. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || + is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) { resetPrograms(); } @@ -943,18 +952,18 @@ bool PathTracer::prepareLighting(RenderContext* pRenderContext) { bool lightingChanged = false; - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RenderSettingsChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::RenderSettingsChanged)) { lightingChanged = true; mRecompile = true; } - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::SDFGridConfigChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::SDFGridConfigChanged)) { mRecompile = true; } - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::EnvMapChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::EnvMapChanged)) { mpEnvMapSampler = nullptr; lightingChanged = true; @@ -983,27 +992,27 @@ bool PathTracer::prepareLighting(RenderContext* pRenderContext) // Request the light collection if emissive lights are enabled. if (mpScene->getRenderSettings().useEmissiveLights) { - mpScene->getLightCollection(pRenderContext); + mpScene->getILightCollection(pRenderContext); } if (mpScene->useEmissiveLights()) { if (!mpEmissiveSampler) { - const auto& pLights = mpScene->getLightCollection(pRenderContext); + const auto& pLights = mpScene->getILightCollection(pRenderContext); FALCOR_ASSERT(pLights && pLights->getActiveLightCount(pRenderContext) > 0); FALCOR_ASSERT(!mpEmissiveSampler); switch (mStaticParams.emissiveSampler) { case EmissiveLightSamplerType::Uniform: - mpEmissiveSampler = std::make_unique(pRenderContext, mpScene); + mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext)); break; case EmissiveLightSamplerType::LightBVH: - mpEmissiveSampler = std::make_unique(pRenderContext, mpScene, mLightBVHOptions); + mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext), mLightBVHOptions); break; case EmissiveLightSamplerType::Power: - mpEmissiveSampler = std::make_unique(pRenderContext, mpScene); + mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getILightCollection(pRenderContext)); break; default: FALCOR_THROW("Unknown emissive light sampler type"); @@ -1030,7 +1039,7 @@ bool PathTracer::prepareLighting(RenderContext* pRenderContext) if (mpEmissiveSampler) { - lightingChanged |= mpEmissiveSampler->update(pRenderContext); + lightingChanged |= mpEmissiveSampler->update(pRenderContext, mpScene->getILightCollection(pRenderContext)); auto defines = mpEmissiveSampler->getDefines(); if (mpTracePass && mpTracePass->pProgram->addDefines(defines)) mRecompile = true; } @@ -1094,7 +1103,7 @@ void PathTracer::bindShaderData(const ShaderVar& var, const RenderData& renderDa setNRDData(var["outputNRD"], renderData); ref pViewDir; - if (mpScene->getCamera()->getApertureRadius() > 0.f) + if (mpScene && mpScene->getCamera()->getApertureRadius() > 0.f) { pViewDir = renderData.getTexture(kInputViewDir); if (!pViewDir) logWarning("Depth-of-field requires the '{}' input. Expect incorrect rendering.", kInputViewDir); @@ -1239,6 +1248,8 @@ bool PathTracer::beginFrame(RenderContext* pRenderContext, const RenderData& ren // Update the random seed. mParams.seed = mParams.useFixedSeed ? mParams.fixedSeed : mParams.frameCount; + mUpdateFlags = IScene::UpdateFlags::None; + return true; } @@ -1317,7 +1328,6 @@ void PathTracer::tracePass(RenderContext* pRenderContext, const RenderData& rend // Bind global resources. auto var = tracePass.pVars->getRootVar(); - mpScene->setRaytracingShaderData(pRenderContext, var); if (mVarsChanged) mpSampleGenerator->bindShaderData(var); if (mpRTXDI) mpRTXDI->bindShaderData(var); @@ -1420,14 +1430,24 @@ DefineList PathTracer::StaticParams::getDefines(const PathTracer& owner) const defines.add("GBUFFER_ADJUST_SHADING_NORMALS", owner.mGBufferAdjustShadingNormals ? "1" : "0"); // Scene-specific configuration. - const auto& scene = owner.mpScene; - if (scene) defines.add(scene->getSceneDefines()); - defines.add("USE_ENV_LIGHT", scene && scene->useEnvLight() ? "1" : "0"); - defines.add("USE_ANALYTIC_LIGHTS", scene && scene->useAnalyticLights() ? "1" : "0"); - defines.add("USE_EMISSIVE_LIGHTS", scene && scene->useEmissiveLights() ? "1" : "0"); - defines.add("USE_CURVES", scene && (scene->hasGeometryType(Scene::GeometryType::Curve)) ? "1" : "0"); - defines.add("USE_SDF_GRIDS", scene && scene->hasGeometryType(Scene::GeometryType::SDFGrid) ? "1" : "0"); - defines.add("USE_HAIR_MATERIAL", scene && scene->getMaterialCountByType(MaterialType::Hair) > 0u ? "1" : "0"); + // Set defaults + defines.add("USE_ENV_LIGHT", "0"); + defines.add("USE_ANALYTIC_LIGHTS", "0"); + defines.add("USE_EMISSIVE_LIGHTS", "0"); + defines.add("USE_CURVES", "0"); + defines.add("USE_SDF_GRIDS", "0"); + defines.add("USE_HAIR_MATERIAL", "0"); + + if (auto scene = dynamic_ref_cast(owner.mpScene)) + { + defines.add(scene->getSceneDefines()); + defines.add("USE_ENV_LIGHT", scene->useEnvLight() ? "1" : "0"); + defines.add("USE_ANALYTIC_LIGHTS", scene->useAnalyticLights() ? "1" : "0"); + defines.add("USE_EMISSIVE_LIGHTS", scene->useEmissiveLights() ? "1" : "0"); + defines.add("USE_CURVES", (scene->hasGeometryType(Scene::GeometryType::Curve)) ? "1" : "0"); + defines.add("USE_SDF_GRIDS", scene->hasGeometryType(Scene::GeometryType::SDFGrid) ? "1" : "0"); + defines.add("USE_HAIR_MATERIAL", scene->getMaterialCountByType(MaterialType::Hair) > 0u ? "1" : "0"); + } // Set default (off) values for additional features. defines.add("USE_VIEW_DIR", "0"); diff --git a/Source/RenderPasses/PathTracer/PathTracer.h b/Source/RenderPasses/PathTracer/PathTracer.h index 579c14bc6..779913d1c 100644 --- a/Source/RenderPasses/PathTracer/PathTracer.h +++ b/Source/RenderPasses/PathTracer/PathTracer.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -78,6 +78,13 @@ class PathTracer : public RenderPass ref pVars; TracePass(ref pDevice, const std::string& name, const std::string& passDefine, const ref& pScene, const DefineList& defines, const TypeConformanceList& globalTypeConformances); + static std::unique_ptr create(ref pDevice, const std::string& name, const std::string& passDefine, const ref& pScene, const DefineList& defines, const TypeConformanceList& globalTypeConformances) + { + if (auto scene = dynamic_ref_cast(pScene)) + return std::make_unique(std::move(pDevice), name, passDefine, std::move(scene), defines, globalTypeConformances); + return {}; + } + void prepareProgram(ref pDevice, const DefineList& defines); }; @@ -158,7 +165,7 @@ class PathTracer : public RenderPass bool mSERSupported = false; ///< True if the device supports SER. // Internal state - ref mpScene; ///< The current scene, or nullptr if no scene loaded. + ref mpScene; ///< The current scene, or nullptr if no scene loaded. ref mpSampleGenerator; ///< GPU pseudo-random sample generator. std::unique_ptr mpEnvMapSampler; ///< Environment map sampler or nullptr if not used. std::unique_ptr mpEmissiveSampler; ///< Emissive light sampler or nullptr if not used. @@ -166,6 +173,10 @@ class PathTracer : public RenderPass std::unique_ptr mpPixelStats; ///< Utility class for collecting pixel stats. std::unique_ptr mpPixelDebug; ///< Utility class for pixel debugging (print in shaders). + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + /// SceneUpdateFlags accumulated since last `beginFrame()` + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + ref mpPathTracerBlock; ///< Parameter block for the path tracer. bool mRecompile = false; ///< Set to true when program specialization has changed. diff --git a/Source/RenderPasses/PathTracer/PathTracer.slang b/Source/RenderPasses/PathTracer/PathTracer.slang index 31f512d40..9813be143 100644 --- a/Source/RenderPasses/PathTracer/PathTracer.slang +++ b/Source/RenderPasses/PathTracer/PathTracer.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,7 +33,6 @@ import Rendering.Materials.IsotropicGGX; import Rendering.Materials.InteriorListHelpers; import Rendering.Volumes.HomogeneousVolumeSampler; import Rendering.Utils.PixelStats; -import Rendering.RTXDI.RTXDI; import RenderPasses.Shared.Denoising.NRDConstants; import RenderPasses.Shared.Denoising.NRDBuffers; import RenderPasses.Shared.Denoising.NRDData; @@ -46,6 +45,19 @@ import ColorType; import NRDHelpers; __exported import PathState; __exported import Params; +import Rendering.RTXDI.RTXDI; + +/** Interface for querying visibility in the scene. + This is used in `handleHit`. +*/ +interface IVisibilityQuery +{ + /** Trace a visibility ray against the scene. + \param[in] ray Ray. + \return Returns true if the ray endpoints are mutually visible (i.e. the ray does NOT intersect the scene). + */ + [mutating] bool traceVisibilityRay(const Ray ray); +}; /** Path tracer. @@ -54,18 +66,6 @@ __exported import Params; */ struct PathTracer { - /** Interface for querying visibility in the scene. - This is used in `handleHit`. - */ - interface IVisibilityQuery - { - /** Trace a visibility ray against the scene. - \param[in] ray Ray. - \return Returns true if the ray endpoints are mutually visible (i.e. the ray does NOT intersect the scene). - */ - [mutating] bool traceVisibilityRay(const Ray ray); - }; - PathTracerParams params; ///< Runtime parameters. // Samplers @@ -290,7 +290,7 @@ struct PathTracer path.sg = SampleGenerator(pixel, params.seed * maxSpp + path.getSampleIdx()); // Load the primary hit info from the V-buffer. - const HitInfo hit = HitInfo(vbuffer[pixel]); + const HitInfo hit = unpackHitInfo(vbuffer[pixel]); // If invalid, the path is still active and treated as a miss. if (hit.isValid()) @@ -796,8 +796,9 @@ struct PathTracer \param[in] isPrimaryTriangleHit True if primary hit on a triangle. \return Texture sampler instance. */ - ITextureSampler createTextureSampler(const PathState path, bool isPrimaryHit, bool isTriangleHit) + ITextureSampler createTextureSampler(const PathState path, bool isPrimaryHit) { + const bool isTriangleHit = (path.hit.getType() == HitType::Triangle); if (kPrimaryLodMode == TexLODMode::RayDiffs && isPrimaryHit && isTriangleHit) { // Filtered lookups at primary hit on triangle. @@ -831,10 +832,8 @@ struct PathTracer const bool isTriangleHit = hitType == HitType::Triangle; const bool isCurveHit = kUseCurves && (hitType == HitType::Curve); - let lod = createTextureSampler(path, isPrimaryHit, isTriangleHit); - // Load shading data. This is a long latency operation. - ShadingData sd = loadShadingData(path.hit, path.origin, path.dir, lod); + ShadingData sd = loadShadingData(path.hit, path.origin, path.dir); const bool isHairMaterial = kUseHairMaterial && (sd.mtl.getMaterialType() == MaterialType::Hair); // TODO: Decouple geometry from the material. @@ -854,6 +853,9 @@ struct PathTracer logPathVertex(); + // Create texture sampler. + let lod = createTextureSampler(path, isPrimaryHit); + // Create BSDF instance and query its properties. let hints = getMaterialInstanceHints(path.hit, isPrimaryHit); const IMaterialInstance mi = gScene.materials.getMaterialInstance(sd, lod, hints); @@ -896,8 +898,8 @@ struct PathTracer // Note that MIS is only applied for hits on emissive triangles (other emissive geometry is not supported). // Prepare hit point struct with data needed for emissive light PDF evaluation. - TriangleHit triangleHit = path.hit.getTriangleHit(); TriangleLightHit hit; + TriangleHit triangleHit = path.hit.getTriangleHit(); hit.triangleIndex = gScene.lightCollection.getTriangleIndex(triangleHit.instanceID, triangleHit.primitiveIndex); hit.posW = sd.posW; hit.normalW = sd.getOrientedFaceNormal(); @@ -969,10 +971,11 @@ struct PathTracer // Setup path vertex. PathVertex vertex = PathVertex(path.getVertexIndex(), sd.posW, sd.faceN, sd.frontFacing); + const bool canNeeSampleSphere = isCurveHit || isCurvePolyTubeHit; // Determine if upper/lower hemispheres need to be sampled. - bool sampleUpperHemisphere = isCurveHit || isCurvePolyTubeHit || ((lobeTypes & (uint)LobeType::NonDeltaReflection) != 0); + bool sampleUpperHemisphere = canNeeSampleSphere || ((lobeTypes & (uint)LobeType::NonDeltaReflection) != 0); + bool sampleLowerHemisphere = canNeeSampleSphere || ((lobeTypes & (uint)LobeType::NonDeltaTransmission) != 0); if (!kUseLightsInDielectricVolumes && path.isInsideDielectricVolume()) sampleUpperHemisphere = false; - bool sampleLowerHemisphere = isCurveHit || isCurvePolyTubeHit || ((lobeTypes & (uint)LobeType::NonDeltaTransmission) != 0); // Sample a light. validSample = generateLightSample(vertex, sampleUpperHemisphere, sampleLowerHemisphere, path.sg, ls); @@ -994,6 +997,7 @@ struct PathTracer { Ray ray = ls.getVisibilityRay(); + if (isCurvePolyTubeHit) { // For curves tessellated into poly-tubes, we make sure that the origin is on the same side as light diff --git a/Source/RenderPasses/PathTracer/PathTracerNRD.slang b/Source/RenderPasses/PathTracer/PathTracerNRD.slang index 44d6c5ee9..1e4e7982a 100644 --- a/Source/RenderPasses/PathTracer/PathTracerNRD.slang +++ b/Source/RenderPasses/PathTracer/PathTracerNRD.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -49,18 +49,18 @@ extension PathTracer // - Terminate const bool isPrimaryHit = path.getVertexIndex() == 1; - const bool isTriangleHit = path.hit.getType() == HitType::Triangle; const uint2 pixel = path.getPixel(); const float3 viewDir = -path.dir; - let lod = createTextureSampler(path, isPrimaryHit, isTriangleHit); - // Load shading data. This is a long latency operation. - ShadingData sd = loadShadingData(path.hit, path.origin, path.dir, lod); + ShadingData sd = loadShadingData(path.hit, path.origin, path.dir); // Reject false hits in nested dielectrics. if (!handleNestedDielectrics(sd, path)) return; + // Create texture sampler. + let lod = createTextureSampler(path, isPrimaryHit); + // Create material instance and query its properties. let hints = getMaterialInstanceHints(path.hit, isPrimaryHit); let mi = gScene.materials.getMaterialInstance(sd, lod, hints); @@ -186,18 +186,18 @@ extension PathTracer // - Sample scatter ray or terminate const bool isPrimaryHit = path.getVertexIndex() == 1; - const bool isTriangleHit = path.hit.getType() == HitType::Triangle; const uint2 pixel = path.getPixel(); const float3 viewDir = -path.dir; - let lod = createTextureSampler(path, isPrimaryHit, isTriangleHit); - // Load shading data. This is a long latency operation. - ShadingData sd = loadShadingData(path.hit, path.origin, path.dir, lod); + ShadingData sd = loadShadingData(path.hit, path.origin, path.dir); // Reject false hits in nested dielectrics. if (!handleNestedDielectrics(sd, path)) return; + // Create texture sampler. + let lod = createTextureSampler(path, isPrimaryHit); + // Create material instance and query its properties. let hints = getMaterialInstanceHints(path.hit, isPrimaryHit); let mi = gScene.materials.getMaterialInstance(sd, lod, hints); diff --git a/Source/RenderPasses/PathTracer/TracePass.rt.slang b/Source/RenderPasses/PathTracer/TracePass.rt.slang index 469f15ada..b15b89196 100644 --- a/Source/RenderPasses/PathTracer/TracePass.rt.slang +++ b/Source/RenderPasses/PathTracer/TracePass.rt.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -26,7 +26,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ #include "Scene/SceneDefines.slangh" - import Scene.Raytracing; import Scene.RaytracingInline; // For visibility queries. import Scene.Intersection; @@ -80,7 +79,7 @@ struct PathPayload p.packed[4].xyz = asuint(path.L); p.packed[4].w = asuint(path.pdf); - p.hit = path.hit.getData(); + p.hit = path.hit.pack(); p.guideData = path.guideData; p.interiorList = path.interiorList; p.sg = path.sg; @@ -108,7 +107,7 @@ struct PathPayload path.L = asfloat(p.packed[4].xyz); path.pdf = asfloat(p.packed[4].w); - path.hit = HitInfo(p.hit); + path.hit = unpackHitInfo(p.hit); path.guideData = p.guideData; path.interiorList = p.interiorList; path.sg = p.sg; @@ -117,12 +116,12 @@ struct PathPayload } }; -struct VisibilityQuery : PathTracer::IVisibilityQuery +struct VisibilityQuery : IVisibilityQuery { bool traceVisibilityRay(const Ray ray) { SceneRayQuery sceneRayQuery; - return sceneRayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff); + return sceneRayQuery.traceVisibilityRay(ray); } }; @@ -195,7 +194,7 @@ HitInfo makeHitInfo(const HitObject hitObject) /** Reordering scheduler using SER/HitObject API. - The API allows to implement the PathTracer::IClosestHitQuery interface, because + The API allows to implement the IClosestHitQuery interface, because after executing ray traversal using HitObject::TraceRay(), control is given back to the caller before the CHS/MS is explicitly invoked using InvokeHitObject(). This allows for executing volume distance sampling as part of PathTracer::nextHit. @@ -281,7 +280,7 @@ struct ReorderingScheduler primitiveIndex, // PrimitiveIndex 0, // HitKind kRayTypeScatter, // RayContributionToHitGroupIndex - rayTypeCount, // MultiplierForGeometryContributionToHitGroupIndex + getRayTypeCount(), // MultiplierForGeometryContributionToHitGroupIndex dummyRay, // Ray dummyAttribs // Attributes ); @@ -315,7 +314,7 @@ struct ReorderingScheduler const Ray ray = path.getScatterRay(); payload = PathPayload::pack(path); const uint rayFlags = kUseAlphaTest ? RAY_FLAG_NONE : RAY_FLAG_FORCE_OPAQUE; - hitObject = HitObject::TraceRay( gScene.rtAccel, rayFlags, 0xff /* instanceInclusionMask */, kRayTypeScatter /* hitIdx */, rayTypeCount, kMissScatter, ray.toRayDesc(), payload ); + hitObject = HitObject::TraceRay(gScene.rtAccel, rayFlags, 0xff /* instanceInclusionMask */, kRayTypeScatter /* hitIdx */, getRayTypeCount(), kMissScatter, ray.toRayDesc(), payload); path = PathPayload::unpack(payload); if (hitObject.IsHit()) @@ -440,7 +439,7 @@ struct Scheduler PathPayload payload = PathPayload::pack(path); uint rayFlags = RAY_FLAG_NONE; if (!kUseAlphaTest) rayFlags |= RAY_FLAG_FORCE_OPAQUE; - TraceRay(gScene.rtAccel, rayFlags, 0xff /* instanceInclusionMask */, kRayTypeScatter /* hitIdx */, rayTypeCount, kMissScatter /* missIdx */, ray.toRayDesc(), payload); + TraceRay(gScene.rtAccel, rayFlags, 0xff /* instanceInclusionMask */, kRayTypeScatter /* hitIdx */, getRayTypeCount(), kMissScatter /* missIdx */, ray.toRayDesc(), payload); path = PathPayload::unpack(payload); } diff --git a/Source/RenderPasses/PixelInspectorPass/PixelInspector.cs.slang b/Source/RenderPasses/PixelInspectorPass/PixelInspector.cs.slang index 281f6d489..f611b5fab 100644 --- a/Source/RenderPasses/PixelInspectorPass/PixelInspector.cs.slang +++ b/Source/RenderPasses/PixelInspectorPass/PixelInspector.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -85,7 +85,7 @@ ITextureSampler createTextureSampler(const uint2 pixel) /** * Helper to load G-buffer data and prepare shading data. */ -bool loadShadingData(const float3 viewDir, const ITextureSampler lod, inout ShadingData sd) +bool loadShadingData(const float3 viewDir, inout ShadingData sd) { float4 worldPos = gWorldPosition[gWorldPositionCoord]; if (worldPos.w != 0.f) // Using w to indicate valid geometry for now. @@ -106,7 +106,7 @@ bool loadShadingData(const float3 viewDir, const ITextureSampler lod, inout Shad v.coneTexLODValue = 0.f; // Prepare shading data. - sd = gScene.materials.prepareShadingData(v, materialID, viewDir, lod); + sd = gScene.materials.prepareShadingData(v, materialID, viewDir); return true; } @@ -126,10 +126,10 @@ void main(uint3 DTid: SV_DispatchThreadID) PixelData data = PixelData(); ShadingData sd = {}; - let lod = createTextureSampler(gTextureGradsCoord); - if (loadShadingData(viewDir, lod, sd)) + if (loadShadingData(viewDir, sd)) { + let lod = createTextureSampler(gTextureGradsCoord); let mi = gScene.materials.getMaterialInstance(sd, lod); let bsdfProperties = mi.getProperties(sd); diff --git a/Source/RenderPasses/PixelInspectorPass/PixelInspectorPass.cpp b/Source/RenderPasses/PixelInspectorPass/PixelInspectorPass.cpp index cf9502569..c87550345 100644 --- a/Source/RenderPasses/PixelInspectorPass/PixelInspectorPass.cpp +++ b/Source/RenderPasses/PixelInspectorPass/PixelInspectorPass.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -84,8 +84,8 @@ void PixelInspectorPass::execute(RenderContext* pRenderContext, const RenderData return; // Check for scene changes that require shader recompilation. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::GeometryChanged)) { recreatePrograms(); } diff --git a/Source/RenderPasses/RTXDIPass/FinalShading.cs.slang b/Source/RenderPasses/RTXDIPass/FinalShading.cs.slang index 17914da45..b8c9d4b3b 100644 --- a/Source/RenderPasses/RTXDIPass/FinalShading.cs.slang +++ b/Source/RenderPasses/RTXDIPass/FinalShading.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -66,10 +66,10 @@ struct FinalShading float hitT = 10000.f; // TODO: Remove magic number also used in SpatioTemporalResampling pass. ShadingData sd; - let lod = ExplicitLodTextureSampler(0.f); // TODO: Implement texture level-of-detail. - if (loadShadingData(pixel, frameDim, gScene.camera, vbuffer, lod, sd)) + if (loadShadingData(pixel, frameDim, gScene.camera, vbuffer, sd)) { // Create material instance and query its properties. + let lod = ExplicitLodTextureSampler(0.f); // TODO: Implement texture level-of-detail. let hints = getMaterialInstanceHints(); let mi = gScene.materials.getMaterialInstance(sd, lod, hints); let bsdfProperties = mi.getProperties(sd); @@ -86,7 +86,7 @@ struct FinalShading // Create a DXR 1.1 query object to trace a ray (the <1> means use alpha testing) SceneRayQuery<1> rayQuery; const Ray ray = Ray(sd.computeRayOrigin(), dir, 0.f, distance); - if (!rayQuery.traceVisibilityRay(ray, RAY_FLAG_NONE, 0xff)) + if (!rayQuery.traceVisibilityRay(ray)) { valid = false; } diff --git a/Source/RenderPasses/RTXDIPass/LoadShadingData.slang b/Source/RenderPasses/RTXDIPass/LoadShadingData.slang index 687331a53..f6c9be65f 100644 --- a/Source/RenderPasses/RTXDIPass/LoadShadingData.slang +++ b/Source/RenderPasses/RTXDIPass/LoadShadingData.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -49,18 +49,10 @@ uint getMaterialInstanceHints() * @param[in] frameDim Frame dimensions in pixel. * @param[in] camera Current camera. * @param[in] vbuffer VBuffer texture. - * @param[in] lod Method for computing texture level-of-detail. * @param[out] sd ShadingData struct. * @return True if the pixel has valid data (not a background pixel). Note sd.V is always valid. */ -bool loadShadingData( - const uint2 pixel, - const uint2 frameDim, - const Camera camera, - Texture2D vbuffer, - const ITextureSampler lod, - out ShadingData sd -) +bool loadShadingData(const uint2 pixel, const uint2 frameDim, const Camera camera, Texture2D vbuffer, out ShadingData sd) { sd = {}; @@ -71,12 +63,9 @@ bool loadShadingData( if (hit.isValid() && hit.getType() == HitType::Triangle) { const TriangleHit triangleHit = hit.getTriangleHit(); - - // Evaluate Falcor's material parameters at the hit point. - // TODO: Implement texLOD to enable texture filtering in prepareShadingData(). const VertexData v = gScene.getVertexData(triangleHit); const uint materialID = gScene.getMaterialID(triangleHit.instanceID); - sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); valid = true; } diff --git a/Source/RenderPasses/RTXDIPass/PrepareSurfaceData.cs.slang b/Source/RenderPasses/RTXDIPass/PrepareSurfaceData.cs.slang index 677b4188a..86df2c262 100644 --- a/Source/RenderPasses/RTXDIPass/PrepareSurfaceData.cs.slang +++ b/Source/RenderPasses/RTXDIPass/PrepareSurfaceData.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -45,12 +45,12 @@ struct PrepareSurfaceData return; ShadingData sd; - let lod = ExplicitLodTextureSampler(0.f); // TODO: Implement texture level-of-detail. - bool isValidSurface = loadShadingData(pixel, frameDim, gScene.camera, vbuffer, lod, sd); + bool isValidSurface = loadShadingData(pixel, frameDim, gScene.camera, vbuffer, sd); if (isValidSurface) { // Create material instance and query its properties. + let lod = ExplicitLodTextureSampler(0.f); // TODO: Implement texture level-of-detail. let hints = getMaterialInstanceHints(); let mi = gScene.materials.getMaterialInstance(sd, lod, hints); let bsdfProperties = mi.getProperties(sd); diff --git a/Source/RenderPasses/RTXDIPass/RTXDIPass.cpp b/Source/RenderPasses/RTXDIPass/RTXDIPass.cpp index 22031bd14..cd4af3881 100644 --- a/Source/RenderPasses/RTXDIPass/RTXDIPass.cpp +++ b/Source/RenderPasses/RTXDIPass/RTXDIPass.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -105,8 +105,8 @@ void RTXDIPass::execute(RenderContext* pRenderContext, const RenderData& renderD } // Check for scene changes that require shader recompilation. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::GeometryChanged)) { recreatePrograms(); } diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp b/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp index cfd145f8a..e754cecc6 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,8 +27,12 @@ **************************************************************************/ #include "SceneDebugger.h" + +#include + namespace { + const char kShaderFile[] = "RenderPasses/SceneDebugger/SceneDebugger.cs.slang"; const std::string kOutput = "output"; @@ -39,6 +43,8 @@ std::string getModeDesc(SceneDebuggerMode mode) { case SceneDebuggerMode::FlatShaded: return "Flat shaded"; + case SceneDebuggerMode::TriangleDensity: + return "Triangle density"; // Geometry case SceneDebuggerMode::HitType: return "Hit type in pseudocolor"; @@ -55,6 +61,8 @@ std::string getModeDesc(SceneDebuggerMode mode) case SceneDebuggerMode::InstancedGeometry: return "Green = instanced geometry\n" "Red = non-instanced geometry"; + case SceneDebuggerMode::MaterialType: + return "Material type in pseudocolor"; // Shading data case SceneDebuggerMode::FaceNormal: return "Face normal in RGB color"; @@ -83,6 +91,7 @@ std::string getModeDesc(SceneDebuggerMode mode) // Scripting const char kMode[] = "mode"; const char kShowVolumes[] = "showVolumes"; +const char kUseVBuffer[] = "useVBuffer"; void registerBindings(pybind11::module& m) { @@ -115,11 +124,16 @@ SceneDebugger::SceneDebugger(ref pDevice, const Properties& props) : Ren mParams.mode = (uint32_t)value.operator SceneDebuggerMode(); else if (key == kShowVolumes) mParams.showVolumes = value; + else if (key == kUseVBuffer) + mParams.useVBuffer = static_cast(value); else logWarning("Unknown property '{}' in a SceneDebugger properties.", key); } mpFence = mpDevice->createFence(); + + mpPixelDebug = std::make_unique(mpDevice); + mpSampleGenerator = SampleGenerator::create(mpDevice, SAMPLE_GENERATOR_TINY_UNIFORM); } Properties SceneDebugger::getProperties() const @@ -127,12 +141,17 @@ Properties SceneDebugger::getProperties() const Properties props; props[kMode] = SceneDebuggerMode(mParams.mode); props[kShowVolumes] = mParams.showVolumes; + props[kUseVBuffer] = mParams.useVBuffer; return props; } RenderPassReflection SceneDebugger::reflect(const CompileData& compileData) { RenderPassReflection reflector; + reflector.addInput("vbuffer", "Visibility buffer in packed format") + .texture2D() + .format(ResourceFormat::RGBA32Uint) + .flags(RenderPassReflection::Field::Flags::Optional); reflector.addOutput(kOutput, "Scene debugger output").bindFlags(ResourceBindFlags::UnorderedAccess).format(ResourceFormat::RGBA32Float); return reflector; @@ -141,22 +160,32 @@ RenderPassReflection SceneDebugger::reflect(const CompileData& compileData) void SceneDebugger::compile(RenderContext* pRenderContext, const CompileData& compileData) { mParams.frameDim = compileData.defaultTexDims; + mVBufferAvailable = compileData.connectedResources.getField("vbuffer"); } void SceneDebugger::setScene(RenderContext* pRenderContext, const ref& pScene) { + mUpdateFlagsConnection = {}; + mUpdateFlags = IScene::UpdateFlags::None; + mpScene = pScene; mpMeshToBlasID = nullptr; mpDebugPass = nullptr; + mUpdateFlags = IScene::UpdateFlags::None; if (mpScene) { + mUpdateFlagsConnection = mpScene->getUpdateFlagsSignal().connect([&](IScene::UpdateFlags flags) { mUpdateFlags |= flags; }); + // Prepare our programs for the scene. ProgramDesc desc; desc.addShaderModules(mpScene->getShaderModules()); desc.addShaderLibrary(kShaderFile).csEntry("main"); desc.addTypeConformances(mpScene->getTypeConformances()); - mpDebugPass = ComputePass::create(mpDevice, desc, mpScene->getSceneDefines()); + + DefineList defines = mpScene->getSceneDefines(); + defines.add(mpSampleGenerator->getDefines()); + mpDebugPass = ComputePass::create(mpDevice, desc, defines); // Create lookup table for mesh to BLAS ID. auto blasIDs = mpScene->getMeshBlasIDs(); @@ -196,30 +225,45 @@ void SceneDebugger::setScene(RenderContext* pRenderContext, const ref& pS } } + void SceneDebugger::execute(RenderContext* pRenderContext, const RenderData& renderData) { mPixelDataAvailable = false; const auto& pOutput = renderData.getTexture(kOutput); + const auto& pVBuffer = renderData.getTexture("vbuffer"); + + if (mParams.useVBuffer && pVBuffer == nullptr) + { + logWarningOnce("SceneDebugger cannot use vbuffer as none is connected"); + mParams.useVBuffer = false; + } if (mpScene == nullptr) { pRenderContext->clearUAV(pOutput->getUAV().get(), float4(0.f)); return; } + // DEMO21: // mpScene->getCamera()->setJitter(0.f, 0.f); - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mUpdateFlags, IScene::UpdateFlags::RecompileNeeded) || is_set(mUpdateFlags, IScene::UpdateFlags::GeometryChanged)) { FALCOR_THROW("This render pass does not support scene changes that require shader recompilation."); } - mpScene->setRaytracingShaderData(pRenderContext, mpDebugPass->getRootVar()); + if (mpScene) + mpScene->bindShaderDataForRaytracing(pRenderContext, mpDebugPass->getRootVar()["gScene"]); + ShaderVar var = mpDebugPass->getRootVar()["CB"]["gSceneDebugger"]; var["params"].setBlob(mParams); var["output"] = pOutput; + var["vbuffer"] = pVBuffer; + + mpPixelDebug->beginFrame(pRenderContext, renderData.getDefaultTextureDims()); + mpPixelDebug->prepareProgram(mpDebugPass->getProgram(), mpDebugPass->getRootVar()); + mpSampleGenerator->bindShaderData(mpDebugPass->getRootVar()); mpDebugPass->execute(pRenderContext, uint3(mParams.frameDim, 1)); @@ -229,13 +273,25 @@ void SceneDebugger::execute(RenderContext* pRenderContext, const RenderData& ren mPixelDataAvailable = true; mParams.frameCount++; + + mpPixelDebug->endFrame(pRenderContext); + + mUpdateFlags = IScene::UpdateFlags::None; } void SceneDebugger::renderUI(Gui::Widgets& widget) { + if (mVBufferAvailable) + widget.checkbox("Use VBuffer", mParams.useVBuffer); + widget.dropdown("Mode", reinterpret_cast(mParams.mode)); widget.tooltip("Selects visualization mode"); + if (mParams.mode == (uint32_t)SceneDebuggerMode::TriangleDensity) + { + widget.var("Triangle density range (log2)", mParams.triangleDensityLogRange); + } + if (mParams.mode == (uint32_t)SceneDebuggerMode::BSDFProperties) { widget.dropdown("BSDF property", reinterpret_cast(mParams.bsdfProperty)); @@ -259,7 +315,7 @@ void SceneDebugger::renderUI(Gui::Widgets& widget) widget.checkbox("Show volumes", mParams.showVolumes); if (mParams.showVolumes) { - widget.var("Density scale", mParams.densityScale, 0.f, 1000.f, 0.1f); + widget.var("Volume density scale", mParams.volumeDensityScale, 0.f, 1000.f, 0.1f); } widget.textWrapped("Description:\n" + getModeDesc((SceneDebuggerMode)mParams.mode)); @@ -268,10 +324,31 @@ void SceneDebugger::renderUI(Gui::Widgets& widget) widget.dummy("#spacer0", {1, 20}); widget.var("Selected pixel", mParams.selectedPixel); - renderPixelDataUI(widget); + if (mpScene) + renderPixelDataUI(widget); widget.dummy("#spacer1", {1, 20}); widget.text("Scene: " + (mpScene ? mpScene->getPath().string() : "No scene loaded")); + + if (auto loggingGroup = widget.group("Logging", false)) + { + mpPixelDebug->renderUI(widget); + } + + if (auto g = widget.group("Profiling", false)) + { + widget.checkbox("Trace secondary rays", mParams.profileSecondaryRays); + if (mParams.profileSecondaryRays) + { + widget.checkbox("Load hit info", mParams.profileSecondaryLoadHit); + + widget.var("Cone angle (deg)", mParams.profileSecondaryConeAngle, 0.f, 90.f, 1.f); + widget.tooltip( + "Traces secondary rays from the primary hits. The secondary rays have directions that are randomly distributed in a cone " + "around the face normal." + ); + } + } } void SceneDebugger::renderPixelDataUI(Gui::Widgets& widget) @@ -469,7 +546,7 @@ bool SceneDebugger::onMouseEvent(const MouseEvent& mouseEvent) mParams.selectedPixel = (uint2)clamp(cursorPos, float2(0.f), float2(mParams.frameDim.x - 1, mParams.frameDim.y - 1)); } - return false; + return mpPixelDebug->onMouseEvent(mouseEvent); } void SceneDebugger::initInstanceInfo() @@ -516,3 +593,4 @@ void SceneDebugger::initInstanceInfo() false ); } + diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang b/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang index 4ccf82cc6..2c3a376e9 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -25,13 +25,23 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ -import Scene.RaytracingInline; +#include "Utils/HostDeviceShared.slangh" +#include "Utils/Math/MathConstants.slangh" + import Utils.Math.HashUtils; +import Utils.Math.ShadingFrame; import Utils.Geometry.IntersectionHelpers; +import Utils.Debug.PixelDebug; import Utils.Color.ColorMap; import Utils.Color.ColorHelpers; +import Utils.Sampling.SampleGenerator; import SharedTypes; +import Scene.RaytracingInline; +import Scene.Scene; + +import Utils.Math.Ray; + struct SceneDebugger { static const int kUseAlphaTest = 1; @@ -39,6 +49,7 @@ struct SceneDebugger SceneDebuggerParams params; StructuredBuffer meshToBlasID; StructuredBuffer instanceInfo; + Texture2D vbuffer; RWTexture2D output; RWStructuredBuffer pixelData; @@ -51,6 +62,8 @@ struct SceneDebugger if (any(pixel >= params.frameDim)) return; + printSetPixel(pixel); + // Initialize pixel data for the selected pixel. if (all(pixel == params.selectedPixel)) { @@ -68,10 +81,28 @@ struct SceneDebugger float3 color = float3(0); - SceneRayQuery sceneRayQuery; HitInfo hit; - float hitT; - if (sceneRayQuery.traceRay(ray, hit, hitT, RAY_FLAG_NONE, 0xff)) + float hitT = 1e30f; + + if ((bool)params.useVBuffer) + { + hit = unpackHitInfo(vbuffer[pixel]); + if (hit.isValid()) + { + if ((bool)params.showVolumes) + { + float3 posW = getPosW(ray.origin, ray.dir, hit); + hitT = length(posW - ray.origin); + } + } + } + else + { + SceneRayQuery sceneRayQuery; + hit = sceneRayQuery.traceRay(ray, hitT); + } + + if (hit.isValid()) { color = handleHit(pixel, ray.origin, ray.dir, hit); } @@ -82,13 +113,13 @@ struct SceneDebugger } // Process volumes. - if (params.showVolumes) + if ((bool)params.showVolumes) { color = handleVolumes(color, ray.origin, ray.dir, hitT); } // Clamp pixel values if necessary. - if (params.clamp) + if ((bool)params.clamp) color = saturate(color); // Write output. @@ -97,9 +128,9 @@ struct SceneDebugger float3 remapVector(float3 v) { - if (params.flipSign) + if ((bool)params.flipSign) v = -v; - if (params.remapRange) + if ((bool)params.remapRange) v = 0.5f * v + 0.5f; return v; } @@ -121,7 +152,6 @@ struct SceneDebugger uint blasID = PixelData::kInvalidID; VertexData v = {}; - switch (hit.getType()) { case HitType::Triangle: @@ -176,10 +206,8 @@ struct SceneDebugger return float3(1, 0, 0); } - let lod = ExplicitLodTextureSampler(0.f); - // Load shading data. - const ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -dir, lod); + const ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -dir); // Write pixel data for the selected pixel. if (all(pixel == params.selectedPixel)) @@ -208,26 +236,33 @@ struct SceneDebugger pixelData[0] = d; } + // Profiling of secondary rays. + if (params.profileSecondaryRays) + { + return traceSecondary(sd, pixel); + } + // Compute zebra stripes. - const float z = (pixel.x + pixel.y - params.frameCount) & 0x8 ? 1.f : 0.f; + const float z = ((pixel.x + pixel.y - params.frameCount) & 0x8) ? 1.f : 0.f; + + // Compute flat shading. + // FIXME: Parameterize this + const float3 frontN = faceforward(sd.faceN, dir, sd.faceN); + const float flatShaded = 0.8f * saturate(dot(-frontN, dir)) + 0.2; switch ((SceneDebuggerMode)params.mode) { case SceneDebuggerMode::FlatShaded: - { - // DEMO21: - // FIXME: Parameterize this - const float3 frontN = faceforward(sd.faceN, dir, sd.faceN); - const float v = 0.8f * saturate(dot(-frontN, dir)) + 0.2; - return float3(v, v, v); - } + return float3(flatShaded); + case SceneDebuggerMode::TriangleDensity: + return colormapViridis(computeTriangleDensity(hit)); // Geometry case SceneDebuggerMode::HitType: return pseudocolor(uint(hit.getType())); case SceneDebuggerMode::InstanceID: return pseudocolor(instanceID); case SceneDebuggerMode::MaterialID: - return pseudocolor(materialID); + return pseudocolor(materialID) * flatShaded; case SceneDebuggerMode::PrimitiveID: return pseudocolor(primitiveID); case SceneDebuggerMode::GeometryID: @@ -248,6 +283,8 @@ struct SceneDebugger return float3(v, v, v); } } + case SceneDebuggerMode::MaterialType: + return pseudocolor((uint)sd.mtl.getMaterialType()) * flatShaded; // Shading data case SceneDebuggerMode::FaceNormal: return remapVector(sd.faceN); @@ -274,6 +311,7 @@ struct SceneDebugger case SceneDebuggerMode::BSDFProperties: { // Create material instance and query its properties. + let lod = ExplicitLodTextureSampler(0.f); let mi = gScene.materials.getMaterialInstance(sd, lod); let bsdfProperties = mi.getProperties(sd); switch ((SceneDebuggerBSDFProperty)params.bsdfProperty) @@ -352,12 +390,155 @@ struct SceneDebugger for (uint step = 0; step < kSteps; ++step) { float t = lerp(nearFar.x, nearFar.y, (step + 0.5f) / kSteps); - float density = densityGrid.lookupIndex(ipos + t * idir, accessor); + float density = densityGrid.lookupIndex(int3(ipos + t * idir), accessor); opticalDepth += density; } - opticalDepth *= (nearFar.y - nearFar.x) / kSteps * gridVolume.data.densityScale * params.densityScale; + opticalDepth *= (nearFar.y - nearFar.x) / kSteps * gridVolume.data.densityScale * params.volumeDensityScale; return exp(-opticalDepth); } + + static float3 getPosW(const float3 orig, const float3 dir, const HitInfo hit) + { + switch (hit.getType()) + { + case HitType::Triangle: + return gScene.getVertexData(hit.getTriangleHit()).posW; + case HitType::DisplacedTriangle: + return gScene.getVertexData(hit.getDisplacedTriangleHit(), -dir).posW; + case HitType::Curve: + return gScene.getVertexDataFromCurve(hit.getCurveHit()).posW; + case HitType::SDFGrid: + { + return gScene.getVertexDataFromSDFGrid(hit.getSDFGridHit(), orig, dir).posW; + } + default: + // Should not happen. Return the origin. + return orig; + } + } + + ShadingData loadHit(const Ray ray, const HitInfo hit) + { + uint instanceID = PixelData::kInvalidID; + uint materialID = PixelData::kInvalidID; + uint primitiveID = PixelData::kInvalidID; + uint geometryID = PixelData::kInvalidID; + uint blasID = PixelData::kInvalidID; + + VertexData v = {}; + switch (hit.getType()) + { + case HitType::Triangle: + { + const TriangleHit triangleHit = hit.getTriangleHit(); + instanceID = triangleHit.instanceID.index; + geometryID = gScene.getGeometryInstance(triangleHit.instanceID).geometryID; + primitiveID = triangleHit.primitiveIndex; + blasID = meshToBlasID[geometryID]; + materialID = gScene.getMaterialID(triangleHit.instanceID); + + // Load vertex attributes. + v = gScene.getVertexData(triangleHit); + break; + } + case HitType::DisplacedTriangle: + { + const DisplacedTriangleHit displacedTriangleHit = hit.getDisplacedTriangleHit(); + instanceID = displacedTriangleHit.instanceID.index; + geometryID = gScene.getGeometryInstance(displacedTriangleHit.instanceID).geometryID; + blasID = meshToBlasID[geometryID]; + materialID = gScene.getMaterialID(displacedTriangleHit.instanceID); + + // Load vertex attributes. + v = gScene.getVertexData(displacedTriangleHit, -ray.dir); + break; + } + case HitType::Curve: + { + const CurveHit curveHit = hit.getCurveHit(); + instanceID = curveHit.instanceID.index; + geometryID = gScene.getGeometryInstance(curveHit.instanceID).geometryID; + materialID = gScene.getMaterialID(curveHit.instanceID); + + // Load vertex attributes. + v = gScene.getVertexDataFromCurve(curveHit); + break; + } + case HitType::SDFGrid: + { + const SDFGridHit sdfGridHit = hit.getSDFGridHit(); + instanceID = sdfGridHit.instanceID.index; + geometryID = gScene.getGeometryInstance(sdfGridHit.instanceID).geometryID; + materialID = gScene.getMaterialID(sdfGridHit.instanceID); + + // Load vertex attributes. + v = gScene.getVertexDataFromSDFGrid(sdfGridHit, ray.origin, ray.dir); + break; + } + default: + // Should not happen. + return {}; + } + + // Load shading data. + return gScene.materials.prepareShadingData(v, materialID, -ray.dir); + } + + float3 traceSecondary(const ShadingData sd, const uint2 pixel) + { + SampleGenerator sg = SampleGenerator(pixel, params.frameCount); + + // Create frame centered around the face normal oriented towards the viewer. + const float3 N = sd.getOrientedFaceNormal(); + bool valid; + let sf = ShadingFrame::createSafe(N, sd.tangentW, valid); + + // Generate random direction in cone centered around +z axis. + float c = cos(params.profileSecondaryConeAngle * (M_2PI / 360.f)); + float s = sqrt(1.f - c * c); + float z = c + (1.f - c) * sampleNext1D(sg); // z uniform on [cos(a),1] + float phi = sampleNext1D(sg) * M_2PI; + float3 dir = float3(cos(phi) * s, sin(phi) * s, z); + dir = sf.fromLocal(dir); + + // Trace secondary ray. + const Ray ray = Ray(sd.computeRayOrigin(), dir); + + float hitT = 1e30f; + SceneRayQuery sceneRayQuery; + HitInfo hit = sceneRayQuery.traceRay(ray, hitT); + + float3 color = {}; + + if (params.profileSecondaryLoadHit) + { + if (hit.isValid()) + { + let hitSD = loadHit(ray, hit); + color = hitSD.frame.N; + } + } + else + { + color = hit.isValid() ? float3(0, 1, 0) : float3(1, 0, 0); + } + + return color; + } + + float computeTriangleDensity(const HitInfo hit) + { + if (hit.getType() != HitType::Triangle) + return 0.f; + + const TriangleHit triangleHit = hit.getTriangleHit(); + const float A = gScene.getFaceAreaW(triangleHit.instanceID, triangleHit.primitiveIndex); + + float density = 1.f / A; // Can be inf + float logDensity = log2(density); + + return (logDensity - params.triangleDensityLogRange.x) / (params.triangleDensityLogRange.y - params.triangleDensityLogRange.x); + } }; cbuffer CB diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.h b/Source/RenderPasses/SceneDebugger/SceneDebugger.h index d51bf21f9..fd3b04002 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.h +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -28,9 +28,11 @@ #pragma once #include "Falcor.h" #include "RenderGraph/RenderPass.h" +#include "Utils/Debug/PixelDebug.h" #include "Scene/HitInfoType.slang" #include "SharedTypes.slang" + using namespace Falcor; /** @@ -66,7 +68,13 @@ class SceneDebugger : public RenderPass // Internal state + std::unique_ptr mpPixelDebug; ///< Utility class for pixel debugging (print in shaders). + ref mpSampleGenerator; ref mpScene; + sigs::Connection mUpdateFlagsConnection; ///< Connection to the UpdateFlags signal. + /// IScene::UpdateFlags accumulated since last `beginFrame()` + IScene::UpdateFlags mUpdateFlags = IScene::UpdateFlags::None; + SceneDebuggerParams mParams; ref mpDebugPass; ref mpFence; @@ -77,4 +85,5 @@ class SceneDebugger : public RenderPass ref mpMeshToBlasID; ref mpInstanceInfo; bool mPixelDataAvailable = false; + bool mVBufferAvailable = false; }; diff --git a/Source/RenderPasses/SceneDebugger/SharedTypes.slang b/Source/RenderPasses/SceneDebugger/SharedTypes.slang index b15d92396..965dc9e0d 100644 --- a/Source/RenderPasses/SceneDebugger/SharedTypes.slang +++ b/Source/RenderPasses/SceneDebugger/SharedTypes.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -33,14 +33,16 @@ BEGIN_NAMESPACE_FALCOR enum class SceneDebuggerMode : uint32_t { FlatShaded, + TriangleDensity, // Geometry HitType, InstanceID, MaterialID, GeometryID, BlasID, - InstancedGeometry, PrimitiveID, + InstancedGeometry, + MaterialType, // Shading data FaceNormal, ShadingNormal, @@ -57,14 +59,16 @@ FALCOR_ENUM_INFO( SceneDebuggerMode, { { SceneDebuggerMode::FlatShaded, "FlatShaded" }, + { SceneDebuggerMode::TriangleDensity, "TriangleDensity" }, // Geometry { SceneDebuggerMode::HitType, "HitType" }, { SceneDebuggerMode::InstanceID, "InstanceID" }, { SceneDebuggerMode::MaterialID, "MaterialID" }, { SceneDebuggerMode::GeometryID, "GeometryID" }, { SceneDebuggerMode::BlasID, "BlasID" }, - { SceneDebuggerMode::InstancedGeometry, "InstancedGeometry" }, { SceneDebuggerMode::PrimitiveID, "PrimitiveID" }, + { SceneDebuggerMode::InstancedGeometry, "InstancedGeometry" }, + { SceneDebuggerMode::MaterialType, "MaterialType" }, // Shading data { SceneDebuggerMode::FaceNormal, "FaceNormal" }, { SceneDebuggerMode::ShadingNormal, "ShadingNormal" }, @@ -122,8 +126,15 @@ struct SceneDebuggerParams int remapRange = true; ///< Remap valid range to [0,1] before output. int clamp = true; ///< Clamp pixel values to [0,1] before output. - int showVolumes = true; ///< Show volumes. - float densityScale = 1.f; ///< Volume density scale factor. + int showVolumes = true; ///< Show volumes. + float volumeDensityScale = 1.f; ///< Volume density scale factor. + int useVBuffer = false; + + int profileSecondaryRays = false; ///< Trace secondary rays for profiling purposes. + int profileSecondaryLoadHit = false; ///< Load HitInfo for secondary rays, otherwise just visibility rays. + float profileSecondaryConeAngle = 90.f; ///< Cone angle (degress) for randomly distributed ray directions. + + float2 triangleDensityLogRange = { -16.f, 16.f }; }; struct PixelData diff --git a/Source/RenderPasses/TestPasses/TestRtProgram.cpp b/Source/RenderPasses/TestPasses/TestRtProgram.cpp index d59f81959..d0d36e338 100644 --- a/Source/RenderPasses/TestPasses/TestRtProgram.cpp +++ b/Source/RenderPasses/TestPasses/TestRtProgram.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -225,8 +225,8 @@ void TestRtProgram::execute(RenderContext* pRenderContext, const RenderData& ren return; // Check for scene changes that require shader recompilation. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::GeometryChanged)) { sceneChanged(); } diff --git a/Source/RenderPasses/ToneMapper/ToneMapper.cpp b/Source/RenderPasses/ToneMapper/ToneMapper.cpp index 8672f53c6..3c851065a 100644 --- a/Source/RenderPasses/ToneMapper/ToneMapper.cpp +++ b/Source/RenderPasses/ToneMapper/ToneMapper.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,6 +27,7 @@ **************************************************************************/ #include "ToneMapper.h" #include "Utils/Color/ColorUtils.h" + #include // TODO C++20: Replace with namespace @@ -403,6 +404,7 @@ void ToneMapper::setScene(RenderContext* pRenderContext, const ref& pScen } } + void ToneMapper::setExposureCompensation(float exposureCompensation) { mExposureCompensation = std::clamp(exposureCompensation, kExposureCompensationMin, kExposureCompensationMax); diff --git a/Source/RenderPasses/ToneMapper/ToneMapper.h b/Source/RenderPasses/ToneMapper/ToneMapper.h index ff0ba3e52..555c1d7eb 100644 --- a/Source/RenderPasses/ToneMapper/ToneMapper.h +++ b/Source/RenderPasses/ToneMapper/ToneMapper.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/Source/RenderPasses/Utils/CrossFade/CrossFade.cpp b/Source/RenderPasses/Utils/CrossFade/CrossFade.cpp index 08be5dc26..976f6a7f9 100644 --- a/Source/RenderPasses/Utils/CrossFade/CrossFade.cpp +++ b/Source/RenderPasses/Utils/CrossFade/CrossFade.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -114,18 +114,18 @@ void CrossFade::execute(RenderContext* pRenderContext, const RenderData& renderD if (mpScene) { auto sceneUpdates = mpScene->getUpdates(); - if ((sceneUpdates & ~Scene::UpdateFlags::CameraPropertiesChanged) != Scene::UpdateFlags::None) + if ((sceneUpdates & ~IScene::UpdateFlags::CameraPropertiesChanged) != IScene::UpdateFlags::None) { shouldReset = true; } - if (is_set(sceneUpdates, Scene::UpdateFlags::CameraPropertiesChanged)) + if (is_set(sceneUpdates, IScene::UpdateFlags::CameraPropertiesChanged)) { auto excluded = Camera::Changes::Jitter | Camera::Changes::History; auto cameraChanges = mpScene->getCamera()->getChanges(); if ((cameraChanges & ~excluded) != Camera::Changes::None) shouldReset = true; } - if (is_set(sceneUpdates, Scene::UpdateFlags::SDFGeometryChanged)) + if (is_set(sceneUpdates, IScene::UpdateFlags::SDFGeometryChanged)) { shouldReset = true; } diff --git a/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang b/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang index 5d28c41b4..1a624b27c 100644 --- a/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang +++ b/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -94,14 +94,7 @@ float evalMIS(float n0, float p0, float n1, float p1) [Differentiable] [PreferRecompute] -ShadingData loadShadingData( - SceneQueryAD sceneQuery, - HitInfo hit, - IntersectionAD isect, - const float3 rayOrigin, - const float3 rayDir, - const ITextureSampler lod -) +ShadingData loadShadingData(SceneQueryAD sceneQuery, HitInfo hit, IntersectionAD isect, const float3 rayOrigin, const float3 rayDir) { VertexData v = {}; uint materialID = {}; @@ -114,7 +107,7 @@ ShadingData loadShadingData( materialID = gScene.getMaterialID(isect.instanceID); } - ShadingData sd = no_diff gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + ShadingData sd = no_diff gScene.materials.prepareShadingData(v, materialID, -rayDir); // Overwrite some fields to enable auto-diff. sd.V = -rayDir; @@ -273,7 +266,7 @@ void handleHit( HitInfo hit = HitInfo(triHit); // Load shading data. - ShadingData sd = loadShadingData(sceneQuery, hit, isect, ray.origin, ray.direction, lod); + ShadingData sd = loadShadingData(sceneQuery, hit, isect, ray.origin, ray.direction); // Create differentiable material instance. DiffMaterialData diffData; diff --git a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp index d09b59d8a..0d247c0f6 100644 --- a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp +++ b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -440,7 +440,7 @@ void WARDiffPathTracer::prepareMaterials(RenderContext* pRenderContext) // For now all we need to do is to trigger a recompile so that the right defines get set. // In the future, we might want to do additional material-specific setup here. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded)) { mRecompile = true; } @@ -450,7 +450,7 @@ bool WARDiffPathTracer::prepareLighting(RenderContext* pRenderContext) { bool lightingChanged = false; - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RenderSettingsChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RenderSettingsChanged)) { lightingChanged = true; mRecompile = true; @@ -477,7 +477,7 @@ bool WARDiffPathTracer::prepareLighting(RenderContext* pRenderContext) // We only use a uniform emissive sampler for now. // LightBVH seems buggy with the Cornell box bunny example. - mpEmissiveSampler = std::make_unique(pRenderContext, mpScene); + mpEmissiveSampler = std::make_unique(pRenderContext, mpScene->getLightCollection(pRenderContext)); lightingChanged = true; mRecompile = true; @@ -496,7 +496,7 @@ bool WARDiffPathTracer::prepareLighting(RenderContext* pRenderContext) if (mpEmissiveSampler) { - lightingChanged |= mpEmissiveSampler->update(pRenderContext); + lightingChanged |= mpEmissiveSampler->update(pRenderContext, mpScene->getLightCollection(pRenderContext)); auto defines = mpEmissiveSampler->getDefines(); if (mpTracePass && mpTracePass->pProgram->addDefines(defines)) mRecompile = true; @@ -613,7 +613,6 @@ void WARDiffPathTracer::tracePass(RenderContext* pRenderContext, const RenderDat // Bind global resources. auto var = tracePass.pVars->getRootVar(); - mpScene->setRaytracingShaderData(pRenderContext, var); if (mVarsChanged) mpSampleGenerator->bindShaderData(var); diff --git a/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.cpp b/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.cpp index 98144e854..de5d3dc97 100644 --- a/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.cpp +++ b/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -149,8 +149,8 @@ void WhittedRayTracer::execute(RenderContext* pRenderContext, const RenderData& } // Check for scene changes that require shader recompilation. - if (is_set(mpScene->getUpdates(), Scene::UpdateFlags::RecompileNeeded) || - is_set(mpScene->getUpdates(), Scene::UpdateFlags::GeometryChanged)) + if (is_set(mpScene->getUpdates(), IScene::UpdateFlags::RecompileNeeded) || + is_set(mpScene->getUpdates(), IScene::UpdateFlags::GeometryChanged)) { FALCOR_THROW("This render pass does not support scene changes that require shader recompilation."); } diff --git a/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang b/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang index bb4a29dd5..e7b867241 100644 --- a/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang +++ b/Source/RenderPasses/WhittedRayTracer/WhittedRayTracer.rt.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -190,7 +190,7 @@ ITextureSampler createTextureSampler(const uint2 pixel) /** * Helper to load G-buffer data and prepare shading data. */ -ShadingData loadShadingData(const uint2 pixel, const float3 posW, const float3 rayDir, const ITextureSampler lod) +ShadingData loadShadingData(const uint2 pixel, const float3 posW, const float3 rayDir) { // Load G-buffer data. float3 normal = gWorldShadingNormal[pixel].xyz; @@ -208,7 +208,7 @@ ShadingData loadShadingData(const uint2 pixel, const float3 posW, const float3 r v.coneTexLODValue = 0.f; // Prepare shading data. - return gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + return gScene.materials.prepareShadingData(v, materialID, -rayDir); } /** @@ -400,7 +400,6 @@ void scatterClosestHit( v = getVertexDataRayCones(instanceID, PrimitiveIndex(), attribs); float curvature = gScene.computeCurvatureIsotropic(instanceID, PrimitiveIndex()); - // Compute texture LOD for prepareShadingData(). if (kRayConeMode == RayConeMode::Combo || kRayConeMode == RayConeMode::Unified) { float rayConeWidth = rayData.rayCone.getSpreadAngle() * hitT + rayData.rayCone.getWidth(); @@ -415,7 +414,7 @@ void scatterClosestHit( { float lambda = rayData.rayCone.computeLOD(v.coneTexLODValue, rayDir, v.normalW); lod = ExplicitRayConesLodTextureSampler(lambda); - sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); } else { @@ -429,7 +428,7 @@ void scatterClosestHit( computeAnisotropicEllipseAxes(v.posW, v.faceNormalW, rayDir, coneWidthAtHitPoint, positions, txcoords, v.texC, ddx, ddy); lod = ExplicitGradientTextureSampler(ddx, ddy); } - sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); eta = computeEta(sd); } else if (kTexLODMode == TexLODMode::RayDiffs) @@ -475,7 +474,7 @@ void scatterClosestHit( // Use the dUVdx and dUVdy directly --> gives you anisotropic filtering. lod = ExplicitGradientTextureSampler(dUVdx, dUVdy); } - sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); eta = computeEta(sd); if (eta == 1.0f) @@ -498,7 +497,7 @@ void scatterClosestHit( { v = getVertexData(instanceID, PrimitiveIndex(), attribs); lod = ExplicitLodTextureSampler(0.f); - sd = gScene.materials.prepareShadingData(v, materialID, -rayDir, lod); + sd = gScene.materials.prepareShadingData(v, materialID, -rayDir); eta = computeEta(sd); if (eta != 1.0f) @@ -645,10 +644,12 @@ void rayGen() if (worldPos.w != 0.f) // Using w to indicate valid geometry for now. { // Pixel represents a valid primary hit. Compute its contribution. - let lod = createTextureSampler(launchIndex); - const ShadingData sd = loadShadingData(launchIndex, worldPos.xyz, rayDir, lod); + const ShadingData sd = loadShadingData(launchIndex, worldPos.xyz, rayDir); const float eta = computeEta(sd); + // Create texture sampler. + let lod = createTextureSampler(launchIndex); + // Create material instance and query its properties. let mi = gScene.materials.getMaterialInstance(sd, lod); let bsdfProperties = mi.getProperties(sd); diff --git a/Source/Samples/HelloDXR/HelloDXR.3d.slang b/Source/Samples/HelloDXR/HelloDXR.3d.slang index 9084f3939..579d2ca35 100644 --- a/Source/Samples/HelloDXR/HelloDXR.3d.slang +++ b/Source/Samples/HelloDXR/HelloDXR.3d.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -41,7 +41,7 @@ float4 psMain(VSOut vsOut, uint triangleIndex: SV_PrimitiveID) : SV_TARGET discard; float3 viewDir = normalize(gScene.camera.getPosition() - vsOut.posW); - ShadingData sd = prepareShadingData(vsOut, triangleIndex, viewDir, lod); + ShadingData sd = prepareShadingData(vsOut, triangleIndex, viewDir); // Create material instance. let mi = gScene.materials.getMaterialInstance(sd, lod); diff --git a/Source/Samples/HelloDXR/HelloDXR.cpp b/Source/Samples/HelloDXR/HelloDXR.cpp index af972e448..ad08c97d4 100644 --- a/Source/Samples/HelloDXR/HelloDXR.cpp +++ b/Source/Samples/HelloDXR/HelloDXR.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -71,10 +71,10 @@ void HelloDXR::onFrameRender(RenderContext* pRenderContext, const ref& pTar if (mpScene) { - Scene::UpdateFlags updates = mpScene->update(pRenderContext, getGlobalClock().getTime()); - if (is_set(updates, Scene::UpdateFlags::GeometryChanged)) + IScene::UpdateFlags updates = mpScene->update(pRenderContext, getGlobalClock().getTime()); + if (is_set(updates, IScene::UpdateFlags::GeometryChanged)) FALCOR_THROW("This sample does not support scene geometry changes."); - if (is_set(updates, Scene::UpdateFlags::RecompileNeeded)) + if (is_set(updates, IScene::UpdateFlags::RecompileNeeded)) FALCOR_THROW("This sample does not support scene changes that require shader recompilation."); if (mRayTrace) diff --git a/Source/Samples/HelloDXR/HelloDXR.rt.slang b/Source/Samples/HelloDXR/HelloDXR.rt.slang index 9f137e7d5..fdaa5fb59 100644 --- a/Source/Samples/HelloDXR/HelloDXR.rt.slang +++ b/Source/Samples/HelloDXR/HelloDXR.rt.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -112,15 +112,14 @@ void primaryClosestHit(inout PrimaryRayData hitData, BuiltInTriangleIntersection float hitT = RayTCurrent(); uint triangleIndex = PrimitiveIndex(); - let lod = ExplicitLodTextureSampler(0.f); - // Prepare the shading data. const GeometryInstanceID instanceID = getGeometryInstanceID(); VertexData v = getVertexData(instanceID, triangleIndex, attribs); uint materialID = gScene.getMaterialID(instanceID); - ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDirW, lod); + ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -rayDirW); // Create material instance and query its properties. + let lod = ExplicitLodTextureSampler(0.f); let mi = gScene.materials.getMaterialInstance(sd, lod); let bsdfProperties = mi.getProperties(sd); diff --git a/Source/Tools/FalcorTest/CMakeLists.txt b/Source/Tools/FalcorTest/CMakeLists.txt index 325501370..b527bd526 100644 --- a/Source/Tools/FalcorTest/CMakeLists.txt +++ b/Source/Tools/FalcorTest/CMakeLists.txt @@ -33,6 +33,8 @@ target_sources(FalcorTest PRIVATE Tests/Core/RootBufferStructTests.cs.slang Tests/Core/RootBufferTests.cpp Tests/Core/RootBufferTests.cs.slang + Tests/Core/TextureArrays.cpp + Tests/Core/TextureArrays.cs.slang Tests/Core/TextureLoadTests.cs.slang Tests/Core/TextureTests.cpp Tests/Core/TextureTests.cs.slang @@ -74,6 +76,8 @@ target_sources(FalcorTest PRIVATE Tests/Scene/Material/HairChiang16Tests.cs.slang Tests/Scene/Material/MERLFileTests.cpp + Tests/Slang/Atomics.cpp + Tests/Slang/Atomics.cs.slang Tests/Slang/CastFloat16.cpp Tests/Slang/CastFloat16.cs.slang Tests/Slang/Float16Tests.cpp @@ -99,6 +103,8 @@ target_sources(FalcorTest PRIVATE Tests/Slang/ShaderStringUtil.slang Tests/Slang/SlangExtension.cpp Tests/Slang/SlangExtension.cs.slang + Tests/Slang/SlangGenerics.cpp + Tests/Slang/SlangGenerics.cs.slang Tests/Slang/SlangInheritance.cpp Tests/Slang/SlangInheritance.cs.slang Tests/Slang/SlangMutatingTests.cpp @@ -163,6 +169,8 @@ target_sources(FalcorTest PRIVATE Tests/Utils/QuaternionTests.cpp Tests/Utils/RectangleTests.cpp Tests/Utils/SettingsTests.cpp + Tests/Utils/SplitBufferTests.cpp + Tests/Utils/SplitBufferTests.cs.slang Tests/Utils/StringUtilsTests.cpp Tests/Utils/TextureAnalyzerTests.cpp Tests/Utils/UnionFindTests.cpp diff --git a/Source/Tools/FalcorTest/Tests/Core/AssetResolverTests.cpp b/Source/Tools/FalcorTest/Tests/Core/AssetResolverTests.cpp index 0c36c675a..fb6f1b7ef 100644 --- a/Source/Tools/FalcorTest/Tests/Core/AssetResolverTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Core/AssetResolverTests.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -69,6 +69,8 @@ static void removeTestFiles(UnitTestContext& ctx) CPU_TEST(AssetResolver) { + using std::filesystem::canonical; + createTestFiles(ctx); const std::filesystem::path unresolved; @@ -78,15 +80,15 @@ CPU_TEST(AssetResolver) AssetResolver resolver; resolver.addSearchPath(kTestRoot / "media1"); - EXPECT_EQ(resolver.resolvePath(kTestRoot / "media2/asset1"), kTestRoot / "media2/asset1"); + EXPECT_EQ(resolver.resolvePath(kTestRoot / "media2/asset1"), canonical(kTestRoot / "media2/asset1")); auto resolved = resolver.resolvePathPattern(kTestRoot / "media4/textures", R"(mip[0-9]\.png)"); EXPECT_EQ(resolved.size(), 4); std::sort(resolved.begin(), resolved.end()); - EXPECT_EQ(resolved[0], kTestRoot / "media4/textures/mip0.png"); - EXPECT_EQ(resolved[1], kTestRoot / "media4/textures/mip1.png"); - EXPECT_EQ(resolved[2], kTestRoot / "media4/textures/mip2.png"); - EXPECT_EQ(resolved[3], kTestRoot / "media4/textures/mip3.png"); + EXPECT_EQ(resolved[0], canonical(kTestRoot / "media4/textures/mip0.png")); + EXPECT_EQ(resolved[1], canonical(kTestRoot / "media4/textures/mip1.png")); + EXPECT_EQ(resolved[2], canonical(kTestRoot / "media4/textures/mip2.png")); + EXPECT_EQ(resolved[3], canonical(kTestRoot / "media4/textures/mip3.png")); } // Test resolving relative paths to working directory. @@ -96,7 +98,7 @@ CPU_TEST(AssetResolver) resolver.addSearchPath(kTestRoot / "media1"); EXPECT_EQ( resolver.resolvePath(std::filesystem::relative(kTestRoot / "media2/asset1", std::filesystem::current_path())), - kTestRoot / "media2/asset1" + canonical(kTestRoot / "media2/asset1") ); auto resolved = resolver.resolvePathPattern( @@ -105,10 +107,10 @@ CPU_TEST(AssetResolver) ); EXPECT_EQ(resolved.size(), 4); std::sort(resolved.begin(), resolved.end()); - EXPECT_EQ(resolved[0], kTestRoot / "media4/textures/mip0.png"); - EXPECT_EQ(resolved[1], kTestRoot / "media4/textures/mip1.png"); - EXPECT_EQ(resolved[2], kTestRoot / "media4/textures/mip2.png"); - EXPECT_EQ(resolved[3], kTestRoot / "media4/textures/mip3.png"); + EXPECT_EQ(resolved[0], canonical(kTestRoot / "media4/textures/mip0.png")); + EXPECT_EQ(resolved[1], canonical(kTestRoot / "media4/textures/mip1.png")); + EXPECT_EQ(resolved[2], canonical(kTestRoot / "media4/textures/mip2.png")); + EXPECT_EQ(resolved[3], canonical(kTestRoot / "media4/textures/mip3.png")); } // Test resolving with search paths. @@ -116,21 +118,21 @@ CPU_TEST(AssetResolver) AssetResolver resolver; resolver.addSearchPath(kTestRoot / "media1"); - EXPECT_EQ(resolver.resolvePath("asset1"), kTestRoot / "media1/asset1"); + EXPECT_EQ(resolver.resolvePath("asset1"), canonical(kTestRoot / "media1/asset1")); EXPECT_EQ(resolver.resolvePath("asset2"), unresolved); EXPECT_EQ(resolver.resolvePath("asset3"), unresolved); EXPECT_EQ(resolver.resolvePath("asset4"), unresolved); resolver.addSearchPath(kTestRoot / "media2"); - EXPECT_EQ(resolver.resolvePath("asset1"), kTestRoot / "media1/asset1"); - EXPECT_EQ(resolver.resolvePath("asset2"), kTestRoot / "media2/asset2"); + EXPECT_EQ(resolver.resolvePath("asset1"), canonical(kTestRoot / "media1/asset1")); + EXPECT_EQ(resolver.resolvePath("asset2"), canonical(kTestRoot / "media2/asset2")); EXPECT_EQ(resolver.resolvePath("asset3"), unresolved); EXPECT_EQ(resolver.resolvePath("asset4"), unresolved); resolver.addSearchPath(kTestRoot / "media3"); - EXPECT_EQ(resolver.resolvePath("asset1"), kTestRoot / "media1/asset1"); - EXPECT_EQ(resolver.resolvePath("asset2"), kTestRoot / "media2/asset2"); - EXPECT_EQ(resolver.resolvePath("asset3"), kTestRoot / "media3/asset3"); + EXPECT_EQ(resolver.resolvePath("asset1"), canonical(kTestRoot / "media1/asset1")); + EXPECT_EQ(resolver.resolvePath("asset2"), canonical(kTestRoot / "media2/asset2")); + EXPECT_EQ(resolver.resolvePath("asset3"), canonical(kTestRoot / "media3/asset3")); EXPECT_EQ(resolver.resolvePath("asset4"), unresolved); } @@ -142,16 +144,18 @@ CPU_TEST(AssetResolver) auto resolved = resolver.resolvePathPattern("textures", R"(mip[0-9]\.png)"); EXPECT_EQ(resolved.size(), 4); std::sort(resolved.begin(), resolved.end()); - EXPECT_EQ(resolved[0], kTestRoot / "media4/textures/mip0.png"); - EXPECT_EQ(resolved[1], kTestRoot / "media4/textures/mip1.png"); - EXPECT_EQ(resolved[2], kTestRoot / "media4/textures/mip2.png"); - EXPECT_EQ(resolved[3], kTestRoot / "media4/textures/mip3.png"); + EXPECT_EQ(resolved[0], canonical(kTestRoot / "media4/textures/mip0.png")); + EXPECT_EQ(resolved[1], canonical(kTestRoot / "media4/textures/mip1.png")); + EXPECT_EQ(resolved[2], canonical(kTestRoot / "media4/textures/mip2.png")); + EXPECT_EQ(resolved[3], canonical(kTestRoot / "media4/textures/mip3.png")); resolved = resolver.resolvePathPattern("textures", R"(mip[0-9]\.png)", true); EXPECT_EQ(resolved.size(), 1); EXPECT( - resolved[0] == kTestRoot / "media4/textures/mip0.png" || resolved[0] == kTestRoot / "media4/textures/mip1.png" || - resolved[0] == kTestRoot / "media4/textures/mip2.png" || resolved[0] == kTestRoot / "media4/textures/mip3.png" + resolved[0] == canonical(kTestRoot / "media4/textures/mip0.png") || + resolved[0] == canonical(kTestRoot / "media4/textures/mip1.png") || + resolved[0] == canonical(kTestRoot / "media4/textures/mip2.png") || + resolved[0] == canonical(kTestRoot / "media4/textures/mip3.png") ); } @@ -163,17 +167,17 @@ CPU_TEST(AssetResolver) resolver.addSearchPath(kTestRoot / "media2", SearchPathPriority::Last, AssetCategory::Scene); resolver.addSearchPath(kTestRoot / "media1", SearchPathPriority::Last, AssetCategory::Texture); - EXPECT_EQ(resolver.resolvePath("asset1", AssetCategory::Any), kTestRoot / "media3/asset1"); - EXPECT_EQ(resolver.resolvePath("asset1", AssetCategory::Scene), kTestRoot / "media2/asset1"); - EXPECT_EQ(resolver.resolvePath("asset1", AssetCategory::Texture), kTestRoot / "media1/asset1"); + EXPECT_EQ(resolver.resolvePath("asset1", AssetCategory::Any), canonical(kTestRoot / "media3/asset1")); + EXPECT_EQ(resolver.resolvePath("asset1", AssetCategory::Scene), canonical(kTestRoot / "media2/asset1")); + EXPECT_EQ(resolver.resolvePath("asset1", AssetCategory::Texture), canonical(kTestRoot / "media1/asset1")); - EXPECT_EQ(resolver.resolvePath("asset2", AssetCategory::Any), kTestRoot / "media3/asset2"); - EXPECT_EQ(resolver.resolvePath("asset2", AssetCategory::Scene), kTestRoot / "media2/asset2"); - EXPECT_EQ(resolver.resolvePath("asset2", AssetCategory::Texture), kTestRoot / "media3/asset2"); + EXPECT_EQ(resolver.resolvePath("asset2", AssetCategory::Any), canonical(kTestRoot / "media3/asset2")); + EXPECT_EQ(resolver.resolvePath("asset2", AssetCategory::Scene), canonical(kTestRoot / "media2/asset2")); + EXPECT_EQ(resolver.resolvePath("asset2", AssetCategory::Texture), canonical(kTestRoot / "media3/asset2")); - EXPECT_EQ(resolver.resolvePath("asset3", AssetCategory::Any), kTestRoot / "media3/asset3"); - EXPECT_EQ(resolver.resolvePath("asset3", AssetCategory::Scene), kTestRoot / "media3/asset3"); - EXPECT_EQ(resolver.resolvePath("asset3", AssetCategory::Texture), kTestRoot / "media3/asset3"); + EXPECT_EQ(resolver.resolvePath("asset3", AssetCategory::Any), canonical(kTestRoot / "media3/asset3")); + EXPECT_EQ(resolver.resolvePath("asset3", AssetCategory::Scene), canonical(kTestRoot / "media3/asset3")); + EXPECT_EQ(resolver.resolvePath("asset3", AssetCategory::Texture), canonical(kTestRoot / "media3/asset3")); } // Test search path priorities. @@ -181,11 +185,11 @@ CPU_TEST(AssetResolver) AssetResolver resolver; resolver.addSearchPath(kTestRoot / "media1"); - EXPECT_EQ(resolver.resolvePath("asset1"), kTestRoot / "media1/asset1"); + EXPECT_EQ(resolver.resolvePath("asset1"), canonical(kTestRoot / "media1/asset1")); resolver.addSearchPath(kTestRoot / "media2", SearchPathPriority::Last); - EXPECT_EQ(resolver.resolvePath("asset1"), kTestRoot / "media1/asset1"); + EXPECT_EQ(resolver.resolvePath("asset1"), canonical(kTestRoot / "media1/asset1")); resolver.addSearchPath(kTestRoot / "media3", SearchPathPriority::First); - EXPECT_EQ(resolver.resolvePath("asset1"), kTestRoot / "media3/asset1"); + EXPECT_EQ(resolver.resolvePath("asset1"), canonical(kTestRoot / "media3/asset1")); } removeTestFiles(ctx); diff --git a/Source/Tools/FalcorTest/Tests/Core/BufferTests.cpp b/Source/Tools/FalcorTest/Tests/Core/BufferTests.cpp index 55eb25b72..2f149860b 100644 --- a/Source/Tools/FalcorTest/Tests/Core/BufferTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Core/BufferTests.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -220,4 +220,21 @@ GPU_TEST(BufferWrite) testWrite(uint4(3, 4, 5, 6), true); } +void checkBufferSize(GPUUnitTestContext& ctx, std::string bufferName, uint32_t expectedSize) +{ + auto buffer = ctx.getDevice()->createStructuredBuffer(ctx.getVars()->getRootVar()[bufferName], 16); + EXPECT_EQ(buffer->getStructSize(), expectedSize); + EXPECT_EQ(buffer->getSize(), expectedSize * 16); +} + +GPU_TEST(BufferStrides) +{ + ctx.createProgram("Tests/Core/BufferTests.cs.slang", "writeSizeTest"); + + checkBufferSize(ctx, "bufferSizeTest1_12B_buffer", 12); + checkBufferSize(ctx, "bufferSizeTest2_2B_buffer", 2); + checkBufferSize(ctx, "bufferSizeTest3_24B_buffer", 24); + checkBufferSize(ctx, "bufferSizeTest4_24B_buffer", 24); +} + } // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang b/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang index 6c08b2abc..1dbcdb3ad 100644 --- a/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Core/BufferTests.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -78,3 +78,41 @@ void readBuffer(uint3 threadId: SV_DispatchThreadID) result[i] = buffer[i]; #endif } + +// Structures for verifying various buffer sizes when allocated on CPU via reflection +struct BufferSizeTest1_12B_T +{ + int16_t a; + float b; + int16_t c; +}; +struct BufferSizeTest2_2B_T +{ + int16_t a; +}; +struct BufferSizeTest3_24B_T +{ + int16_t a; + float64_t b; + int16_t c; +}; +struct BufferSizeTest4_24B_T +{ + int16_t a; + float4 b; + int16_t c; +}; +RWStructuredBuffer bufferSizeTest1_12B_buffer; +RWStructuredBuffer bufferSizeTest2_2B_buffer; +RWStructuredBuffer bufferSizeTest3_24B_buffer; +RWStructuredBuffer bufferSizeTest4_24B_buffer; + +// Dummy computer shader to write to buffers, as this allows DXIL to be generated that references them +[numthreads(1, 1, 1)] +void writeSizeTest(uint3 threadId: SV_DispatchThreadID) +{ + bufferSizeTest1_12B_buffer[0] = {}; + bufferSizeTest2_2B_buffer[0] = {}; + bufferSizeTest3_24B_buffer[0] = {}; + bufferSizeTest4_24B_buffer[0] = {}; +} diff --git a/Source/Tools/FalcorTest/Tests/Core/TextureArrays.cpp b/Source/Tools/FalcorTest/Tests/Core/TextureArrays.cpp new file mode 100644 index 000000000..3a07fd286 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Core/TextureArrays.cpp @@ -0,0 +1,132 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "Testing/UnitTest.h" +#include "Core/Pass/ComputePass.h" + +namespace Falcor +{ +namespace +{ +void runTest(GPUUnitTestContext& ctx, std::array bits) +{ + ref pDevice = ctx.getDevice(); + + FALCOR_ASSERT(bits[0] + bits[1] + bits[2] == 3); + DefineList defines = { + {"BITS_I", std::to_string(bits[0])}, + {"BITS_J", std::to_string(bits[1])}, + {"BITS_K", std::to_string(bits[2])}, + }; + + // Create textures. + std::vector init(16 * 16, 0.f); + ref tex[8]; + for (size_t i = 0; i < 8; i++) + tex[i] = pDevice->createTexture2D( + 16, 16, ResourceFormat::R32Float, 1, 1, init.data(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess + ); + + auto bindTextures = [&](ShaderVar var) + { + for (size_t idx = 0; idx < 8; idx++) + { + uint i = idx & ((1 << bits[0]) - 1); + uint j = (idx >> bits[0]) & ((1 << bits[1]) - 1); + uint k = (idx >> (bits[0] + bits[1])) & ((1 << bits[2]) - 1); + var["tex"][i][j][k] = tex[idx]; + } + }; + + // Create programs. + { + ProgramDesc desc; + desc.addShaderLibrary("Tests/Core/TextureArrays.cs.slang").csEntry("testWrite"); + ctx.createProgram(desc, defines); + } + + ref readPass; + { + ProgramDesc desc; + desc.addShaderLibrary("Tests/Core/TextureArrays.cs.slang").csEntry("testRead"); + readPass = ComputePass::create(pDevice, desc, defines); + } + + std::vector zeros(16 * 16 * 8, 0.f); + auto resultBuf = pDevice->createBuffer( + 16 * 16 * 8 * sizeof(float), + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + zeros.data() + ); + + // Write textures. + bindTextures(ctx.getVars()->getRootVar()); + ctx.getVars()->getRootVar()["result"] = resultBuf; + ctx.runProgram(16, 16, 8); + + // Read textures. + bindTextures(readPass->getRootVar()); + readPass->getRootVar()["result"] = resultBuf; + readPass->execute(pDevice->getRenderContext(), 16, 16, 8); + + // Verify result. + std::vector result(16 * 16 * 8); + resultBuf->getBlob(result.data(), 0, 16 * 16 * 8 * sizeof(float)); + + size_t i = 0; + for (uint32_t z = 0; z < 8; z++) + { + for (uint32_t y = 0; y < 16; y++) + { + for (uint32_t x = 0; x < 16; x++) + { + EXPECT_EQ(result[i], (float)x * y + z) << "i=" << i << " x=" << x << " y=" << y << " z=" << z; + ++i; + } + } + } +} +} // namespace + +GPU_TEST(Texture_NestedArrays) +{ + // Nested texture arrays are not supported on Vulkan, so expect error when using Vulkan device + if (ctx.getDevice()->getType() != Device::Type::Vulkan) + { + runTest(ctx, {1, 1, 1}); + runTest(ctx, {2, 0, 1}); + runTest(ctx, {0, 0, 3}); + } + else + { + EXPECT_THROW(runTest(ctx, {1, 1, 1})); + EXPECT_THROW(runTest(ctx, {2, 0, 1})); + EXPECT_THROW(runTest(ctx, {0, 0, 3})); + } +} +} // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Core/TextureArrays.cs.slang b/Source/Tools/FalcorTest/Tests/Core/TextureArrays.cs.slang new file mode 100644 index 000000000..c5c7ef1d0 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Core/TextureArrays.cs.slang @@ -0,0 +1,62 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#define I (1 << BITS_I) +#define J (1 << BITS_J) +#define K (1 << BITS_K) + +RWStructuredBuffer result; +RWTexture2D tex[I][J][K]; + +[numthreads(16, 16, 1)] +void testWrite(uint3 threadId: SV_DispatchThreadID) +{ + if (threadId.x >= 16 || threadId.y >= 16 || threadId.z >= 8) + return; + + uint i = threadId.z & ((I)-1); + uint j = (threadId.z >> (BITS_I)) & ((J)-1); + uint k = (threadId.z >> ((BITS_I) + (BITS_J))) & ((K)-1); + uint2 p = threadId.xy; + + tex[i][j][k][p] = (float)threadId.x * threadId.y + threadId.z; +} + +[numthreads(16, 16, 1)] +void testRead(uint3 threadId: SV_DispatchThreadID) +{ + if (threadId.x >= 16 || threadId.y >= 16 || threadId.z >= 8) + return; + + uint i = threadId.z & ((I)-1); + uint j = (threadId.z >> (BITS_I)) & ((J)-1); + uint k = (threadId.z >> ((BITS_I) + (BITS_J))) & ((K)-1); + uint2 p = threadId.xy; + + uint idx = threadId.z * 256 + threadId.y * 16 + threadId.x; + result[idx] = tex[i][j][k][p]; +} diff --git a/Source/Tools/FalcorTest/Tests/Scene/Material/BSDFTests.cs.slang b/Source/Tools/FalcorTest/Tests/Scene/Material/BSDFTests.cs.slang index 3c2422a49..26bf94973 100644 --- a/Source/Tools/FalcorTest/Tests/Scene/Material/BSDFTests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Scene/Material/BSDFTests.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -74,7 +74,7 @@ struct SamplingTest float pdf; float3 weight; uint lobeType; - if (bsdf.sample(wi, wo, pdf, weight, lobeType, sg)) + if (bsdf.sample(wi, wo, pdf, weight, lobeType, sg, BSDFContext())) { if (wo.z < -1.f + kCosThetaMargin || wo.z > 1.f - kCosThetaMargin) continue; @@ -127,7 +127,7 @@ struct SamplingTest float cosPhi = cos(phi); float3 wo = normalize(float3(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta)); - pdf += bsdf.evalPdf(wi, wo); + pdf += bsdf.evalPdf(wi, wo, BSDFContext()); } } @@ -160,10 +160,10 @@ struct SamplingTest float pdf; float3 weight; uint lobeType; - if (bsdf.sample(wi, wo, pdf, weight, lobeType, sg)) + if (bsdf.sample(wi, wo, pdf, weight, lobeType, sg, BSDFContext())) { - float3 value = bsdf.eval(wi, wo, sg); - float pdfRef = bsdf.evalPdf(wi, wo); + float3 value = bsdf.eval(wi, wo, sg, BSDFContext()); + float pdfRef = bsdf.evalPdf(wi, wo, BSDFContext()); float3 weightRef = pdfRef > 0.f ? (value / pdfRef) : float3(0.f); float3 weightErrors = abs(weight - weightRef) / (weightRef > 0.f ? weightRef : 1.f); diff --git a/Source/Tools/FalcorTest/Tests/Scene/Material/HairChiang16Tests.cs.slang b/Source/Tools/FalcorTest/Tests/Scene/Material/HairChiang16Tests.cs.slang index cc3300e7e..190726994 100644 --- a/Source/Tools/FalcorTest/Tests/Scene/Material/HairChiang16Tests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Scene/Material/HairChiang16Tests.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-22, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -71,7 +71,7 @@ void testPbrtReference(uint3 threadId: SV_DispatchThreadID) SampleGenerator sg = SampleGenerator(threadId.xy, 0); - gResultOurs[idx] = bcsdf.eval(gWi[idx], gWo[idx], sg); + gResultOurs[idx] = bcsdf.eval(gWi[idx], gWo[idx], sg, BSDFContext()); } [numthreads(16, 1, 1)] @@ -104,7 +104,7 @@ void testWhiteFurnaceUniform(uint3 threadId: SV_DispatchThreadID) bcsdf.precompute(); float3 wo = sample_sphere(sampleNext2D(sg)); - sum += bcsdf.eval(wi, wo, sg); + sum += bcsdf.eval(wi, wo, sg, BSDFContext()); } result[threadId.x] = luminance(sum) / sampleCount * M_4PI; @@ -143,7 +143,7 @@ void testWhiteFurnaceImportanceSampling(uint3 threadId: SV_DispatchThreadID) float pdf; uint lobeType; - if (bcsdf.sample(wi, wo, pdf, weight, lobeType, sg)) + if (bcsdf.sample(wi, wo, pdf, weight, lobeType, sg, BSDFContext())) { sum += weight; } @@ -180,7 +180,7 @@ void testImportanceSamplingWeights(uint3 threadId: SV_DispatchThreadID) float3 wo, weight; float pdf; uint lobeType; - if (bcsdf.sample(wi, wo, pdf, weight, lobeType, sg)) + if (bcsdf.sample(wi, wo, pdf, weight, lobeType, sg, BSDFContext())) { result[threadId.x * sampleCount + threadId.y] = luminance(weight); } @@ -229,14 +229,14 @@ void testSamplingConsistency(uint3 threadId: SV_DispatchThreadID) // Assume Li = wo.z * wo.z. // Importance sampling. - if (bcsdf.sample(wi, wo, pdf, weight, lobeType, sg)) + if (bcsdf.sample(wi, wo, pdf, weight, lobeType, sg, BSDFContext())) { fIS += weight * (wo.z * wo.z) / sampleCount; } // Uniform sampling. wo = sample_sphere(sampleNext2D(sg)); - fUniform += bcsdf.eval(wi, wo, sg) * M_4PI * (wo.z * wo.z) / sampleCount; + fUniform += bcsdf.eval(wi, wo, sg, BSDFContext()) * M_4PI * (wo.z * wo.z) / sampleCount; } result[threadId.x] = abs(luminance(fIS) - luminance(fUniform)) / luminance(fUniform); diff --git a/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp new file mode 100644 index 000000000..f2ddbc2df --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cpp @@ -0,0 +1,196 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "Testing/UnitTest.h" +#include "Core/Pass/ComputePass.h" +#include + +namespace Falcor +{ +namespace +{ +const std::string_view kShaderFile = "Tests/Slang/Atomics.cs.slang"; + +const uint32_t kNumElems = 256; +std::mt19937 r; +std::uniform_real_distribution u; + +void testInterlockedAddF16(GPUUnitTestContext& ctx, std::string_view entryPoint) +{ + ref pDevice = ctx.getDevice(); + + ProgramDesc desc; + desc.addShaderLibrary(kShaderFile).csEntry("testBufferAddF16"); + desc.setUseSPIRVBackend(); // NOTE: The SPIR-V backend is required for RWByteAddressBuffer.InterlockedAddF16() on Vulkan! + ctx.createProgram(desc); + + std::vector elems(kNumElems * 2); + for (auto& v : elems) + v = (float16_t)u(r); + auto dataBuf = + pDevice->createBuffer(kNumElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, elems.data()); + + float zeros[2] = {}; + auto resultBuf = pDevice->createBuffer( + 2 * sizeof(float), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, zeros + ); + + auto var = ctx.vars().getRootVar(); + var["data"] = dataBuf; + var["resultBuf"] = resultBuf; + + ctx.runProgram(kNumElems, 1, 1); + + // Verify results. + float16_t result[4] = {}; + resultBuf->getBlob(&result, 0, 2 * sizeof(float)); + + float16_t a[2] = {}, b[2] = {}; + for (uint32_t i = 0; i < 2 * kNumElems; i += 2) + { + a[0] += elems[i]; + a[1] += elems[i + 1]; + b[0] -= elems[i]; + b[1] -= elems[i + 1]; + } + float e = 1.f; + EXPECT_GE(result[0] + e, a[0]); + EXPECT_LE(result[0] - e, a[0]); + EXPECT_GE(result[1] + e, a[1]); + EXPECT_LE(result[1] - e, a[1]); + EXPECT_GE(result[2] + e, b[0]); + EXPECT_LE(result[2] - e, b[0]); + EXPECT_GE(result[3] + e, b[1]); + EXPECT_LE(result[3] - e, b[1]); +} +} // namespace + +GPU_TEST(Atomics_Buffer_InterlockedAddF16) +{ + testInterlockedAddF16(ctx, "testBufferAddF16"); +} + +GPU_TEST(Atomics_Buffer_InterlockedAddF16_2) +{ + testInterlockedAddF16(ctx, "testBufferAddF16_2"); +} + +GPU_TEST(Atomics_Buffer_InterlockedAddF32) +{ + ref pDevice = ctx.getDevice(); + + ctx.createProgram(kShaderFile, "testBufferAddF32"); + + std::vector elems(kNumElems); + for (auto& v : elems) + v = u(r); + auto dataBuf = + pDevice->createBuffer(kNumElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, elems.data()); + + float zeros[2] = {}; + auto resultBuf = pDevice->createBuffer( + 2 * sizeof(float), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, zeros + ); + + auto var = ctx.vars().getRootVar(); + var["data"] = dataBuf; + var["resultBuf"] = resultBuf; + + ctx.runProgram(kNumElems, 1, 1); + + // Verify results. + float result[2] = {}; + resultBuf->getBlob(&result, 0, 2 * sizeof(float)); + + float a = 0.f, b = 0.f; + for (uint32_t i = 0; i < kNumElems; i++) + { + a += elems[i]; + b -= elems[i]; + } + float e = 1e-3f; + EXPECT_GE(result[0] + e, a); + EXPECT_LE(result[0] - e, a); + EXPECT_GE(result[1] + e, b); + EXPECT_LE(result[1] - e, b); +} + +GPU_TEST(Atomics_Texture2D_InterlockedAddF32) +{ + ref pDevice = ctx.getDevice(); + + ProgramDesc desc; + desc.addShaderLibrary(kShaderFile).csEntry("testTextureAddF32"); + desc.setUseSPIRVBackend(); // NOTE: The SPIR-V backend is required for RWTexture2D.InterlockedAddF32() on Vulkan! + ctx.createProgram(desc); + + std::vector elems(kNumElems); + for (auto& v : elems) + v = u(r); + auto dataBuf = + pDevice->createBuffer(kNumElems * sizeof(float), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, elems.data()); + + float zeros[2] = {}; + auto resultTex = pDevice->createTexture2D( + 2, 1, ResourceFormat::R32Float, 1, 1, zeros, ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess + ); + auto resultBuf = pDevice->createBuffer( + 2 * sizeof(float), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, MemoryType::DeviceLocal, zeros + ); + + auto var = ctx.vars().getRootVar(); + var["data"] = dataBuf; + var["resultTex"] = resultTex; + + ctx.runProgram(kNumElems, 1, 1); + + // Copy result into readback buffer. + { + auto copyPass = ComputePass::create(pDevice, kShaderFile, "copyResult"); + auto copyVar = copyPass->getRootVar(); + copyVar["resultBuf"] = resultBuf; + copyVar["resultTex"] = resultTex; + copyPass->execute(pDevice->getRenderContext(), 256, 1); + } + + // Verify results. + float result[2] = {}; + resultBuf->getBlob(&result, 0, 2 * sizeof(float)); + + float a = 0.f, b = 0.f; + for (uint32_t i = 0; i < kNumElems; i++) + { + a += elems[i]; + b -= elems[i]; + } + float e = 1e-3f; + EXPECT_GE(result[0] + e, a); + EXPECT_LE(result[0] - e, a); + EXPECT_GE(result[1] + e, b); + EXPECT_LE(result[1] - e, b); +} +} // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang new file mode 100644 index 000000000..c0025a5f3 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Slang/Atomics.cs.slang @@ -0,0 +1,92 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +import Utils.NVAPI; // TODO: This is currently needed for using FP atomics on D3D12. It's safe to import on Vulkan. + +StructuredBuffer data; +RWByteAddressBuffer resultBuf; +[format("r32f")] +RWTexture2D resultTex; + +[numthreads(256, 1, 1)] +void testBufferAddF16(uint3 threadID: SV_DispatchThreadID) +{ + uint x = asuint(data[threadID.x]); + uint y = x ^ 0x80008000; // Flip signs + + // This should work on both D3D12 and Vulkan. + // NOTE: The SPIR-V backend is required for RWByteAddressBuffer.InterlockedAddF16() on Vulkan! + float16_t origVal; + resultBuf.InterlockedAddF16(0, asfloat16((uint16_t)x), origVal); + resultBuf.InterlockedAddF16(2, asfloat16((uint16_t)(x >> 16)), origVal); + resultBuf.InterlockedAddF16(4, asfloat16((uint16_t)y), origVal); + resultBuf.InterlockedAddF16(6, asfloat16((uint16_t)(y >> 16)), origVal); +} + +[numthreads(256, 1, 1)] +void testBufferAddF16_2(uint3 threadID: SV_DispatchThreadID) +{ + uint x = asuint(data[threadID.x]); + uint y = x ^ 0x80008000; // Flip signs + + // This should work on both D3D12 and Vulkan. + // NOTE: The SPIR-V backend is required for RWByteAddressBuffer._NvInterlockedAddFp16x2() on Vulkan! + resultBuf._NvInterlockedAddFp16x2(0, x); + resultBuf._NvInterlockedAddFp16x2(4, y); +} + +[numthreads(256, 1, 1)] +void testBufferAddF32(uint3 threadID: SV_DispatchThreadID) +{ + float x = data[threadID.x]; + + // This should work on both D3D12 and Vulkan. + resultBuf.InterlockedAddF32(0, x); + resultBuf.InterlockedAddF32(4, -x); +} + +[numthreads(256, 1, 1)] +void testTextureAddF32(uint3 threadID: SV_DispatchThreadID) +{ + float x = data[threadID.x]; + + // This should work on both D3D12 and Vulkan. + // NOTE: The SPIR-V backend is required for RWTexture2D.InterlockedAddF32() on Vulkan! + resultTex.InterlockedAddF32(uint2(0, 0), x); + resultTex.InterlockedAddF32(uint2(1, 0), -x); +} + +[numthreads(256, 1, 1)] +void copyResult(uint3 threadID: SV_DispatchThreadID) +{ + uint i = threadID.x; + if (i < 2) + { + float value = resultTex[uint2(i, 0)]; + resultBuf.Store(i * 4, value); + } +} diff --git a/Source/Tools/FalcorTest/Tests/Slang/CastFloat16.cpp b/Source/Tools/FalcorTest/Tests/Slang/CastFloat16.cpp index 8dead80c3..e423e387a 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/CastFloat16.cpp +++ b/Source/Tools/FalcorTest/Tests/Slang/CastFloat16.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -49,9 +49,13 @@ GPU_TEST(CastFloat16) for (auto& v : elems) v = f32tof16(float(u(r))); auto var = ctx.vars().getRootVar(); - var["data"] = pDevice->createStructuredBuffer( + auto buf = pDevice->createStructuredBuffer( var["data"], (uint32_t)elems.size(), ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, elems.data() ); + ASSERT_EQ(buf->getStructSize(), sizeof(float16_t)); + ASSERT_EQ(buf->getElementCount(), elems.size()); + + var["data"] = buf; ctx.runProgram(kNumElems, 1, 1); diff --git a/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cpp b/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cpp index e6e55d57d..fae108438 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cpp +++ b/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -42,7 +42,7 @@ const uint32_t kNumElems = 256; std::mt19937 r; std::uniform_real_distribution u; -void test(GPUUnitTestContext& ctx, ShaderModel shaderModel, bool useUav) +void test(GPUUnitTestContext& ctx, ShaderModel shaderModel, bool useUav, bool useStructured) { ref pDevice = ctx.getDevice(); @@ -55,14 +55,31 @@ void test(GPUUnitTestContext& ctx, ShaderModel shaderModel, bool useUav) for (auto& v : elems) v = f32tof16(float(u(r))); auto var = ctx.vars().getRootVar(); - auto pBuf = pDevice->createStructuredBuffer( - var["data"], - kNumElems, - ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, - MemoryType::DeviceLocal, - elems.data() - ); - var["data"] = pBuf; + + if (useStructured) + { + auto buf = pDevice->createStructuredBuffer( + var["data"], + kNumElems, + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + elems.data() + ); + ASSERT_EQ(buf->getStructSize(), sizeof(float16_t)); + ASSERT_EQ(buf->getElementCount(), kNumElems); + var["data"] = buf; + } + else + { + auto buf = pDevice->createBuffer( + kNumElems * sizeof(float16_t), + ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType::DeviceLocal, + elems.data() + ); + ASSERT_EQ(buf->getSize(), kNumElems * sizeof(float16_t)); + var["data"] = buf; + } ctx.runProgram(kNumElems, 1, 1); @@ -75,15 +92,28 @@ void test(GPUUnitTestContext& ctx, ShaderModel shaderModel, bool useUav) } } // namespace -GPU_TEST(StructuredBufferLoadFloat16) +GPU_TEST(StructuredBuffer_LoadFloat16_Structured) +{ + for (auto sm : kShaderModels) + test(ctx, sm, false, true); +} + +GPU_TEST(StructuredBuffer_LoadFloat16_Raw) { for (auto sm : kShaderModels) - test(ctx, sm, false); + test(ctx, sm, false, false); } -GPU_TEST(RWStructuredBufferLoadFloat16) +GPU_TEST(RWStructuredBuffer_LoadFloat16_Structured) { for (auto sm : kShaderModels) - test(ctx, sm, true); + test(ctx, sm, true, true); } + +GPU_TEST(RWStructuredBuffer_LoadFloat16_Raw) +{ + for (auto sm : kShaderModels) + test(ctx, sm, true, false); +} + } // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cs.slang index 3e06e6c53..6b20f025c 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Slang/Float16Tests.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -40,5 +40,5 @@ void testFloat16(uint3 threadID: SV_DispatchThreadID) float16_t value = data[i]; // 16-bit load - result[i] = f32tof16(value); + result[i] = asuint16(value); } diff --git a/Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cpp b/Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cpp new file mode 100644 index 000000000..12f214cc6 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "Testing/UnitTest.h" + +namespace Falcor +{ +namespace +{ +void runTest(GPUUnitTestContext& ctx, const std::string& entryPoint, DefineList defines) +{ + ref pDevice = ctx.getDevice(); + + ProgramDesc desc; + desc.addShaderLibrary("Tests/Slang/SlangGenerics.cs.slang").csEntry(entryPoint); + ctx.createProgram(desc, defines); + ctx.allocateStructuredBuffer("result", 128); + + // Run program. + ctx.runProgram(32, 1, 1); + + std::vector result = ctx.readBuffer("result"); + for (uint32_t i = 0; i < 32; i++) + { + EXPECT_EQ(result[4 * i + 0], (i + 0) * 12); + EXPECT_EQ(result[4 * i + 1], (i + 1) * 12); + EXPECT_EQ(result[4 * i + 2], (i + 2) * 12); + EXPECT_EQ(result[4 * i + 3], (i + 3) * 12); + } +} +} // namespace + +GPU_TEST(Slang_GenericsInterface_Int) +{ + runTest(ctx, "testGenericsInterface", DefineList{{"TEST_A", "1"}, {"USE_INT", "1"}}); +} + +GPU_TEST(Slang_GenericsInterface_UInt) +{ + runTest(ctx, "testGenericsInterface", DefineList{{"TEST_A", "1"}}); +} + +GPU_TEST(Slang_GenericsFunction_Int) +{ + runTest(ctx, "testGenericsFunction", DefineList{{"TEST_B", "1"}, {"USE_INT", "1"}}); +} + +GPU_TEST(Slang_GenericsFunction_UInt) +{ + runTest(ctx, "testGenericsFunction", DefineList{{"TEST_B", "1"}}); +} +} // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cs.slang new file mode 100644 index 000000000..adc19f97b --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Slang/SlangGenerics.cs.slang @@ -0,0 +1,115 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +RWStructuredBuffer result; + +#ifdef USE_INT +typedef int Type +#else +typedef uint Type +#endif + +#ifdef TEST_A + + // + // Test generic interface. + // + + interface ITestA { + static const Type scale; + static void eval(Type val[N], out Type res[N]); +}; + +struct TestA : ITestA +{ + static const Type scale = 3 * N; + static void eval(Type val[N], out Type res[N]) + { + for (uint i = 0; i < N; i++) + res[i] = scale * val[i]; + } +}; + +[numthreads(32, 1, 1)] +void testGenericsInterface(uint3 threadID: SV_DispatchThreadID) +{ + const uint i = threadID.x; + if (i >= 32) + return; + + Type val[4] = { i, i + 1, i + 2, i + 3 }; + Type res[4]; + + TestA<4> test; + test.eval(val, res); + + result[i * 4 + 0] = res[0]; + result[i * 4 + 1] = res[1]; + result[i * 4 + 2] = res[2]; + result[i * 4 + 3] = res[3]; +} + +#elif defined(TEST_B) + + // + // Test generic function in interface. + // + + interface ITestB { + static void eval(Type val[N], out Type res[N]); +}; + +struct TestB : ITestB +{ + static void eval(Type val[N], out Type res[N]) + { + static const Type scale = 3 * N; + for (uint i = 0; i < N; i++) + res[i] = scale * val[i]; + } +}; + +[numthreads(32, 1, 1)] +void testGenericsFunction(uint3 threadID: SV_DispatchThreadID) +{ + const uint i = threadID.x; + if (i >= 32) + return; + + Type val[4] = { i, i + 1, i + 2, i + 3 }; + Type res[4]; + + TestB test; + test.eval<4>(val, res); + + result[i * 4 + 0] = res[0]; + result[i * 4 + 1] = res[1]; + result[i * 4 + 2] = res[2]; + result[i * 4 + 3] = res[3]; +} + +#endif diff --git a/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cpp b/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cpp index 4cfe4b228..24e8ace1c 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cpp +++ b/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -44,7 +44,7 @@ void runTest2(GPUUnitTestContext& ctx, DefineList defines) pDevice->createStructuredBuffer(var["data2"], 1, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, nullptr, false); EXPECT_EQ(pData->getElementCount(), 1); - EXPECT_EQ(pData->getElementSize(), 32); + EXPECT_EQ(pData->getStructSize(), 32); std::vector initData(16); for (size_t i = 0; i < 16; i++) @@ -76,7 +76,7 @@ GPU_TEST(StructuredBufferMatrixLoad1) pDevice->createStructuredBuffer(var["data1"], 1, ResourceBindFlags::ShaderResource, MemoryType::DeviceLocal, nullptr, false); EXPECT_EQ(pData->getElementCount(), 1); - EXPECT_EQ(pData->getElementSize(), 100); + EXPECT_EQ(pData->getStructSize(), 100); std::vector initData(100); for (size_t i = 0; i < 18; i++) diff --git a/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cs.slang index 8d19bef00..b6a66e96d 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Slang/StructuredBufferMatrix.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -38,6 +38,11 @@ struct Test1 struct Test2 { +// Silence compiler warnings. +#ifndef LAYOUT +#define LAYOUT 0 +#endif + #if LAYOUT == 1 float16_t4 a, b, c, d; float16_t2 getFloat16_2(uint i) diff --git a/Source/Tools/FalcorTest/Tests/Slang/WaveOps.cs.slang b/Source/Tools/FalcorTest/Tests/Slang/WaveOps.cs.slang index 2bd43b01d..ce534a41a 100644 --- a/Source/Tools/FalcorTest/Tests/Slang/WaveOps.cs.slang +++ b/Source/Tools/FalcorTest/Tests/Slang/WaveOps.cs.slang @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -55,6 +55,11 @@ void testWaveMinMax(uint3 threadID: SV_DispatchThreadID) float value = asfloat(testData[i]); +// Silence compiler warnings. +#ifndef CONDITIONAL +#define CONDITIONAL 0 +#endif + #if CONDITIONAL == 0 float minVal = WaveActiveMin(value); float maxVal = WaveActiveMax(value); diff --git a/Source/Tools/FalcorTest/Tests/Utils/MatrixTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/MatrixTests.cpp index 1aeedd351..31b85216f 100644 --- a/Source/Tools/FalcorTest/Tests/Utils/MatrixTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Utils/MatrixTests.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,6 +27,7 @@ **************************************************************************/ #include "Testing/UnitTest.h" #include "Utils/Math/Matrix.h" +#include "Utils/Math/MatrixJson.h" #include #include @@ -830,4 +831,29 @@ CPU_TEST(Matrix_FloatFormatter) EXPECT_EQ(fmt::format("{:.2f}", test0), "{{1.10, 1.20, 1.30}, {2.10, 2.20, 2.30}, {3.10, 3.20, 3.30}}"); } +template +void test_json(CPUUnitTestContext& ctx, const math::matrix& src) +{ + using fmatrix = math::matrix; + + nlohmann::json j = src; + fmatrix dst = j.get(); + EXPECT_TRUE(math::all(dst == src)); + j.get_to(dst); + EXPECT_TRUE(math::all(dst == src)); +} + +CPU_TEST(Matrix_Json) +{ + test_json(ctx, float2x2({1.1f, 2.2f, 3.3f, 4.4f})); + test_json(ctx, float3x3({1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f, 9.9f})); + + test_json(ctx, float1x4({1.1f, 2.2f, 3.3f, 4.4f})); + test_json(ctx, float2x4({1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f})); + test_json(ctx, float3x4({1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f, 9.9f, 10.10f, 11.11f, 12.12f})); + test_json( + ctx, float4x4({1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f, 9.9f, 10.10f, 11.11f, 12.12f, 13.13f, 14.14f, 15.15f, 16.16f}) + ); +} + } // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp new file mode 100644 index 000000000..2836fbb25 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cpp @@ -0,0 +1,655 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "Testing/UnitTest.h" +#include "Utils/SplitBuffer.h" + +#include +#include + +namespace Falcor +{ +namespace +{ + +/// Creates a Structured GPU buffer from span. +template +ref createBufferFromVector( + std::string_view name, + const ref& device, + fstd::span data, + ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType memoryType = MemoryType::DeviceLocal +) +{ + if (data.empty()) + return ref(); + auto result = device->createStructuredBuffer(sizeof(T), data.size(), bindFlags, memoryType, data.data()); + result->setName(std::string(name)); + return result; +} + +/// Creates a Structured GPU buffer from span. +template +ref createBufferFromVector( + std::string_view name, + const ref& device, + const std::vector& data, + ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess, + MemoryType memoryType = MemoryType::DeviceLocal +) +{ + return createBufferFromVector(name, device, fstd::span(data.data(), data.size()), bindFlags, memoryType); +} + +struct S32B +{ + S32B() = default; + S32B(float _f) : f0(_f), f1(_f) {} + float4 f0, f1; + + bool operator==(const S32B& rhs) const { return math::all(f0 == rhs.f0) && math::all(f1 == rhs.f1); } + + friend std::ostream& operator<<(std::ostream& os, const S32B& x) + { + os << fmt::format("({},{})", x.f0, x.f1); + return os; + } +}; + +struct S4B +{ + S4B() = default; + S4B(float _f) : f(_f) {} + float f; + + bool operator==(const S4B& rhs) const { return f == rhs.f; } + + friend std::ostream& operator<<(std::ostream& os, const S4B& x) + { + os << x.f; + return os; + } +}; + +using uint16_t3 = uint16_t[3]; +using uint32_t3 = uint32_t[3]; + +template +struct BaseTypeTrait +{ + using BaseType = T; +}; + +template<> +struct BaseTypeTrait +{ + using BaseType = float; +}; + +template<> +struct BaseTypeTrait +{ + using BaseType = float; +}; + +template<> +struct BaseTypeTrait +{ + using BaseType = uint16_t; +}; + +template<> +struct BaseTypeTrait +{ + using BaseType = uint32_t; +}; + +struct RangeDesc +{ + // The tested concept + uint offset; + uint count; + // The debugging info + uint bufferIndex; + uint bufferOffset; +}; + +template +RangeDesc insertData(SplitBuffer& buffer, uint32_t count) +{ + using BaseType = typename BaseTypeTrait::BaseType; + std::vector dataToInsert; + dataToInsert.resize((count * sizeof(T) + sizeof(U) - 1) / sizeof(U), U(0)); + auto data = reinterpret_cast(dataToInsert.data()); + for (uint32_t i = 0; i < (count * sizeof(T) / sizeof(BaseType)); ++i) + data[i] = BaseType(i); + + RangeDesc result; + result.offset = buffer.insert(dataToInsert.begin(), dataToInsert.end()); + result.count = count; + result.bufferIndex = buffer.getBufferIndex(result.offset); + result.bufferOffset = buffer.getElementIndex(result.offset); + return result; +} + +template +RangeDesc insertEmpty(SplitBuffer& buffer, uint32_t count) +{ + RangeDesc result; + result.offset = buffer.insertEmpty(count); + result.count = count; + result.bufferIndex = buffer.getBufferIndex(result.offset); + result.bufferOffset = buffer.getElementIndex(result.offset); + return result; +} + +} // namespace + +GPU_TEST(SplitBuffer_ByteBuffer_Large48b) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = uint32_t; + using DataElementType = uint16_t3; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + insertEmpty(buffer, size_t(k2GB - buffer.getByteSize() * 1.5) / sizeof(BufferElementType)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_BYTE_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitByteBuffer48b", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitByteBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gByteBufferUint16_t3", ctx.getDevice(), empty); + ; + } + + ctx["gByteBufferUint16_t3"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_ByteBuffer_Large96b) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = uint32_t; + using DataElementType = uint32_t3; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + insertEmpty(buffer, size_t(k2GB - buffer.getByteSize() * 1.5) / sizeof(BufferElementType)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_BYTE_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitByteBuffer96b", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitByteBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gByteBufferUint32_t3", ctx.getDevice(), empty); + ; + } + + ctx["gByteBufferUint32_t3"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_StructuredBuffer_Large4B) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = S4B; + using DataElementType = S4B; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + insertEmpty(buffer, size_t(k2GB - buffer.getByteSize() * 1.5) / sizeof(BufferElementType)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_STRUCT_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + if (sizeof(BufferElementType) == 4) + defines.add("USE_4B_SIZE", "1"); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitStructuredBuffer", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitStructBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gStructuredBuffer", ctx.getDevice(), empty); + ; + } + + ctx["gStructuredBuffer"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_StructuredBuffer_Large32B) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = S32B; + using DataElementType = S32B; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + insertEmpty(buffer, size_t(k4GB - buffer.getByteSize() * 1.5) / sizeof(BufferElementType)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_STRUCT_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + if (sizeof(BufferElementType) == 4) + defines.add("USE_4B_SIZE", "1"); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitStructuredBuffer", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitStructBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gStructuredBuffer", ctx.getDevice(), empty); + ; + } + + ctx["gStructuredBuffer"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_ByteBuffer_Many48b) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = uint32_t; + using DataElementType = uint16_t3; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + buffer.setBufferCount(buffer.getMaxBufferCount()); + + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + + buffer.setBufferCountDefinePrefix("SPLIT_BYTE_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitByteBuffer48b", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitByteBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gByteBufferUint16_t3", ctx.getDevice(), empty); + ; + } + + ctx["gByteBufferUint16_t3"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_ByteBuffer_Many96b) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = uint32_t; + using DataElementType = uint32_t3; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + buffer.setBufferCount(buffer.getMaxBufferCount()); + + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_BYTE_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitByteBuffer96b", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitByteBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gByteBufferUint32_t3", ctx.getDevice(), empty); + ; + } + + ctx["gByteBufferUint32_t3"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_StructuredBuffer_Many4B) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = S4B; + using DataElementType = S4B; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + buffer.setBufferCount(buffer.getMaxBufferCount()); + + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_STRUCT_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + if (sizeof(BufferElementType) == 4) + defines.add("USE_4B_SIZE", "1"); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitStructuredBuffer", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitStructBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gStructuredBuffer", ctx.getDevice(), empty); + ; + } + + ctx["gStructuredBuffer"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +GPU_TEST(SplitBuffer_StructuredBuffer_Many32B) +{ + static size_t k2GB = UINT64_C(1) << UINT64_C(31); + static size_t k4GB = UINT64_C(1) << UINT64_C(32); + + using BufferElementType = S32B; + using DataElementType = S32B; + size_t minCount = 32 * 1024; + size_t maxCount = 256 * 1024; + + SplitBuffer buffer; + buffer.setBufferCount(buffer.getMaxBufferCount()); + + std::vector ranges; + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + for (size_t i = 0; i < 16; ++i) + ranges.push_back(insertData(buffer, (32 + i) * 1024 + i)); + EXPECT_EQ(ranges.front().bufferIndex, 0u); + EXPECT_GT(ranges.back().bufferIndex, 0u); + + buffer.setBufferCountDefinePrefix("SPLIT_STRUCT_BUFFER"); + buffer.createGpuBuffers(ctx.getDevice(), ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess); + + std::vector> results(buffer.getBufferCount()); + + DefineList defines; + buffer.getShaderDefines(defines); + if (sizeof(BufferElementType) == 4) + defines.add("USE_4B_SIZE", "1"); + ctx.createProgram("Tests/Utils/SplitBufferTests.cs.slang", "testSplitStructuredBuffer", defines); + ctx["gRangeCount"] = uint32_t(ranges.size()); + ctx["gRangeDescs"] = createBufferFromVector("gRangeDescs", ctx.getDevice(), ranges); + buffer.bindShaderData(ctx["gSplitStructBuffer"]); + std::vector empty; + for (size_t i = 0; i < buffer.getBufferCount(); ++i) + { + auto& cpuBuffer = buffer.getCpuBuffer(i); + if (!cpuBuffer.empty()) + { + empty.assign(cpuBuffer.size(), BufferElementType{0}); + results[i] = createBufferFromVector("gStructuredBuffer", ctx.getDevice(), empty); + ; + } + + ctx["gStructuredBuffer"][i] = results[i]; + } + + ctx.runProgram(ranges.size()); + + for (const RangeDesc& it : ranges) + { + auto& cpuBuffer = buffer.getCpuBuffer(it.bufferIndex); + EXPECT_FALSE(cpuBuffer.empty()); + auto fromGpu = results[it.bufferIndex]->getElements(it.bufferOffset, it.count); + fstd::span fromCpu(cpuBuffer.data() + it.bufferOffset, cpuBuffer.data() + it.bufferOffset + it.count); + + EXPECT_EQ(fromCpu.size(), fromGpu.size()); + EXPECT(memcmp(fromCpu.data(), fromGpu.data(), fromCpu.size() * sizeof(BufferElementType)) == 0); + } +} + +} // namespace Falcor diff --git a/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cs.slang b/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cs.slang new file mode 100644 index 000000000..111fb1601 --- /dev/null +++ b/Source/Tools/FalcorTest/Tests/Utils/SplitBufferTests.cs.slang @@ -0,0 +1,179 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "Utils/HostDeviceShared.slangh" +import Utils.Attributes; + +struct S32B +{ + float4 f0, f1; +}; + +struct S4B +{ + float f; +}; + +#ifdef USE_4B_SIZE +typedef S4B TElementType; +#else +typedef S32B TElementType; +#endif + +#ifndef SPLIT_BYTE_BUFFER_BUFFER_COUNT +#define SPLIT_BYTE_BUFFER_BUFFER_COUNT 1 // here for the benefit of the IntelliSense +#define SPLIT_BYTE_BUFFER_BUFFER_INDEX_BITS 1 +#endif // SPLIT_BYTE_BUFFER_BUFFER_COUNT + +struct SplitByteBuffer +{ + typedef uint ElementType; + static constexpr uint kBufferIndexBits = SPLIT_BYTE_BUFFER_BUFFER_INDEX_BITS; + static constexpr uint kBufferIndexOffset = 32 - kBufferIndexBits; + static constexpr uint kElementAddressMask = (1u << kBufferIndexOffset) - 1; + static constexpr uint kBufferCount = SPLIT_BYTE_BUFFER_BUFFER_COUNT; + +#if SPLIT_BYTE_BUFFER_BUFFER_COUNT > 0 + ByteAddressBuffer data[kBufferCount]; + + /// baseOffset - offset to the start of the mesh + /// byteIndex - bytes to read from offset + /// Returns bytes starting baseOffset*4 + byteIndex, handling the uint overflow + /// for large buffers. + /// expected usage is LoadUint16_t3(baseOffset, baseOffset*4 + index) + uint16_t3 Load16b(uint baseOffset, uint triangleIndex) + { + if (kBufferCount == 1) + return data[0].Load(baseOffset * 4 + triangleIndex * 6); + uint bufferIndex = baseOffset >> kBufferIndexOffset; + uint byteOffset = (baseOffset & kElementAddressMask) * 4; + return data[bufferIndex].Load(byteOffset + triangleIndex * 6); + } + + uint3 Load32b(uint baseOffset, uint triangleIndex) + { + if (kBufferCount == 1) + return data[0].Load3(baseOffset * 4 + triangleIndex * 12); + uint bufferIndex = baseOffset >> kBufferIndexOffset; + uint byteOffset = (baseOffset & kElementAddressMask) * 4; + return data[bufferIndex].Load3(byteOffset + triangleIndex * 12); + } +#endif // SCENE_INDEX_BUFFER_COUNT > 0 +}; + +#ifndef SPLIT_STRUCT_BUFFER_BUFFER_COUNT +#define SPLIT_STRUCT_BUFFER_BUFFER_COUNT 1 // here for the benefit of the IntelliSense +#define SPLIT_STRUCT_BUFFER_BUFFER_INDEX_BITS 1 +#endif // SPLIT_STRUCT_BUFFER_BUFFER_COUNT + +struct SplitStructBuffer +{ + typedef TElementType ElementType; + static constexpr uint kBufferIndexBits = SPLIT_STRUCT_BUFFER_BUFFER_INDEX_BITS; + static constexpr uint kBufferIndexOffset = 32 - kBufferIndexBits; + static constexpr uint kElementIndexMask = (1u << kBufferIndexOffset) - 1; + static constexpr uint kBufferCount = SPLIT_STRUCT_BUFFER_BUFFER_COUNT; + + StructuredBuffer data[kBufferCount]; + + __subscript(uint index)->ElementType + { + get { + if (kBufferCount == 1) + return data[0][index]; + uint bufferIndex = index >> kBufferIndexOffset; + uint elementIndex = index & kElementIndexMask; + return data[bufferIndex][elementIndex]; + } + } +}; + +RWStructuredBuffer gByteBufferUint16_t3[SPLIT_BYTE_BUFFER_BUFFER_COUNT]; +RWStructuredBuffer gByteBufferUint32_t3[SPLIT_BYTE_BUFFER_BUFFER_COUNT]; +SplitByteBuffer gSplitByteBuffer; + +RWStructuredBuffer gStructuredBuffer[SPLIT_STRUCT_BUFFER_BUFFER_COUNT]; +SplitStructBuffer gSplitStructBuffer; + +struct RangeDesc +{ + // The tested concept + uint offset; + uint count; + // The debugging info + uint bufferIndex; + uint bufferOffset; +}; + +uniform uint gRangeCount; +StructuredBuffer gRangeDescs; + +[numthreads(256, 1, 1)] +void testSplitByteBuffer48b(uint3 threadId: SV_DispatchThreadID) +{ + if (threadId.x >= gRangeCount) + return; + + RangeDesc desc = gRangeDescs[threadId.x]; + for (uint i = 0; i < desc.count; ++i) + { + uint16_t3 item = gSplitByteBuffer.Load16b(desc.offset, i); + gByteBufferUint16_t3[desc.bufferIndex][desc.bufferOffset * 2 + i * 3 + 0] = item[0]; + gByteBufferUint16_t3[desc.bufferIndex][desc.bufferOffset * 2 + i * 3 + 1] = item[1]; + gByteBufferUint16_t3[desc.bufferIndex][desc.bufferOffset * 2 + i * 3 + 2] = item[2]; + } +} + +[numthreads(256, 1, 1)] +void testSplitByteBuffer96b(uint3 threadId: SV_DispatchThreadID) +{ + if (threadId.x >= gRangeCount) + return; + + RangeDesc desc = gRangeDescs[threadId.x]; + for (uint i = 0; i < desc.count; ++i) + { + uint32_t3 item = gSplitByteBuffer.Load32b(desc.offset, i); + gByteBufferUint32_t3[desc.bufferIndex][desc.bufferOffset + i * 3 + 0] = item[0]; + gByteBufferUint32_t3[desc.bufferIndex][desc.bufferOffset + i * 3 + 1] = item[1]; + gByteBufferUint32_t3[desc.bufferIndex][desc.bufferOffset + i * 3 + 2] = item[2]; + } +} + +[numthreads(256, 1, 1)] +void testSplitStructuredBuffer(uint3 threadId: SV_DispatchThreadID) +{ + if (threadId.x >= gRangeCount) + return; + + RangeDesc desc = gRangeDescs[threadId.x]; + for (uint i = 0; i < desc.count; ++i) + { + TElementType item = gSplitStructBuffer[desc.offset + i]; + gStructuredBuffer[desc.bufferIndex][desc.bufferOffset + i] = item; + } +} diff --git a/Source/Tools/FalcorTest/Tests/Utils/VectorTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/VectorTests.cpp index d3f958473..e607f6aa9 100644 --- a/Source/Tools/FalcorTest/Tests/Utils/VectorTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Utils/VectorTests.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -27,8 +27,10 @@ **************************************************************************/ #include "Testing/UnitTest.h" #include "Utils/Math/Vector.h" +#include "Utils/Math/VectorJson.h" #include +#include #include #include #include @@ -76,4 +78,44 @@ CPU_TEST(Vector_Comparison) } } +template +void test_json(CPUUnitTestContext& ctx, const math::vector& src) +{ + using fvector = math::vector; + + nlohmann::json j = src; + fvector dst = j.get(); + EXPECT_TRUE(math::all(dst == src)); + j.get_to(dst); + EXPECT_TRUE(math::all(dst == src)); +} + +CPU_TEST(Vector_Json) +{ + test_json(ctx, bool1(true)); + test_json(ctx, bool2(true, false)); + test_json(ctx, bool3(true, false, true)); + test_json(ctx, bool4(true, false, true, false)); + + test_json(ctx, int1(1)); + test_json(ctx, int2(1, -2)); + test_json(ctx, int3(1, -2, 3)); + test_json(ctx, int4(1, -2, 3, -4)); + + test_json(ctx, uint1(1)); + test_json(ctx, uint2(1, 2)); + test_json(ctx, uint3(1, 2, 3)); + test_json(ctx, uint4(1, 2, 3, 4)); + + test_json(ctx, float1(1.1f)); + test_json(ctx, float2(1.1f, 2.2f)); + test_json(ctx, float3(1.1f, 2.2f, 3.3f)); + test_json(ctx, float4(1.1f, 2.2f, 3.3f, 4.4f)); + + test_json(ctx, float16_t1(1.1f)); + test_json(ctx, float16_t2(1.1f, 2.2f)); + test_json(ctx, float16_t3(1.1f, 2.2f, 3.3f)); + test_json(ctx, float16_t4(1.1f, 2.2f, 3.3f, 4.4f)); +} + } // namespace Falcor diff --git a/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp b/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp index 09bc42858..1cf165eac 100644 --- a/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp +++ b/Source/plugins/importers/AssimpImporter/AssimpImporter.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -151,7 +151,7 @@ class ImporterData const aiScene* pScene; SceneBuilder& builder; std::map> materialMap; - std::map meshMap; // Assimp mesh index to Falcor mesh ID + std::vector meshMap; // Assimp mesh index to Falcor mesh ID std::map localToBindPoseMatrices; NodeID getFalcorNodeID(const aiNode* pNode) const { return mAiToFalcorNodeID.at(pNode); } @@ -574,11 +574,13 @@ void createMeshes(ImporterData& data) if (!pMesh->HasFaces()) { logWarning("AssimpImporter: Mesh '{}' has no faces, ignoring.", pMesh->mName.C_Str()); + meshes.push_back(nullptr); continue; } if (pMesh->mFaces->mNumIndices != 3) { logWarning("AssimpImporter: Mesh '{}' is not a triangle mesh, ignoring.", pMesh->mName.C_Str()); + meshes.push_back(nullptr); continue; } meshes.push_back(pMesh); @@ -594,6 +596,8 @@ void createMeshes(ImporterData& data) [&](size_t i) { const aiMesh* pAiMesh = meshes[i]; + if (!pAiMesh) + return; const uint32_t perFaceIndexCount = pAiMesh->mFaces[0].mNumIndices; SceneBuilder::Mesh mesh; @@ -658,11 +662,13 @@ void createMeshes(ImporterData& data) // Add meshes to the scene. // We retain a deterministic order of the meshes in the global scene buffer by adding // them sequentially after being processed in parallel. - uint32_t i = 0; - for (const auto& mesh : processedMeshes) + data.meshMap.clear(); + data.meshMap.resize(processedMeshes.size(), MeshID::Invalid()); + for (size_t i = 0; i < processedMeshes.size(); ++i) { - MeshID meshID = data.builder.addProcessedMesh(mesh); - data.meshMap[i++] = meshID; + if (!meshes[i]) + continue; + data.meshMap[i] = data.builder.addProcessedMesh(processedMeshes[i]); } } @@ -769,7 +775,9 @@ void addMeshInstances(ImporterData& data, aiNode* pNode) NodeID nodeID = data.getFalcorNodeID(pNode); for (uint32_t mesh = 0; mesh < pNode->mNumMeshes; mesh++) { - MeshID meshID = data.meshMap.at(pNode->mMeshes[mesh]); + MeshID meshID = data.meshMap[pNode->mMeshes[mesh]]; + if (!meshID.isValid()) + continue; data.builder.addMeshInstance(nodeID, meshID); } diff --git a/Source/plugins/importers/CMakeLists.txt b/Source/plugins/importers/CMakeLists.txt index 07158a0f7..2a305fde7 100644 --- a/Source/plugins/importers/CMakeLists.txt +++ b/Source/plugins/importers/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(AssimpImporter) +add_subdirectory(MitsubaImporter) add_subdirectory(PBRTImporter) add_subdirectory(PythonImporter) add_subdirectory(USDImporter) diff --git a/Source/plugins/importers/MitsubaImporter/CMakeLists.txt b/Source/plugins/importers/MitsubaImporter/CMakeLists.txt new file mode 100644 index 000000000..3da20297c --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/CMakeLists.txt @@ -0,0 +1,21 @@ +add_plugin(MitsubaImporter) + +target_sources(MitsubaImporter PRIVATE + MitsubaImporter.cpp + MitsubaImporter.h + Parser.h + Resolver.h + Tables.h +) + +set(DEP_DIR ../../../../external) + +target_include_directories(MitsubaImporter PRIVATE ${DEP_DIR}/packman/deps/include) +target_link_directories(MitsubaImporter PRIVATE ${DEP_DIR}/packman/deps/lib) +target_link_libraries(MitsubaImporter PRIVATE pugixml) + +target_copy_shaders(MitsubaImporter plugins/importers/MitsubaImporter) + +target_source_group(MitsubaImporter "Plugins/Importers") + +validate_headers(MitsubaImporter) diff --git a/Source/plugins/importers/MitsubaImporter/MitsubaImporter.cpp b/Source/plugins/importers/MitsubaImporter/MitsubaImporter.cpp new file mode 100644 index 000000000..f1d796991 --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/MitsubaImporter.cpp @@ -0,0 +1,834 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#include "MitsubaImporter.h" +#include "Parser.h" +#include "Tables.h" +#include "Utils/Math/Common.h" +#include "Utils/Math/FalcorMath.h" +#include "Utils/Math/MathHelpers.h" +#include "Scene/Material/PBRT/PBRTDiffuseMaterial.h" +#include "Scene/Material/PBRT/PBRTDielectricMaterial.h" +#include "Scene/Material/PBRT/PBRTConductorMaterial.h" + +#include + +#include + +namespace Falcor +{ +namespace Mitsuba +{ +float3 fresnelDieletricConductor(float3 eta, float3 k, float cosTheta) +{ + float cosTheta2 = cosTheta * cosTheta; + float sinTheta2 = 1.f - cosTheta2; + float3 eta2 = eta * eta; + float3 k2 = k * k; + + float3 t0 = eta2 - k2 - sinTheta2; + float3 a2plusb2 = sqrt(t0 * t0 + 4.f * eta2 * k2); + float3 t1 = a2plusb2 + cosTheta2; + float3 a = sqrt(0.5f * (a2plusb2 + t0)); + float3 t2 = 2.f * a * cosTheta; + float3 Rs = (t1 - t2) / (t1 + t2); + + float3 t3 = cosTheta2 * a2plusb2 + sinTheta2 * sinTheta2; + float3 t4 = t2 * sinTheta2; + float3 Rp = Rs * (t3 - t4) / (t3 + t4); + + return 0.5f * (Rp + Rs); +} + +Transform transformFromMatrix4x4(const float4x4& m) +{ + float3 scale, translation, skew; + quatf rotation; + float4 perspective; + decompose(m, scale, rotation, translation, skew, perspective); + + Transform t; + t.setScaling(scale); + t.setRotation(rotation); + t.setTranslation(translation); + return t; +} + +struct BuilderContext +{ + SceneBuilder& builder; + std::unordered_map& instances; + std::unordered_set warnings; + + void forEachReference(const XMLObject& inst, Class cls, std::function func) + { + for (const auto& [name, id] : inst.props.getNamedReferences()) + { + const auto& child = instances[id]; + if (child.cls == cls) + func(child); + } + } + + template + void logWarningOnce(const std::string_view fmtString, Args&&... args) + { + auto msg = fmt::format(fmtString, std::forward(args)...); + auto it = warnings.find(msg); + if (it == warnings.end()) + { + warnings.insert(msg); + Falcor::logWarning("MitsubaImporter: {}", msg); + } + } + + void unsupportedParameter(const std::string& name) { logWarningOnce("Parameter '{}' is not supported.", name); } + + void unsupportedType(const std::string& name) { logWarningOnce("Type '{}' is not supported.", name); } +}; + +struct ShapeInfo +{ + ref pMesh; + float4x4 transform; + ref pMaterial; +}; + +struct SensorInfo +{ + ref pCamera; + float4x4 transform; +}; + +struct EmitterInfo +{ + ref pEnvMap; + ref pLight; + float4x4 transform; +}; + +struct TextureInfo +{ + float4 value; + ref pTexture; + float4x4 transform; +}; + +struct BSDFInfo +{ + ref pMaterial; +}; + +struct MediumInfo +{ + struct Homogeneous + { + using SharedPtr = std::shared_ptr; + static SharedPtr create() { return SharedPtr(new Homogeneous()); } + float3 sigmaS = float3(0.f); + float3 sigmaA = float3(0.f); + }; + + Homogeneous::SharedPtr pHomogeneous; +}; + +float lookupIOR(const Properties& props, const std::string& name, const std::string& defaultIOR) +{ + if (props.hasFloat(name)) + { + return props.getFloat(name); + } + else + { + return lookupIOR(props.getString(name, defaultIOR)); + } +} + +TextureInfo buildTexture(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::Texture); + + const auto& props = inst.props; + + // Common properties. + auto toUV = props.getTransform("to_uv", float4x4::identity()); + toUV = inverse(toUV); + + TextureInfo texture; + + if (inst.type == "bitmap") + { + auto filename = props.getString("filename"); + auto raw = props.getBool("raw", false); + + if (props.hasString("filter_type")) + ctx.unsupportedParameter("filter_type"); + if (props.hasString("wrap_mode")) + ctx.unsupportedParameter("wrap_mode"); + + texture.pTexture = Texture::createFromFile(ctx.builder.getDevice(), filename, true, !raw); + texture.transform = toUV; + } + else if (inst.type == "checkerboard") + { + auto color0 = props.getColor3("color0", Color3(0.4f)); + auto color1 = props.getColor3("color1", Color3(0.2f)); + + const uint32_t kSize = 512; + std::vector pixels(kSize * kSize); + for (uint32_t y = 0; y < kSize; ++y) + { + for (uint32_t x = 0; x < kSize; ++x) + { + float4 c(1.f); + c.xyz() = (x < kSize / 2) ^ (y < kSize / 2) ? color1 : color0; + pixels[y * kSize + x] = c; + } + } + + auto pDevice = ctx.builder.getDevice(); + texture.pTexture = pDevice->createTexture2D(kSize, kSize, ResourceFormat::RGBA32Float, 1, Resource::kMaxPossible, pixels.data()); + texture.transform = toUV; + } + else + { + // Unsupported: mesh_attribute, volume + ctx.unsupportedType(inst.type); + } + + return texture; +} + +TextureInfo lookupTexture(BuilderContext& ctx, const Properties& props, const std::string& name, float4 defaultValue) +{ + if (props.hasFloat(name)) + { + return {float4(props.getFloat(name))}; + } + else if (props.hasColor3(name)) + { + return {props.getColor3(name)}; + } + else if (props.hasNamedReference(name)) + { + const auto& inst = ctx.instances[props.getNamedReference(name)]; + if (inst.cls != Class::Texture) + FALCOR_THROW("Parameter '{}' needs to be a color or texture.", name); + return buildTexture(ctx, inst); + } + else + { + return {defaultValue}; + } +} + +void setMicrofacetProperties(ref pMaterial, BuilderContext& ctx, const Properties& props, float defaultAlpha = 0.1f) +{ + if (props.hasString("distribution")) + ctx.unsupportedParameter("distribution"); + if (props.hasBool("sample_visible")) + ctx.unsupportedParameter("sample_visible"); + auto alpha = lookupTexture(ctx, props, "alpha", float4(defaultAlpha)); + if (alpha.pTexture) + ctx.logWarningOnce("Microfacet alpha texture is not supported."); + pMaterial->setRoughness(std::sqrt(alpha.pTexture ? defaultAlpha : alpha.value.x)); + // TODO: set roughness texture +} + +BSDFInfo buildBSDF(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::BSDF); + + const auto& props = inst.props; + + ref pMaterial; + + if (inst.type == "diffuse") + { + auto pPBRTMaterial = PBRTDiffuseMaterial::create(ctx.builder.getDevice(), inst.id); + auto reflectance = lookupTexture(ctx, props, "reflectance", float4(0.5f)); + if (reflectance.pTexture) + { + pPBRTMaterial->setTexture(Material::TextureSlot::BaseColor, reflectance.pTexture); + pPBRTMaterial->setTextureTransform(transformFromMatrix4x4(reflectance.transform)); + } + else + { + pPBRTMaterial->setBaseColor(reflectance.value); + } + + pMaterial = pPBRTMaterial; + } + else if (inst.type == "dielectric" || inst.type == "roughdielectric") + { + auto pStandardMaterial = StandardMaterial::create(ctx.builder.getDevice(), inst.id); + auto intIOR = lookupIOR(props, "int_ior", "bk7"); + auto extIOR = lookupIOR(props, "ext_ior", "air"); + + if (props.hasFloat("specular_reflectance")) + ctx.unsupportedParameter("specular_reflectance"); + if (props.hasFloat("specular_transmittance")) + ctx.unsupportedParameter("specular_transmittance"); + + pStandardMaterial->setSpecularTransmission(1.f); + pStandardMaterial->setDoubleSided(true); + pStandardMaterial->setRoughness(0.f); + pStandardMaterial->setIndexOfRefraction(intIOR / extIOR); + + if (inst.type == "roughdielectric") + setMicrofacetProperties(pStandardMaterial, ctx, props); + + pMaterial = pStandardMaterial; + } + else if (inst.type == "thindielectric") + { + auto pStandardMaterial = StandardMaterial::create(ctx.builder.getDevice(), inst.id); + auto intIOR = lookupIOR(props, "int_ior", "bk7"); + auto extIOR = lookupIOR(props, "ext_ior", "air"); + + if (props.hasFloat("specular_reflectance")) + ctx.unsupportedParameter("specular_reflectance"); + if (props.hasFloat("specular_transmittance")) + ctx.unsupportedParameter("specular_transmittance"); + + pStandardMaterial->setSpecularTransmission(1.f); + pStandardMaterial->setDoubleSided(true); + pStandardMaterial->setThinSurface(true); + pStandardMaterial->setRoughness(0.f); + pStandardMaterial->setIndexOfRefraction(intIOR / extIOR); + + pMaterial = pStandardMaterial; + } + else if (inst.type == "conductor" || inst.type == "roughconductor") + { + auto pPBRTMaterial = PBRTConductorMaterial::create(ctx.builder.getDevice(), inst.id); + if (props.hasFloat("specular_reflectance")) + ctx.unsupportedParameter("specular_reflectance"); + + if (props.hasColor3("eta") && props.hasColor3("k")) + { + auto eta = props.getColor3("eta"); + pPBRTMaterial->setBaseColor(float4(eta.r, eta.g, eta.b, 1.f)); + pPBRTMaterial->setTransmissionColor(props.getColor3("k")); + } + + pPBRTMaterial->setRoughness(float2(0.f)); + pPBRTMaterial->setDoubleSided(true); + + if (inst.type == "roughconductor") + { + const float defaultAlpha = 0.1f; + auto alpha = lookupTexture(ctx, props, "alpha", float4(defaultAlpha)); + if (alpha.pTexture) + ctx.logWarningOnce("Microfacet alpha texture is not supported."); + pPBRTMaterial->setRoughness(alpha.pTexture ? float2(defaultAlpha) : alpha.value.xy()); + } + + pMaterial = pPBRTMaterial; + } + else if (inst.type == "plastic" || inst.type == "roughplastic") + { + auto pStandardMaterial = StandardMaterial::create(ctx.builder.getDevice(), inst.id); + auto diffuseReflectance = lookupTexture(ctx, props, "diffuse_reflectance", float4(0.5f)); + if (diffuseReflectance.pTexture) + { + pStandardMaterial->setTexture(Material::TextureSlot::BaseColor, diffuseReflectance.pTexture); + pStandardMaterial->setTextureTransform(transformFromMatrix4x4(diffuseReflectance.transform)); + } + else + { + pStandardMaterial->setBaseColor(diffuseReflectance.value); + } + + auto intIOR = lookupIOR(props, "int_ior", "polypropylene"); + auto extIOR = lookupIOR(props, "ext_ior", "air"); + + if (props.hasBool("nonlinear")) + ctx.unsupportedParameter("nonlinear"); + if (props.hasFloat("specular_reflectance")) + ctx.unsupportedParameter("specular_reflectance"); + + pStandardMaterial->setRoughness(0.f); + pStandardMaterial->setIndexOfRefraction(intIOR / extIOR); + + if (inst.type == "roughplastic") + setMicrofacetProperties(pStandardMaterial, ctx, props); + + pMaterial = pStandardMaterial; + } + else if (inst.type == "twosided") + { + ref pInnerMaterial = nullptr; + for (const auto& [name, id] : props.getNamedReferences()) + { + const auto& child = ctx.instances[id]; + if (child.cls == Class::BSDF) + { + if (pInnerMaterial) + FALCOR_THROW("'twosided' BSDF can only have one nested BSDF."); + pInnerMaterial = buildBSDF(ctx, child).pMaterial; + if (pInnerMaterial) + pMaterial->setDoubleSided(true); + } + } + + pMaterial = pInnerMaterial; + } + else + { + ctx.unsupportedType(inst.type); + pMaterial = nullptr; + } + + return {pMaterial}; +} + +MediumInfo buildMedium(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::Medium); + + const auto& props = inst.props; + + MediumInfo medium; + + if (inst.type == "homogeneous") + { + auto scale = props.getFloat("scale", 1.f); + + if (props.hasString("material")) + { + ctx.unsupportedParameter("material"); + } + else if (props.hasColor3("sigma_s") && props.hasColor3("sigma_a")) + { + float3 sigmaS = props.getColor3("sigma_s"); + float3 sigmaA = props.getColor3("sigma_a"); + medium.pHomogeneous = MediumInfo::Homogeneous::create(); + medium.pHomogeneous->sigmaS = scale * sigmaS; + medium.pHomogeneous->sigmaA = scale * sigmaA; + } + else if (props.hasColor3("albedo") && props.hasColor3("sigma_t")) + { + float3 albedo = props.getColor3("albedo"); + float3 sigmaT = props.getColor3("sigma_s"); + medium.pHomogeneous = MediumInfo::Homogeneous::create(); + medium.pHomogeneous->sigmaS = scale * (albedo * sigmaT); + medium.pHomogeneous->sigmaA = scale * (sigmaT - medium.pHomogeneous->sigmaS); + } + } + else + { + // Unsupported: heterogeneous + ctx.unsupportedType(inst.type); + } + + return medium; +} + +ShapeInfo buildShape(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::Shape); + + const auto& props = inst.props; + + // Common properties. + auto toWorld = props.getTransform("to_world", float4x4::identity()); + auto flipNormals = props.getBool("flip_normals", false); + if (props.hasBool("flip_normals")) + ctx.unsupportedParameter("flip_normals"); + + const float4x4 transformYtoZ({1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f}); + + ShapeInfo shape; + + if (inst.type == "obj" || inst.type == "ply") + { + auto filename = props.getString("filename"); + auto faceNormals = props.getBool("face_normals", false); + auto flipTexCoords = props.getBool("flip_tex_coords", true); + + if (props.hasBool("flip_tex_coords")) + ctx.unsupportedParameter("flip_tex_coords"); + + TriangleMesh::ImportFlags flags = TriangleMesh::ImportFlags::None; + if (faceNormals) + { + flags = TriangleMesh::ImportFlags::JoinIdenticalVertices; + } + else + { + // Recommend `faceNormals=false` for inverse/differentiable rendering to avoid vertex duplication. + flags = TriangleMesh::ImportFlags::GenSmoothNormals | TriangleMesh::ImportFlags::JoinIdenticalVertices; + } + + shape.pMesh = TriangleMesh::createFromFile(filename, flags); + if (shape.pMesh) + shape.pMesh->setName(inst.id); + shape.transform = toWorld; + } + else if (inst.type == "sphere") + { + auto center = props.getFloat3("center", float3(0.f)); + auto radius = props.getFloat("radius", 1.f); + + shape.pMesh = TriangleMesh::createSphere(radius); + shape.pMesh->setName(inst.id); + shape.transform = mul(toWorld, math::matrixFromTranslation(center)); + } + else if (inst.type == "disk") + { + shape.pMesh = TriangleMesh::createDisk(1.f); + shape.pMesh->setName(inst.id); + shape.transform = mul(toWorld, transformYtoZ); + } + else if (inst.type == "rectangle") + { + shape.pMesh = TriangleMesh::createQuad(float2(2.f)); + shape.pMesh->setName(inst.id); + shape.transform = mul(toWorld, transformYtoZ); + } + else if (inst.type == "cube") + { + shape.pMesh = TriangleMesh::createCube(float3(2.f)); + shape.pMesh->setName(inst.id); + shape.transform = toWorld; + } + else + { + ctx.unsupportedType(inst.type); + } + + // Look for nested BSDF. + for (const auto& [name, id] : props.getNamedReferences()) + { + const auto& child = ctx.instances[id]; + if (child.cls == Class::BSDF) + { + if (shape.pMaterial) + FALCOR_THROW("Shape can only have one BSDF."); + auto bsdf = buildBSDF(ctx, child); + shape.pMaterial = bsdf.pMaterial; + } + } + + // Create default material. + if (!shape.pMaterial) + shape.pMaterial = StandardMaterial::create(ctx.builder.getDevice()); + + // Look for interior medium. + for (const auto& [name, id] : props.getNamedReferences()) + { + const auto& child = ctx.instances[id]; + if (child.cls == Class::Medium && name == "interior") + { + auto medium = buildMedium(ctx, child); + if (medium.pHomogeneous) + { + auto pBasicMaterial = static_cast(shape.pMaterial.get()); + pBasicMaterial->setVolumeScattering(medium.pHomogeneous->sigmaS); + pBasicMaterial->setVolumeAbsorption(medium.pHomogeneous->sigmaA); + } + } + } + + // Look for nested area emitter. + for (const auto& [name, id] : props.getNamedReferences()) + { + const auto& child = ctx.instances[id]; + if (child.cls == Class::Emitter && child.type == "area") + { + float4 baseColor; + if (shape.pMaterial) + { + if (shape.pMaterial->getType() != MaterialType::PBRTDiffuse) + { + FALCOR_THROW("Shape with area emitter must have a diffuse material."); + } + + // Store base color. + auto pBasicMaterial = static_cast(shape.pMaterial.get()); + baseColor = pBasicMaterial->getBaseColor(); + + shape.pMaterial = nullptr; + } + + // Create a new StandardMaterial with emissive properties. + shape.pMaterial = StandardMaterial::create(ctx.builder.getDevice()); + float3 radiance = child.props.getColor3("radiance"); + float factor = std::max(radiance.x, std::max(radiance.y, radiance.z)); + if (factor > 0.f) + radiance /= factor; + + auto pMaterial = static_cast(shape.pMaterial.get()); + pMaterial->setEmissiveColor(radiance); + pMaterial->setEmissiveFactor(factor); + pMaterial->setMetallic(0.f); + pMaterial->setRoughness(1.f); + pMaterial->setBaseColor(baseColor); + } + } + + return shape; +} + +SensorInfo buildSensor(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::Sensor) + + const auto& props = inst.props; + + // Common properties. + auto toWorld = props.getTransform("to_world", float4x4::identity()); + + // Check for film to get resolution. + uint32_t width = 768; + uint32_t height = 576; + for (const auto& [name, id] : props.getNamedReferences()) + { + const auto& child = ctx.instances[id]; + if (child.cls == Class::Film) + { + width = (uint32_t)child.props.getInt("width", 768); + height = (uint32_t)child.props.getInt("height", 576); + } + } + + SensorInfo sensor; + + if (inst.type == "perspective" || inst.type == "thinlens") + { + if (props.hasFloat("focal_length") && props.hasFloat("fov")) + { + FALCOR_THROW("Cannot specify both 'focal_length' and 'fov'."); + } + float focalLength = props.getFloat("focal_length", 50.f); + if (props.hasFloat("fov")) + { + float filmWidth = (24.f / height) * width; + focalLength = fovYToFocalLength(math::radians(props.getFloat("fov")), filmWidth); + } + + if (props.hasString("fov_axis")) + ctx.unsupportedParameter("fov_axis"); + + // TODO handle fov_axis + auto pCamera = Camera::create(); + pCamera = Camera::create(); + pCamera->setFocalLength(focalLength); + // pCamera->setFrameHeight(24.f); + pCamera->setNearPlane(props.getFloat("near_clip", 1e-2f)); + pCamera->setFarPlane(props.getFloat("far_clip", 1e4f)); + pCamera->setFocalDistance(props.getFloat("focus_distance", 1.f)); + pCamera->setApertureRadius(props.getFloat("aperture_radius", 0.f)); + + const float4x4 flipZ({1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 1.f}); + + sensor.pCamera = pCamera; + sensor.transform = mul(toWorld, flipZ); + } + else + { + // Unsupported: orthographic, radiancemeter, irradiancemeter, distant, batch + ctx.unsupportedType(inst.type); + } + + return sensor; +} + +EmitterInfo buildEmitter(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::Emitter); + + const auto& props = inst.props; + + // Common properties. + auto toWorld = props.getTransform("to_world", float4x4::identity()); + + EmitterInfo emitter; + + if (inst.type == "area") + { + FALCOR_THROW("'area' emitter needs to be nested in a shape."); + } + else if (inst.type == "constant") + { + auto radiance = props.getColor3("radiance"); + float4 data = radiance; + auto pDevice = ctx.builder.getDevice(); + auto pTexture = pDevice->createTexture2D(1, 1, ResourceFormat::RGBA32Float, 1, Texture::kMaxPossible, &data); + auto pEnvMap = EnvMap::create(ctx.builder.getDevice(), pTexture); + emitter.pEnvMap = pEnvMap; + } + else if (inst.type == "envmap") + { + auto filename = props.getString("filename"); + auto scale = props.getFloat("scale", 1.f); + auto pEnvMap = EnvMap::createFromFile(ctx.builder.getDevice(), filename); + if (pEnvMap) + { + const float4x4 flipZ({1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, -1.f, 0.f, 0.f, 0.f, 0.f, 1.f}); + toWorld = mul(toWorld, flipZ); + + pEnvMap->setIntensity(scale); + float3 rotation; + extractEulerAngleXYZ(toWorld, rotation.x, rotation.y, rotation.z); + pEnvMap->setRotation(math::degrees(rotation)); + } + emitter.pEnvMap = pEnvMap; + } + else if (inst.type == "point") + { + auto intensity = props.getColor3("intensity", Color3(1.f)); + // TODO optional position prop + + if (props.hasFloat3("position")) + { + if (props.hasTransform("to_world")) + FALCOR_THROW("Either 'to_world' or 'position' can be specified, not both."); + toWorld = math::matrixFromTranslation(props.getFloat3("position")); + } + + ctx.unsupportedType(inst.type); + } + else + { + // Unsupported: spot, projector, directional, directionalarea + ctx.unsupportedType(inst.type); + } + + return emitter; +} + +void buildScene(BuilderContext& ctx, const XMLObject& inst) +{ + FALCOR_ASSERT(inst.cls == Class::Scene); + + const auto& props = inst.props; + + for (const auto& [name, id] : props.getNamedReferences()) + { + const auto& child = ctx.instances[id]; + + switch (child.cls) + { + case Class::Sensor: + { + auto sensor = buildSensor(ctx, child); + + if (sensor.pCamera) + { + SceneBuilder::Node node{id, sensor.transform}; + auto nodeID = ctx.builder.addNode(node); + sensor.pCamera->setNodeID(nodeID); + ctx.builder.addCamera(sensor.pCamera); + } + } + break; + + case Class::Emitter: + { + auto emitter = buildEmitter(ctx, child); + + if (emitter.pEnvMap) + { + if (ctx.builder.getEnvMap() != nullptr) + FALCOR_THROW("Only one envmap can be added."); + ctx.builder.setEnvMap(emitter.pEnvMap); + } + else if (emitter.pLight) + { + SceneBuilder::Node node{id, emitter.transform}; + auto nodeID = ctx.builder.addNode(node); + emitter.pLight->setNodeID(nodeID); + ctx.builder.addLight(emitter.pLight); + } + } + break; + + case Class::Shape: + { + auto shape = buildShape(ctx, child); + + if (shape.pMesh && shape.pMaterial) + { + SceneBuilder::Node node{id, shape.transform}; + auto nodeID = ctx.builder.addNode(node); + auto meshID = ctx.builder.addTriangleMesh(shape.pMesh, shape.pMaterial); + ctx.builder.addMeshInstance(nodeID, meshID); + } + } + break; + } + } +} + +} // namespace Mitsuba + +std::unique_ptr MitsubaImporter::create() +{ + return std::make_unique(); +} + +void MitsubaImporter::importScene( + const std::filesystem::path& path, + SceneBuilder& builder, + const std::map& materialToShortName +) +{ + if (!path.is_absolute()) + throw ImporterError(path, "Path must be absolute."); + + try + { + pugi::xml_document doc; + auto result = doc.load_file(path.c_str(), pugi::parse_default | pugi::parse_comments); + if (!result) + throw ImporterError(path, "Failed to parse XML: {}", result.description()); + + Mitsuba::XMLSource src{path.string(), doc}; + Mitsuba::XMLContext ctx; + ctx.resolver.append(std::filesystem::path(path).parent_path()); + Mitsuba::Properties props; + pugi::xml_node root = doc.document_element(); + size_t argCounter = 0; + auto sceneID = Mitsuba::parseXML(src, ctx, root, Mitsuba::Tag::Invalid, props, argCounter).second; + + Mitsuba::BuilderContext builderCtx{builder, ctx.instances}; + Mitsuba::buildScene(builderCtx, builderCtx.instances[sceneID]); + } + catch (const RuntimeError& e) + { + throw ImporterError(path, e.what()); + } +} + +extern "C" FALCOR_API_EXPORT void registerPlugin(Falcor::PluginRegistry& registry) +{ + registry.registerClass(); +} + +} // namespace Falcor diff --git a/Source/plugins/importers/MitsubaImporter/MitsubaImporter.h b/Source/plugins/importers/MitsubaImporter/MitsubaImporter.h new file mode 100644 index 000000000..0dee60a52 --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/MitsubaImporter.h @@ -0,0 +1,54 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Scene/Importer.h" +#include "Scene/SceneBuilder.h" +#include +#include + +namespace Falcor +{ + +/** + * Scene importer for Mitsuba scenes. + */ +class MitsubaImporter : public Importer +{ +public: + FALCOR_PLUGIN_CLASS(MitsubaImporter, "MitsubaImporter", PluginInfo({"Importer for mitsuba assets", {"xml"}})); + + static std::unique_ptr create(); + + void importScene( + const std::filesystem::path& path, + SceneBuilder& builder, + const std::map& materialToShortName + ) override; +}; + +} // namespace Falcor diff --git a/Source/plugins/importers/MitsubaImporter/Parser.h b/Source/plugins/importers/MitsubaImporter/Parser.h new file mode 100644 index 000000000..2bdfd67dd --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/Parser.h @@ -0,0 +1,1040 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once +#include "Resolver.h" +#include "Utils/Math/MathHelpers.h" +#include "Utils/StringUtils.h" +#include "Utils/Math/ScalarMath.h" +#include "Utils/Math/VectorTypes.h" +#include "Utils/Math/VectorMath.h" +#include "Utils/Math/MatrixTypes.h" +#include "Utils/Math/MatrixMath.h" + +#include + +#include +#include +#include +#include + +namespace Falcor +{ +namespace Mitsuba +{ +struct Color3 +{ + Color3() = default; + Color3(float c) : r(c), g(c), b(c) {} + Color3(float r, float g, float b) : r(r), g(g), b(b) {} + operator float3() const { return float3(r, g, b); } + operator float4() const { return float4(r, g, b, 1.f); } + float r, g, b; +}; + +/** Represents a mitsuba object and it's properties. + */ +class Properties +{ +public: + enum class Type + { + Bool, + Int, + Float, + String, + Float3, + Color3, + Transform, + }; + + using VariantType = std::variant; + + const std::map& getProperties() const { return mProperties; } + const std::map& getNamedReferences() const { return mNamedReferences; } + + void addNamedReference(const std::string& name, const std::string& id) { mNamedReferences.emplace(name, id); } + + bool hasNamedReference(const std::string& name) const { return mNamedReferences.find(name) != mNamedReferences.end(); } + + std::string getNamedReference(const std::string& name) const + { + auto it = mNamedReferences.find(name); + if (it == mNamedReferences.end()) + FALCOR_THROW("Cannot find named reference '{}'.", name); + return it->second; + } + +#define PROPERTY_ACCESSOR(_type_, _getter_, _setter_, _has_) \ + const _type_& _getter_(const std::string& name) const \ + { \ + return getter<_type_>(name); \ + } \ + const _type_& _getter_(const std::string& name, const _type_& defaultValue) const \ + { \ + return getter<_type_>(name, defaultValue); \ + } \ + void _setter_(const std::string& name, const _type_& value) \ + { \ + setter<_type_>(name, value); \ + } \ + bool _has_(const std::string& name) const \ + { \ + return has<_type_>(name); \ + } + + PROPERTY_ACCESSOR(bool, getBool, setBool, hasBool) + PROPERTY_ACCESSOR(int64_t, getInt, setInt, hasInt) + PROPERTY_ACCESSOR(float, getFloat, setFloat, hasFloat) + PROPERTY_ACCESSOR(std::string, getString, setString, hasString) + PROPERTY_ACCESSOR(float3, getFloat3, setFloat3, hasFloat3) + PROPERTY_ACCESSOR(Color3, getColor3, setColor3, hasColor3) + PROPERTY_ACCESSOR(float4x4, getTransform, setTransform, hasTransform) + +#undef PROPERTY_ACCESSOR + +private: + template + const T& getter(const std::string& name) const + { + auto it = mProperties.find(name); + if (it == mProperties.end()) + { + FALCOR_THROW("Property '{}' not found.", name); + } + if (!std::holds_alternative(it->second)) + { + FALCOR_THROW("Property '{}' has invalid type.", name); + } + return std::get(it->second); + } + + template + const T& getter(const std::string& name, const T& defaultValue) const + { + auto it = mProperties.find(name); + if (it == mProperties.end()) + { + return defaultValue; + } + if (!std::holds_alternative(it->second)) + { + FALCOR_THROW("Property '{}' has invalid type.", name); + } + return std::get(it->second); + } + + template + void setter(const std::string& name, const T& value) + { + mProperties.emplace(name, value); + } + + template + bool has(const std::string& name) const + { + auto it = mProperties.find(name); + if (it == mProperties.end()) + { + return false; + } + return std::holds_alternative(it->second); + } + + std::map mProperties; + std::map mNamedReferences; +}; + +/** Mitsuba scene tags. + */ +enum class Tag +{ + Boolean, + Integer, + Float, + String, + Point, + Vector, + Spectrum, + RGB, + Transform, + Translate, + Matrix, + Rotate, + Scale, + LookAt, + Object, + NamedReference, + Include, + Alias, + Default, + Resource, + Invalid +}; + +/** Mitsuba plugin classes. + */ +enum class Class +{ + Scene, + Integrator, + Sensor, + Emitter, + Sampler, + Film, + RFilter, + Shape, + BSDF, + Texture, + Medium +}; + +/** Map to lookup tag type from string. + */ +const std::unordered_map kTags = { + {"boolean", Tag::Boolean}, + {"integer", Tag::Integer}, + {"float", Tag::Float}, + {"string", Tag::String}, + {"point", Tag::Point}, + {"vector", Tag::Vector}, + {"transform", Tag::Transform}, + {"translate", Tag::Translate}, + {"matrix", Tag::Matrix}, + {"rotate", Tag::Rotate}, + {"scale", Tag::Scale}, + {"lookat", Tag::LookAt}, + {"ref", Tag::NamedReference}, + {"spectrum", Tag::Spectrum}, + {"rgb", Tag::RGB}, + {"include", Tag::Include}, + {"alias", Tag::Alias}, + {"default", Tag::Default}, + {"path", Tag::Resource}, + // Classes + {"scene", Tag::Object}, + {"integrator", Tag::Object}, + {"sensor", Tag::Object}, + {"emitter", Tag::Object}, + {"sampler", Tag::Object}, + {"film", Tag::Object}, + {"rfilter", Tag::Object}, + {"shape", Tag::Object}, + {"bsdf", Tag::Object}, + {"texture", Tag::Object}, + {"medium", Tag::Object}, +}; + +const std::unordered_map kClasses = { + {"scene", Class::Scene}, + {"integrator", Class::Integrator}, + {"sensor", Class::Sensor}, + {"emitter", Class::Emitter}, + {"sampler", Class::Sampler}, + {"film", Class::Film}, + {"rfilter", Class::RFilter}, + {"shape", Class::Shape}, + {"bsdf", Class::BSDF}, + {"texture", Class::Texture}, + {"medium", Class::Medium}, +}; + +/** Version helper. + */ +struct Version +{ + uint32_t major, minor, patch; + + Version() = default; + + Version(int major, int minor, int patch) : major(major), minor(minor), patch(patch) {} + + Version(const char* value) + { + if (std::sscanf(value, "%lu.%lu.%lu", &major, &minor, &patch) != 3) + { + FALCOR_THROW("Version string must have x.x.x format."); + } + } + + bool operator==(const Version& other) const { return std::tie(major, minor, patch) == std::tie(other.major, other.minor, other.patch); } + + bool operator!=(const Version& other) const { return std::tie(major, minor, patch) != std::tie(other.major, other.minor, other.patch); } + + bool operator<(const Version& other) const { return std::tie(major, minor, patch) < std::tie(other.major, other.minor, other.patch); } + + friend std::ostream& operator<<(std::ostream& os, const Version& v) + { + os << v.major << "." << v.minor << "." << v.patch; + return os; + } +}; + +struct XMLObject +{ + std::string id; + Class cls; + std::string type; + Properties props; + size_t location; +}; + +struct XMLContext +{ + std::unordered_map instances; + size_t idCounter = 0; + float4x4 transform; + Resolver resolver; + + std::string offset(size_t location) { return fmt::format("{}", location); } +}; + +struct XMLSource +{ + std::string id; + const pugi::xml_document& doc; + + template + [[noreturn]] void throwError(const pugi::xml_node& node, const std::string& fmtString, Args&&... args) + { + FALCOR_THROW(fmtString, std::forward(args)...); + } +}; + +/// Throws if non-whitespace characters are found after the given index. +void checkWhitespaceOnly(const std::string& s, size_t offset) +{ + for (size_t i = offset; i < s.size(); ++i) + { + if (!std::isspace(s[i])) + FALCOR_THROW("Invalid whitespace"); + } +} + +float parseFloat(const std::string& s) +{ + size_t offset = 0; + float result = std::stof(s, &offset); + checkWhitespaceOnly(s, offset); + return result; +} + +int64_t parseInt(const std::string& s) +{ + size_t offset = 0; + int64_t result = std::stoll(s, &offset); + checkWhitespaceOnly(s, offset); + return result; +} + +/** Helper to check if attributes are specified. + */ +void checkAttributes(XMLSource& src, const pugi::xml_node& node, std::set&& attrs, bool expectAll = true) +{ + bool foundOne = false; + for (auto attr : node.attributes()) + { + auto it = attrs.find(attr.name()); + if (it == attrs.end()) + { + src.throwError(node, "Unexpected attribute '{}' in node '{}'.", attr.name(), node.name()); + } + attrs.erase(it); + foundOne = true; + } + if (!attrs.empty() && (!foundOne || expectAll)) + { + src.throwError(node, "Missing attribute '{}' in node '{}'.", *attrs.begin(), node.name()); + } +} + +/// Helper function to split the 'value' attribute into X/Y/Z components +void expandValueToXYZ(XMLSource& src, pugi::xml_node& node) +{ + if (node.attribute("value")) + { + auto tokens = splitString(node.attribute("value").value(), ","); + if (node.attribute("x") || node.attribute("y") || node.attribute("z")) + { + src.throwError(node, "Can't mix and match 'value' and 'x'/'y'/'z' attributes."); + } + if (tokens.size() == 1) + { + node.append_attribute("x") = tokens[0].c_str(); + node.append_attribute("y") = tokens[0].c_str(); + node.append_attribute("z") = tokens[0].c_str(); + } + else if (tokens.size() == 3) + { + node.append_attribute("x") = tokens[0].c_str(); + node.append_attribute("y") = tokens[1].c_str(); + node.append_attribute("z") = tokens[2].c_str(); + } + else + { + src.throwError(node, "'value' attribute must have exactly 1 or 3 elements."); + } + node.remove_attribute("value"); + } +} + +float3 parseNamedVector(XMLSource& src, pugi::xml_node& node, const std::string& attrName) +{ + auto vecStr = node.attribute(attrName.c_str()).value(); + auto list = splitString(vecStr, ","); + if (list.size() != 3) + src.throwError(node, "'{}' attribute must have exactly 3 elements.", attrName); + try + { + return float3(parseFloat(list[0]), parseFloat(list[1]), parseFloat(list[2])); + } + catch (...) + { + src.throwError(node, "Could not parse floating point values in '{}'.", vecStr); + } +} + +float3 parseVector(XMLSource& src, const pugi::xml_node& node, float defaultValue = 0.f) +{ + std::string value; + try + { + float x = defaultValue, y = defaultValue, z = defaultValue; + value = node.attribute("x").value(); + if (!value.empty()) + x = parseFloat(value); + value = node.attribute("y").value(); + if (!value.empty()) + y = parseFloat(value); + value = node.attribute("z").value(); + if (!value.empty()) + z = parseFloat(value); + return float3(x, y, z); + } + catch (...) + { + src.throwError(node, "Could not parse floating point value '{}'.", value); + } +} + +Color3 parseRGB(XMLSource& src, const pugi::xml_node& node) +{ + auto value = node.attribute("value").value(); + auto tokens = splitString(value, ","); + + if (tokens.size() == 1) + { + tokens.push_back(tokens[0]); + tokens.push_back(tokens[0]); + } + if (tokens.size() != 3) + { + src.throwError(node, "RGB value requires one or three components (got '{}').", value); + } + + Color3 color; + try + { + color = {parseFloat(tokens[0]), parseFloat(tokens[1]), parseFloat(tokens[2])}; + } + catch (...) + { + src.throwError(node, "Could not parse RGB value '{}'.", value); + } + return color; +} + +void upgradeTree(XMLSource& src, pugi::xml_node& node, const Version& version) +{ + if (version < Version(2, 0, 0)) + { + // Upgrade all attribute names from camelCase to underscore_case + for (pugi::xpath_node result : node.select_nodes("//*[@name]")) + { + pugi::xml_node n = result.node(); + if (std::strcmp(n.name(), "default") == 0) + continue; + pugi::xml_attribute attr = n.attribute("name"); + std::string name = attr.value(); + for (size_t i = 0; i < name.length() - 1; ++i) + { + if (std::islower(name[i]) && std::isupper(name[i + 1])) + { + name = name.substr(0, i + 1) + std::string("_") + name.substr(i + 1); + i += 2; + while (i < name.length() && std::isupper(name[i])) + { + name[i] = std::tolower(name[i]); + ++i; + } + } + } + attr.set_value(name.c_str()); + } + + for (pugi::xpath_node result : node.select_nodes("//lookAt")) + { + result.node().set_name("lookat"); + } + + // Automatically rename reserved identifiers. + for (pugi::xpath_node result : node.select_nodes("//@id")) + { + pugi::xml_attribute attr = result.attribute(); + char const* val = attr.value(); + if (val && val[0] == '_') + { + std::string new_id = std::string("ID") + val + "__UPGR"; + // Log(Warn, "Changing identifier: \"%s\" -> \"%s\"", val, new_id.c_str()); TODO + attr = new_id.c_str(); + } + } + + // Changed parameters. + for (pugi::xpath_node result : node.select_nodes("//bsdf[@type='diffuse']/*/@name[.='diffuse_reflectance']")) + { + result.attribute() = "reflectance"; + } + + // Update 'uoffset', 'voffset', 'uscale', 'vscale' to transform block + for (pugi::xpath_node result : + node.select_nodes("//node()[float[@name='uoffset' or @name='voffset' or @name='uscale' or @name='vscale']]")) + { + pugi::xml_node n = result.node(); + pugi::xml_node uoffset = n.select_node("float[@name='uoffset']").node(); + pugi::xml_node voffset = n.select_node("float[@name='voffset']").node(); + pugi::xml_node uscale = n.select_node("float[@name='uscale']").node(); + pugi::xml_node vscale = n.select_node("float[@name='vscale']").node(); + + float2 offset(0.f); + float2 scale(1.f); + if (uoffset) + { + offset.x = parseFloat(uoffset.attribute("value").value()); + n.remove_child(uoffset); + } + if (voffset) + { + offset.y = parseFloat(voffset.attribute("value").value()); + n.remove_child(voffset); + } + if (uscale) + { + scale.x = parseFloat(uscale.attribute("value").value()); + n.remove_child(uscale); + } + if (vscale) + { + scale.y = parseFloat(vscale.attribute("value").value()); + n.remove_child(vscale); + } + + pugi::xml_node trafo = n.append_child("transform"); + trafo.append_attribute("name") = "to_uv"; + + if (any(offset != float2(0.f))) + { + pugi::xml_node element = trafo.append_child("translate"); + element.append_attribute("x") = std::to_string(offset.x).c_str(); + element.append_attribute("y") = std::to_string(offset.y).c_str(); + } + + if (any(scale != float2(1.f))) + { + pugi::xml_node element = trafo.append_child("scale"); + element.append_attribute("x") = std::to_string(scale.x).c_str(); + element.append_attribute("y") = std::to_string(scale.y).c_str(); + } + } + } + + // src.modified = true; TODO +} + +std::pair parseXML( + XMLSource& src, + XMLContext& ctx, + pugi::xml_node& node, + Tag parentTag, + Properties& props, + size_t& argCounter, + uint32_t depth = 0, + bool withinEmitter = false, + bool withinSpectrum = false +) +{ + logDebug("Parsing tag {}", node.name()); + + // Skip over comments. + if (node.type() == pugi::node_comment || node.type() == pugi::node_declaration) + { + return std::make_pair("", ""); + } + + // Check for valid element. + if (node.type() != pugi::node_element) + { + src.throwError(node, "Unexpected content."); + } + + // Check for valid tag. + auto it = kTags.find(node.name()); + if (it == kTags.end()) + { + src.throwError(node, "Unexpected tag '{}'.", node.name()); + } + const Tag tag = it->second; + + // Perform some safety checks to make sure that the XML tree really makes sense + const bool hasParent = parentTag != Tag::Invalid; + const bool parentIsObject = hasParent && parentTag == Tag::Object; + const bool currentIsObject = tag == Tag::Object; + const bool parentIsTransform = parentTag == Tag::Transform; + const bool currentIsTransformOp = + tag == Tag::Translate || tag == Tag::Rotate || tag == Tag::Scale || tag == Tag::LookAt || tag == Tag::Matrix; + + if (!hasParent && !currentIsObject) + { + src.throwError(node, "Root node '{}' must be an object.", node.name()); + } + + if (parentIsTransform != currentIsTransformOp) + { + if (parentIsTransform) + src.throwError(node, "Transform nodes can only contain transform operations."); + else + src.throwError(node, "Transform operations can only occur in a transform node."); + } + + if (hasParent && !parentIsObject && !(parentIsTransform && currentIsTransformOp)) + { + src.throwError(node, "Node '{}' cannot occur as child of a property.", node.name()); + } + + // Check version. + if (depth == 0) + { + if (!node.attribute("version")) + { + src.throwError(node, "Missing version attribute in root element '{}'.", node.name()); + } + + const Version version(node.attribute("version").value()); + + // Upgrade XML tree to version 2.0.0. + upgradeTree(src, node, version); + + // Remove version attribute, otherwise it will be detected as an unexpected attribute later. + node.remove_attribute("version"); + } + + // Set type on scene node. + if (std::string(node.name()) == "scene") + { + node.append_attribute("type") = "scene"; + } + + // Reset transform. + if (tag == Tag::Transform) + { + // TODO reset transform + // ctx.transform = Transform4f(); + } + + // Check for valid name and set generic name if none is set. + if (node.attribute("name")) + { + std::string name = node.attribute("name").value(); + if (name.empty()) + src.throwError(node, "Node '{}' has empty name attribute.", node.name()); + else if (name[0] == '_') + src.throwError(node, "Node '{}' has invalid name '{}' with leading underscores.", node.name(), name); + } + else if (currentIsObject || tag == Tag::NamedReference) + { + // To keep shape/bsdf/etc in the same order by padding leading zeros. + node.append_attribute("name") = fmt::format("_arg_{:04d}", argCounter++).c_str(); + } + + // Check for valid id and set generic id if none is set. + if (node.attribute("id")) + { + std::string id = node.attribute("id").value(); + if (id.empty()) + src.throwError(node, "Node '{}' has empty id attribute.", node.name()); + else if (id[0] == '_') + src.throwError(node, "Node '{}' has invalid id '{}' with leading underscores.", node.name(), id); + } + else if (currentIsObject) + { + // TODO set id + node.append_attribute("id") = fmt::format("_unnamed_{}", ctx.idCounter++).c_str(); + } + + switch (tag) + { + case Tag::Object: + { + checkAttributes(src, node, {"type", "id", "name"}); + std::string nodeName = node.name(); + std::string id = node.attribute("id").value(); + std::string name = node.attribute("name").value(); + std::string type = node.attribute("type").value(); + + Properties nestedProps; // TODO (type); + // props_nested.set_id(id); + + // Check if instance with this id already exists. + { + auto it2 = ctx.instances.find(id); + if (it2 != ctx.instances.end()) + { + src.throwError(node, "Node '{}' has duplicate id '{}' (previous was at {}).", nodeName, id, it2->second.location); + } + } + + // TODO + // auto it2 = tag_class->find(class_key(node_name, ctx.variant)); + // if (it2 == tag_class->end()) + // src.throw_error(node, "could not retrieve class object for " + // "tag \"%s\" and variant \"%s\"", node_name, ctx.variant); + + size_t argCounterNested = 0; + for (pugi::xml_node& child : node.children()) + { + auto [nestedName, nestedID] = + parseXML(src, ctx, child, tag, nestedProps, argCounter, depth + 1, nodeName == "emitter", nodeName == "spectrum"); + if (!nestedID.empty()) + { + nestedProps.addNamedReference(nestedName, nestedID); + } + } + + auto cls = kClasses.at(nodeName); + + // Create instance. + auto& inst = ctx.instances[id]; + inst.id = id; + inst.cls = cls; + inst.type = type; + inst.props = nestedProps; + // inst.class_ = it2->second; + // inst.offset = src.offset; + // inst.src_id = src.id; + inst.location = node.offset_debug(); + return std::make_pair(name, id); + } + break; + + case Tag::NamedReference: + { + checkAttributes(src, node, {"name", "id"}); + auto name = node.attribute("name").value(); + auto id = node.attribute("id").value(); + return std::make_pair(name, id); + } + break; + + case Tag::Alias: + { + // TODO implement + FALCOR_THROW("not implemented"); + } + break; + + case Tag::Default: + { + // TODO implement + FALCOR_THROW("not implemented"); + } + break; + + case Tag::Resource: + { + // TODO implement + FALCOR_THROW("not implemented"); + } + break; + + case Tag::Include: + { + // TODO implement + FALCOR_THROW("not implemented"); + } + break; + + case Tag::String: + { + checkAttributes(src, node, {"name", "value"}); + + std::string name = node.attribute("name").value(); + std::string value = node.attribute("value").value(); + if (name == "filename") + { + value = ctx.resolver.resolve(value).string(); + } + + props.setString(name, value); + } + break; + + case Tag::Float: + { + checkAttributes(src, node, {"name", "value"}); + std::string value = node.attribute("value").value(); + float floatValue; + try + { + floatValue = parseFloat(value); + } + catch (...) + { + src.throwError(node, "Could not parse floating point value '{}'.", value); + } + props.setFloat(node.attribute("name").value(), floatValue); + } + break; + + case Tag::Integer: + { + checkAttributes(src, node, {"name", "value"}); + std::string value = node.attribute("value").value(); + int64_t valueInt; + try + { + valueInt = parseInt(value); + } + catch (...) + { + src.throwError(node, "Could not parse integer value '{}'.", value); + } + props.setInt(node.attribute("name").value(), valueInt); + } + break; + + case Tag::Boolean: + { + checkAttributes(src, node, {"name", "value"}); + std::string value = node.attribute("value").value(); + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + bool boolValue = false; + if (value == "true") + boolValue = true; + else if (value == "false") + boolValue = false; + else + src.throwError(node, "Could not parse boolean value '{}', must be 'true' or 'false'.", value); + props.setBool(node.attribute("name").value(), boolValue); + } + break; + + case Tag::Vector: + { + expandValueToXYZ(src, node); + checkAttributes(src, node, {"name", "x", "y", "z"}); + props.setFloat3(node.attribute("name").value(), parseVector(src, node)); + } + break; + + case Tag::Point: + { + expandValueToXYZ(src, node); + checkAttributes(src, node, {"name", "x", "y", "z"}); + props.setFloat3(node.attribute("name").value(), parseVector(src, node)); + } + break; + + case Tag::RGB: + { + checkAttributes(src, node, {"name", "value"}); + auto color = parseRGB(src, node); + props.setColor3(node.attribute("name").value(), color); + // if (!within_spectrum) { + // std::string name = node.attribute("name").value(); + // ref obj = detail::create_texture_from_rgb( + // name, color, ctx.variant, within_emitter); + // props.set_object(name, obj); + // } else { + // props.set_color("color", color); + // } + } + break; + + case Tag::Spectrum: + { + // TODO: Support real multi-channel spectrum + checkAttributes(src, node, {"name", "value"}); + auto color = parseRGB(src, node); + props.setColor3(node.attribute("name").value(), color); + } + break; + + case Tag::Transform: + { + checkAttributes(src, node, {"name"}); + ctx.transform = float4x4::identity(); + } + break; + + case Tag::Rotate: + { + expandValueToXYZ(src, node); + checkAttributes(src, node, {"angle", "x", "y", "z"}, false); + auto vec = parseVector(src, node); + auto value = node.attribute("angle").value(); + float angle; + try + { + angle = parseFloat(value); + } + catch (...) + { + src.throwError(node, "Could not parse floating point value '{}'.", value); + } + angle = angle / 180.f * M_PI; // Degree to radian + ctx.transform = mul(math::matrixFromRotation(angle, vec), ctx.transform); + } + break; + + case Tag::Translate: + { + expandValueToXYZ(src, node); + checkAttributes(src, node, {"x", "y", "z"}, false); + auto vec = parseVector(src, node); + ctx.transform = mul(math::matrixFromTranslation(vec), ctx.transform); + } + break; + + case Tag::Scale: + { + expandValueToXYZ(src, node); + checkAttributes(src, node, {"x", "y", "z"}, false); + auto vec = parseVector(src, node, 1.f); + ctx.transform = mul(math::matrixFromScaling(vec), ctx.transform); + } + break; + + case Tag::LookAt: + { + if (!node.attribute("up")) + node.append_attribute("up") = "0,0,0"; + checkAttributes(src, node, {"origin", "target", "up"}); + + auto origin = parseNamedVector(src, node, "origin"); + auto target = parseNamedVector(src, node, "target"); + auto up = parseNamedVector(src, node, "up"); + + if (length(up) == 0.f) + { + float3 tmp; + buildFrame(normalize(target - origin), up, tmp); + } + + auto vDir = normalize(target - origin); + auto vRight = normalize(cross(normalize(up), vDir)); + auto vNewUp = cross(vDir, vRight); + + float4x4 lookAtMatrix; + lookAtMatrix.setCol(0, float4(vRight, 0.f)); + lookAtMatrix.setCol(1, float4(vNewUp, 0.f)); + lookAtMatrix.setCol(2, float4(vDir, 0.f)); + lookAtMatrix.setCol(3, float4(origin, 1.f)); + + ctx.transform = mul(lookAtMatrix, ctx.transform); + } + break; + + case Tag::Matrix: + { + checkAttributes(src, node, {"value"}); + auto tokens = splitString(node.attribute("value").value(), " "); + if (tokens.size() != 16 && tokens.size() != 9) + { + src.throwError(node, "Matrix needs 9 or 16 values."); + } + + float4x4 mat; + if (tokens.size() == 16) + { + for (int i = 0; i < 4; ++i) + { + for (int j = 0; j < 4; ++j) + { + try + { + mat[j][i] = parseFloat(tokens[i * 4 + j]); + } + catch (...) + { + src.throwError(node, "Could not parse floating point value '{}'.", tokens[i * 4 + j]); + } + } + } + } + else + { + float3x3 mat3; + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + try + { + mat3[j][i] = parseFloat(tokens[i * 3 + j]); + } + catch (...) + { + src.throwError(node, "Could not parse floating point value '{}'.", tokens[i * 3 + j]); + } + } + } + mat = float4x4(mat3); + } + ctx.transform = mul(mat, ctx.transform); + } + break; + + default: + FALCOR_THROW("Unknown tag!"); + } + + for (pugi::xml_node& child : node.children()) + { + parseXML(src, ctx, child, tag, props, argCounter, depth + 1); + } + + if (tag == Tag::Transform) + { + props.setTransform(node.attribute("name").value(), ctx.transform); + } + + return {"", ""}; +} + +} // namespace Mitsuba + +} // namespace Falcor diff --git a/Source/plugins/importers/MitsubaImporter/README.md b/Source/plugins/importers/MitsubaImporter/README.md new file mode 100644 index 000000000..cdc56ede5 --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/README.md @@ -0,0 +1,244 @@ +# MitsubaImporter + +This is a scene importer for Mitsuba 3 XML scene files. +Falcor only supports a very limited subset of the features implemented in Mitsuba 3, +therefore the scene conversion is far from perfect. The list below is an overview +of the objects and parameters currently supported in this importer. + +## Supported objects / parameters + +- Sensors + - [x] `perspective` + - [x] `to_world` + - [x] `fov` + - [x] `focal_length` + - [ ] `fov_axis` + - [x] `near_clip` + - [x] `far_clip` + - [ ] `principal_point_offset_x` + - [ ] `principal_point_offset_y` + - [ ] `srf` + - [ ] `orthographic` + - [ ] `to_world` + - [ ] `near_clip` + - [ ] `far_clip` + - [ ] `srf` + - [x] `thinlens` + - [x] `to_world` + - [x] `aperture_radius` + - [x] `focus_distance` + - [x] `focal_length` + - [x] `fov` + - [ ] `fov_axis` + - [x] `near_clip` + - [x] `far_clip` + - [ ] `srf` + - [ ] `radiancemeter` + - [ ] `to_world` + - [ ] `origin` + - [ ] `direction` + - [ ] `srf` + - [ ] `irradiancemeter` + - [ ] `srf` + - [ ] `distant` + - [ ] `to_world` + - [ ] `direction` + - [ ] `target` + - [ ] `srf` + - [ ] `batch` + - [ ] `srf` +- Emitters + - [x] `area` + - [x] `radiance` + - [x] `point` + - [x] `intensity` + - [x] `position` + - [x] `to_world` + - [x] `constant` + - [x] `radiance` + - [x] `envmap` + - [x] `filename` + - [ ] `bitmap` + - [ ] `scale` + - [x] `to_world` + - [ ] `spot` + - [ ] `intensity` + - [ ] `cutoff_angle` + - [ ] `beam_width` + - [ ] `texture` + - [ ] `to_world` + - [ ] `projector` + - [ ] `irradiance` + - [ ] `scale` + - [ ] `to_world` + - [ ] `fov` + - [ ] `focal_length` + - [ ] `fov_axis` + - [ ] `directional` + - [ ] `irradiance` + - [ ] `to_world` + - [ ] `direction` + - [ ] `directionalarea` + - [ ] `radiance` +- Textures + - [x] `bitmap` + - [x] `filename` + - [ ] `bitmap` + - [ ] `data` + - [ ] `filter_type` + - [ ] `wrap_mode` + - [ ] `format` + - [x] `raw` + - [x] `to_uv` + - [ ] `accel` + - [x] `checkerboard` + - [x] `color0` + - [x] `color1` + - [x] `to_uv` + - [ ] `mesh_attribute` + - [ ] `name` + - [ ] `scale` + - [ ] `volume` + - [ ] `volume` +- BSDFs + - [x] `diffuse` + - [x] `reflectance` + - [x] `dielectric` + - [x] `int_ior` + - [x] `ext_ior` + - [ ] `specular_reflectance` + - [ ] `specular_transmittance` + - [x] `thindielectric` + - [x] `int_ior` + - [x] `ext_ior` + - [ ] `specular_reflectance` + - [ ] `specular_transmittance` + - [x] `roughdielectric` + - [x] `int_ior` + - [x] `ext_ior` + - [ ] `specular_reflectance` + - [ ] `specular_transmittance` + - [ ] `distribution` + - [x] `alpha` (only constant isotropic values are supported) + - [ ] `alpha_u` + - [ ] `alpha_v` + - [ ] `sample_visible` + - [x] `conductor` + - [ ] `material` + - [x] `eta` (only constant value is supported) + - [x] `k` (only constant value is supported) + - [ ] `specular_reflectance` + - [x] `roughconductor` + - [ ] `material` + - [x] `eta` (only constant value is supported) + - [x] `k` (only constant value is supported) + - [ ] `specular_reflectance` + - [ ] `distribution` + - [x] `alpha` (only constant isotropic values are supported) + - [ ] `alpha_u` + - [ ] `alpha_v` + - [ ] `sample_visible` + - [ ] `hair` + - [ ] `measured` + - [ ] `measured_polarized` + - [x] `plastic` + - [x] `diffuse_reflectance` + - [ ] `nonlinear` + - [x] `int_ior` + - [x] `ext_ior` + - [ ] `specular_reflectance` + - [x] `roughplastic` + - [x] `diffuse_reflectance` + - [ ] `nonlinear` + - [x] `int_ior` + - [x] `ext_ior` + - [ ] `specular_reflectance` + - [ ] `distribution` + - [x] `alpha` (only constant isotropic values are supported) + - [ ] `sample_visible` + - [ ] `bumpmap` + - [ ] `normalmap` + - [ ] `blendbsdf` + - [ ] `mask` + - [x] `twosided` + - [x] `bsdf` (only one nested BSDF is supported) + - [ ] `null` + - [ ] `polarizer` + - [ ] `retarder` + - [ ] `circular` + - [ ] `pplastic` + - [ ] `principled` + - [ ] `principledthin` +- Media + - [x] `homogeneous` + - [x] `albedo` + - [x] `sigma_t` + - [x] `scale` + - [ ] `sample_emitters` + - [ ] `phase` + - [ ] `heterogeneous` + - [ ] `albedo` + - [ ] `sigma_t` + - [ ] `scale` + - [ ] `sample_emitters` + - [ ] `phase` +- Shapes + - [x] `obj` + - [x] `filename` + - [x] `face_normals` + - [ ] `flip_tex_coords` + - [ ] `flip_normals` + - [x] `to_world` + - [x] `ply` + - [x] `filename` + - [x] `face_normals` + - [ ] `flip_tex_coords` + - [ ] `flip_normals` + - [x] `to_world` + - [ ] `serialized` + - [ ] `filename` + - [ ] `shape_index` + - [ ] `face_normals` + - [ ] `flip_normals` + - [ ] `to_world` + - [x] `disk` + - [ ] `flip_normals` + - [x] `to_world` + - [ ] `silhouette_sampling_weight` + - [ ] `cylinder` + - [ ] `p0` + - [ ] `p1` + - [ ] `radius` + - [ ] `flip_normals` + - [ ] `to_world` + - [ ] `silhouette_sampling_weight` + - [ ] `bsplinecurve` + - [ ] `filename` + - [ ] `to_world` + - [ ] `silhouette_sampling_weight` + - [ ] `linearcurve` + - [ ] `filename` + - [ ] `to_world` + - [x] `rectangle` + - [ ] `flip_normals` + - [x] `to_world` + - [ ] `silhouette_sampling_weight` + - [x] `cube` + - [ ] `flip_normals` + - [x] `to_world` + - [ ] `sdfgrid` + - [ ] `filename` + - [ ] `grid` + - [ ] `normals` + - [ ] `to_world` + - [ ] `shapegroup` + - [ ] `shape` + - [ ] `instance` + - [ ] `shapegroup` + - [ ] `to_world` + - [x] `sphere` + - [x] `center` + - [x] `radius` + - [ ] `flip_normals` + - [x] `to_world` + - [ ] `silhouette_sampling_weight` diff --git a/Source/plugins/importers/MitsubaImporter/Resolver.h b/Source/plugins/importers/MitsubaImporter/Resolver.h new file mode 100644 index 000000000..e8a58015b --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/Resolver.h @@ -0,0 +1,77 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once + +#include +#include + +namespace Falcor +{ +namespace Mitsuba +{ +class Resolver +{ +public: + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + size_t size() const { return mPaths.size(); } + + iterator begin() { return mPaths.begin(); } + iterator end() { return mPaths.end(); } + + const_iterator begin() const { return mPaths.begin(); } + const_iterator end() const { return mPaths.end(); } + + void erase(iterator it) { mPaths.erase(it); } + + void prepend(const std::filesystem::path& path) { mPaths.insert(mPaths.begin(), path); } + void append(const std::filesystem::path& path) { mPaths.push_back(path); } + const std::filesystem::path& operator[](size_t index) const { return mPaths[index]; } + std::filesystem::path& operator[](size_t index) { return mPaths[index]; } + + std::filesystem::path resolve(const std::filesystem::path& path) const + { + for (const_iterator it = mPaths.begin(); it != mPaths.end(); ++it) + { + std::filesystem::path combined = *it / path; + if (std::filesystem::exists(combined)) + { + return combined; + } + } + return path; + } + +private: + std::vector mPaths; +}; + +} // namespace Mitsuba + +} // namespace Falcor diff --git a/Source/plugins/importers/MitsubaImporter/Tables.h b/Source/plugins/importers/MitsubaImporter/Tables.h new file mode 100644 index 000000000..24be22475 --- /dev/null +++ b/Source/plugins/importers/MitsubaImporter/Tables.h @@ -0,0 +1,84 @@ +/*************************************************************************** + # Copyright (c) 2015-24, NVIDIA CORPORATION. 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 NVIDIA CORPORATION 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 "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. + **************************************************************************/ +#pragma once + +#include "Falcor.h" + +namespace Falcor +{ +namespace Mitsuba +{ +std::map kIORTable = { + {"vacuum", 1.0f}, + {"helium", 1.000036f}, + {"hydrogen", 1.000132f}, + {"air", 1.000277f}, + {"carbon dioxide", 1.00045f}, + + {"water", 1.3330f}, + {"acetone", 1.36f}, + {"ethanol", 1.361f}, + {"carbon tetrachloride", 1.461f}, + {"glycerol", 1.4729f}, + {"benzene", 1.501f}, + {"silicone oil", 1.52045f}, + {"bromine", 1.661f}, + + {"water ice", 1.31f}, + {"fused quartz", 1.458f}, + {"pyrex", 1.470f}, + {"acrylic glass", 1.49f}, + {"polypropylene", 1.49f}, + {"bk7", 1.5046f}, + {"sodium chloride", 1.544f}, + {"amber", 1.55f}, + {"pet", 1.5750f}, + {"diamond", 2.419f}, +}; + +float lookupIOR(std::string name) +{ + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + + auto it = kIORTable.find(name); + if (it != kIORTable.end()) + return it->second; + + std::string list; + for (const auto& [key, value] : kIORTable) + { + list += key + "\n"; + } + logWarning("'{}' is not a valid IOR name. Valid choises are:\n{}", name, list); + + return 0.f; +} + +} // namespace Mitsuba + +} // namespace Falcor diff --git a/Source/plugins/importers/USDImporter/CMakeLists.txt b/Source/plugins/importers/USDImporter/CMakeLists.txt index 88734b8ab..15b2c0a17 100644 --- a/Source/plugins/importers/USDImporter/CMakeLists.txt +++ b/Source/plugins/importers/USDImporter/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(USDImporter PRIVATE target_link_libraries(USDImporter PRIVATE nv-usd USDUtils) + target_copy_shaders(USDImporter plugins/importers/USDImporter) target_source_group(USDImporter "Plugins/Importers") diff --git a/Source/plugins/importers/USDImporter/ImporterContext.cpp b/Source/plugins/importers/USDImporter/ImporterContext.cpp index 0d14ebdcf..dcf6c917e 100644 --- a/Source/plugins/importers/USDImporter/ImporterContext.cpp +++ b/Source/plugins/importers/USDImporter/ImporterContext.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -37,6 +37,7 @@ #include "USDUtils/USDUtils.h" #include "USDUtils/USDScene1Utils.h" #include "USDUtils/Tessellator/Tessellation.h" +#include "Utils/Settings/Settings.h" #include @@ -228,8 +229,8 @@ namespace Falcor if (texCoordPrimvars.size() > 1) { // USD allows specifying multiple sets of texture coordinates, whereas Falcor only supports a single set. - logWarning("Prim '{}' specifies more than one set of texture coordinates. Using '{}', ignoring others.", - prim.GetPath().GetString(), primvar.GetName().GetString()); + logWarning("Prim '{}' specifies {} sets of texture coordinates. Using '{}', ignoring others.", + prim.GetPath().GetString(), texCoordPrimvars.size(), primvar.GetName().GetString()); } return true; } @@ -476,7 +477,7 @@ namespace Falcor { for (int idx : unassignedIndices) { - FALCOR_ASSERT(faceMap[idx] == -1); + FALCOR_CHECK(faceMap[idx] == -1, "A face appears in more than one subset"); faceMap[idx] = subsetCount; } ++subsetCount; @@ -1801,7 +1802,7 @@ namespace Falcor if (material) { // Note that this call will block if another thread is in the process of converting the same material. - pMaterial = mpPreviewSurfaceConverter->convert(material, primName, builder.getDevice()->getRenderContext()); + pMaterial = mpPreviewSurfaceConverter->convert(material, builder.getDevice()->getRenderContext()); } if (!pMaterial) { @@ -1937,6 +1938,31 @@ namespace Falcor } } + void ImporterContext::applyVariantOverrides(UsdPrim& prim, const Falcor::Settings& settings) + { + UsdVariantSets variantSets(prim.GetVariantSets()); + std::vector variantSetNames(variantSets.GetNames()); + const std::string primName(prim.GetPath().GetString()); + // Because the attribute dictionary is flattened, we must iterate over + // "variants:variant-name", rather than querying to see if any varients are specified. + for (const auto& variantSetName : variantSetNames) + { + std::string attributeKey = "variants:" + variantSetName; + std::string variantValue(settings.getAttribute(primName, attributeKey, "")); + if (!variantValue.empty()) + { + if (variantSets[variantSetName].HasAuthoredVariant(variantValue)) + { + variantSets[variantSetName].SetVariantSelection(variantValue); + } + else + { + logWarning("Variant set '{}' specified for '{}' on prim '{}' does not exist. Ignoring.", variantValue, variantSetName, primName); + } + } + } + } + void ImporterContext::createPrototype(const UsdPrim& rootPrim) { PrototypeGeom proto(rootPrim, timeCodesPerSecond); @@ -1956,6 +1982,11 @@ namespace Falcor { // Pre visits + if (prim.HasVariantSets()) + { + applyVariantOverrides(prim, builder.getSettings()); + } + // If this prim has an xform associated with it, push it onto the xform stack if (prim.IsA()) { diff --git a/Source/plugins/importers/USDImporter/ImporterContext.h b/Source/plugins/importers/USDImporter/ImporterContext.h index 4d5b2b6b3..cccfa9d2e 100644 --- a/Source/plugins/importers/USDImporter/ImporterContext.h +++ b/Source/plugins/importers/USDImporter/ImporterContext.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -263,6 +263,7 @@ namespace Falcor bool hasPrototype(const UsdPrim& protoPrim) const; const PrototypeGeom& getPrototypeGeom(const UsdPrim& protoPrim) { return prototypeGeoms[prototypeGeomMap.at(protoPrim)]; } void addPrototypeInstance(const PrototypeInstance& inst); + void applyVariantOverrides(UsdPrim& prim, const Falcor::Settings& settings); // Curves void addCurve(const UsdPrim& curvePrim); diff --git a/Source/plugins/importers/USDImporter/USDImporter.cpp b/Source/plugins/importers/USDImporter/USDImporter.cpp index 91de8aa52..13f53e75b 100644 --- a/Source/plugins/importers/USDImporter/USDImporter.cpp +++ b/Source/plugins/importers/USDImporter/USDImporter.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -32,6 +32,7 @@ #include "Utils/Settings/Settings.h" #include "Scene/Importer.h" + #include "USDUtils/USDHelpers.h" #include @@ -86,6 +87,10 @@ namespace Falcor if (!it.IsPostVisit()) { // Pre visits + if (prim.HasVariantSets()) + { + ctx.applyVariantOverrides(prim, ctx.builder.getSettings()); + } // If this prim has an xform associated with it, push it onto the xform stack if (prim.IsA()) @@ -325,6 +330,7 @@ namespace Falcor auto resolverContext = ArGetResolver().CreateDefaultContextForAsset(path.string()); ArResolverContextBinder binder(resolverContext); + UsdStageRefPtr pStage = UsdStage::Open(path.string()); if (!pStage) { diff --git a/Source/plugins/importers/USDImporter/USDImporter.h b/Source/plugins/importers/USDImporter/USDImporter.h index 069383ddf..8821bdcf1 100644 --- a/Source/plugins/importers/USDImporter/USDImporter.h +++ b/Source/plugins/importers/USDImporter/USDImporter.h @@ -1,5 +1,5 @@ /*************************************************************************** - # Copyright (c) 2015-23, NVIDIA CORPORATION. All rights reserved. + # Copyright (c) 2015-24, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -35,7 +35,7 @@ namespace Falcor class USDImporter : public Importer { public: - FALCOR_PLUGIN_CLASS(USDImporter, "USDImporter", PluginInfo({"Importer for USD assets", {"usd", "usda", "usdc"}})); + FALCOR_PLUGIN_CLASS(USDImporter, "USDImporter", PluginInfo({"Importer for USD assets", {"usd", "usda", "usdc", "usdz"}})); static std::unique_ptr create(); diff --git a/build_scripts/deploycommon.bat b/build_scripts/deploycommon.bat index 232c90fa3..6062a4b7f 100644 --- a/build_scripts/deploycommon.bat +++ b/build_scripts/deploycommon.bat @@ -4,7 +4,7 @@ setlocal rem %1 -> Project directory rem %2 -> Binary output directory rem %3 -> Build configuration -rem %4 -> Slang build configuration +rem %4 -> Slang directory rem %5 -> DLSS directory set ExtDir=%1\external\packman\ @@ -27,7 +27,7 @@ if %IsDebug% EQU 0 ( ) robocopy %ExtDir%\python\ %OutDir% python*.dll /r:0 >nul robocopy %ExtDir%\python %OutDir%\pythondist /E /r:0 >nul -robocopy %ExtDir%\slang\bin\windows-x64\%SlangDir% %OutDir% *.dll /r:0 >nul +robocopy %SlangDir%\bin %OutDir% *.dll /r:0 >nul robocopy %ExtDir%\pix\bin\x64 %OutDir% WinPixEventRuntime.dll /r:0 >nul robocopy %ExtDir%\dxcompiler\bin\x64 %OutDir% dxil.dll /r:0 >nul robocopy %ExtDir%\dxcompiler\bin\x64 %OutDir% dxcompiler.dll /r:0 >nul diff --git a/build_scripts/deploycommon.sh b/build_scripts/deploycommon.sh index 50824c979..0b87e6a78 100755 --- a/build_scripts/deploycommon.sh +++ b/build_scripts/deploycommon.sh @@ -3,7 +3,7 @@ # $1 -> Project directory # $2 -> Binary output directory # $3 -> Build configuration -# $4 -> Slang build configuration +# $4 -> Slang directory # $5 -> DLSS directory EXT_DIR=$1/external/packman/ @@ -27,7 +27,7 @@ mkdir -p ${OUT_DIR}/pythondist cp -frp ${EXT_DIR}/python/* ${OUT_DIR}/pythondist # Copy slang -cp -f ${EXT_DIR}/slang/bin/linux-x64/${SLANG_DIR}/lib*.so ${OUT_DIR} +cp -f ${SLANG_DIR}/lib/lib*.so ${OUT_DIR} # Copy CUDA CUDA_DIR=${EXT_DIR}/cuda diff --git a/dependencies.xml b/dependencies.xml index ecbfebe24..9ed0665b7 100644 --- a/dependencies.xml +++ b/dependencies.xml @@ -4,7 +4,7 @@ - + @@ -14,17 +14,15 @@ - + - - + - - + diff --git a/docs/usage/scenes.md b/docs/usage/scenes.md index 8a4530f8a..6439f0bed 100644 --- a/docs/usage/scenes.md +++ b/docs/usage/scenes.md @@ -66,7 +66,7 @@ bool YourRenderer::onMouseEvent(const MouseEvent& mouseEvent) } ``` -`Scene::update()` returns a set of flags indicating which objects in the scene has changed. This is useful if your renderer or technique needs to reset values, update resources, etc based on scene changes. See `Scene::UpdateFlags` in `Scene.h` for more details. +`Scene::update()` returns a set of flags indicating which objects in the scene has changed. This is useful if your renderer or technique needs to reset values, update resources, etc based on scene changes. See `IScene::UpdateFlags` in `IScene::UpdateFlags.h` for more details. ### Acceleration Structures diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index ef36da805..96ebc6a4d 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -96,7 +96,10 @@ set(FALCOR_DEPS_DIR ${PACKMAN_DIR}/deps) # Note: Using an INTERFACE target to simplify linking against all the various libraries in OpenEXR if(FALCOR_WINDOWS) add_library(OpenEXR INTERFACE) - target_include_directories(OpenEXR INTERFACE ${FALCOR_DEPS_DIR}/include) + target_include_directories(OpenEXR INTERFACE + ${FALCOR_DEPS_DIR}/include/OpenEXR + ${FALCOR_DEPS_DIR}/include/Imath + ) target_link_directories(OpenEXR INTERFACE $<$:${FALCOR_DEPS_DIR}/lib> $<$:${FALCOR_DEPS_DIR}/debug/lib> @@ -107,7 +110,10 @@ if(FALCOR_WINDOWS) ) elseif(FALCOR_LINUX) add_library(OpenEXR INTERFACE) - target_include_directories(OpenEXR INTERFACE ${FALCOR_DEPS_DIR}/include) + target_include_directories(OpenEXR INTERFACE + ${FALCOR_DEPS_DIR}/include/OpenEXR + ${FALCOR_DEPS_DIR}/include/Imath + ) target_link_directories(OpenEXR INTERFACE $<$:${FALCOR_DEPS_DIR}/lib> $<$:${FALCOR_DEPS_DIR}/debug/lib> @@ -331,21 +337,32 @@ else() endif() # slang -set(SLANG_DIR ${PACKMAN_DIR}/slang) -set(FALCOR_SLANG_CONFIG release CACHE STRING "Slang config to use.") -set_property(CACHE FALCOR_SLANG_CONFIG PROPERTY STRINGS release debug) + +set(FALCOR_LOCAL_SLANG OFF CACHE BOOL "Use a local build of slang instead of the binary package.") +set(FALCOR_LOCAL_SLANG_DIR "${CMAKE_SOURCE_DIR}/../slang" CACHE PATH "Path to a local slang directory.") +set(FALCOR_LOCAL_SLANG_BUILD_DIR "build/Debug" CACHE PATH "Path to the build directory of slang.") + +if(FALCOR_LOCAL_SLANG) + set(SLANG_DIR ${FALCOR_LOCAL_SLANG_DIR}/${FALCOR_LOCAL_SLANG_BUILD_DIR}) + set(SLANG_INCLUDE_DIR ${FALCOR_LOCAL_SLANG_DIR}/include) +else() + set(SLANG_DIR ${PACKMAN_DIR}/slang) + set(SLANG_INCLUDE_DIR ${PACKMAN_DIR}/slang/include) +endif() +set(FALCOR_SLANG_DIR ${SLANG_DIR} PARENT_SCOPE) + if(FALCOR_WINDOWS) add_library(slang SHARED IMPORTED GLOBAL) set_target_properties(slang PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${SLANG_DIR} - IMPORTED_IMPLIB ${SLANG_DIR}/bin/windows-x64/${FALCOR_SLANG_CONFIG}/slang.lib - IMPORTED_LOCATION ${SLANG_DIR}/bin/windows-x64/${FALCOR_SLANG_CONFIG}/slang.dll + INTERFACE_INCLUDE_DIRECTORIES ${SLANG_INCLUDE_DIR} + IMPORTED_IMPLIB ${SLANG_DIR}/lib/slang.lib + IMPORTED_LOCATION ${SLANG_DIR}/bin/slang.dll ) elseif(FALCOR_LINUX) add_library(slang SHARED IMPORTED GLOBAL) set_target_properties(slang PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${SLANG_DIR} - IMPORTED_LOCATION ${SLANG_DIR}/bin/linux-x64/${FALCOR_SLANG_CONFIG}/libslang.so + INTERFACE_INCLUDE_DIRECTORIES ${SLANG_INCLUDE_DIR} + IMPORTED_LOCATION ${SLANG_DIR}/lib/libslang.so ) endif() @@ -353,15 +370,15 @@ endif() if(FALCOR_WINDOWS) add_library(slang-gfx SHARED IMPORTED GLOBAL) set_target_properties(slang-gfx PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${SLANG_DIR} - IMPORTED_IMPLIB ${SLANG_DIR}/bin/windows-x64/${FALCOR_SLANG_CONFIG}/gfx.lib - IMPORTED_LOCATION ${SLANG_DIR}/bin/windows-x64/${FALCOR_SLANG_CONFIG}/gfx.dll + INTERFACE_INCLUDE_DIRECTORIES ${SLANG_INCLUDE_DIR} + IMPORTED_IMPLIB ${SLANG_DIR}/lib/gfx.lib + IMPORTED_LOCATION ${SLANG_DIR}/bin/gfx.dll ) elseif(FALCOR_LINUX) add_library(slang-gfx SHARED IMPORTED GLOBAL) set_target_properties(slang-gfx PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${SLANG_DIR} - IMPORTED_LOCATION ${SLANG_DIR}/bin/linux-x64/${FALCOR_SLANG_CONFIG}/libgfx.so + INTERFACE_INCLUDE_DIRECTORIES ${SLANG_INCLUDE_DIR} + IMPORTED_LOCATION ${SLANG_DIR}/lib/libgfx.so ) endif() @@ -513,4 +530,3 @@ endif() set(RTXDI_DIR ${PACKMAN_DIR}/rtxdi) add_library(rtxdi INTERFACE) target_include_directories(rtxdi INTERFACE ${RTXDI_DIR}/rtxdi-sdk/include) - diff --git a/external/include/BS_thread_pool.hpp b/external/include/BS_thread_pool/BS_thread_pool.hpp similarity index 100% rename from external/include/BS_thread_pool.hpp rename to external/include/BS_thread_pool/BS_thread_pool.hpp diff --git a/external/include/BS_thread_pool_light.hpp b/external/include/BS_thread_pool/BS_thread_pool_light.hpp similarity index 100% rename from external/include/BS_thread_pool_light.hpp rename to external/include/BS_thread_pool/BS_thread_pool_light.hpp diff --git a/external/include/sigs/sigs.h b/external/include/sigs/sigs.h new file mode 100644 index 000000000..6ec99700b --- /dev/null +++ b/external/include/sigs/sigs.h @@ -0,0 +1,515 @@ +/*************************************************************************************************** +sigs + +Simple thread-safe signal/slot C++17 include-only library. + +The MIT License (MIT) + +Copyright (c) 2015-2020 Morten Kristensen, me AT mortens DOT dev + +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. +***************************************************************************************************/ + +#ifndef SIGS_SIGNAL_SLOT_H +#define SIGS_SIGNAL_SLOT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// The following is used for making variadic type lists for binding member functions and their +// parameters. +namespace sigs { + +template +class Seq { +}; + +template +class MakeSeq : public MakeSeq { +}; + +template +class MakeSeq<0, Ns...> : public Seq { +}; + +template +class Placeholder { +}; + +} // namespace sigs + +// std::bind uses std::is_placeholder to detect placeholders for unbounded arguments, so it must be +// overridden to accept the custom sigs::Placeholder type. +namespace std { + +template +class is_placeholder> : public integral_constant { +}; + +} // namespace std + +namespace sigs { + +/// When a member function has muliple overloads and you need to use just one of them. +/** Example: + signal.connect(&instance, sigs::Use::overloadOf(&TheClass::func)); + */ +template +struct Use final { + template + [[nodiscard]] static inline auto overloadOf(Ret (Cls::*MembFunc)(Args...)) noexcept + { + return MembFunc; + } +}; + +class ConnectionBase final { + template + friend class BasicSignal; + +public: + void disconnect() + { + if (deleter) deleter(); + } + +private: + std::function deleter; +}; + +using Connection = std::shared_ptr; + +namespace detail { + +/// VoidableFunction is used internally to generate a function type depending on whether the return +/// type of the signal is non-void. +template +class VoidableFunction final { +public: + using func = std::function; +}; + +/// Specialization for void return types. +template <> +class VoidableFunction final { +public: + using func = std::function; +}; + +} // namespace detail + +template +class BasicSignal; + +template +class SignalBlocker { + static_assert(std::is_base_of_v, Sig>, + "Sig must extend sigs::BasicSignal"); + +public: + explicit SignalBlocker(Sig *sig) noexcept : sig_(sig) + { + reblock(); + } + + explicit SignalBlocker(Sig &sig) noexcept : sig_(&sig) + { + reblock(); + } + + virtual ~SignalBlocker() noexcept + { + unblock(); + } + + SignalBlocker(const SignalBlocker &rhs) = delete; + SignalBlocker &operator=(const SignalBlocker &rhs) = delete; + + SignalBlocker(SignalBlocker &&rhs) noexcept + { + moveAssign(std::move(rhs)); + } + + /// Unblocks `this` if signals of `this` and `rhs` aren't the same. + SignalBlocker &operator=(SignalBlocker &&rhs) noexcept + { + if (sig_ != rhs.sig_) { + unblock(); + } + + moveAssign(std::move(rhs)); + return *this; + } + + void reblock() noexcept + { + if (sig_) { + previous = sig_->setBlocked(true); + } + } + + void unblock() noexcept + { + if (sig_) { + sig_->setBlocked(previous); + } + } + +private: + void moveAssign(SignalBlocker &&rhs) + { + sig_ = rhs.sig_; + previous = rhs.previous; + + // `rhs` won't do any action on destruction. + rhs.sig_ = nullptr; + } + + Sig *sig_ = nullptr; + bool previous = false; +}; + +/// Suppress CTAD warning. +template +SignalBlocker(Sig) -> SignalBlocker; + +template +class BasicSignal { +public: + using RetArgs = Ret(Args...); + using SignalType = BasicSignal; + using LockType = Lock; + using ReturnType = Ret; + +private: + using Slot = std::function; + using Mutex = typename Lock::mutex_type; + + class Entry final { + public: + Entry(const Slot &slot, Connection conn) noexcept + : slot_(slot), conn_(std::move(conn)), signal_(nullptr) + { + } + + Entry(Slot &&slot, Connection conn) noexcept + : slot_(std::move(slot)), conn_(std::move(conn)), signal_(nullptr) + { + } + + Entry(BasicSignal *signal, Connection conn) noexcept : conn_(std::move(conn)), signal_(signal) + { + } + + const Slot &slot() const noexcept + { + return slot_; + } + + BasicSignal *signal() const noexcept + { + return signal_; + } + + [[nodiscard]] Connection conn() const noexcept + { + return conn_; + } + + private: + Slot slot_; + Connection conn_; + BasicSignal *signal_; + }; + + using Cont = std::vector; + +public: + using SlotType = Slot; + + /// Interface that only exposes connect and disconnect methods. + class Interface final { + public: + explicit Interface(SignalType *sig) noexcept : sig_(sig) + { + } + + inline Connection connect(const Slot &slot) noexcept + { + return sig_->connect(slot); + } + + inline Connection connect(Slot &&slot) noexcept + { + return sig_->connect(slot); + } + + template + inline Connection connect(Instance *instance, MembFunc Instance::*mf) noexcept + { + return sig_->connect(instance, mf); + } + + inline Connection connect(BasicSignal &signal) noexcept + { + return sig_->connect(signal); + } + + inline void disconnect(std::optional conn) noexcept + { + sig_->disconnect(conn); + } + + inline void disconnect(BasicSignal &signal) noexcept + { + sig_->disconnect(signal); + } + + private: + SignalType *sig_ = nullptr; + }; + + BasicSignal() noexcept = default; + + virtual ~BasicSignal() noexcept + { + Lock lock(entriesMutex); + for (auto &entry : entries) { + if (auto conn = entry.conn(); conn) { + conn->deleter = nullptr; + } + } + } + + BasicSignal(const BasicSignal &rhs) noexcept : BasicSignal() + { + Lock lock1(entriesMutex); + Lock lock2(rhs.entriesMutex); + entries = rhs.entries; + + // `atomic_bool` can't be copied, so copy value. + blocked_ = rhs.blocked_.load(); + } + + BasicSignal &operator=(const BasicSignal &rhs) noexcept + { + Lock lock1(entriesMutex); + Lock lock2(rhs.entriesMutex); + entries = rhs.entries; + blocked_ = rhs.blocked_.load(); + return *this; + } + + BasicSignal(BasicSignal &&rhs) noexcept = default; + BasicSignal &operator=(BasicSignal &&rhs) noexcept = default; + + std::size_t size() const noexcept + { + Lock lock(entriesMutex); + return entries.size(); + } + + bool empty() const noexcept + { + return 0 == size(); + } + + Connection connect(const Slot &slot) noexcept + { + Lock lock(entriesMutex); + auto conn = makeConnection(); + entries.emplace_back(Entry(slot, conn)); + return conn; + } + + Connection connect(Slot &&slot) noexcept + { + Lock lock(entriesMutex); + auto conn = makeConnection(); + entries.emplace_back(Entry(std::move(slot), conn)); + return conn; + } + + template + Connection connect(Instance *instance, MembFunc Instance::*mf) noexcept + { + Lock lock(entriesMutex); + auto slot = bindMf(instance, mf); + auto conn = makeConnection(); + entries.emplace_back(Entry(slot, conn)); + return conn; + } + + /// Connecting a signal will trigger all of its slots when this signal is triggered. + Connection connect(BasicSignal &signal) noexcept + { + Lock lock(entriesMutex); + auto conn = makeConnection(); + entries.emplace_back(Entry(&signal, conn)); + return conn; + } + + void clear() noexcept + { + Lock lock(entriesMutex); + eraseEntries(); + } + + /// Disconnects \p conn from signal. + /** If no value is given, all slots are disconnected. */ + void disconnect(const std::optional &conn = std::nullopt) noexcept + { + if (!conn) { + clear(); + return; + } + + Lock lock(entriesMutex); + eraseEntries([conn](auto it) { return it->conn() == conn; }); + } + + void disconnect(BasicSignal &signal) noexcept + { + assert(&signal != this && "Disconnecting from self has no effect."); + + Lock lock(entriesMutex); + eraseEntries([sig = &signal](auto it) { return it->signal() == sig; }); + } + + void operator()(const Args &...args) noexcept + { + if (blocked()) return; + + Lock lock(entriesMutex); + for (auto &entry : entries) { + if (auto *sig = entry.signal(); sig) { + (*sig)(std::forward(args)...); + } + else { + entry.slot()(std::forward(args)...); + } + } + } + + template ::func> + void operator()(const RetFunc &retFunc, const Args &...args) noexcept + { + static_assert(!std::is_void_v, "Must have non-void return type!"); + + if (blocked()) return; + + Lock lock(entriesMutex); + for (auto &entry : entries) { + if (auto *sig = entry.signal(); sig) { + (*sig)(retFunc, args...); + } + else { + retFunc(entry.slot()(args...)); + } + } + } + + [[nodiscard]] inline Interface getInterface() noexcept + { + return Interface(this); + } + + /// Returns the previous blocked state. + bool setBlocked(bool blocked) + { + const auto previous = blocked_.load(); + blocked_ = blocked; + return previous; + } + + bool blocked() const + { + return blocked_; + } + +private: + [[nodiscard]] inline Connection makeConnection() noexcept + { + auto conn = std::make_shared(); + conn->deleter = [this, conn] { this->disconnect(conn); }; + return conn; + } + + /// Expects entries container to be locked beforehand. + [[nodiscard]] typename Cont::iterator eraseEntry(typename Cont::iterator it) noexcept + { + auto conn = it->conn(); + if (conn) { + conn->deleter = nullptr; + } + return entries.erase(it); + } + + void eraseEntries(std::function pred = [](auto /*unused*/) { + return true; + }) noexcept + { + for (auto it = entries.begin(); it != entries.end();) { + if (pred(it)) { + it = eraseEntry(it); + } + else { + ++it; + } + } + } + + template + [[nodiscard]] inline Slot bindMf(Instance *instance, MembFunc Instance::*mf, + Seq /*unused*/) noexcept + { + return std::bind(mf, instance, Placeholder()...); + } + + template + [[nodiscard]] inline Slot bindMf(Instance *instance, MembFunc Instance::*mf) noexcept + { + return bindMf(instance, mf, MakeSeq()); + } + + Cont entries; + mutable Mutex entriesMutex; + std::atomic_bool blocked_ = false; +}; + +using BasicLock = std::lock_guard; + +/// Default signal types. +//@{ + +template +using Signal = BasicSignal; + +//@} + +} // namespace sigs + +#endif // SIGS_SIGNAL_SLOT_H diff --git a/tests/environment/gitlab_linux.json b/tests/environment/gitlab_linux.json new file mode 100644 index 000000000..860e7f1c8 --- /dev/null +++ b/tests/environment/gitlab_linux.json @@ -0,0 +1,7 @@ +{ + "name": "GitLab Environment (Linux)", + "image_tests": { + "result_dir": "/home/gitlab-runner/FalcorResults/${vcs_root}/${build_id}", + "ref_dir": "/home/gitlab-runner/FalcorRefs/${vcs_root}" + } +} diff --git a/tests/environment/gitlab_windows.json b/tests/environment/gitlab_windows.json new file mode 100644 index 000000000..69c9d43a4 --- /dev/null +++ b/tests/environment/gitlab_windows.json @@ -0,0 +1,7 @@ +{ + "name": "GitLab Environment (Windows)", + "image_tests": { + "result_dir": "${project_drive}/FalcorResults/${vcs_root}/${build_id}", + "ref_dir": "${project_drive}/FalcorRefs/${vcs_root}" + } +} diff --git a/tests/image_tests/renderpasses/graphs/PathTracerAdaptive.py b/tests/image_tests/renderpasses/graphs/PathTracerAdaptive.py index 9e5a854e4..1fd185d1b 100644 --- a/tests/image_tests/renderpasses/graphs/PathTracerAdaptive.py +++ b/tests/image_tests/renderpasses/graphs/PathTracerAdaptive.py @@ -2,7 +2,7 @@ def render_graph_PathTracerAdaptive(): g = RenderGraph("PathTracerAdaptive") - PathTracer = createPass("PathTracer") + PathTracer = createPass("PathTracer", {'useSER': False}) g.addPass(PathTracer, "PathTracer") VBufferRT = createPass("VBufferRT", {'samplePattern': 'Center', 'sampleCount': 16, 'useAlphaTest': True}) g.addPass(VBufferRT, "VBufferRT") diff --git a/tests/run_image_tests.sh b/tests/run_image_tests.sh index 63ec5e08d..a336c2329 100755 --- a/tests/run_image_tests.sh +++ b/tests/run_image_tests.sh @@ -1,6 +1,6 @@ #!/bin/sh -export pwd=`pwd` +export pwd="$(dirname "$(realpath "$0")")" export project_dir=$pwd/.. export python_dir=$project_dir/tools/.packman/python export python=$python_dir/bin/python3 diff --git a/tests/run_python_tests.sh b/tests/run_python_tests.sh index 9049f1825..b02d6ef97 100755 --- a/tests/run_python_tests.sh +++ b/tests/run_python_tests.sh @@ -1,6 +1,6 @@ #!/bin/sh -DIR="$( dirname -- "$BASH_SOURCE"; )"; +DIR="$(dirname "$(realpath "$0")")" if [ -z "${CONDA_PYTHON_EXE}" ]; then echo "Python tests require conda environment to run." diff --git a/tests/run_unit_tests.sh b/tests/run_unit_tests.sh index 18ebeaa76..3cbd93b2a 100755 --- a/tests/run_unit_tests.sh +++ b/tests/run_unit_tests.sh @@ -1,6 +1,6 @@ #!/bin/sh -export pwd=`pwd` +export pwd="$(dirname "$(realpath "$0")")" export project_dir=$pwd/.. export python_dir=$project_dir/tools/.packman/python export python=$python_dir/bin/python3 diff --git a/tests/testing/core/environment.py b/tests/testing/core/environment.py index 02cb08c36..7421ecd93 100644 --- a/tests/testing/core/environment.py +++ b/tests/testing/core/environment.py @@ -6,6 +6,7 @@ import json import string from pathlib import Path +import hashlib from . import config, helpers @@ -59,6 +60,7 @@ def __init__(self, json_file, build_config): ''' self.project_dir = Path(__file__).parents[3].resolve() + self.temp_dir = self.project_dir / "tests/temp" if json_file == None: json_file = self.project_dir / config.DEFAULT_ENVIRONMENT @@ -102,11 +104,14 @@ def __init__(self, json_file, build_config): self.cmake_dir = self.build_dir.parents[1].resolve() self.cmake_config = self.build_dir.parts[-1] self.image_tests_dir = self.project_dir / config.IMAGE_TESTS_DIR - self.image_tests_result_dir = env['image_tests']['result_dir'] - self.image_tests_ref_dir = env['image_tests']['ref_dir'] - self.image_tests_remote_ref_dir = env['image_tests'].get('remote_ref_dir', None) + self.image_tests_result_dir: str = env['image_tests']['result_dir'] + self.image_tests_ref_dir: str = env['image_tests']['ref_dir'] + self.image_tests_remote_ref_dir: str = env['image_tests'].get('remote_ref_dir', None) self.python_tests_dir = self.project_dir / config.PYTHON_TESTS_DIR + self.vcs_root = helpers.get_vcs_root(self.project_dir) + self.hostname = helpers.get_hostname() + self.build_config = build_config self.branch = helpers.get_git_head_branch(self.project_dir) @@ -122,14 +127,24 @@ def resolve_image_dir(self, image_dir, branch, build_id): Substitues ${xxx} placeholders in directory name with values in environment. ''' branch = branch.replace("/", "^").replace("\\", "^") + build_config = self.build_config + + build_hash_str = f"{self.vcs_root}/{self.hostname}/{build_id}/{build_config}" + branch_hash_str = f"{self.vcs_root}/{self.hostname}/{branch}/{build_config}" + build_hash = hashlib.sha1(build_hash_str.encode()).hexdigest()[0:8] + branch_hash = hashlib.sha1(branch_hash_str.encode()).hexdigest()[0:8] + template = string.Template(image_dir) variables = { 'project_dir': self.project_dir, - 'build_config': self.build_config, - 'vcs_root': helpers.get_vcs_root(self.project_dir), - 'hostname': helpers.get_hostname(), + 'build_config': build_config, + 'vcs_root': self.vcs_root, + 'hostname': self.hostname, 'build_id': build_id, - 'branch': branch + 'branch': branch, + 'build_hash': build_hash, + 'branch_hash': branch_hash, + 'project_drive': self.project_dir.drive } return Path(template.substitute(variables)).resolve() diff --git a/tests/testing/core/helpers.py b/tests/testing/core/helpers.py index 5f62bced3..13d2781c3 100644 --- a/tests/testing/core/helpers.py +++ b/tests/testing/core/helpers.py @@ -43,11 +43,11 @@ def get_vcs_root(path): ''' url = get_git_remote_origin(path) url = urlparse(url) - url = url.netloc.split('.') - for u in url: - if u.startswith("git@"): u = u.replace("git@", "") - if u == "gitlab-master" or u == "github": return u - print("Error. Unknown VCS root `" + url[0] + "`") + roots = ["gitlab-master", "github"] + for root in roots: + if root in url.netloc: + return root + print("Error. Unknown VCS root `" + url.netloc + "`") return url[0].lower() def mirror_folders(src_dir, dst_dir): diff --git a/tests/testing/run_image_tests.py b/tests/testing/run_image_tests.py index da6d9b169..5ce86ddb6 100644 --- a/tests/testing/run_image_tests.py +++ b/tests/testing/run_image_tests.py @@ -2,6 +2,7 @@ Frontend for running image tests. ''' +import hashlib import os import sys import re @@ -119,7 +120,7 @@ class Result(Enum): process_controller = None - def __init__(self, script_file, root_dir, device_type, header): + def __init__(self, script_file, root_dir: Path, device_type, header): self.script_file = script_file self.device_type = device_type self.header = header @@ -166,7 +167,7 @@ def collect_images(self, image_dir): files = filter(lambda f: f.suffix.lower() in config.IMAGE_EXTENSIONS, files) return list(files) - def generate_images(self, output_dir, mogwai_exe, run_only=False): + def generate_images(self, output_dir: Path, mogwai_exe: Path, run_only: bool, temp_dir: Path): ''' Run Mogwai to generate a set of images and store them in output_dir. Returns a tuple containing the result code and a list of messages. @@ -178,6 +179,7 @@ def generate_images(self, output_dir, mogwai_exe, run_only=False): # Determine full output directory. output_dir = output_dir / self.test_dir output_dir.mkdir(parents=True, exist_ok=True) + temp_dir.mkdir(parents=True, exist_ok=True) # In order to simplify module imports in test scripts, we run Mogwai with # a working directory set to the directory the test script resides in. @@ -187,8 +189,14 @@ def generate_images(self, output_dir, mogwai_exe, run_only=False): cwd = self.script_file.parent relative_to_cwd = lambda p: os.path.relpath(p, cwd) + # Calculate string to hash for generate string + hash_str = f'{self.script_file}' + + # Calculate short name for useful generate.py filename + short_name = self.name.split('/')[-1] + # Write helper script to run test. - generate_file = output_dir / 'generate.py' + generate_file = temp_dir / f'{short_name}_{hashlib.sha1(hash_str.encode()).hexdigest()[:8]}.py' with open(generate_file, 'w') as f: # Hack to pass run only flag to helpers.py if run_only: @@ -230,7 +238,7 @@ def generate_images(self, output_dir, mogwai_exe, run_only=False): return Test.Result.PASSED, [], rerun_env - def compare_images(self, ref_dir, result_dir, image_compare_exe): + def compare_images(self, ref_dir: Path, result_dir: Path, image_compare_exe: Path): ''' Run ImageCompare on a set of images in ref_dir and result_dir. Checks if error between reference and result image is within a given tolerance. @@ -306,7 +314,7 @@ def compare_images(self, ref_dir, result_dir, image_compare_exe): return result, messages, image_reports - def run(self, run_only, compare_only, ref_dir, result_dir, mogwai_exe, image_compare_exe): + def run(self, run_only: bool, compare_only: bool, ref_dir: Path, result_dir: Path, mogwai_exe: Path, image_compare_exe: Path, temp_dir: Path): ''' Run the image test. First, result images are generated (unless compare_only is True). @@ -328,7 +336,7 @@ def run(self, run_only, compare_only, ref_dir, result_dir, mogwai_exe, image_com # Generate results images. if not compare_only: - result, messages, rerun_env = self.generate_images(result_dir, mogwai_exe, run_only) + result, messages, rerun_env = self.generate_images(result_dir, mogwai_exe, run_only, temp_dir) # Compare to references. if not run_only and result == Test.Result.PASSED: @@ -349,19 +357,19 @@ def run(self, run_only, compare_only, ref_dir, result_dir, mogwai_exe, image_com return result, messages -def generate_ref(env, test, ref_dir, process_controller): +def generate_ref(env: Environment, test: Test, ref_dir: Path, process_controller): if process_controller.is_interrupted(): return with print_mutex: print(f' {test.name:<60} : STARTED') test.process_controller = process_controller start_time = time.time() - result, messages, rerun_env = test.generate_images(ref_dir, env.mogwai_exe) + result, messages, rerun_env = test.generate_images(ref_dir, env.mogwai_exe, False, env.temp_dir) elapsed_time = time.time() - start_time return {"name": test.name, "elapsed_time": elapsed_time, "result": result, "messages": messages, "rerun_env": rerun_env} -def generate_refs(env, tests, ref_dir, process_controller): +def generate_refs(env: Environment, tests: list[Test], ref_dir, process_controller): ''' Computes references for a set of tests and stores them into ref_dir. ''' @@ -412,7 +420,7 @@ def generate_refs(env, tests, ref_dir, process_controller): return success -def run_test(env, test, run_only, compare_only, ref_dir, result_dir, min_tolerance, process_controller): +def run_test(env: Environment, test: Test, run_only: bool, compare_only: bool, ref_dir: Path, result_dir: Path, min_tolerance, process_controller: ProcessController, build_id: str): if process_controller.is_interrupted(): return with print_mutex: @@ -420,11 +428,15 @@ def run_test(env, test, run_only, compare_only, ref_dir, result_dir, min_toleran test.tolerance = max(test.tolerance, min_tolerance) test.process_controller = process_controller start_time = time.time() - result, messages = test.run(run_only, compare_only, ref_dir, result_dir, env.mogwai_exe, env.image_compare_exe) + result, messages = test.run(run_only, compare_only, ref_dir, result_dir, env.mogwai_exe, env.image_compare_exe, env.temp_dir) elapsed_time = time.time() - start_time + + if result != Test.Result.SKIPPED: + messages.append(f'View test at: http://{env.hostname}:8080/{env.vcs_root}/{build_id}/{test.name}') + return {"name": test.name, "elapsed_time": elapsed_time, "result": result, "messages": messages} -def run_tests(env, tests, run_only, compare_only, ref_dir, result_dir, min_tolerance, xml_report, process_controller): +def run_tests(env: Environment, tests: list[Test], run_only: bool, compare_only: bool, ref_dir: Path, result_dir: Path, min_tolerance, xml_report, process_controller: ProcessController, build_id: str): ''' Runs a set of tests, stores them into result_dir and compares them to ref_dir. ''' @@ -443,7 +455,7 @@ def run_tests(env, tests, run_only, compare_only, ref_dir, result_dir, min_toler try: # Run tests on #CPU - 2 (to retain some performance control) with concurrent.futures.ThreadPoolExecutor(process_controller.thread_count) as executor: - futures = {executor.submit(run_test, env, test, run_only, compare_only, ref_dir, result_dir, min_tolerance, process_controller) for test in tests} + futures = {executor.submit(run_test, env, test, run_only, compare_only, ref_dir, result_dir, min_tolerance, process_controller,build_id) for test in tests} try: for future in concurrent.futures.as_completed(futures): run_result = future.result() @@ -508,7 +520,7 @@ def run_tests(env, tests, run_only, compare_only, ref_dir, result_dir, min_toler return success -def list_tests(tests): +def list_tests(tests: list[Test]): ''' Print a list of tests. ''' @@ -516,7 +528,7 @@ def list_tests(tests): for test in tests: print(f' {test.name}') -def collect_tests(root_dir, filter_regex, tags): +def collect_tests(root_dir, filter_regex, tags)->list[Test]: ''' Collect a list of all tests found in root_dir that are matching the filter_regex and tags. A test script needs to be named test_*.py to be detected. @@ -553,6 +565,7 @@ def collect_tests(root_dir, filter_regex, tags): # Filter using tags. tags = tags.split(',') tests = list(filter(lambda t: t.matches_tags(tags), tests)) + tests = sorted(tests, key=lambda t: t.name) return tests @@ -635,7 +648,9 @@ def main(): elif args.gen_refs: # Generate references. ref_dir = env.resolve_image_dir(env.image_tests_ref_dir, env.branch, args.build_id) - if not generate_refs(env, tests, ref_dir, process_controller): + result = generate_refs(env, tests, ref_dir, process_controller) + shutil.rmtree(env.temp_dir, ignore_errors=True) + if not result: sys.exit(1) # Push references to remote. @@ -678,7 +693,14 @@ def main(): sys.exit(1) # Run tests. - if not run_tests(env, tests, args.run_only, args.compare_only, ref_dir, result_dir, args.tolerance, args.xml_report, process_controller): + result = run_tests(env, tests, args.run_only, args.compare_only, ref_dir, result_dir, args.tolerance, args.xml_report, process_controller, args.build_id) + shutil.rmtree(env.temp_dir, ignore_errors=True) + + # Print out url to test viewer + print(f"View test results at: http://{env.hostname}:8080/{env.vcs_root}/{args.build_id}") + + # Exit with error if failed + if not result: sys.exit(1) sys.exit(0) diff --git a/tests/testing/run_unit_tests.py b/tests/testing/run_unit_tests.py index 0da28063c..27232ebbb 100644 --- a/tests/testing/run_unit_tests.py +++ b/tests/testing/run_unit_tests.py @@ -19,7 +19,7 @@ def run_unit_tests(env: Environment, args): p = subprocess.Popen(args) try: - p.communicate(timeout=600) + p.communicate(timeout=1200) except subprocess.TimeoutExpired: p.kill() print('\n\nProcess killed due to timeout') diff --git a/tests/testing/view_image_tests.py b/tests/testing/view_image_tests.py index b2ce02089..bc660e445 100644 --- a/tests/testing/view_image_tests.py +++ b/tests/testing/view_image_tests.py @@ -55,13 +55,15 @@ class Database: ''' Helper for accessing image test results. ''' - def __init__(self, env): + def __init__(self, env: Environment): result_dir_template = env.image_tests_result_dir ref_dir_template = env.image_tests_ref_dir # Substitute project_dir as it is a static part of the path. result_dir_template = result_dir_template.replace('${project_dir}', str(env.project_dir)) ref_dir_template = ref_dir_template.replace('${project_dir}', str(env.project_dir)) + result_dir_template = result_dir_template.replace('${project_drive}', str(env.project_dir.drive)) + ref_dir_template = ref_dir_template.replace('${project_drive}', str(env.project_dir.drive)) # Extract result directory and run pattern. index = result_dir_template.index('$') diff --git a/tools/packman/bootstrap/configure.bat b/tools/packman/bootstrap/configure.bat index 8f8aa66d6..1511537ca 100644 --- a/tools/packman/bootstrap/configure.bat +++ b/tools/packman/bootstrap/configure.bat @@ -12,7 +12,7 @@ :: See the License for the specific language governing permissions and :: limitations under the License. -set PM_PACKMAN_VERSION=7.15.1 +set PM_PACKMAN_VERSION=7.23.2 :: Specify where packman command is rooted set PM_INSTALL_PATH=%~dp0.. diff --git a/tools/packman/bootstrap/install_package.py b/tools/packman/bootstrap/install_package.py index b8ae7f642..5baa9eca2 100644 --- a/tools/packman/bootstrap/install_package.py +++ b/tools/packman/bootstrap/install_package.py @@ -19,7 +19,8 @@ import os import stat import time -from typing import Any, Callable +import hashlib +from typing import Any, Callable, Union RENAME_RETRY_COUNT = 100 @@ -130,7 +131,24 @@ def rename_folder_with_retry(staging_dir: StagingDirectory, folder_name): ) -def install_package(package_path, install_path): +def generate_sha256_for_file(file_path: Union[str, os.PathLike]) -> str: + """Returns the SHA-256 hex digest for the file at `file_path`""" + hash = hashlib.sha256() + # Read the file in binary mode and update the hash object with data + with open(file_path, "rb") as file: + for chunk in iter(lambda: file.read(4096), b""): + hash.update(chunk) + return hash.hexdigest() + + +def install_common_module(package_path, install_path): + COMMON_SHA256 = "dc5b18d2b507f04ce560b3d3e8b6b1eeac55b8931653096378cb45337874fd33" + package_sha256 = generate_sha256_for_file(package_path) + if package_sha256 != COMMON_SHA256: + raise RuntimeError( + f"Package at '{package_path}' must have a sha256 of '{COMMON_SHA256}' " + f"but was found to have '{package_sha256}'" + ) staging_path, version = os.path.split(install_path) with StagingDirectory(staging_path) as staging_dir: output_folder = staging_dir.get_temp_folder_path() @@ -151,4 +169,4 @@ def install_package(package_path, install_path): for exec_path in paths_list: if os.path.normcase(os.path.normpath(exec_path)) == target_path_np_nc: raise RuntimeError(f"packman will not install to executable path '{exec_path}'") - install_package(sys.argv[1], target_path_np) + install_common_module(sys.argv[1], target_path_np) diff --git a/tools/packman/packman b/tools/packman/packman index d93d80ebd..2577fbe19 100755 --- a/tools/packman/packman +++ b/tools/packman/packman @@ -24,7 +24,7 @@ else PM_CURL_SILENT="-s -S" PM_WGET_QUIET="--quiet" fi -PM_PACKMAN_VERSION=7.15.1 +export PM_PACKMAN_VERSION=7.23.2 # This is necessary for newer macOS if [ `uname` == 'Darwin' ]; then diff --git a/tools/packman/packmanconf.py b/tools/packman/packmanconf.py index 539d05625..db220d27e 100644 --- a/tools/packman/packmanconf.py +++ b/tools/packman/packmanconf.py @@ -1,4 +1,18 @@ -# Use this file to bootstrap packman into your Python environment (3.7.x). Simply +# Copyright 2021-2024 NVIDIA CORPORATION + +# 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. + +# Use this file to bootstrap packman into your Python environment. Simply # add the path by doing sys.insert to where packmanconf.py is located and then execute: # # >>> import packmanconf @@ -32,11 +46,16 @@ def init(): >>> import packmanapi >>> packmanapi.set_verbosity_level(packmanapi.VERBOSITY_HIGH) """ - major = sys.version_info[0] - minor = sys.version_info[1] - if major != 3 or minor != 10: + major = sys.version_info.major + minor = sys.version_info.minor + patch = sys.version_info.micro + if major == 3 and (minor == 10 or (minor == 11 and patch <= 2)): + # we are good + pass + else: raise RuntimeError( - f"This version of packman requires Python 3.10.x, but {major}.{minor} was provided" + f"This version of packman requires Python 3.10.0 up to 3.11.2, " + f"but {major}.{minor}.{patch} was provided" ) conf_dir = os.path.dirname(os.path.abspath(__file__)) os.environ["PM_INSTALL_PATH"] = conf_dir @@ -90,7 +109,7 @@ def get_module_dir(conf_dir, packages_root: str, version: str) -> str: script_path = os.path.join(conf_dir, "bootstrap", "install_package.py") ip = SourceFileLoader("install_package", script_path).load_module() print("Unpacking ...") - ip.install_package(target_name, module_dir) + ip.install_common_module(target_name, module_dir) os.unlink(tf.name) return module_dir @@ -101,7 +120,7 @@ def get_version(conf_dir: str): path += ".sh" with open(path, "rt", encoding="utf8") as launch_file: for line in launch_file.readlines(): - if line.startswith("PM_PACKMAN_VERSION"): + if "PM_PACKMAN_VERSION" in line: _, value = line.split("=") return value.strip() raise RuntimeError(f"Unable to find 'PM_PACKMAN_VERSION' in '{path}'")