diff --git a/README.md b/README.md index 3690b649b..67f88faf0 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,10 @@ For more information on how to use Falcor as a Python module see [Falcor In Pyth ## Microsoft DirectX 12 Agility SDK Falcor uses the [Microsoft DirectX 12 Agility SDK](https://devblogs.microsoft.com/directx/directx12agility/) to get access to the latest DirectX 12 features. Applications can enable the Agility SDK by putting `FALCOR_EXPORT_D3D12_AGILITY_SDK` in the main `.cpp` file. `Mogwai`, `FalcorTest` and `RenderGraphEditor` have the Agility SDK enabled by default. +## NVAPI +To enable NVAPI support, head over to https://developer.nvidia.com/nvapi and download the latest version of NVAPI (this build is tested against version R535). +Extract the content of the zip file into `external/packman/` and rename `R535-developer` to `nvapi`. + ## NSight Aftermath To enable NSight Aftermath support, head over to https://developer.nvidia.com/nsight-aftermath and download the latest version of Aftermath (this build is tested against version 2023.1). Extract the content of the zip file into `external/packman/aftermath`. diff --git a/Source/Falcor/CMakeLists.txt b/Source/Falcor/CMakeLists.txt index e2fd9bb0f..e229a43c0 100644 --- a/Source/Falcor/CMakeLists.txt +++ b/Source/Falcor/CMakeLists.txt @@ -154,11 +154,12 @@ target_sources(Falcor PRIVATE Core/State/StateGraph.h DiffRendering/AggregateGradients.cs.slang - DiffRendering/DiffDebug.slang + DiffRendering/DiffDebugParams.slang DiffRendering/DiffMaterialData.slang DiffRendering/DiffSceneIO.slang DiffRendering/DiffSceneQuery.slang DiffRendering/GradientIOWrapper.slang + DiffRendering/InverseOptimizationParams.slang DiffRendering/SceneGradientInfo.slang DiffRendering/SceneGradients.cpp DiffRendering/SceneGradients.h @@ -543,6 +544,7 @@ target_sources(Falcor PRIVATE Utils/Dictionary.h Utils/fast_vector.h Utils/HostDeviceShared.slangh + Utils/IndexedVector.h Utils/Logger.cpp Utils/Logger.h Utils/NumericRange.h @@ -554,13 +556,13 @@ target_sources(Falcor PRIVATE Utils/PathResolving.h Utils/Properties.cpp Utils/Properties.h - Utils/Settings.cpp - Utils/Settings.h Utils/SharedCache.h Utils/SlangUtils.slang Utils/StringFormatters.h Utils/StringUtils.cpp Utils/StringUtils.h + Utils/TaskManager.cpp + Utils/TaskManager.h Utils/TermColor.cpp Utils/TermColor.h Utils/Threading.cpp @@ -700,6 +702,13 @@ target_sources(Falcor PRIVATE Utils/SDF/SDFOperations.slang Utils/SDF/SDFOperationType.slang + Utils/Settings/AttributeFilters.cpp + Utils/Settings/AttributeFilters.h + Utils/Settings/Attributes.h + Utils/Settings/Settings.cpp + Utils/Settings/Settings.h + Utils/Settings/SettingsUtils.h + Utils/Timing/Clock.cpp Utils/Timing/Clock.h Utils/Timing/CpuTimer.h @@ -923,6 +932,7 @@ target_compile_definitions(Falcor $<$:_SCL_SECURE_NO_WARNINGS> $<$:_CRT_SECURE_NO_WARNINGS> $<$:_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING> + $<$:_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS> # Clang. $<$:_MSC_EXTENSIONS> # enable MS extensions # Falcor feature flags. @@ -963,7 +973,7 @@ target_link_libraries(Falcor $<$:CUDA::cudart_static> PRIVATE git_version - FreeImage assimp OpenEXR OpenVDB lz4 zlib pugixml + FreeImage assimp OpenEXR OpenVDB lz4 zlib pugixml opensubdiv glfw mikktspace nvtt $<$:d3d12> $<$:agility-sdk> diff --git a/Source/Falcor/Core/API/Texture.cpp b/Source/Falcor/Core/API/Texture.cpp index 708fa2a0a..2a6193145 100644 --- a/Source/Falcor/Core/API/Texture.cpp +++ b/Source/Falcor/Core/API/Texture.cpp @@ -264,7 +264,8 @@ ref Texture::createMippedFromFiles( ref pDevice, fstd::span paths, bool loadAsSrgb, - ResourceBindFlags bindFlags + ResourceBindFlags bindFlags, + Bitmap::ImportFlags importFlags ) { std::vector mips; @@ -281,7 +282,7 @@ ref Texture::createMippedFromFiles( } else { - pBitmap = Bitmap::createFromFile(path, kTopDown); + pBitmap = Bitmap::createFromFile(path, kTopDown, importFlags); } if (!pBitmap) { @@ -335,7 +336,7 @@ ref Texture::createMippedFromFiles( if (loadAsSrgb) texFormat = linearToSrgbFormat(texFormat); - // Create mip mapped latent texture + // Create mip mapped texture. pTex = pDevice->createTexture2D(mips[0]->getWidth(), mips[0]->getHeight(), texFormat, 1, mips.size(), combinedData.get(), bindFlags); } @@ -343,6 +344,7 @@ ref Texture::createMippedFromFiles( if (pTex != nullptr) { pTex->setSourcePath(fullPathMip0); + pTex->mImportFlags = importFlags; // Log debug info. std::string str = fmt::format( @@ -364,7 +366,8 @@ ref Texture::createFromFile( const std::filesystem::path& path, bool generateMipLevels, bool loadAsSrgb, - ResourceBindFlags bindFlags + ResourceBindFlags bindFlags, + Bitmap::ImportFlags importFlags ) { if (!std::filesystem::exists(path)) @@ -387,7 +390,7 @@ ref Texture::createFromFile( } else { - Bitmap::UniqueConstPtr pBitmap = Bitmap::createFromFile(path, kTopDown); + Bitmap::UniqueConstPtr pBitmap = Bitmap::createFromFile(path, kTopDown, importFlags); if (pBitmap) { ResourceFormat texFormat = pBitmap->getFormat(); @@ -411,6 +414,7 @@ ref Texture::createFromFile( if (pTex != nullptr) { pTex->setSourcePath(path); + pTex->mImportFlags = importFlags; // Log debug info. std::string str = fmt::format( diff --git a/Source/Falcor/Core/API/Texture.h b/Source/Falcor/Core/API/Texture.h index 29f3e347d..4db328a92 100644 --- a/Source/Falcor/Core/API/Texture.h +++ b/Source/Falcor/Core/API/Texture.h @@ -166,13 +166,15 @@ class FALCOR_API Texture : public Resource * @param[in] paths List of full paths of all mips, starting from mip0. * @param[in] loadAsSrgb Load the texture using sRGB format. Only valid for 3 or 4 component textures. * @param[in] bindFlags The bind flags to create the texture with. + * @param[in] importFlags Optional flags for the file import. * @return A new texture, or nullptr if the texture failed to load. */ static ref createMippedFromFiles( ref pDevice, fstd::span paths, bool loadAsSrgb, - ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource + ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, + Bitmap::ImportFlags importFlags = Bitmap::ImportFlags::None ); /** @@ -181,6 +183,7 @@ class FALCOR_API Texture : public Resource * @param[in] generateMipLevels Whether the mip-chain should be generated. * @param[in] loadAsSrgb Load the texture using sRGB format. Only valid for 3 or 4 component textures. * @param[in] bindFlags The bind flags to create the texture with. + * @param[in] importFlags Optional flags for the file import. * @return A new texture, or nullptr if the texture failed to load. */ static ref createFromFile( @@ -188,7 +191,8 @@ class FALCOR_API Texture : public Resource const std::filesystem::path& path, bool generateMipLevels, bool loadAsSrgb, - ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource + ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, + Bitmap::ImportFlags importFlags = Bitmap::ImportFlags::None ); gfx::ITextureResource* getGfxTextureResource() const { return mGfxTextureResource; } @@ -306,6 +310,11 @@ class FALCOR_API Texture : public Resource */ const std::filesystem::path& getSourcePath() const { return mSourcePath; } + /** + * In case the texture was loaded from a file, get the import flags used. + */ + Bitmap::ImportFlags getImportFlags() const { return mImportFlags; } + /** * Returns the total number of texels across all mip levels and array slices. */ @@ -329,6 +338,7 @@ class FALCOR_API Texture : public Resource bool mReleaseRtvsAfterGenMips = true; std::filesystem::path mSourcePath; + Bitmap::ImportFlags mImportFlags = Bitmap::ImportFlags::None; ///< Flags used for import if loaded from file. ResourceFormat mFormat = ResourceFormat::Unknown; uint32_t mWidth = 0; diff --git a/Source/Falcor/Core/SampleApp.cpp b/Source/Falcor/Core/SampleApp.cpp index 17dbb99df..145bbaca0 100644 --- a/Source/Falcor/Core/SampleApp.cpp +++ b/Source/Falcor/Core/SampleApp.cpp @@ -40,7 +40,7 @@ #include "Utils/Scripting/Scripting.h" #include "Utils/Scripting/ScriptBindings.h" #include "Utils/UI/TextRenderer.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include "Utils/StringUtils.h" #include diff --git a/Source/Falcor/DiffRendering/DiffDebug.slang b/Source/Falcor/DiffRendering/DiffDebugParams.slang similarity index 100% rename from Source/Falcor/DiffRendering/DiffDebug.slang rename to Source/Falcor/DiffRendering/DiffDebugParams.slang diff --git a/Source/Falcor/DiffRendering/DiffSceneIO.slang b/Source/Falcor/DiffRendering/DiffSceneIO.slang index 959ecfa6e..877726cfd 100644 --- a/Source/Falcor/DiffRendering/DiffSceneIO.slang +++ b/Source/Falcor/DiffRendering/DiffSceneIO.slang @@ -32,7 +32,10 @@ import Scene.Scene; import Scene.SceneTypes; __exported import DiffRendering.SceneGradientInfo; -import DiffRendering.DiffDebug; +import DiffRendering.DiffDebugParams; +import DiffRendering.InverseOptimizationParams; +import DiffRendering.SceneGradients; +import DiffRendering.GradientIOWrapper; RWTexture2D gOutputDColor; @@ -44,6 +47,15 @@ RWTexture2D gOutputDColor; // Scene IO wrapper for propagating gradients. struct DiffSceneIO { + // Returns the local vertex indices for a given triangle. + uint3 getVtxIndices(const GeometryInstanceID instanceID, const uint triangleID) + { + const GeometryInstanceData instance = gScene.getGeometryInstance(instanceID); + uint3 vtxIndices = + gScene.getLocalIndices(instance.ibOffset, triangleID, instance.flags & uint(GeometryInstanceFlags::Use16BitIndices)); + return vtxIndices; + } + // Vertex position [ForwardDerivative(fwd_loadVertexPositionsW)] [BackwardDerivative(bwd_loadVertexPositionsW)] @@ -93,7 +105,21 @@ struct DiffSceneIO in float3.Differential dPos[3] ) { -#if DIFF_MODE == 3 // BackwardDiffDebug +#if DIFF_MODE == 1 // BackwardDiff + if (dpGradInfo.d.flag.gradMode == GradientMode.Scene && gInvOpt.meshID == instanceID.index) // Indicate which mesh to compute + // gradients for. + { + uint3 vtxIndices = getVtxIndices(instanceID, triangleID); + uint hashIndex = hashFunction(dpGradInfo.p.pixelID, gSceneGradients.getHashSize(GradientType::MeshPosition)); + [ForceUnroll] + for (uint i = 0; i < 3; i++) + { + [ForceUnroll] + for (uint j = 0; j < 3; j++) + gSceneGradients.atomicAddGrad(GradientType::MeshPosition, vtxIndices[i] * 3 + j, hashIndex, dPos[i][j]); + } + } +#elif DIFF_MODE == 3 // BackwardDiffDebug // For visualizing a gradient image. if (dpGradInfo.d.flag.gradMode == GradientMode.Scene && gDiffDebug.varType == DiffVariableType::GeometryTranslation && gDiffDebug.id.x == instanceID.index) @@ -105,10 +131,10 @@ struct DiffSceneIO #endif } - // TODO: Add custom derivatives for the following functions. - // Vertex normal - [Differentiable] + [ForwardDerivative(fwd_loadVertexNormalsW)] + [BackwardDerivative(bwd_loadVertexNormalsW)] + [PreferRecompute] void loadVertexNormalsW(SceneGradientInfo gradInfo, GeometryInstanceID instanceID, uint triangleID, out float3 n[3]) { uint3 indices = gScene.getIndices(instanceID, triangleID); @@ -122,6 +148,115 @@ struct DiffSceneIO } } + [Differentiable] + [PreferRecompute] + void fwd_loadVertexNormalsW( + DifferentialPair dpGradInfo, + GeometryInstanceID instanceID, + uint triangleID, + out DifferentialPair dpNorm + ) + { + float3 n[3]; + loadVertexNormalsW(dpGradInfo.p, instanceID, triangleID, n); + + float3.Differential dN[3]; + [ForceUnroll] + for (uint i = 0; i < 3; i++) + dN[i] = float3(0.f); + + dpNorm = diffPair(n, dN); + } + + [Differentiable] + [PreferRecompute] + void bwd_loadVertexNormalsW( + inout DifferentialPair dpGradInfo, + GeometryInstanceID instanceID, + uint triangleID, + in float3.Differential dNorm[3] + ) + { +#if DIFF_MODE == 1 // BackwardDiff + if (dpGradInfo.d.flag.gradMode == GradientMode.Scene && gInvOpt.meshID == instanceID.index) // Indicate which mesh to compute + // gradients for. + { + uint3 vtxIndices = getVtxIndices(instanceID, triangleID); + uint hashIndex = hashFunction(dpGradInfo.p.pixelID, gSceneGradients.getHashSize(GradientType::MeshNormal)); + [ForceUnroll] + for (uint i = 0; i < 3; i++) + { + [ForceUnroll] + for (uint j = 0; j < 3; j++) + gSceneGradients.atomicAddGrad(GradientType::MeshNormal, vtxIndices[i] * 3 + j, hashIndex, dNorm[i][j]); + } + } +#endif + } + + // Vertex tangent + [ForwardDerivative(fwd_loadVertexTangentsW)] + [BackwardDerivative(bwd_loadVertexTangentsW)] + [PreferRecompute] + void loadVertexTangentsW(SceneGradientInfo gradInfo, GeometryInstanceID instanceID, uint triangleID, out float3 t[3]) + { + uint3 indices = gScene.getIndices(instanceID, triangleID); + float3x3 mat = float3x3(no_diff gScene.getWorldMatrix(instanceID)); + + [ForceUnroll] + for (int i = 0; i < 3; i++) + { + var v = no_diff gScene.getVertex(indices[i]); + t[i] = normalize(mul(mat, v.tangent.xyz)); + } + } + + [Differentiable] + [PreferRecompute] + void fwd_loadVertexTangentsW( + DifferentialPair dpGradInfo, + GeometryInstanceID instanceID, + uint triangleID, + out DifferentialPair dpTang + ) + { + float3 t[3]; + loadVertexTangentsW(dpGradInfo.p, instanceID, triangleID, t); + + float3.Differential dT[3]; + [ForceUnroll] + for (uint i = 0; i < 3; i++) + dT[i] = float3(0.f); + + dpTang = diffPair(t, dT); + } + + [Differentiable] + [PreferRecompute] + void bwd_loadVertexTangentsW( + inout DifferentialPair dpGradInfo, + GeometryInstanceID instanceID, + uint triangleID, + in float3.Differential dTang[3] + ) + { +#if DIFF_MODE == 1 // BackwardDiff + if (dpGradInfo.d.flag.gradMode == GradientMode.Scene && gInvOpt.meshID == instanceID.index) // Indicate which mesh to compute + // gradients for. + { + uint3 vtxIndices = getVtxIndices(instanceID, triangleID); + uint hashIndex = hashFunction(dpGradInfo.p.pixelID, gSceneGradients.getHashSize(GradientType::MeshTangent)); + [ForceUnroll] + for (uint i = 0; i < 3; i++) + { + [ForceUnroll] + for (uint j = 0; j < 3; j++) + gSceneGradients.atomicAddGrad(GradientType::MeshTangent, vtxIndices[i] * 3 + j, hashIndex, dTang[i][j]); + } + } +#endif + } + // Camera position [Differentiable] float3 loadCameraPositionW(SceneGradientInfo gradInfo) { return no_diff gScene.camera.getPosition(); } diff --git a/Source/Falcor/DiffRendering/DiffSceneQuery.slang b/Source/Falcor/DiffRendering/DiffSceneQuery.slang index fd90a6936..2a03b524d 100644 --- a/Source/Falcor/DiffRendering/DiffSceneQuery.slang +++ b/Source/Falcor/DiffRendering/DiffSceneQuery.slang @@ -57,8 +57,9 @@ struct IntersectionAD : IDifferentiable GeometryInstanceID instanceID; // Instance ID of the mesh instance that contains the hit triangle. uint triangleID; // Triangle ID of the hit triangle. float3 barycentrics; // Barycentrics of the hit point - float3 normalW; // World normal of the hit point float3 posW; // World position of the hit point + float3 normalW; // World normal of the hit point + float3 tangentW; // World tangent of the hit point float hitT; // Distance from ray origin to hit point [Differentiable] @@ -68,8 +69,9 @@ struct IntersectionAD : IDifferentiable this.instanceID = GeometryInstanceID(0, 0); this.triangleID = 0; this.barycentrics = float3(0.f); - this.normalW = float3(0.f); this.posW = float3(0.f); + this.normalW = float3(0.f); + this.tangentW = float3(0.f); this.hitT = 0.f; } }; @@ -158,23 +160,21 @@ struct SceneQueryAD : IDifferentiable isect.hitT = length(isect.posW - ray.origin); } - // Note: We want to pass to vertex positions through shading normals. It hasn't been implemented yet. - // TODO: Handle shading (vertex) normals correctly. - isect.normalW = computeShadingNormal(isect.instanceID, isect.triangleID, isect.barycentrics); + computeShadingFrame(isect.instanceID, isect.triangleID, isect.barycentrics, isect.normalW, isect.tangentW); return isect; } [Differentiable] [PreferRecompute] - float3 computeShadingNormal(GeometryInstanceID instanceID, uint primitiveIndex, float3 barycentrics) + void computeShadingFrame(GeometryInstanceID instanceID, uint primitiveIndex, float3 barycentrics, out float3 normal, out float3 tangent) { - float3 n[3]; + float3 n[3], t[3]; diffSceneIO.loadVertexNormalsW(gradInfo, instanceID, primitiveIndex, n); + diffSceneIO.loadVertexTangentsW(gradInfo, instanceID, primitiveIndex, t); - float3 normal = n[0] * barycentrics.x + n[1] * barycentrics.y + n[2] * barycentrics.z; - - return normal; + normal = n[0] * barycentrics.x + n[1] * barycentrics.y + n[2] * barycentrics.z; + tangent = t[0] * barycentrics.x + t[1] * barycentrics.y + t[2] * barycentrics.z; } // Compute vertex position in world space using fixed barycentrics. diff --git a/Source/Falcor/DiffRendering/GradientIOWrapper.slang b/Source/Falcor/DiffRendering/GradientIOWrapper.slang index 061ebefd4..90b2fc400 100644 --- a/Source/Falcor/DiffRendering/GradientIOWrapper.slang +++ b/Source/Falcor/DiffRendering/GradientIOWrapper.slang @@ -26,7 +26,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ import DiffRendering.SceneGradients; -import DiffRendering.DiffDebug; +import DiffRendering.DiffDebugParams; import Scene.Material.MaterialParamLayout; import Utils.Math.HashUtils; @@ -58,26 +58,26 @@ struct GradientIOWrapper this.hashIndex = _hashIndex; } - [ForwardDerivative(__fwd_d_getFloat)] - [BackwardDerivative(__bwd_d_getFloat)] + [ForwardDerivative(fwd_d_getFloat)] + [BackwardDerivative(bwd_d_getFloat)] float getFloat(float val, uint offset) { return val; } [TreatAsDifferentiable] - DifferentialPair __fwd_d_getFloat(DifferentialPair dpVal, uint offset) { return diffPair(dpVal.p, 0.f); } + DifferentialPair fwd_d_getFloat(DifferentialPair dpVal, uint offset) { return diffPair(dpVal.p, 0.f); } [TreatAsDifferentiable] - void __bwd_d_getFloat(inout DifferentialPair dpVal, uint offset, float dOut) + void bwd_d_getFloat(inout DifferentialPair dpVal, uint offset, float dOut) { offset += baseOffset; gSceneGradients.atomicAddGrad(gradType, offset, hashIndex, dOut); } - [ForwardDerivative(__fwd_d_getFloats)] - [BackwardDerivative(__bwd_d_getFloats)] + [ForwardDerivative(fwd_d_getFloats)] + [BackwardDerivative(bwd_d_getFloats)] __generic vector getFloat(vector val, uint offset) { return val; } [TreatAsDifferentiable] - __generic DifferentialPair> __fwd_d_getFloats(DifferentialPair> dpVal, uint offset) + __generic DifferentialPair> fwd_d_getFloats(DifferentialPair> dpVal, uint offset) { vector d; [ForceUnroll] @@ -97,7 +97,7 @@ struct GradientIOWrapper } [TreatAsDifferentiable] - __generic void __bwd_d_getFloats(inout DifferentialPair> dpVal, uint offset, vector dOut) + __generic void bwd_d_getFloats(inout DifferentialPair> dpVal, uint offset, vector dOut) { offset += baseOffset; [ForceUnroll] diff --git a/Source/Falcor/DiffRendering/InverseOptimizationParams.slang b/Source/Falcor/DiffRendering/InverseOptimizationParams.slang new file mode 100644 index 000000000..584e2bea5 --- /dev/null +++ b/Source/Falcor/DiffRendering/InverseOptimizationParams.slang @@ -0,0 +1,30 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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. + **************************************************************************/ +__exported import DiffRendering.SharedTypes; + +ParameterBlock gInvOpt; diff --git a/Source/Falcor/DiffRendering/SceneGradientInfo.slang b/Source/Falcor/DiffRendering/SceneGradientInfo.slang index f9857da9f..bcbd80826 100644 --- a/Source/Falcor/DiffRendering/SceneGradientInfo.slang +++ b/Source/Falcor/DiffRendering/SceneGradientInfo.slang @@ -92,11 +92,13 @@ struct SceneGradientInfo : IDifferentiable // Extra data. uint2 pixel; + uint pixelID; [Differentiable] - __init(SceneGradientFlag _flag, uint2 _pixel) + __init(SceneGradientFlag _flag, uint2 _pixel, uint _pixelID) { flag = _flag; pixel = _pixel; + pixelID = _pixelID; } }; diff --git a/Source/Falcor/DiffRendering/SceneGradients.cpp b/Source/Falcor/DiffRendering/SceneGradients.cpp index 3da792dcd..b844d6e60 100644 --- a/Source/Falcor/DiffRendering/SceneGradients.cpp +++ b/Source/Falcor/DiffRendering/SceneGradients.cpp @@ -39,9 +39,18 @@ const char kTmpGradsBufferName[] = "tmpGrads"; const char kAggregateShaderFilename[] = "DiffRendering/AggregateGradients.cs.slang"; } // namespace -SceneGradients::SceneGradients(ref pDevice, uint2 gradDim, uint2 hashSize, GradientAggregateMode mode) - : mpDevice(pDevice), mGradDim(gradDim), mHashSize(hashSize), mAggregateMode(mode) +SceneGradients::SceneGradients(ref pDevice, const std::vector& gradConfigs, GradientAggregateMode mode) + : mpDevice(pDevice), mAggregateMode(mode) { + // Initialization. + mGradInfos.fill({false, 0, 0}); + + for (size_t i = 0; i < gradConfigs.size(); i++) + { + auto type = size_t(gradConfigs[i].type); + mGradInfos[type] = {true, gradConfigs[i].dim, gradConfigs[i].hashSize}; + } + createParameterBlock(); // Create a pass for aggregating gradients. @@ -66,12 +75,19 @@ void SceneGradients::createParameterBlock() auto bindFlags = ResourceBindFlags::ShaderResource | ResourceBindFlags::UnorderedAccess; for (size_t i = 0; i < size_t(GradientType::Count); i++) { - uint32_t elemCount = std::max(mGradDim[i], 1u); - mpGrads[i] = - mpDevice->createBuffer(elemCount * sizeof(float), bindFlags | ResourceBindFlags::Shared, MemoryType::DeviceLocal, nullptr); - - uint32_t hashSize = std::max(mHashSize[i], 1u); - mpTmpGrads[i] = mpDevice->createBuffer(elemCount * hashSize * sizeof(float), bindFlags, MemoryType::DeviceLocal, nullptr); + if (mGradInfos[i].active) + { + mpGrads[i] = mpDevice->createBuffer( + mGradInfos[i].dim * sizeof(float), bindFlags | ResourceBindFlags::Shared, MemoryType::DeviceLocal, nullptr + ); + mpTmpGrads[i] = mpDevice->createBuffer( + mGradInfos[i].dim * mGradInfos[i].hashSize * sizeof(float), bindFlags, MemoryType::DeviceLocal, nullptr + ); + } + else + { + mpGrads[i] = mpTmpGrads[i] = nullptr; + } } // Bind resources to parameter block. @@ -79,8 +95,8 @@ void SceneGradients::createParameterBlock() auto var = mpSceneGradientsBlock->getRootVar(); for (size_t i = 0; i < size_t(GradientType::Count); i++) { - var["gradDim"][i] = mGradDim[i]; - var["hashSize"][i] = mHashSize[i]; + var["gradDim"][i] = mGradInfos[i].dim; + var["hashSize"][i] = mGradInfos[i].hashSize; var[kTmpGradsBufferName][i] = mpTmpGrads[i]; } } @@ -88,6 +104,8 @@ void SceneGradients::createParameterBlock() void SceneGradients::clearGrads(RenderContext* pRenderContext, GradientType _gradType) { uint32_t gradType = uint32_t(_gradType); + if (!mGradInfos[gradType].active) + return; pRenderContext->clearUAV(mpTmpGrads[gradType]->getUAV().get(), uint4(0)); pRenderContext->clearUAV(mpGrads[gradType]->getUAV().get(), uint4(0)); } @@ -95,18 +113,52 @@ void SceneGradients::clearGrads(RenderContext* pRenderContext, GradientType _gra void SceneGradients::aggregateGrads(RenderContext* pRenderContext, GradientType _gradType) { uint32_t gradType = uint32_t(_gradType); + if (!mGradInfos[gradType].active) + return; - uint32_t hashSize = (mAggregateMode == GradientAggregateMode::Direct ? 1 : mHashSize[gradType]); + uint32_t hashSize = (mAggregateMode == GradientAggregateMode::Direct ? 1 : mGradInfos[gradType].hashSize); // Bind resources. auto var = mpAggregatePass->getRootVar()["gAggregator"]; - var["gradDim"] = mGradDim[gradType]; + var["gradDim"] = mGradInfos[gradType].dim; var["hashSize"] = hashSize; var[kTmpGradsBufferName] = mpTmpGrads[gradType]; var[kGradsBufferName] = mpGrads[gradType]; // Dispatch. - mpAggregatePass->execute(pRenderContext, uint3(mGradDim[gradType], hashSize, 1)); + mpAggregatePass->execute(pRenderContext, uint3(mGradInfos[gradType].dim, hashSize, 1)); +} + +void SceneGradients::clearAllGrads(RenderContext* pRenderContext) +{ + for (size_t i = 0; i < size_t(GradientType::Count); i++) + clearGrads(pRenderContext, GradientType(i)); +} + +void SceneGradients::aggregateAllGrads(RenderContext* pRenderContext) +{ + for (size_t i = 0; i < size_t(GradientType::Count); i++) + aggregateGrads(pRenderContext, GradientType(i)); +} + +std::vector SceneGradients::getActiveGradTypes() const +{ + std::vector activeGradTypes; + for (size_t i = 0; i < size_t(GradientType::Count); i++) + if (mGradInfos[i].active) + activeGradTypes.push_back(GradientType(i)); + return activeGradTypes; +} + +inline static ref createPython(ref device, const pybind11::list& gradConfigList) +{ + std::vector gradConfigs; + for (const auto& gradConfig : gradConfigList) + { + auto config = gradConfig.cast(); + gradConfigs.push_back(config); + } + return SceneGradients::create(device, gradConfigs); } inline void aggregate(SceneGradients& self, RenderContext* pRenderContext, GradientType gradType) @@ -117,16 +169,39 @@ inline void aggregate(SceneGradients& self, RenderContext* pRenderContext, Gradi #endif } +inline void aggregateAll(SceneGradients& self, RenderContext* pRenderContext) +{ + self.aggregateAllGrads(pRenderContext); +#if FALCOR_HAS_CUDA + pRenderContext->waitForFalcor(); +#endif +} + +inline pybind11::list getGradTypes(SceneGradients& self) +{ + pybind11::list gradTypes; + for (const auto& gradType : self.getActiveGradTypes()) + gradTypes.append(gradType); + return gradTypes; +} + FALCOR_SCRIPT_BINDING(SceneGradients) { using namespace pybind11::literals; pybind11::falcor_enum(m, "GradientType"); + pybind11::class_ gc(m, "GradConfig"); + gc.def(pybind11::init<>()); + gc.def(pybind11::init(), "grad_type"_a, "dim"_a, "hash_size"_a); + pybind11::class_> sg(m, "SceneGradients"); - sg.def_static("create", &SceneGradients::create, "device"_a, "grad_dim"_a, "hash_size"_a); - sg.def("clear", &SceneGradients::clearGrads, "render_context"_a, "grad_type"_a); + sg.def_static("create", createPython, "device"_a, "grad_config_list"_a); + sg.def("clear_grads", &SceneGradients::clearGrads, "render_context"_a, "grad_type"_a); + sg.def("aggregate_grads", aggregate, "render_context"_a, "grad_type"_a); + sg.def("clear_all_grads", &SceneGradients::clearAllGrads, "render_context"_a); + sg.def("aggregate_all_grads", aggregateAll, "render_context"_a); + sg.def("get_grad_types", getGradTypes); sg.def("get_grads_buffer", &SceneGradients::getGradsBuffer, "grad_type"_a); - sg.def("aggregate", aggregate, "render_context"_a, "grad_type"_a); } } // namespace Falcor diff --git a/Source/Falcor/DiffRendering/SceneGradients.h b/Source/Falcor/DiffRendering/SceneGradients.h index ad8198ab8..01d3b0e9e 100644 --- a/Source/Falcor/DiffRendering/SceneGradients.h +++ b/Source/Falcor/DiffRendering/SceneGradients.h @@ -37,11 +37,25 @@ class FALCOR_API SceneGradients : public Object FALCOR_OBJECT(SceneGradients); public: - SceneGradients(ref pDevice, uint2 gradDim, uint2 hashSize, GradientAggregateMode mode = GradientAggregateMode::HashGrid); + struct GradConfig + { + GradientType type; + uint32_t dim; + uint32_t hashSize; + + GradConfig() {} + GradConfig(GradientType _type, uint32_t _dim, uint32_t _hashSize) : type(_type), dim(_dim), hashSize(_hashSize) {} + }; - static ref create(ref pDevice, uint2 gradDim, uint2 hashSize) + SceneGradients( + ref pDevice, + const std::vector& gradConfigs, + GradientAggregateMode mode = GradientAggregateMode::HashGrid + ); + + static ref create(ref pDevice, const std::vector& gradConfigs) { - return make_ref(pDevice, gradDim, hashSize, GradientAggregateMode::HashGrid); + return make_ref(pDevice, gradConfigs, GradientAggregateMode::HashGrid); } ~SceneGradients() = default; @@ -51,18 +65,29 @@ class FALCOR_API SceneGradients : public Object void clearGrads(RenderContext* pRenderContext, GradientType gradType); void aggregateGrads(RenderContext* pRenderContext, GradientType gradType); - uint32_t getGradDim(GradientType gradType) const { return mGradDim[size_t(gradType)]; } - uint32_t getHashSize(GradientType gradType) const { return mHashSize[size_t(gradType)]; } + void clearAllGrads(RenderContext* pRenderContext); + void aggregateAllGrads(RenderContext* pRenderContext); + + uint32_t getGradDim(GradientType gradType) const { return mGradInfos[size_t(gradType)].dim; } + uint32_t getHashSize(GradientType gradType) const { return mGradInfos[size_t(gradType)].hashSize; } const ref& getTmpGradsBuffer(GradientType gradType) const { return mpTmpGrads[size_t(gradType)]; } const ref& getGradsBuffer(GradientType gradType) const { return mpGrads[size_t(gradType)]; } + std::vector getActiveGradTypes() const; + private: + struct GradInfo + { + bool active; + uint32_t dim; + uint32_t hashSize; + }; + void createParameterBlock(); ref mpDevice; - uint2 mGradDim; - uint2 mHashSize; + std::array mGradInfos; GradientAggregateMode mAggregateMode; ref mpSceneGradientsBlock; diff --git a/Source/Falcor/DiffRendering/SharedTypes.slang b/Source/Falcor/DiffRendering/SharedTypes.slang index 54a3538c2..054ef80d0 100644 --- a/Source/Falcor/DiffRendering/SharedTypes.slang +++ b/Source/Falcor/DiffRendering/SharedTypes.slang @@ -53,7 +53,9 @@ FALCOR_ENUM_REGISTER(DiffMode); enum class GradientType : uint32_t { Material, - Geometry, + MeshPosition, + MeshNormal, + MeshTangent, Count, }; @@ -61,7 +63,9 @@ FALCOR_ENUM_INFO( GradientType, { { GradientType::Material, "Material" }, - { GradientType::Geometry, "Geometry" }, + { GradientType::MeshPosition, "MeshPosition" }, + { GradientType::MeshNormal, "MeshNormal" }, + { GradientType::MeshTangent, "MeshTangent" }, } ); FALCOR_ENUM_REGISTER(GradientType); @@ -100,4 +104,12 @@ struct DiffDebugParams float4 grad; }; +// For inverse rendering optimization. + +struct InverseOptimizationParams +{ + uint32_t meshID; + uint3 _pad0; +}; + END_NAMESPACE_FALCOR diff --git a/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang b/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang index 7f863b5d9..76ba6bb4f 100644 --- a/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang +++ b/Source/Falcor/Rendering/Materials/BSDFs/SheenBSDF.slang @@ -78,7 +78,7 @@ struct SheenBSDF : IBSDF float G(float NdotO, float NdotI) { // this is the full visibility function; note that we have the questionable terminator softening from equ. 4 - return 1.f / ((1.f + pow(lambda(NdotO), 1.f + 2.f * pow(1.f - NdotO, 8)) + lambda(NdotI)) * (4.f * NdotO * NdotI)); + 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) @@ -90,8 +90,8 @@ struct SheenBSDF : IBSDF float NdotH = wh.z; const float3 D = SheenD(NdotH); - - float3 Fr = D * G(wo.z, wi.z) * color * wo.z * M_PI; + // We multiply by wo.z + float3 Fr = D * G(wo.z, wi.z) * color / (4.f * wi.z); return Fr; } @@ -104,11 +104,11 @@ struct SheenBSDF : IBSDF pdf = {}; lobeType = {}; - float3 albedoT = evalAlbedo(wi, LobeType::All).transmission; - // Without a baselayer, this BSDF should be transparent. Until we can overwrite local importance sampling with the lobetypes, we // will pretend for sample() that there is no transparency. This is a performance optimisation for the LayeredMaterialInstance. - /* const float selectionWeight = luminance(albedoT); + /* + float3 albedoT = evalAlbedo(wi, LobeType::All).transmission; + const float selectionWeight = luminance(albedoT); if (sampleNext1D(sg) < selectionWeight) { wo = float3(-wi.x, -wi.y, -wi.z); @@ -145,17 +145,17 @@ struct SheenBSDF : IBSDF AlbedoContributions evalAlbedo(const float3 wi, const LobeType lobetype) { const float coefficients[11][5] = { - { 1.00519, 164.794, -8.91257, 0.000614987, -1.00615 }, - { 1.07632, 121.269, -2.6416, 0.000548297, -1.04411 }, - { 1.20174, 9.90325, -1.32211, 0.0238483, -1.11425 }, - { 4.55388, 0.0233967, 1.12007, 0.431478, -5.41286 }, - { 3.67585, 0.104744, -3.88354, 0.155255, -4.3272 }, - { 2.84549, 0.182875, -5.17779, 0.132618, -3.40558 }, - { 5.1483, 0.110417, -6.03901, 0.114556, -5.63637 }, - { 4.24518, 0.152001, -6.80592, 0.101996, -4.68505 }, - { 4.43179, 0.154597, -5.59283, 0.0928506, -4.83838 }, - { 4.52583, 0.157636, -6.06809, 0.0861137, -4.90953 }, - { 4.53904, 0.160742, -9.04844, 0.0820168, -4.91045 } + { 1.01108, 75.2286, -9.4671, 0.000973445, -1.01325 }, + { 8.44447, 0.0135054, 1.11637, 0.479139, -9.30873 }, + { 5.67124, 0.0194668, 1.12489, 0.453551, -6.53104 }, + { 4.50086, 0.0237758, 1.09376, 0.423665, -5.36521 }, + { 3.77781, 0.0273286, 1.0666, 0.400267, -4.65601 }, + { 3.35338, 0.0307392, 0.989189, 0.371074, -4.24089 }, + { 3.39605, 0.0308828, 0.845455, 0.333813, -4.28662 }, + { 3.62583, 0.0303531, 0.79754, 0.325588, -4.51725 }, + { 4.11524, 0.0172797, 0.701166, 0.275242, -5.04394 }, + { 4.4012, 0.0159469, 0.685892, 0.270221, -5.32997 }, + { 5.8912, 0.0171461, 0.66366, 0.288735, -6.78867 } }; // fit was [1, 21] for cosI(0) = 21 @@ -171,9 +171,9 @@ struct SheenBSDF : IBSDF coefficients[indexB][0] / (1.f + coefficients[indexB][1] * pow(x, coefficients[indexB][2])); const float albedoTT = coefficients[indexT][4] + pow(x, coefficients[indexT][3]) + coefficients[indexT][0] / (1.f + coefficients[indexT][1] * pow(x, coefficients[indexT][2])); - const float albedoT = 1.f - clamp(albedoTB * indexInt + albedoTT * (1.f - indexInt), 0.f, 1.f); + const float albedoR = 1.f - clamp(albedoTB * indexInt + albedoTT * (1.f - indexInt), 0.f, 1.f); - return AlbedoContributions(color * albedoT, (albedoT - 1.f) * (color - 1.f), albedoT, 0.f); + return AlbedoContributions(color * albedoR, albedoR - (albedoR * color), (1.f - albedoR), 0.f); } RoughnessInformation getRoughnessInformation(const float3 wi) diff --git a/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp b/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp index 03f4790c5..c0ca512e8 100644 --- a/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp +++ b/Source/Falcor/Scene/Animation/AnimatedVertexCache.cpp @@ -78,8 +78,6 @@ namespace Falcor keyframeIndices.x = (uint32_t)timeSamples.size() - 1; keyframeIndices.y = 0; - // The first keyframe has timeCode >= 1 (see processCurve() in ImporterContext.cpp). - FALCOR_ASSERT(timeSamples.front() >= 1.0); t = (float)(time / timeSamples.front()); } } diff --git a/Source/Falcor/Scene/Animation/Animation.cpp b/Source/Falcor/Scene/Animation/Animation.cpp index 41b91f4f4..ef4369349 100644 --- a/Source/Falcor/Scene/Animation/Animation.cpp +++ b/Source/Falcor/Scene/Animation/Animation.cpp @@ -105,7 +105,7 @@ namespace Falcor } } - Animation::Animation(const std::string& name, NodeID nodeID, double duration) + Animation::Animation(std::string_view name, NodeID nodeID, double duration) : mName(name) , mNodeID(nodeID) , mDuration(duration) diff --git a/Source/Falcor/Scene/Animation/Animation.h b/Source/Falcor/Scene/Animation/Animation.h index 44ec4bc4a..890767f26 100644 --- a/Source/Falcor/Scene/Animation/Animation.h +++ b/Source/Falcor/Scene/Animation/Animation.h @@ -67,14 +67,14 @@ namespace Falcor quatf rotation = quatf::identity(); }; - static ref create(const std::string& name, NodeID nodeID, double duration) { return make_ref(name, nodeID, duration); } + static ref create(std::string_view name, NodeID nodeID, double duration) { return make_ref(name, nodeID, duration); } /** Create a new animation. \param[in] name Animation name. \param[in] nodeID ID of the animated node. \param[in] Animation duration in seconds. */ - Animation(const std::string& name, NodeID nodeID, double duration); + Animation(std::string_view name, NodeID nodeID, double duration); /** Get the animation name. */ diff --git a/Source/Falcor/Scene/Camera/Camera.cpp b/Source/Falcor/Scene/Camera/Camera.cpp index 9f1286400..d997fcec2 100644 --- a/Source/Falcor/Scene/Camera/Camera.cpp +++ b/Source/Falcor/Scene/Camera/Camera.cpp @@ -399,6 +399,17 @@ namespace Falcor d["focalDistance"] = getFocalDistance(); d["apertureRadius"] = getApertureRadius(); std::cout << pybind11::str(d) << std::endl; + + fmt::print("camera = Camera('{}')\n", getName()); + fmt::print("camera.position = [{:.9g}, {:.9g}, {:.9g}]\n", getPosition()[0], getPosition()[1], getPosition()[2]); + fmt::print("camera.target = [{:.9g}, {:.9g}, {:.9g}]\n", getTarget()[0], getTarget()[1], getTarget()[2]); + fmt::print("camera.up = [{:.9g}, {:.9g}, {:.9g}]\n", getUpVector()[0], getUpVector()[1], getUpVector()[2]); + fmt::print("camera.focalLength = {:.9g}\n", getFocalLength()); + fmt::print("camera.focalDistance = {:.9g}\n", getFocalDistance()); + fmt::print("camera.apertureRadius = {:.9g}\n", getApertureRadius()); + fmt::print("camera.nearPlane = {:.9g}\n", getNearPlane()); + fmt::print("camera.farPlane = {:.9g}\n", getFarPlane()); + std::cout.flush(); } FALCOR_SCRIPT_BINDING(Camera) diff --git a/Source/Falcor/Scene/Material/MERLMaterial.cpp b/Source/Falcor/Scene/Material/MERLMaterial.cpp index 287dc1298..0721096c0 100644 --- a/Source/Falcor/Scene/Material/MERLMaterial.cpp +++ b/Source/Falcor/Scene/Material/MERLMaterial.cpp @@ -46,6 +46,8 @@ namespace Falcor MERLMaterial::MERLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path) : Material(pDevice, name, MaterialType::MERL) { + FALCOR_CHECK(!path.empty(), "Missing path."); + MERLFile merlFile(path); init(merlFile); diff --git a/Source/Falcor/Scene/Material/MaterialSystem.cpp b/Source/Falcor/Scene/Material/MaterialSystem.cpp index 00645005a..d6e8a58c2 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.cpp +++ b/Source/Falcor/Scene/Material/MaterialSystem.cpp @@ -236,17 +236,32 @@ namespace Falcor return materialID; } - void MaterialSystem::replaceMaterial(const MaterialID materialID, const ref& pReplacement) + void MaterialSystem::removeMaterial(const MaterialID materialID) { FALCOR_CHECK(materialID.isValid() && materialID.get() < mMaterials.size(), "Material ID is invalid."); - FALCOR_CHECK(pReplacement != nullptr, "Replacement material is missing."); // Mark descriptors used by the deleted material as reserved. // This is a workaround until we have dynamically sized descriptor arrays and proper resource tracking. - const auto& prevMtl = mMaterials[materialID.get()]; - mReservedTextureDescCount += prevMtl->getMaxTextureCount(); - mReservedBufferDescCount += prevMtl->getMaxBufferCount(); - mReservedTexture3DDescCount += prevMtl->getMaxTexture3DCount(); + const auto& material = mMaterials[materialID.get()]; + mReservedTextureDescCount += material->getMaxTextureCount(); + mReservedBufferDescCount += material->getMaxBufferCount(); + mReservedTexture3DDescCount += material->getMaxTexture3DCount(); + + // Remove textures that were used by the material and loaded via the texture manager. + mpTextureManager->removeTextures(material.get()); + + // Remove the material. + mMaterials[materialID.get()] = nullptr; + mMaterialsChanged = true; + } + + void MaterialSystem::replaceMaterial(const MaterialID materialID, const ref& pReplacement) + { + FALCOR_CHECK(materialID.isValid() && materialID.get() < mMaterials.size(), "Material ID is invalid."); + FALCOR_CHECK(pReplacement != nullptr, "Replacement material is missing."); + + // Remove the previous material. + removeMaterial(materialID); // Prepare replacement material. if (pReplacement->getDefaultTextureSampler() == nullptr) @@ -303,7 +318,7 @@ namespace Falcor const ref& MaterialSystem::getMaterial(const MaterialID materialID) const { - FALCOR_CHECK(materialID.get() < mMaterials.size(), "MaterialID is out of range."); + FALCOR_CHECK(materialID.get() < mMaterials.size(), "Material ID is out of range."); return mMaterials[materialID.get()]; } @@ -649,7 +664,7 @@ namespace Falcor { if (auto it = defines.find(name); it != defines.end()) { - FALCOR_CHECK(it->second == value, "Mismatching values '{}' and '{}' for material define '{}'.", name, it->second, value); + FALCOR_CHECK(it->second == value, "Mismatching values '{}' and '{}' for material define '{}'.", it->second, value, name); } else { diff --git a/Source/Falcor/Scene/Material/MaterialSystem.h b/Source/Falcor/Scene/Material/MaterialSystem.h index beff7f2ec..af2955382 100644 --- a/Source/Falcor/Scene/Material/MaterialSystem.h +++ b/Source/Falcor/Scene/Material/MaterialSystem.h @@ -182,6 +182,11 @@ namespace Falcor */ MaterialID addMaterial(const ref& pMaterial); + /** Remove a material. + \param[in] materialID The ID of the material to remove. + */ + void removeMaterial(const MaterialID materialID); + /** Replace a material. \param materialID The ID of the material to replace. \param pReplacement The material to replace it with. diff --git a/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp b/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp index b7ab9f975..aeda33fd2 100644 --- a/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp +++ b/Source/Falcor/Scene/Material/MaterialTextureLoader.cpp @@ -53,7 +53,17 @@ namespace Falcor bool srgb = mUseSrgb && pMaterial->getTextureSlotInfo(slot).srgb; // Request texture to be loaded. - auto handle = mTextureManager.loadTexture(path, true, srgb); + auto handle = mTextureManager.loadTexture( + path, + true /*mips*/, + srgb, + ResourceBindFlags::ShaderResource, + true /*async*/, + Bitmap::ImportFlags::None, + nullptr /*search dirs*/, + nullptr /*load count*/, + pMaterial.get() + ); // Store assignment to material for later. mTextureAssignments.emplace_back(TextureAssignment{ pMaterial, slot, handle }); @@ -69,5 +79,6 @@ namespace Falcor auto pTexture = mTextureManager.getTexture(assignment.handle); assignment.pMaterial->setTexture(assignment.textureSlot, pTexture); } + mTextureAssignments.clear(); } } diff --git a/Source/Falcor/Scene/Material/MaterialTextureLoader.h b/Source/Falcor/Scene/Material/MaterialTextureLoader.h index 4b7a5194d..15582efc1 100644 --- a/Source/Falcor/Scene/Material/MaterialTextureLoader.h +++ b/Source/Falcor/Scene/Material/MaterialTextureLoader.h @@ -42,7 +42,7 @@ namespace Falcor `MaterialTextureLoader`, it blocks until all textures are loaded and assigns them to the materials. */ - class MaterialTextureLoader + class FALCOR_API MaterialTextureLoader { public: MaterialTextureLoader(TextureManager& textureManager, bool useSrgb); @@ -55,6 +55,10 @@ namespace Falcor */ void loadTexture(const ref& pMaterial, Material::TextureSlot slot, const std::filesystem::path& path); + void finishLoading() + { + assignTextures(); + } private: void assignTextures(); diff --git a/Source/Falcor/Scene/Material/RGLMaterial.cpp b/Source/Falcor/Scene/Material/RGLMaterial.cpp index b11de2e76..2acf934bc 100644 --- a/Source/Falcor/Scene/Material/RGLMaterial.cpp +++ b/Source/Falcor/Scene/Material/RGLMaterial.cpp @@ -53,6 +53,8 @@ namespace Falcor RGLMaterial::RGLMaterial(ref pDevice, const std::string& name, const std::filesystem::path& path) : Material(pDevice, name, MaterialType::RGL) { + FALCOR_CHECK(!path.empty(), "Missing path."); + if (!loadBRDF(path)) { FALCOR_THROW("RGLMaterial() - Failed to load BRDF from '{}'.", path); diff --git a/Source/Falcor/Scene/Scene.cpp b/Source/Falcor/Scene/Scene.cpp index 086aa2a39..f0e0c81bd 100644 --- a/Source/Falcor/Scene/Scene.cpp +++ b/Source/Falcor/Scene/Scene.cpp @@ -4352,6 +4352,7 @@ namespace Falcor pScene->setCameraBounds(AABB(minPoint, maxPoint)); }, "minPoint"_a, "maxPoint"_a); scene.def("getGeometryUVTiles", &Scene::getGeometryUVTiles, "geometryID"_a); + scene.def_property_readonly("memory_usage", &Scene::getMemoryUsageInBytes); // Materials scene.def_property_readonly(kMaterials.c_str(), &Scene::getMaterials); diff --git a/Source/Falcor/Scene/Scene.h b/Source/Falcor/Scene/Scene.h index 16a1af69e..f738010f0 100644 --- a/Source/Falcor/Scene/Scene.h +++ b/Source/Falcor/Scene/Scene.h @@ -52,7 +52,7 @@ #include "Utils/Math/Vector.h" #include "Utils/Math/Matrix.h" #include "Utils/UI/Gui.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include #include @@ -434,7 +434,7 @@ namespace Falcor uint64_t gridVoxelCount = 0; ///< Total number of voxels in all grids. uint64_t gridMemoryInBytes = 0; ///< Total memory in bytes used by the grids. - /** Get the total memory usage. + /** Get the total memory usage in bytes. */ uint64_t getTotalMemory() const { @@ -1119,6 +1119,8 @@ namespace Falcor std::string getScript(const std::string& sceneVar); + uint64_t getMemoryUsageInBytes() const { return getSceneStats().getTotalMemory(); } + private: friend class AnimationController; friend class AnimatedVertexCache; diff --git a/Source/Falcor/Scene/SceneBuilder.h b/Source/Falcor/Scene/SceneBuilder.h index 86d5853f2..0855e42c8 100644 --- a/Source/Falcor/Scene/SceneBuilder.h +++ b/Source/Falcor/Scene/SceneBuilder.h @@ -41,7 +41,7 @@ #include "Utils/Math/AABB.h" #include "Utils/Math/Vector.h" #include "Utils/Math/Matrix.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include diff --git a/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp b/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp index 48228ceba..e78d93e3e 100644 --- a/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp +++ b/Source/Falcor/Utils/Algorithm/ParallelReduction.cpp @@ -213,6 +213,14 @@ void ParallelReduction::execute( } } +uint64_t ParallelReduction::getMemoryUsageInBytes() const +{ + uint64_t m = 0; + m += mpBuffers[0] ? mpBuffers[0]->getSize() : 0; + m += mpBuffers[1] ? mpBuffers[1]->getSize() : 0; + return m; +} + // Explicit template instantiation of the supported types. // clang-format off template FALCOR_API void ParallelReduction::execute(RenderContext* pRenderContext, const ref& pInput, Type operation, float4* pResult, ref pResultBuffer, uint64_t resultOffset); diff --git a/Source/Falcor/Utils/Algorithm/ParallelReduction.h b/Source/Falcor/Utils/Algorithm/ParallelReduction.h index 1c8737f95..9b69a015f 100644 --- a/Source/Falcor/Utils/Algorithm/ParallelReduction.h +++ b/Source/Falcor/Utils/Algorithm/ParallelReduction.h @@ -91,6 +91,8 @@ class FALCOR_API ParallelReduction uint64_t resultOffset = 0 ); + uint64_t getMemoryUsageInBytes() const; + private: void allocate(uint32_t elementCount, uint32_t elementSize); diff --git a/Source/Falcor/Utils/Image/AsyncTextureLoader.cpp b/Source/Falcor/Utils/Image/AsyncTextureLoader.cpp index 0a6925783..3c2fb2b8d 100644 --- a/Source/Falcor/Utils/Image/AsyncTextureLoader.cpp +++ b/Source/Falcor/Utils/Image/AsyncTextureLoader.cpp @@ -52,11 +52,12 @@ std::future> AsyncTextureLoader::loadMippedFromFiles( fstd::span paths, bool loadAsSrgb, ResourceBindFlags bindFlags, + Bitmap::ImportFlags importFlags, LoadCallback callback ) { std::lock_guard lock(mMutex); - mLoadRequestQueue.push(LoadRequest{{paths.begin(), paths.end()}, false, loadAsSrgb, bindFlags, callback}); + mLoadRequestQueue.push(LoadRequest{{paths.begin(), paths.end()}, false, loadAsSrgb, bindFlags, importFlags, callback}); mCondition.notify_one(); return mLoadRequestQueue.back().promise.get_future(); } @@ -66,11 +67,12 @@ std::future> AsyncTextureLoader::loadFromFile( bool generateMipLevels, bool loadAsSrgb, ResourceBindFlags bindFlags, + Bitmap::ImportFlags importFlags, LoadCallback callback ) { std::lock_guard lock(mMutex); - mLoadRequestQueue.push(LoadRequest{{path}, generateMipLevels, loadAsSrgb, bindFlags, callback}); + mLoadRequestQueue.push(LoadRequest{{path}, generateMipLevels, loadAsSrgb, bindFlags, importFlags, callback}); mCondition.notify_one(); return mLoadRequestQueue.back().promise.get_future(); } @@ -134,12 +136,13 @@ void AsyncTextureLoader::runWorker() ref pTexture; if (request.paths.size() == 1) { - pTexture = - Texture::createFromFile(mpDevice, request.paths[0], request.generateMipLevels, request.loadAsSRGB, request.bindFlags); + pTexture = Texture::createFromFile( + mpDevice, request.paths[0], request.generateMipLevels, request.loadAsSRGB, request.bindFlags, request.importFlags + ); } else { - pTexture = Texture::createMippedFromFiles(mpDevice, request.paths, request.loadAsSRGB, request.bindFlags); + pTexture = Texture::createMippedFromFiles(mpDevice, request.paths, request.loadAsSRGB, request.bindFlags, request.importFlags); } request.promise.set_value(pTexture); diff --git a/Source/Falcor/Utils/Image/AsyncTextureLoader.h b/Source/Falcor/Utils/Image/AsyncTextureLoader.h index 27c24984b..56c214cd0 100644 --- a/Source/Falcor/Utils/Image/AsyncTextureLoader.h +++ b/Source/Falcor/Utils/Image/AsyncTextureLoader.h @@ -69,6 +69,7 @@ class FALCOR_API AsyncTextureLoader * @param[in] path List of full paths of all mips, starting from mip0. * @param[in] loadAsSRGB Load the texture as sRGB format if supported, otherwise linear color. * @param[in] bindFlags The bind flags for the texture resource. + * @param[in] importFlags Optional flags for the file import. * @param[in] callback Function called after the texture load has finished. * @return A future to a new texture, or nullptr if the texture failed to load. */ @@ -76,6 +77,7 @@ class FALCOR_API AsyncTextureLoader fstd::span paths, bool loadAsSRGB, ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, + Bitmap::ImportFlags importFlags = Bitmap::ImportFlags::None, LoadCallback callback = {} ); @@ -85,6 +87,7 @@ class FALCOR_API AsyncTextureLoader * @param[in] generateMipLevels Whether the full mip-chain should be generated. * @param[in] loadAsSRGB Load the texture as sRGB format if supported, otherwise linear color. * @param[in] bindFlags The bind flags for the texture resource. + * @param[in] importFlags Optional flags for the file import. * @param[in] callback Function called after the texture load has finished. * @return A future to a new texture, or nullptr if the texture failed to load. */ @@ -93,6 +96,7 @@ class FALCOR_API AsyncTextureLoader bool generateMipLevels, bool loadAsSRGB, ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, + Bitmap::ImportFlags importFlags = Bitmap::ImportFlags::None, LoadCallback callback = {} ); @@ -107,6 +111,7 @@ class FALCOR_API AsyncTextureLoader bool generateMipLevels; bool loadAsSRGB; ResourceBindFlags bindFlags; + Bitmap::ImportFlags importFlags; LoadCallback callback; std::promise> promise; }; diff --git a/Source/Falcor/Utils/Image/Bitmap.cpp b/Source/Falcor/Utils/Image/Bitmap.cpp index 3fbaba20a..7559fc56d 100644 --- a/Source/Falcor/Utils/Image/Bitmap.cpp +++ b/Source/Falcor/Utils/Image/Bitmap.cpp @@ -30,6 +30,7 @@ #include "Core/API/Texture.h" #include "Core/Platform/MemoryMappedFile.h" #include "Utils/Math/ScalarMath.h" +#include "Utils/Math/Float16.h" #include "Utils/Logger.h" #include "Utils/StringUtils.h" @@ -190,12 +191,53 @@ static FIBITMAP* convertToRGBAF(FIBITMAP* pDib) return pNew; } +/** + * Converts 96/128bpp to 64bpp RGBA floating-point image. + * Note that FreeImage doesn't support 16-bit float formats. + */ +static FIBITMAP* convertToRGBA16Float(FIBITMAP* pDib) +{ + const auto type = FreeImage_GetImageType(pDib); + const uint32_t bpp = FreeImage_GetBPP(pDib); + FALCOR_CHECK(type == FIT_RGBF || type == FIT_RGBAF, "Image type must be RGB/RGBA with 32-bit float per channel."); + FALCOR_CHECK(bpp == 96 || bpp == 128, "Image must be 96 or 128bpp."); + + const uint32_t width = FreeImage_GetWidth(pDib); + const uint32_t height = FreeImage_GetHeight(pDib); + + auto pNew = FreeImage_AllocateT(FIT_RGBA16, width, height); + FreeImage_CloneMetadata(pNew, pDib); + + const uint32_t src_pitch = FreeImage_GetPitch(pDib); + const uint32_t dst_pitch = FreeImage_GetPitch(pNew); + + const BYTE* src_bits = (BYTE*)FreeImage_GetBits(pDib); + BYTE* dst_bits = (BYTE*)FreeImage_GetBits(pNew); + + for (uint32_t y = 0; y < height; y++) + { + const FIRGBAF* src_pixel = (FIRGBAF*)src_bits; + FIRGBA16* dst_pixel = (tagFIRGBA16*)dst_bits; + + for (uint32_t x = 0; x < width; x++) + { + // Convert pixels to float16_t directly, while adding a "dummy" alpha of 1.0 if source format doesn't have alpha. + dst_pixel[x].red = float16_t(src_pixel[x].red).toBits(); + dst_pixel[x].green = float16_t(src_pixel[x].green).toBits(); + dst_pixel[x].blue = float16_t(src_pixel[x].blue).toBits(); + dst_pixel[x].alpha = float16_t(type == FIT_RGBAF ? src_pixel[x].alpha : 1.0f).toBits(); + } + src_bits += src_pitch; + dst_bits += dst_pitch; + } + return pNew; +} Bitmap::UniqueConstPtr Bitmap::create(uint32_t width, uint32_t height, ResourceFormat format, const uint8_t* pData) { return Bitmap::UniqueConstPtr(new Bitmap(width, height, format, pData)); } -Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, bool isTopDown) +Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, bool isTopDown, ImportFlags importFlags) { if (!std::filesystem::exists(path)) { @@ -268,6 +310,7 @@ Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, } } + // Identify resource format based on bit depth. ResourceFormat format = ResourceFormat::Unknown; uint32_t bpp = FreeImage_GetBPP(pDib); switch (bpp) @@ -306,6 +349,14 @@ Bitmap::UniqueConstPtr Bitmap::createFromFile(const std::filesystem::path& path, FreeImage_Unload(pDib); pDib = pNew; } + else if ((bpp == 96 || bpp == 128) && is_set(importFlags, ImportFlags::ConvertToFloat16)) + { + bpp = 64; + format = ResourceFormat::RGBA16Float; + auto pNew = convertToRGBA16Float(pDib); + FreeImage_Unload(pDib); + pDib = pNew; + } else if (bpp == 96 && (isRGB32fSupported() == false)) { bpp = 128; diff --git a/Source/Falcor/Utils/Image/Bitmap.h b/Source/Falcor/Utils/Image/Bitmap.h index 2c6a9c845..9ae2e1109 100644 --- a/Source/Falcor/Utils/Image/Bitmap.h +++ b/Source/Falcor/Utils/Image/Bitmap.h @@ -50,6 +50,12 @@ class FALCOR_API Bitmap Uncompressed = 1u << 2, //< Prefer faster load to a more compact file size }; + enum class ImportFlags : uint32_t + { + None = 0u, ///< Default. + ConvertToFloat16 = 1u << 0, ///< Convert HDR images to 16-bit float per channel on import. + }; + enum class FileFormat { PngFile, //< PNG file for lossless compressed 8-bits images with optional alpha @@ -57,7 +63,7 @@ class FALCOR_API Bitmap TgaFile, //< TGA file for lossless uncompressed 8-bits images with optional alpha BmpFile, //< BMP file for lossless uncompressed 8-bits images with optional alpha PfmFile, //< PFM file for floating point HDR images with 32-bit float per channel - ExrFile, //< EXR file for floating point HDR images with 16-bit float per channel + ExrFile, //< EXR file for floating point HDR images with 16/32-bit float per channel DdsFile, //< DDS file for storing GPU resource formats, including block compressed formats //< See ImageIO. TODO: Remove(?) Bitmap IO implementation when ImageIO supports other formats }; @@ -80,9 +86,10 @@ class FALCOR_API Bitmap * @param[in] path Path to load from (absolute or relative to working directory). * @param[in] isTopDown Control the memory layout of the image. If true, the top-left pixel is the first pixel in the buffer, otherwise * the bottom-left pixel is first. + * @param[in] importFlags Flags to control how the file is imported. See ImportFlags above. * @return If loading was successful, a new object. Otherwise, nullptr. */ - static UniqueConstPtr createFromFile(const std::filesystem::path& path, bool isTopDown); + static UniqueConstPtr createFromFile(const std::filesystem::path& path, bool isTopDown, ImportFlags importFlags = ImportFlags::None); /** * Store a memory buffer to a file. @@ -155,12 +162,13 @@ class FALCOR_API Bitmap Bitmap(uint32_t width, uint32_t height, ResourceFormat format, const uint8_t* pData); std::unique_ptr mpData; - uint32_t mWidth = 0; - uint32_t mHeight = 0; - uint32_t mRowPitch = 0; - uint32_t mSize = 0; + 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. ResourceFormat mFormat = ResourceFormat::Unknown; }; FALCOR_ENUM_CLASS_OPERATORS(Bitmap::ExportFlags); +FALCOR_ENUM_CLASS_OPERATORS(Bitmap::ImportFlags); } // namespace Falcor diff --git a/Source/Falcor/Utils/Image/TextureManager.cpp b/Source/Falcor/Utils/Image/TextureManager.cpp index a01e37ad6..fb00e10ab 100644 --- a/Source/Falcor/Utils/Image/TextureManager.cpp +++ b/Source/Falcor/Utils/Image/TextureManager.cpp @@ -59,7 +59,7 @@ TextureManager::CpuTextureHandle TextureManager::addTexture(const ref& FALCOR_THROW("Only single-sample 2D textures can be added"); } - std::unique_lock lock(mMutex); + std::lock_guard lock(mMutex); CpuTextureHandle handle; if (auto it = mTextureToHandle.find(pTexture.get()); it != mTextureToHandle.end()) @@ -83,7 +83,9 @@ TextureManager::CpuTextureHandle TextureManager::addTexture(const ref& { bool hasMips = pTexture->getMipCount() > 1; bool isSrgb = isSrgbFormat(pTexture->getFormat()); - TextureKey textureKey({pTexture->getSourcePath().string()}, hasMips, isSrgb, pTexture->getBindFlags()); + TextureKey textureKey( + {pTexture->getSourcePath().string()}, hasMips, isSrgb, pTexture->getBindFlags(), pTexture->getImportFlags() + ); if (mKeyToHandle.find(textureKey) == mKeyToHandle.end()) { @@ -109,6 +111,7 @@ TextureManager::CpuTextureHandle TextureManager::loadUdimTexture( bool loadAsSRGB, ResourceBindFlags bindFlags, bool async, + Bitmap::ImportFlags importFlags, const AssetResolver* assetResolver, size_t* loadedTextureCount ) @@ -122,7 +125,7 @@ TextureManager::CpuTextureHandle TextureManager::loadUdimTexture( auto pos = filename.find(""); if (pos == std::string::npos) - return loadTexture(path, generateMipLevels, loadAsSRGB, bindFlags, async); + return loadTexture(path, generateMipLevels, loadAsSRGB, bindFlags, async, importFlags, assetResolver, loadedTextureCount); std::filesystem::path dirpath = path.parent_path(); filename.replace(pos, 6, "[1-9][0-9][0-9][0-9]"); @@ -134,10 +137,12 @@ TextureManager::CpuTextureHandle TextureManager::loadUdimTexture( else texturePaths = globFilesInDirectory(dirpath, udimRegex, true /* firstMatchOnly */); - // nothing found, return an invalid handle + // Nothing found, return an invalid handle if (texturePaths.empty()) { logWarning("Can't find UDIM texture files '{}'.", path); + if (loadedTextureCount) + *loadedTextureCount = 0; return CpuTextureHandle(); } @@ -162,7 +167,8 @@ TextureManager::CpuTextureHandle TextureManager::loadUdimTexture( size_t udim = std::stol(udimStr); maxIndex = std::max(maxIndex, udim); udimIndices.push_back(udim); - handles.push_back(loadTexture(it, generateMipLevels, loadAsSRGB, bindFlags, async)); + // Do not pass on assetResolver as paths are already resolved, nor loadedTextureCount as we've already set it above. + handles.push_back(loadTexture(it, generateMipLevels, loadAsSRGB, bindFlags, async, importFlags)); FALCOR_CHECK(udim >= 1001, "Texture {} is not a valid UDIM texture, as it violates the valid UDIM range of 1001-9999", it); } @@ -185,12 +191,22 @@ TextureManager::CpuTextureHandle TextureManager::loadTexture( bool loadAsSRGB, ResourceBindFlags bindFlags, bool async, + Bitmap::ImportFlags importFlags, const AssetResolver* assetResolver, - size_t* loadedTextureCount + size_t* loadedTextureCount, + const Object* owner ) { if (path.string().find("") != std::string::npos) - return loadUdimTexture(path, generateMipLevels, loadAsSRGB, bindFlags, async, assetResolver, loadedTextureCount); + { + CpuTextureHandle handle = + loadUdimTexture(path, generateMipLevels, loadAsSRGB, bindFlags, async, importFlags, assetResolver, loadedTextureCount); + + std::lock_guard lock(mMutex); + registerOwner(handle, owner); + + return handle; + } std::vector paths; auto addPath = [&](const std::filesystem::path& p) @@ -244,7 +260,8 @@ TextureManager::CpuTextureHandle TextureManager::loadTexture( } std::unique_lock lock(mMutex); - const TextureKey textureKey(paths, generateMipLevels, loadAsSRGB, bindFlags); + const TextureKey textureKey(paths, generateMipLevels, loadAsSRGB, bindFlags, importFlags); + if (auto it = mKeyToHandle.find(textureKey); it != mKeyToHandle.end()) { // Texture is already managed. Return its handle. @@ -257,6 +274,7 @@ TextureManager::CpuTextureHandle TextureManager::loadTexture( // Add new texture desc. TextureDesc desc = {TextureState::Referenced, nullptr}; handle = addDesc(desc); + registerOwner(handle, owner); // Add to key-to-handle map. mKeyToHandle[textureKey] = handle; @@ -297,22 +315,22 @@ TextureManager::CpuTextureHandle TextureManager::loadTexture( // Issue load request to texture loader. if (paths.size() > 1) { - mAsyncTextureLoader.loadMippedFromFiles(paths, loadAsSRGB, bindFlags, callback); + mAsyncTextureLoader.loadMippedFromFiles(paths, loadAsSRGB, bindFlags, importFlags, callback); } else { - mAsyncTextureLoader.loadFromFile(paths[0], generateMipLevels, loadAsSRGB, bindFlags, callback); + mAsyncTextureLoader.loadFromFile(paths[0], generateMipLevels, loadAsSRGB, bindFlags, importFlags, callback); } #else // Load texture from main thread. ref pTexture; if (paths.size() > 1) { - pTexture = Texture::createMippedFromFiles(mpDevice, paths, loadAsSRGB, bindFlags); + pTexture = Texture::createMippedFromFiles(mpDevice, paths, loadAsSRGB, bindFlags, importFlags); } else { - pTexture = Texture::createFromFile(mpDevice, paths[0], generateMipLevels, loadAsSRGB, bindFlags); + pTexture = Texture::createFromFile(mpDevice, paths[0], generateMipLevels, loadAsSRGB, bindFlags, importFlags); } // Add new texture desc. @@ -330,6 +348,7 @@ TextureManager::CpuTextureHandle TextureManager::loadTexture( #endif } + registerOwner(handle, owner); lock.unlock(); if (!mUseDeferredLoading && !async) @@ -402,13 +421,14 @@ void TextureManager::endDeferredLoading() if (job.key.fullPaths.size() == 1) { desc.pTexture = Texture::createFromFile( - mpDevice, job.key.fullPaths[0], job.key.generateMipLevels, job.key.loadAsSRGB, job.key.bindFlags + mpDevice, job.key.fullPaths[0], job.key.generateMipLevels, job.key.loadAsSRGB, job.key.bindFlags, job.key.importFlags ); logDebug("Loading texture from '{}'", job.key.fullPaths[0]); } else { - desc.pTexture = Texture::createMippedFromFiles(mpDevice, job.key.fullPaths, job.key.loadAsSRGB, job.key.bindFlags); + desc.pTexture = + Texture::createMippedFromFiles(mpDevice, job.key.fullPaths, job.key.loadAsSRGB, job.key.bindFlags, job.key.importFlags); logDebug("Loading mipped texture from '{}'", job.key.fullPaths[0]); } if (texturesLoaded.fetch_add(1) % 10 == 9) @@ -432,22 +452,40 @@ void TextureManager::endDeferredLoading() void TextureManager::removeTexture(const CpuTextureHandle& handle) { - if (handle.isUdim()) - { - removeUdimTexture(handle); - } if (!handle) return; - waitForTextureLoading(handle); + waitForAllTexturesLoading(); std::lock_guard lock(mMutex); + FALCOR_CHECK(mHandleToObjects.find(handle) == mHandleToObjects.end(), "Texture is in use by one or more objects."); + + removeTextureInternal(handle); +} + +void TextureManager::removeTextureInternal(const CpuTextureHandle& handle) +{ + if (handle.isUdim()) + removeUdimTexture(handle); + else + removeNonUdimTexture(handle); + + mHandleToObjects.erase(handle); +} + +void TextureManager::removeNonUdimTexture(const CpuTextureHandle& handle) +{ + // Internal helper to remove an individual texture. + // At this point UDIMs have been resolved and the handle is guaranteed to refer to a non-UDIM texture. // Get texture desc. If it's already cleared, we're done. auto& desc = getDesc(handle); if (!desc.isValid()) return; + // It is assumed that textures have been fully loaded before we proceed to modify the data structures. + FALCOR_CHECK(desc.state == TextureState::Loaded, "Texture is not yet loaded. Invalid operation."); + // Remove handle from maps. // Note not all handles exist in key-to-handle map so search for it. This can be optimized if needed. auto it = std::find_if(mKeyToHandle.begin(), mKeyToHandle.end(), [handle](const auto& keyVal) { return keyVal.second == handle; }); @@ -467,6 +505,40 @@ void TextureManager::removeTexture(const CpuTextureHandle& handle) mFreeList.push_back(handle); } +void TextureManager::removeTextures(const Object* object) +{ + FALCOR_CHECK(object != nullptr, "Missing object."); + + waitForAllTexturesLoading(); + + std::lock_guard lock(mMutex); + + // Remove object from ownership of all its associated textures. + // All textures that are left without any owners will be removed below. + std::vector handles; + { + auto obj = mObjectToHandles.find(object); + if (obj == mObjectToHandles.end()) + return; + for (auto handle : obj->second) + { + auto it = mHandleToObjects.find(handle); + FALCOR_ASSERT(it != mHandleToObjects.end()); + it->second.erase(object); + if (it->second.empty()) + handles.push_back(handle); + } + mObjectToHandles.erase(obj); + } + + // Remove all textures that are now unowned. + for (const auto& handle : handles) + removeTextureInternal(handle); + + if (!handles.empty()) + logInfo("Texture manager: Removed {} textures.", handles.size()); +} + TextureManager::TextureDesc TextureManager::getTextureDesc(const CpuTextureHandle& handle) const { if (!handle) @@ -484,6 +556,28 @@ size_t TextureManager::getTextureDescCount() const return mTextureDescs.size(); } +std::vector TextureManager::getUdimIDs(const CpuTextureHandle& handle) const +{ + if (!handle || !handle.isUdim()) + return {}; + + std::lock_guard lock(mMutex); + size_t rangeStart = handle.getID(); + FALCOR_CHECK(rangeStart < mUdimIndirectionSize.size(), "Handle is out of range."); + size_t rangeSize = mUdimIndirectionSize[rangeStart]; + + std::vector udimIDs; + for (size_t i = rangeStart; i < rangeStart + rangeSize; ++i) + { + if (mUdimIndirection[i] < 0) + continue; + uint32_t udimID = (uint32_t)(i - rangeStart) + 1001; + udimIDs.push_back(udimID); + } + + return udimIDs; +} + void TextureManager::bindShaderData(const ShaderVar& texturesVar, const size_t descCount, const ShaderVar& udimsVar) const { std::lock_guard lock(mMutex); @@ -582,6 +676,18 @@ TextureManager::TextureDesc& TextureManager::getDesc(const CpuTextureHandle& han return mTextureDescs[handle.getID()]; } +void TextureManager::registerOwner(const CpuTextureHandle& handle, const Object* owner) +{ + // Register object as owner of texture. + // A texture can have multiple owners (e.g. same texture used by multiple materials). + // We track ownership in order to be able to free textures when no longer in use. + if (handle && owner != nullptr) + { + mHandleToObjects[handle].insert(owner); + mObjectToHandles[owner].insert(handle); + } +} + size_t TextureManager::getUdimRange(size_t requiredSize) { // But first look in the freed ranges for the smallest one that we can reuse @@ -634,7 +740,7 @@ void TextureManager::removeUdimTexture(const CpuTextureHandle& handle) if (mUdimIndirection[i] < 0) continue; CpuTextureHandle texHandle(mUdimIndirection[i]); - removeTexture(texHandle); + removeNonUdimTexture(texHandle); mUdimIndirection[i] = -1; } diff --git a/Source/Falcor/Utils/Image/TextureManager.h b/Source/Falcor/Utils/Image/TextureManager.h index f0831866b..137012e47 100644 --- a/Source/Falcor/Utils/Image/TextureManager.h +++ b/Source/Falcor/Utils/Image/TextureManager.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,7 @@ class FALCOR_API TextureManager uint32_t getID() const { return mID; } bool isUdim() const { return mIsUdim; } + bool operator<(const CpuTextureHandle& other) const { return mID < other.mID; } bool operator==(const CpuTextureHandle& other) const { return mID == other.mID && mIsUdim == other.mIsUdim; } TextureHandle toGpuHandle() const @@ -164,6 +166,7 @@ class FALCOR_API TextureManager * @param[in] loadAsSRGB Load the texture as sRGB format if supported, otherwise linear color. * @param[in] bindFlags The bind flags for the texture resource. * @param[in] async Load asynchronously, otherwise the function blocks until the texture data is loaded. + * @param[in] importFlags Optional flags for the file import. * @param[in] assetResolver Optional asset resolver for resolving file paths. * @param[out] loadedTextureCount Optionally can provided the number of actually loaded textures (2+ can happen with UDIMs) * @return Unique handle to the texture, or an invalid handle if the texture can't be found. @@ -174,22 +177,10 @@ class FALCOR_API TextureManager bool loadAsSRGB, ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, bool async = true, + Bitmap::ImportFlags importFlags = Bitmap::ImportFlags::None, const AssetResolver* assetResolver = nullptr, - size_t* loadedTextureCount = nullptr - ); - - /** - * Same as loadTexture, but explicitly handles Udim textures. If the texture isn't Udim, it falls back to loadTexture. - * Also, loadTexture will detect UDIM and call loadUdimTexture if needed. - */ - CpuTextureHandle loadUdimTexture( - const std::filesystem::path& path, - bool generateMipLevels, - bool loadAsSRGB, - ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, - bool async = true, - const AssetResolver* assetResolver = nullptr, - size_t* loadedTextureCount = nullptr + size_t* loadedTextureCount = nullptr, + const Object* owner = nullptr ); /** @@ -220,6 +211,12 @@ class FALCOR_API TextureManager */ void removeTexture(const CpuTextureHandle& handle); + /** + * Remove all textures loaded by the given object. + * @param[in] object The object for which to remove textures. + */ + void removeTextures(const Object* object); + /** * Get a loaded texture. Call getTextureDesc() for more info. * This function handles non-UDIM textures. If UDIMs are expected, supply UDIM ID or uv coordinate. @@ -246,6 +243,12 @@ class FALCOR_API TextureManager return getTextureDesc(resolveUdimTexture(handle, udimID)); } + /** + * Get list of UDIM IDs for a texture. + * If the texture is not using UDIMs, an empty list is returned. + */ + std::vector getUdimIDs(const CpuTextureHandle& handle) const; + /** * Get texture desc count. * @return Number of texture descs. @@ -279,10 +282,27 @@ class FALCOR_API TextureManager private: size_t getUdimRange(size_t requiredSize); void freeUdimRange(size_t rangeStart); + void removeTextureInternal(const CpuTextureHandle& handle); void removeUdimTexture(const CpuTextureHandle& handle); + void removeNonUdimTexture(const CpuTextureHandle& handle); CpuTextureHandle resolveUdimTexture(const CpuTextureHandle& handle, const float2& uv) const; CpuTextureHandle resolveUdimTexture(const CpuTextureHandle& handle, const uint32_t udimID) const; + /** + * Same as loadTexture, but explicitly handles Udim textures. If the texture isn't Udim, it falls back to loadTexture. + * Also, loadTexture will detect UDIM and call loadUdimTexture if needed. + */ + CpuTextureHandle loadUdimTexture( + const std::filesystem::path& path, + bool generateMipLevels, + bool loadAsSRGB, + ResourceBindFlags bindFlags = ResourceBindFlags::ShaderResource, + bool async = true, + Bitmap::ImportFlags importFlags = Bitmap::ImportFlags::None, + const AssetResolver* assetResolver = nullptr, + size_t* loadedTextureCount = nullptr + ); + /** * Key to uniquely identify a managed texture. */ @@ -292,9 +312,16 @@ class FALCOR_API TextureManager bool generateMipLevels; bool loadAsSRGB; ResourceBindFlags bindFlags; - - TextureKey(const std::vector& paths, bool mips, bool srgb, ResourceBindFlags flags) - : fullPaths(paths), generateMipLevels(mips), loadAsSRGB(srgb), bindFlags(flags) + Bitmap::ImportFlags importFlags; + + TextureKey( + const std::vector& paths, + bool mips, + bool srgb, + ResourceBindFlags flags, + Bitmap::ImportFlags importFlags + ) + : fullPaths(paths), generateMipLevels(mips), loadAsSRGB(srgb), bindFlags(flags), importFlags(importFlags) {} bool operator<(const TextureKey& rhs) const @@ -305,6 +332,8 @@ class FALCOR_API TextureManager return generateMipLevels < rhs.generateMipLevels; else if (loadAsSRGB != rhs.loadAsSRGB) return loadAsSRGB < rhs.loadAsSRGB; + else if (importFlags != rhs.importFlags) + return importFlags < rhs.importFlags; else return bindFlags < rhs.bindFlags; } @@ -312,6 +341,7 @@ class FALCOR_API TextureManager CpuTextureHandle addDesc(const TextureDesc& desc); TextureDesc& getDesc(const CpuTextureHandle& handle); + void registerOwner(const CpuTextureHandle& handle, const Object* owner); ref mpDevice; @@ -333,6 +363,11 @@ class FALCOR_API TextureManager mutable bool mUdimIndirectionDirty = true; mutable ref mpUdimIndirection; + using Objects = std::set; + using Handles = std::set; + std::map mHandleToObjects; ///< Map from texture handle to set of objects using the handle. + std::map mObjectToHandles; ///< Map from object to set of texture handles used by the object. + bool mUseDeferredLoading = false; AsyncTextureLoader mAsyncTextureLoader; ///< Utility for asynchronous texture loading. diff --git a/Source/Modules/USDUtils/Tessellator/IndexedVector.h b/Source/Falcor/Utils/IndexedVector.h similarity index 92% rename from Source/Modules/USDUtils/Tessellator/IndexedVector.h rename to Source/Falcor/Utils/IndexedVector.h index 8f77941ac..b8b5b327a 100644 --- a/Source/Modules/USDUtils/Tessellator/IndexedVector.h +++ b/Source/Falcor/Utils/IndexedVector.h @@ -27,8 +27,11 @@ **************************************************************************/ #pragma once -#include +#include "Utils/fast_vector.h" + #include +#include +#include namespace Falcor { @@ -82,16 +85,16 @@ class IndexedVector /** * @brief Get the set of unique data items. */ - const pxr::VtArray& getValues() const { return mValues; } + fstd::span getValues() const { return mValues; } /** * @brief Get the ordered list of item indices. */ - const pxr::VtArray& getIndices() const { return mIndices; } + fstd::span getIndices() const { return mIndices; } private: std::unordered_map mIndexMap; - pxr::VtArray mValues; - pxr::VtArray mIndices; + std::vector mValues; + std::vector mIndices; }; } // namespace Falcor diff --git a/Source/Falcor/Utils/Math/MatrixMath.h b/Source/Falcor/Utils/Math/MatrixMath.h index fcae389ba..280bb2c12 100644 --- a/Source/Falcor/Utils/Math/MatrixMath.h +++ b/Source/Falcor/Utils/Math/MatrixMath.h @@ -62,6 +62,17 @@ template return result; } +/// Binary * operator +template +[[nodiscard]] matrix operator+(const matrix& lhs, const matrix& rhs) +{ + matrix result; + for (int r = 0; r < R; ++r) + for (int c = 0; c < C; ++c) + result[r][c] = lhs[r][c] + rhs[r][c]; + return result; +} + // ---------------------------------------------------------------------------- // Multiplication // ---------------------------------------------------------------------------- diff --git a/Source/Falcor/Utils/ObjectID.h b/Source/Falcor/Utils/ObjectID.h index 130523499..858fecaa1 100644 --- a/Source/Falcor/Utils/ObjectID.h +++ b/Source/Falcor/Utils/ObjectID.h @@ -46,11 +46,12 @@ namespace Falcor * @param TKind Kind of the ID. * @param TIntType the underlying numeric type. It is advised that it should be the same for all TKinds in the same enum. */ -template +template class ObjectID { public: using IntType = TIntType; + using KindEnum = TKindEnum; static constexpr TKindEnum kKind = TKind; static constexpr IntType kInvalidID = std::numeric_limits::max(); diff --git a/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang b/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang index 5e8a2def7..bf40e4910 100644 --- a/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang +++ b/Source/Falcor/Utils/Sampling/TinyUniformSampleGenerator.slang @@ -36,7 +36,7 @@ import Utils.Math.BitTricks; * This generator has only 32 bit state and sub-optimal statistical properties. * Do not use for anything critical; correlation artifacts may be prevalent. */ -public struct TinyUniformSampleGenerator : ISampleGenerator +export struct TinyUniformSampleGenerator : ISampleGenerator { struct Padded { diff --git a/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang b/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang index f0afb15c6..c7c36db3c 100644 --- a/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang +++ b/Source/Falcor/Utils/Sampling/UniformSampleGenerator.slang @@ -38,7 +38,7 @@ import Utils.Math.BitTricks; * * This sample generator requires shader model 6.0 or above. */ -public struct UniformSampleGenerator : ISampleGenerator +export struct UniformSampleGenerator : ISampleGenerator { struct Padded { diff --git a/Source/Falcor/Utils/Settings.h b/Source/Falcor/Utils/Settings.h deleted file mode 100644 index feb221ffd..000000000 --- a/Source/Falcor/Utils/Settings.h +++ /dev/null @@ -1,538 +0,0 @@ -/*************************************************************************** - # Copyright (c) 2015-23, 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/Error.h" - -#include - -#include -#include -#include -#include -#include -#include - -namespace pybind11 -{ -class dict; -} - -namespace Falcor -{ -class Settings; -class SettingsProperties -{ -public: - class TypeError : public std::runtime_error - { - public: - using std::runtime_error::runtime_error; - }; - -private: - template, bool> = true> - static bool isType(const nlohmann::json& json) - { - return json.is_number() || json.is_boolean(); - } - - template, bool> = true> - static bool isType(const nlohmann::json& json) - { - return json.is_string(); - } - - template, bool> = true> - static bool isType(const nlohmann::json& json) - { - return json.is_object(); - } - - template, bool> = true> - static bool isType(const nlohmann::json& json) - { - return true; - } - - template>, bool> = true> - static bool isType(const nlohmann::json& json) - { - if (!json.is_array()) - return false; - if (json.size() != N) - return false; - for (size_t i = 0; i < N; ++i) - if (!isType(json[i])) - return false; - return true; - } - - // The "gccfix" parameter is used to avoid "explicit specialization in non-namespace scope" in gcc. - // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282 - template - struct JsonCaster - { - static T cast(const nlohmann::json& json) - { - if constexpr (std::is_arithmetic_v) - { - if (json.is_boolean()) - return json.get() ? T(1) : T(0); - } - return json.get(); - } - }; - - template - struct JsonCaster - { - static bool cast(const nlohmann::json& json) - { - if (json.is_boolean()) - return json.get(); - return json.get() != 0; - } - }; - - template - struct JsonCaster - { - static SettingsProperties cast(const nlohmann::json& json) - { - FALCOR_ASSERT(json.is_object()); - if (!json.is_object()) - throw TypeError("Trying to get SettingsProperties from a non-object json"); - return SettingsProperties(&json); - } - }; - - template - struct JsonCaster - { - static nlohmann::json cast(const nlohmann::json& json) { return json; } - }; - - template - struct JsonCaster, gccfix> - { - using ArrayType = std::array; - static bool assertHelper(const nlohmann::json& json) { return isType(json); } - - static ArrayType cast(const nlohmann::json& json) - { - FALCOR_ASSERT(assertHelper(json)); - - ArrayType result; - for (size_t i = 0; i < N; ++i) - result[i] = JsonCaster::cast(json[i]); - return result; - } - }; - -public: - SettingsProperties() = default; - - // Returns a property of the given name - template - std::optional get(const std::string_view name) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!name.empty() && name.data()[name.size()] == 0); - - // Handle the : in names as nesting separator - { - std::string_view::size_type pos = name.find_first_of(':'); - if (pos != std::string_view::npos) - { - // The parent *must* be std::string, because the underlying pybind11 - // operates on const char*, so the length in string_view would be ignored, - // and the whole name would be looked up, rather than just a subsection of it. - - // Name up to the :, excluding - std::string parent(name.data(), pos); - // Name after the : - std::string_view child(name.data() + pos + 1, name.size() - pos - 1); - if (!hasExact(parent)) - return std::optional(); - return get(parent)->get(child); - } - } - - if (!hasExact(name)) - return std::optional(); - - try - { - return JsonCaster::cast((*mDictionary)[name.data()]); - } - catch (const nlohmann::json::type_error& e) - { - throw TypeError(e.what()); - } - } - - // Returns the property of the given name. - // Throws TypeError if the type does not match - template - T get(const std::string_view name, const T& def) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!name.empty() && name.data()[name.size()] == 0); - - std::optional opt = get(name); - return opt ? *opt : def; - } - - // Do we have a property of the given name - bool has(const std::string_view name) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!name.empty() && name.data()[name.size()] == 0); - - // Handle the : in names as nesting separator - { - std::string_view::size_type pos = name.find_first_of(':'); - if (pos != std::string_view::npos) - { - // The parent *must* be std::string, because the underlying pybind11 - // operates on const char*, so the length in string_view would be ignored, - // and the whole name would be looked up, rather than just a subsection of it. - - // Name up to the :, excluding - std::string parent(name.data(), pos); - // Name after the : - std::string_view child(name.data() + pos + 1, name.size() - pos - 1); - if (!hasExact(parent)) - return false; - return get(parent)->has(child); - } - } - - return hasExact(name); - } - - // Do we have a property of the given name and type - template - bool has(const std::string_view name) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!name.empty() && name.data()[name.size()] == 0); - - // Handle the : in names as nesting separator - { - std::string_view::size_type pos = name.find_first_of(':'); - if (pos != std::string_view::npos) - { - // The parent *must* be std::string, because the underlying pybind11 - // operates on const char*, so the length in string_view would be ignored, - // and the whole name would be looked up, rather than just a subsection of it. - - // Name up to the :, excluding - std::string parent(name.data(), pos); - // Name after the : - std::string_view child(name.data() + pos + 1, name.size() - pos - 1); - if (!hasExact(parent)) - return false; - return get(parent)->has(child); - } - } - - return hasExact(name); - } - - std::string toString() const { return mDictionary->dump(); } - -private: - SettingsProperties(const nlohmann::json* dictionary) : mDictionary(dictionary) {} - - // Do we have a property of the given name - bool hasExact(const std::string_view name) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!name.empty() && name.data()[name.size()] == 0); - - return mDictionary->contains(name); - } - - // Do we have a property of the given name and type - template - bool hasExact(const std::string_view name) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!name.empty() && name.data()[name.size()] == 0); - - if (!has(name)) - return false; - - return isType((*mDictionary)[name.data()]); - } - - friend class Settings; - -private: - const nlohmann::json* mDictionary{nullptr}; -}; - -class FALCOR_API Settings -{ -public: - using SearchDirectories = std::vector; - - /// Get the global settings instance. - static Settings& getGlobalSettings(); - - Settings() : mData(1) {} - - SettingsProperties getOptions() const { return SettingsProperties(&getActive().mOptions); } - - template - std::optional getOption(const std::string_view name) const - { - return SettingsProperties(&getActive().mOptions).get(name); - } - - template - T getOption(const std::string_view name, const T& def) const - { - return SettingsProperties(&getActive().mOptions).get(name, def); - } - - // Adds to global option list. - // It is a nested list of dictionaries - void addOptions(const pybind11::dict& options); - - /// Add options from a JSON file, returning true on success and false on failure - bool addOptions(const std::filesystem::path& path); - - // Clears the global options to defaults - void clearOptions() { getActive().mOptions.clear(); } - - /** - * Attributes don't really belong here. They should be part of the loadScene. - * However, if you load scenes from the GUI, you want the attributes to get applied to all the scenes - * (think setting attributes to "make all curves tessellate fine"). - * - * So this, which is effectively an attribute filter (RIF, if you will) need to live in some - * reasonably global place. This will be replaced with a more principled solution in the new Scene. - */ - - template - std::optional getAttribute(const std::string_view shapeName, const std::string_view attributeName) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!attributeName.empty() && attributeName.data()[attributeName.size()] == 0); - - SettingsProperties props(&getActive().mFilteredAttributes); - - std::string str = props.toString(); - - // Don't have it at all (or have it with a wrong type) - if (!props.has(attributeName)) - return std::optional(); - - std::string attributeNameFilter = std::string(attributeName) + ".filter"; - - // If we don't have the filter at all, we can return the value right away - if (!props.has(attributeNameFilter)) - return props.get(attributeName); - - // filter can be either std::string, or a pybind11::list of 1 or 2 values - // (first being std::string, second optionally boolean). - // Unfortunately, pybind makes us do a ton of unnecessary lookups here: - std::string expression; - bool negateRegex = false; - if (props.has(attributeNameFilter)) - { - expression = props.get(attributeNameFilter, expression); - } - else - { - nlohmann::json array = *props.get(attributeNameFilter); - FALCOR_CHECK(array.is_array(), "Expecting an array"); - expression = array[0].get(); - if (array.size() > 1) - negateRegex = array[1].get(); - } - - if (matches(shapeName, expression) == negateRegex) - return std::optional(); - - return props.get(attributeName); - } - - template - T getAttribute(const std::string_view shapeName, const std::string_view attributeName, const T& def) const - { - // The underlying library requires attribute names to be null terminated - FALCOR_ASSERT(!attributeName.empty() && attributeName.data()[attributeName.size()] == 0); - - auto opt = getAttribute(shapeName, attributeName); - return opt ? *opt : def; - } - - // This is a global way to add per-object attributes. - // Again, follows a key-value mapping, logic, but adds a logic for attaching the - // values to objects by name. This is only/mostly useful for the Tiger demo, - // where the Tiger asset gets a different behavior than the others. - // - // The value is always an array of either 1, 2 or 3 items. - // The first item is the assigned value. - // The second is a "basic" C++ regex regular expression (POSIX), - // and the value is applied to all objects whose name matches. - // If the second value is not present, the Attribute is applied to all objects - // If the second value is present, the third value (bool) says whether to negate - // the expression. If false (default), Attribute is applied to all names that do match, - // if true, it is applied to all names that do not match. - // - // Important, these generally kick in only during loading/processing of the scene. - // The changes are NOT retroactive (e.g., if you load a scene and then disable motion, - // it won't do anything unless you unload and reload the scene). - // - // The values are not combined in any smart way, the last value is applied, - // e.g., you cannot use two subsequent calls to combine the regex in any way. - // Reason: This is not really meant to replace DCC, just to get us out of the hot water - // - // Example usage: - // m.addFilteredAttributes({'usdImporter':{'enableMotion':False}}); // disables motion on everything - // or - // m.addFilteredAttributes({'usdImporter':{'enableMotion':False, 'enableMotion.filter':['/World/Tiger/.*']}}); // disables motion on - // Tiger only or m.addFilteredAttributes({'usdImporter':{'enableMotion':False, 'enableMotion.filter':['/World/Tiger/.*', False]}}); // - // disables motion on all but Tiger - // - // Important, these two consequently - // m.addFilteredAttributes({'usdImporter:enableMotion':[False]}); - // m.addFilteredAttributes({'usdImporter:enableMotion':[False, '/World/Tiger/.*']}); - // will *NOT* disable motion on everything and then re-enable it for the tiger, - // they will only set the "enableMotion = False" on the Tiger, while leaving - // everything else to default (which happens to be "False" as well) - void addFilteredAttributes(const pybind11::dict& attributes); - - // Clears all the attributes to default - void clearFilteredAttributes(); - - /** - * Returns search paths from the given category. - * - * If the searchpath is not defined, it returns the standardsearch path. - * If that's not defined either, it returns an empty result. - * - * @param category - * @return - */ - const SearchDirectories& getSearchDirectories(std::string_view category) - { - auto it = mSearchDirectories.find(std::string(category)); - if (it != mSearchDirectories.end()) - return it->second; - it = mStandardSearchDirectories.find(std::string(category)); - if (it != mStandardSearchDirectories.end()) - return it->second; - - static SearchDirectories sEmpty; // TODO: REMOVEGLOBAL - return sEmpty; - } - - void pushSettingsStack() { mData.push_back(mData.back()); } - - void popSettingsStack() - { - mData.pop_back(); - FALCOR_ASSERT(!mData.empty()); - } - -private: - struct SettingsData - { - nlohmann::json mOptions; - nlohmann::json mFilteredAttributes; - }; - - SettingsData& getActive() { return mData.back(); } - const SettingsData& getActive() const { return mData.back(); } - - bool addOptionsJSON(const std::filesystem::path& path); - bool addOptionsTOML(const std::filesystem::path& path); - - static bool matches(const std::string_view shapeName, const std::string_view expression) - { - std::regex attrRegex(expression.data(), expression.size()); - std::smatch match; - return std::regex_match(shapeName.data(), attrRegex); - } - - static void deep_merge(nlohmann::json& lhs, const nlohmann::json& rhs) - { - FALCOR_ASSERT(lhs.is_object() && rhs.is_object()); - - for (auto& rhsIt : rhs.items()) - { - auto lhsIt = lhs.find(rhsIt.key()); - // Both are dictionaries, so we need to recurse (lhs will be passed by reference, but that reference is dictionary) - if (lhsIt != lhs.end() && lhsIt.value().is_object() && rhsIt.value().is_object()) - { - deep_merge(lhsIt.value(), rhsIt.value()); - continue; - } - lhs[rhsIt.key()] = rhsIt.value(); - } - } - - static void merge(nlohmann::json& lhsJson, const nlohmann::json& rhsJson) - { - if (rhsJson.is_null()) - return; - if (lhsJson.is_null()) - { - lhsJson = rhsJson; - return; - } - deep_merge(lhsJson, rhsJson); - } - - void updateSearchPaths(const nlohmann::json& update); - -private: - std::vector mData; - - std::map mStandardSearchDirectories; - std::map mSearchDirectories; -}; - -class ScopedSettings -{ -public: - ScopedSettings(Settings& settings) : mSettings(settings) { mSettings.pushSettingsStack(); } - - ~ScopedSettings() { mSettings.popSettingsStack(); } - -private: - Settings& mSettings; -}; - -} // namespace Falcor diff --git a/Source/Falcor/Utils/Settings/AttributeFilters.cpp b/Source/Falcor/Utils/Settings/AttributeFilters.cpp new file mode 100644 index 000000000..7bd9bc102 --- /dev/null +++ b/Source/Falcor/Utils/Settings/AttributeFilters.cpp @@ -0,0 +1,192 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 "AttributeFilters.h" + +#include + +namespace Falcor +{ +namespace settings +{ +Attributes AttributeFilter::getAttributes(std::string_view shapeName) const +{ + Attributes result; + for (const Record& recordIt : mAttributes) + { + if (!std::regex_match(shapeName.begin(), shapeName.end(), recordIt.regex)) + continue; + + result.addDict(recordIt.attributes); + } + + return result; +} + +void AttributeFilter::addJson(const nlohmann::json& json) +{ + if (json.is_array()) + addArray(json); + else + addDictionary(json); +} + +void AttributeFilter::addArray(const nlohmann::json& array) +{ + for (auto& arrayIt : array) + addDictionary(arrayIt); +} + +void AttributeFilter::addDictionary(const nlohmann::json& dict) +{ + FALCOR_CHECK(dict.is_object(), "Must be object/dictionary."); + + auto nameIt = dict.find("name"); + auto regexIt = dict.find("regex"); + auto attrIt = dict.find("attributes"); + + FALCOR_CHECK(regexIt == dict.end() || regexIt.value().is_string(), "Filter `regex` must have string value."); + FALCOR_CHECK(nameIt == dict.end() || nameIt.value().is_string(), "Filter `name` must have string value."); + FALCOR_CHECK(attrIt == dict.end() || attrIt.value().is_object(), "`Filter attributes` have dictionary/object value."); + FALCOR_CHECK( + regexIt == dict.end() || (regexIt != dict.end() && attrIt != dict.end()), + "If `filter` is present, `attributes` must be present as well." + ); + + Record record; + + record.name = fmt::format("filter_{}", mAttributes.size()); + if (nameIt != dict.end()) + record.name = nameIt.value().get(); + + std::string regexStr = ".*"; + if (regexIt != dict.end()) + regexStr = regexIt.value().get(); + record.regex = std::regex(regexStr); + + nlohmann::json allFlattened; + if (attrIt != dict.end()) + allFlattened = detail::flattenDictionary(attrIt.value()); + else + allFlattened = detail::flattenDictionary(dict); + + // filter out the .filter of the old attributes + record.attributes = processDeprecatedFilters(record.name, allFlattened, regexStr); + + if (!record.attributes.empty()) + mAttributes.push_back(std::move(record)); +} + +/// Filters out all attributes using the deprecated `name.filter` syntax, +/// processes into filters, and returns the remaining attributes +nlohmann::json AttributeFilter::processDeprecatedFilters(std::string_view name, nlohmann::json flattened, const std::string& regexStr) +{ + std::set processed; + for (auto& filterIt : flattened.items()) + { + const std::string& filterKey = filterIt.key(); + size_t pos = filterKey.find(".filter"); + if (pos != filterKey.size() - 7) + continue; + + std::string attrKey = filterKey.substr(0, pos); + auto attrIT = flattened.find(attrKey); + FALCOR_CHECK(attrIT != flattened.end(), "Found an attribute filter `{}`, but not the actual attribute `{}`.", filterKey, attrKey); + FALCOR_CHECK( + regexStr == ".*", + "Found a filtered attribute `{}` found along with regex `{}`. Filtered attributes are only supported when regex is " + "`.*`.", + attrKey, + regexStr + ); + + { + std::string filterRegexStr; + bool isNegatedRegex = false; + if (filterIt.value().is_string()) + { + filterRegexStr = filterIt.value().get(); + } + else + { + FALCOR_CHECK( + filterIt.value().is_array() && filterIt.value().size() <= 2 && filterIt.value()[0].is_string() && + (filterIt.value().size() == 1 || filterIt.value()[1].is_boolean()), + "`{}` must be either a string or [, ] array.", + filterKey + ); + filterRegexStr = filterIt.value()[0].get(); + if (filterIt.value().size() == 2) + isNegatedRegex = filterIt.value()[1].get(); + } + + if (!isNegatedRegex) + { + Record filteredRecord; + filteredRecord.name = fmt::format("{}_{}", name, filterKey); + filteredRecord.regex = std::regex(filterRegexStr); + filteredRecord.attributes = nlohmann::json::object(); + filteredRecord.attributes[attrIT.key()] = attrIT.value(); + mAttributes.push_back(std::move(filteredRecord)); + } + else + { + Record filteredRecord; + filteredRecord.name = fmt::format("{}_{}_apply", name, filterKey); + filteredRecord.regex = std::regex(".*"); + filteredRecord.attributes = nlohmann::json::object(); + filteredRecord.attributes[attrIT.key()] = attrIT.value(); + mAttributes.push_back(std::move(filteredRecord)); + + filteredRecord.name = fmt::format("{}_{}_unapply", name, filterKey); + filteredRecord.regex = std::regex(filterRegexStr); + filteredRecord.attributes = nlohmann::json::object(); + filteredRecord.attributes[attrIT.key()] = nullptr; + mAttributes.push_back(std::move(filteredRecord)); + } + } + + processed.insert(filterKey); + processed.insert(attrKey); + } + + if (processed.empty()) + return flattened; + + nlohmann::json result = nlohmann::json::object(); + for (auto& filterIt : flattened.items()) + { + if (processed.count(filterIt.key()) > 0) + continue; + result[filterIt.key()] = filterIt.value(); + } + + return result; +} + +} // namespace settings +} // namespace Falcor diff --git a/Source/Falcor/Utils/Settings/AttributeFilters.h b/Source/Falcor/Utils/Settings/AttributeFilters.h new file mode 100644 index 000000000..d24736ebd --- /dev/null +++ b/Source/Falcor/Utils/Settings/AttributeFilters.h @@ -0,0 +1,122 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 "SettingsUtils.h" +#include "Attributes.h" +#include "Core/Macros.h" +#include "Core/Error.h" +#include "Utils/Logger.h" + +#include +#include +#include +#include +#include + +#include + + +namespace pybind11 +{ +class dict; +} + +namespace Falcor +{ + +namespace settings +{ + +class AttributeFilter +{ + struct Record + { + std::string name; + std::regex regex; + nlohmann::json attributes; + }; + +public: + void add(const nlohmann::json& json) { addJson(json); } + void clear() { mAttributes.clear(); } + + Attributes getAttributes(std::string_view shapeName_) const; + + template + std::optional getAttribute(std::string_view shapeName, std::string_view attrName) const + { + nlohmann::json attribute = nullptr; + + for (const Record& recordIt : mAttributes) + { + if (!std::regex_match(shapeName.begin(), shapeName.end(), recordIt.regex)) + continue; + auto attrIt = recordIt.attributes.find(attrName); + if (attrIt != recordIt.attributes.end()) + attribute = attrIt.value(); + } + + if (attribute.is_null()) + return {}; + + if (!detail::TypeChecker::validType(attribute)) + throw detail::TypeError("Attribute's type does not match the requested type."); + + if constexpr (std::is_arithmetic_v) + { + if (attribute.is_boolean()) + return attribute.get() ? T(1) : T(0); + } + + return attribute.get(); + } + + template + T getAttribute(std::string_view shapeName, std::string_view attrName, const T& def) const + { + auto result = getAttribute(shapeName, attrName); + return result ? *result : def; + } + +private: + void addJson(const nlohmann::json& json); + void addArray(const nlohmann::json& array); + void addDictionary(const nlohmann::json& dict); + +private: + /// Filters out all attributes using the deprecated `name.filter` syntax, + /// processes into filters, and returns the remaining attributes + nlohmann::json processDeprecatedFilters(std::string_view name, nlohmann::json flattened, const std::string& regexStr); + +private: + std::vector mAttributes; +}; + +} // namespace settings + +} // namespace Falcor diff --git a/Source/Falcor/Utils/Settings/Attributes.h b/Source/Falcor/Utils/Settings/Attributes.h new file mode 100644 index 000000000..fa61aa8e6 --- /dev/null +++ b/Source/Falcor/Utils/Settings/Attributes.h @@ -0,0 +1,133 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 "SettingsUtils.h" +#include "Core/Macros.h" +#include "Core/Error.h" + +#include +#include +#include +#include + +#include + +namespace Falcor +{ +namespace settings +{ + +class Attributes +{ +public: + Attributes() = default; + Attributes(nlohmann::json jsonDict) : mJsonDict(jsonDict) {} + + template + std::optional get(std::string_view attrName) const + { + nlohmann::json attribute = nullptr; + + auto attrIt = mJsonDict.find(attrName); + if (attrIt != mJsonDict.end()) + attribute = attrIt.value(); + + if (attribute.is_null()) + return {}; + + 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) + if constexpr (std::is_same_v) + { + if (attribute.is_boolean()) + return attribute.get(); + return attribute.get() != 0; + } + + // Handle return value of int, if the actual is a boolean + if constexpr (std::is_arithmetic_v) + { + if (attribute.is_boolean()) + return attribute.get() ? T(1) : T(0); + } + + return attribute.get(); + } + + template + T get(std::string_view attrName, const T& def) const + { + auto result = get(attrName); + return result ? *result : def; + } + + bool has(std::string_view attrName) const + { + auto attrIt = mJsonDict.find(attrName); + return attrIt != mJsonDict.end(); + } + + void addDict(nlohmann::json jsonDict) + { + for (auto& it : jsonDict.items()) + mJsonDict[it.key()] = it.value(); + } + + void clear() + { + mJsonDict = nlohmann::json::object(); + } + + void removePrefix(std::string_view prefix) + { + nlohmann::json filtered; + for (auto& it : mJsonDict.items()) + { + if (it.key().find(prefix) != 0) + filtered[it.key()] = it.value(); + } + mJsonDict = std::move(filtered); + } + + void removeExact(std::string_view name) + { + mJsonDict.erase(name); + } + + std::string to_string() const + { + return mJsonDict.dump(); + } +private: + nlohmann::json mJsonDict; +}; + +} // namespace settings +} // namespace Falcor diff --git a/Source/Falcor/Utils/Settings.cpp b/Source/Falcor/Utils/Settings/Settings.cpp similarity index 82% rename from Source/Falcor/Utils/Settings.cpp rename to Source/Falcor/Utils/Settings/Settings.cpp index 96a5b9876..4b5012336 100644 --- a/Source/Falcor/Utils/Settings.cpp +++ b/Source/Falcor/Utils/Settings/Settings.cpp @@ -50,8 +50,7 @@ std::vector toStrings(const nlohmann::json& value) result.push_back(value.get()); return result; } - - if (value.is_array()) + else if (value.is_array()) { for (size_t i = 0; i < value.size(); ++i) { @@ -80,41 +79,63 @@ Settings& Settings::getGlobalSettings() return globalSettings; } +void Settings::addOptions(const nlohmann::json& options) +{ + nlohmann::json flattened = settings::detail::flattenDictionary(options); + for (auto& it : flattened.items()) + getActive().mOptions.removePrefix(it.key()); + getActive().mOptions.addDict(flattened); + updateSearchPaths(flattened); +} + void Settings::addOptions(const pybind11::dict& options) { - auto json = pyjson::to_json(options); - merge(getActive().mOptions, json); - updateSearchPaths(json); + addOptions(settings::detail::flattenDictionary(pyjson::to_json(options))); } -bool Settings::addOptions(const std::filesystem::path& path) +void Settings::addOptions(const pybind11::list& options) { - if (path.extension() == ".json") - return addOptionsJSON(path); - return false; + addOptions(settings::detail::flattenDictionary(pyjson::to_json(options))); } -bool Settings::addOptionsJSON(const std::filesystem::path& path) + +bool Settings::addOptions(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*/); - merge(getActive().mOptions, jf); + 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); updateSearchPaths(jf); return true; } void Settings::addFilteredAttributes(const pybind11::dict& attributes) { - merge(getActive().mFilteredAttributes, pyjson::to_json(attributes)); + addFilteredAttributes(pyjson::to_json(attributes)); +} + +void Settings::addFilteredAttributes(const pybind11::list& attributes) +{ + addFilteredAttributes(pyjson::to_json(attributes)); +} + +void Settings::addFilteredAttributes(const nlohmann::json& attributes) +{ + FALCOR_CHECK(attributes.is_array() || attributes.is_object(), "The attributes must be a dictionary, or an array of dictionaries."); + getActive().mAttributeFilters.add(attributes); } void Settings::clearFilteredAttributes() { - getActive().mFilteredAttributes.clear(); + getActive().mAttributeFilters.clear(); } void Settings::updateSearchPaths(const nlohmann::json& update) @@ -205,6 +226,7 @@ FALCOR_SCRIPT_BINDING(Settings) pybind11::class_ settings(m, "Settings"); settings.def("addOptions", pybind11::overload_cast(&Settings::addOptions), "dict"_a); settings.def("addFilteredAttributes", pybind11::overload_cast(&Settings::addFilteredAttributes), "dict"_a); + settings.def("addFilteredAttributes", pybind11::overload_cast(&Settings::addFilteredAttributes), "list"_a); settings.def("clearOptions", &Settings::clearOptions); settings.def("clearFilteredAttributes", &Settings::clearFilteredAttributes); } diff --git a/Source/Falcor/Utils/Settings/Settings.h b/Source/Falcor/Utils/Settings/Settings.h new file mode 100644 index 000000000..2f73ab26a --- /dev/null +++ b/Source/Falcor/Utils/Settings/Settings.h @@ -0,0 +1,222 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 "AttributeFilters.h" +#include "Core/Macros.h" +#include "Core/Error.h" + +#include + +#include +#include +#include +#include + +namespace pybind11 +{ +class dict; +class list; +} + +namespace Falcor +{ + +class FALCOR_API Settings +{ +public: + using Options = settings::Attributes; + using Attributes = settings::Attributes; + using TypeError = settings::detail::TypeError; +public: + using SearchDirectories = std::vector; + + /// Get the global settings instance. + static Settings& getGlobalSettings(); + + Settings() : mData(1) {} + + const Options& getOptions() const + { + return getActive().mOptions; + } + + template + std::optional getOption(std::string_view optionName) const + { + return getActive().mOptions.get(optionName); + } + + template + T getOption(std::string_view optionName, const T& def) const + { + return getActive().mOptions.get(optionName, def); + } + + // Adds to global option list. + // It is a nested list of dictionaries + void addOptions(const nlohmann::json& options); + void addOptions(const pybind11::dict& options); + void addOptions(const pybind11::list& options); + + /// Add options from a JSON file, returning true on success and false on failure + bool addOptions(const std::filesystem::path& path); + + // Clears the global options to defaults + void clearOptions() { getActive().mOptions.clear(); } + + /** + * The Settings object now contains Attribute filters, that are applied once by one + * to a shape based on it name, to provide the final Attribute. + */ + + template + std::optional getAttribute(std::string_view shapeName, std::string_view attributeName) const + { + return getActive().mAttributeFilters.getAttribute(shapeName, attributeName); + } + + template + T getAttribute(std::string_view shapeName, std::string_view attributeName, const T& def) const + { + return getActive().mAttributeFilters.getAttribute(shapeName, attributeName, def); + } + + /** + * @brief Adds filtered attributes with the following syntax. + * + * Examples use the JSON with comments syntax. + * Full example, applies "foo":4 and "bar:foobar":5 to all shapes that start with "Fur": + * { + * // optional name of the filter, not currently used + * "name" : "name of the filter", + * // Optional regex that determines to what shape names will the attributes apply. + * // Will be ".*" (apply to all) if not present. + * "regex" : "Fur.*", + * // Attributes that will be applied. Can be nested dictionaries, but will be flattened to colon + * // separated names, e.g.: { "foo" : 4, "bar:foobar" : 5 } + * "attributes" : + * { + * "foo" : 4 + * "bar" : + * { + * "foobar" : 5 + * } + * } + * } + * + * Simplified example, applies "foo":6 to every shape: + * { + * // If "attributes" is not part of the dictionary, the whole dictionary is taken as attributes block + * "foo":6 + * } + * + * Can also set multiple such filters at once, as an array: + * [ { dict1 }, { dict2 } ] + * + * Attributes are applied in the order in which they were added. + * + * Can still correctly process the deprecated syntax with .filter name: + * { + * // apply "foo":4 to shapes matching regex + * "foo":4 + * "foo.filter": "" + * // apply "bar":5 to all shapes not matching the regex + * "bar":5 + * "bar.filter": ["", true] + * } + * + * @param attributes Dictionary or array of dictionaries that provides attributes and their filters + */ + void addFilteredAttributes(const nlohmann::json& attributes); + void addFilteredAttributes(const pybind11::dict& attributes); + void addFilteredAttributes(const pybind11::list& attributes); + + // Clears all the attributes to default + void clearFilteredAttributes(); + + /** + * Returns search paths from the given category. + * + * If the searchpath is not defined, it returns the standardsearch path. + * If that's not defined either, it returns an empty result. + * + * @param category + * @return + */ + const SearchDirectories& getSearchDirectories(std::string_view category) + { + auto it = mSearchDirectories.find(category); + if (it != mSearchDirectories.end()) + return it->second; + it = mStandardSearchDirectories.find(category); + if (it != mStandardSearchDirectories.end()) + return it->second; + + static SearchDirectories sEmpty; // TODO: REMOVEGLOBAL + return sEmpty; + } + + void pushSettingsStack() { mData.push_back(mData.back()); } + + void popSettingsStack() + { + mData.pop_back(); + FALCOR_ASSERT(!mData.empty()); + } + +private: + struct SettingsData + { + Options mOptions; + settings::AttributeFilter mAttributeFilters; + }; + + SettingsData& getActive() { return mData.back(); } + const SettingsData& getActive() const { return mData.back(); } + + void updateSearchPaths(const nlohmann::json& update); + +private: + std::vector mData; + + std::map> mStandardSearchDirectories; + std::map> mSearchDirectories; +}; + +class ScopedSettings +{ +public: + ScopedSettings(Settings& settings) : mSettings(settings) { mSettings.pushSettingsStack(); } + + ~ScopedSettings() { mSettings.popSettingsStack(); } + +private: + Settings& mSettings; +}; + +} // namespace Falcor diff --git a/Source/Falcor/Utils/Settings/SettingsUtils.h b/Source/Falcor/Utils/Settings/SettingsUtils.h new file mode 100644 index 000000000..8951cda74 --- /dev/null +++ b/Source/Falcor/Utils/Settings/SettingsUtils.h @@ -0,0 +1,127 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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/Error.h" + +#include +#include +#include +#include + +#include + +#include + +namespace Falcor +{ +namespace settings::detail +{ + +namespace +{ +void flattenDictionary(const nlohmann::json& dict, const std::string& prefix, nlohmann::json& flattened) +{ + FALCOR_ASSERT(flattened.is_object()); + if (!dict.is_object()) + { + flattened[prefix] = dict; + return; + } + + for (auto& it : dict.items()) + { + std::string name = fmt::format("{}{}{}", prefix, prefix.empty() ? "" : ":", it.key()); + flattenDictionary(it.value(), name, flattened); + } +} +} + +/// Flattens nested dictionaries into colon separated name, +/// e.g. {"foo":{"bar":4}} becomes {"foo:bar":4} +inline nlohmann::json flattenDictionary(const nlohmann::json& dict) +{ + nlohmann::json flattened = nlohmann::json::object(); + flattenDictionary(dict, "", flattened); + return flattened; +} + +template, bool> = true> +inline bool isType(const nlohmann::json& json) +{ + return json.is_number() || json.is_boolean(); +} + +template, bool> = true> +inline bool isType(const nlohmann::json& json) +{ + return json.is_string(); +} + +template>, bool> = true> +inline bool isType(const nlohmann::json& json) +{ + if (!json.is_array()) + return false; + if (json.size() != N) + return false; + for (size_t i = 0; i < N; ++i) + if (!isType(json[i])) + return false; + return true; +} + +// The "gccfix" parameter is used to avoid "explicit specialization in non-namespace scope" in gcc. +// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282 +template +struct TypeChecker +{ + 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); + } +}; + +class TypeError : public std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; + +} // namespace settings::detail +} // namespace Falcor diff --git a/Source/Falcor/Utils/TaskManager.cpp b/Source/Falcor/Utils/TaskManager.cpp new file mode 100644 index 000000000..fdb341391 --- /dev/null +++ b/Source/Falcor/Utils/TaskManager.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 "TaskManager.h" + +namespace Falcor +{ + +TaskManager::TaskManager(bool startPaused) +{ + if (startPaused) + mThreadPool.pause(); +} + +void TaskManager::addTask(CpuTask&& task) +{ + std::lock_guard l(mTaskMutex); + ++mCurrentlyScheduled; + mThreadPool.push_task( + [task = std::move(task), this]() mutable + { + ++mCurrentlyRunning; + --mCurrentlyScheduled; + executeCpuTask(std::move(task)); + size_t running = --mCurrentlyRunning; + // If nothing is running, lets wake up and try to exit. + if (running == 0) + mGpuTaskCond.notify_all(); + } + ); +} + +void TaskManager::addTask(GpuTask&& task) +{ + std::lock_guard l(mTaskMutex); + ++mCurrentlyScheduled; + mGpuTasks.push_back(std::move(task)); + mGpuTaskCond.notify_all(); +} + +void TaskManager::finish(RenderContext* renderContext) +{ + mThreadPool.unpause(); + while (true) + { + while (true) + { + std::unique_lock l(mTaskMutex); + if (mGpuTasks.empty()) + break; + auto task = std::move(mGpuTasks.back()); + mGpuTasks.pop_back(); + l.unlock(); + ++mCurrentlyRunning; + --mCurrentlyScheduled; + task(renderContext); + --mCurrentlyRunning; + } + + std::unique_lock l(mTaskMutex); + while (true) + { + // If there are GPU tasks, go do them + if (!mGpuTasks.empty()) + break; + // If there are absolutely no tasks, go finish + if (mCurrentlyRunning == 0 && mCurrentlyScheduled == 0) + break; + // Otherwise wait for either a new GPU task, or last running to notify us to check + mGpuTaskCond.wait(l); + } + + if (mCurrentlyRunning == 0 && mCurrentlyScheduled == 0) + break; + } + rethrowException(); +} + +void TaskManager::storeException() +{ + std::lock_guard l(mExceptionMutex); + mException = std::current_exception(); +} + +void TaskManager::rethrowException() +{ + std::lock_guard l(mExceptionMutex); + if (mException) + std::rethrow_exception(mException); +} + +void TaskManager::executeCpuTask(CpuTask&& task) +{ + try + { + task(); + } + catch (...) + { + storeException(); + } +} + +} // namespace Falcor diff --git a/Source/Falcor/Utils/TaskManager.h b/Source/Falcor/Utils/TaskManager.h new file mode 100644 index 000000000..6aecff674 --- /dev/null +++ b/Source/Falcor/Utils/TaskManager.h @@ -0,0 +1,84 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 + +#include +#include +#include +#include +#include +#include + +namespace Falcor +{ +class RenderContext; +class FALCOR_API TaskManager +{ +public: + using CpuTask = std::function; + using GpuTask = std::function; + +public: + TaskManager(bool startPaused = false); + + /// Adds a CPU only task to the manager, if unpaused, the task starts right away + void addTask(CpuTask&& task); + /// Adds a GPU task to the manager, GPU tasks only start in the finish call and are sequential + void addTask(GpuTask&& task); + + /// Unpauses and waits for all tasks to finish. + /// The renderContext might be needed even if the TaskManager contains no GPU tasks, + /// as those could be spawned from the CPU tasks + void finish(RenderContext* renderContext); + +private: + /// Thread safe way to store an exception + void storeException(); + /// Thread safe way to retrow a stored exception + void rethrowException(); + /// CPU task execution wrapped so it stores exception if the task throws + void executeCpuTask(CpuTask&& task); + +private: + BS::thread_pool mThreadPool; + std::atomic_size_t mCurrentlyRunning{0}; + std::atomic_size_t mCurrentlyScheduled{0}; + + std::mutex mTaskMutex; + std::condition_variable mGpuTaskCond; + std::vector mGpuTasks; + + std::mutex mExceptionMutex; + std::exception_ptr mException; +}; + +} // namespace Falcor diff --git a/Source/Modules/USDUtils/CMakeLists.txt b/Source/Modules/USDUtils/CMakeLists.txt index eb17dc65b..68a28c534 100644 --- a/Source/Modules/USDUtils/CMakeLists.txt +++ b/Source/Modules/USDUtils/CMakeLists.txt @@ -9,9 +9,9 @@ target_sources(USDUtils PRIVATE PreviewSurfaceConverter/SampleTexture.slang PreviewSurfaceConverter/StandardMaterialSpec.h - Tessellator/IndexedVector.h Tessellator/Tessellation.cpp Tessellator/Tessellation.h + Tessellator/UsdIndexedVector.h USDHelpers.h USDScene1Utils.cpp diff --git a/Source/Modules/USDUtils/Tessellator/Tessellation.cpp b/Source/Modules/USDUtils/Tessellator/Tessellation.cpp index 1ad503d91..245be8a60 100644 --- a/Source/Modules/USDUtils/Tessellator/Tessellation.cpp +++ b/Source/Modules/USDUtils/Tessellator/Tessellation.cpp @@ -29,7 +29,7 @@ #include "Core/Error.h" #include "Utils/Logger.h" #include "Utils/Math/FNVHash.h" -#include "IndexedVector.h" +#include "UsdIndexedVector.h" #include #include @@ -141,7 +141,7 @@ class MeshIndexer private: TfToken mUVInterp; - IndexedVector mPositionSet; + UsdIndexedVector mPositionSet; VtIntArray mIndices; VtVec3fArray mPositions; VtVec3fArray mNormals; @@ -411,7 +411,7 @@ UsdMeshData tessellate( float const* uvData = (float*)baseMesh.uvs.data(); TfToken uvInterp = uvData != nullptr ? baseMesh.uvInterp : UsdGeomTokens->none; - IndexedVector indexedUVSet; + UsdIndexedVector indexedUVSet; if (baseMesh.uvs.size() > 0 && uvInterp == UsdGeomTokens->faceVarying) { diff --git a/Source/Modules/USDUtils/Tessellator/UsdIndexedVector.h b/Source/Modules/USDUtils/Tessellator/UsdIndexedVector.h new file mode 100644 index 000000000..a2ee15d01 --- /dev/null +++ b/Source/Modules/USDUtils/Tessellator/UsdIndexedVector.h @@ -0,0 +1,97 @@ +/*************************************************************************** + # Copyright (c) 2015-23, 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 +{ + +/** + * @brief Class to convert vector of possibly-duplicate items to a vector of indices into a set of unique data items. + * + * @tparam T Underlying type + * @tparam I Index value type + * @tparam H Hash object on type T, used to determine data item equivalence + */ +template> +class UsdIndexedVector +{ +public: + /** + * @brief Append data item. + * @param[in] v Data item to append + */ + void append(const T& v) + { + uint32_t idx; + append(v, idx); + } + + /** + * @brief Append data item. + * + * @param[in] v Data item to append + * @param[out] idx Index of the unique item corresponding to v + * @return True if @p v was newly inserted into the set of unique data item + */ + bool append(const T& v, uint32_t& outIdx) + { + bool insertedNew = false; + auto iter = mIndexMap.find(v); + if (iter == mIndexMap.end()) + { + iter = mIndexMap.insert(std::make_pair(v, I(mValues.size()))).first; + outIdx = mValues.size(); + mValues.push_back(v); + insertedNew = true; + } + else + { + outIdx = iter->second; + } + mIndices.push_back(iter->second); + return insertedNew; + } + /** + * @brief Get the set of unique data items. + */ + const pxr::VtArray& getValues() const { return mValues; } + + /** + * @brief Get the ordered list of item indices. + */ + const pxr::VtArray& getIndices() const { return mIndices; } + +private: + std::unordered_map mIndexMap; + pxr::VtArray mValues; + pxr::VtArray mIndices; +}; +} // namespace Falcor diff --git a/Source/Mogwai/Mogwai.cpp b/Source/Mogwai/Mogwai.cpp index 773f483ce..3849a71b6 100644 --- a/Source/Mogwai/Mogwai.cpp +++ b/Source/Mogwai/Mogwai.cpp @@ -35,7 +35,7 @@ #include "RenderGraph/RenderPassStandardFlags.h" #include "Utils/Scripting/Scripting.h" #include "Utils/Timing/TimeReport.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include diff --git a/Source/Mogwai/Mogwai.h b/Source/Mogwai/Mogwai.h index 3dbaaa2b4..ff0de8453 100644 --- a/Source/Mogwai/Mogwai.h +++ b/Source/Mogwai/Mogwai.h @@ -67,7 +67,7 @@ namespace Mogwai virtual void setActiveGraph(RenderGraph* pGraph) {}; virtual void removeGraph(RenderGraph* pGraph) {}; virtual void activeGraphChanged(RenderGraph* pNewGraph, RenderGraph* pPrevGraph) {}; - virtual void onOptionsChange(const SettingsProperties& settings){} + virtual void onOptionsChange(const Settings::Options& options){} protected: Extension(Renderer* pRenderer, const std::string& name) : mpRenderer(pRenderer), mName(name) {} diff --git a/Source/Mogwai/MogwaiScripting.cpp b/Source/Mogwai/MogwaiScripting.cpp index 25612dc9e..1acfc7304 100644 --- a/Source/Mogwai/MogwaiScripting.cpp +++ b/Source/Mogwai/MogwaiScripting.cpp @@ -31,8 +31,9 @@ #include "RenderGraph/RenderGraphImportExport.h" #include "Utils/Scripting/Scripting.h" #include "Utils/Scripting/ScriptWriter.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include +#include namespace Mogwai { @@ -209,6 +210,10 @@ namespace Mogwai { r->getSettings().addFilteredAttributes(d); }, "dict"_a = pybind11::dict()); + renderer.def("addFilteredAttributes", [](Renderer* r, pybind11::list l = pybind11::list{0}) + { + r->getSettings().addFilteredAttributes(l); + }, "list"_a = pybind11::list()); renderer.def("clearOptions", [](Renderer* r) { r->getSettings().clearOptions(); diff --git a/Source/Mogwai/MogwaiSettings.cpp b/Source/Mogwai/MogwaiSettings.cpp index 948212a2f..a10698651 100644 --- a/Source/Mogwai/MogwaiSettings.cpp +++ b/Source/Mogwai/MogwaiSettings.cpp @@ -29,7 +29,7 @@ #include "MogwaiSettings.h" #include "Core/Program/ProgramManager.h" #include "Utils/Scripting/Console.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include #include @@ -404,17 +404,14 @@ namespace Mogwai return false; } - void MogwaiSettings::onOptionsChange(const SettingsProperties& options) + void MogwaiSettings::onOptionsChange(const Settings::Options& options) { - if (auto local = options.get("MogwaiSettings")) - { - mAutoHideMenu = local->get("mAutoHideMenu", mAutoHideMenu); - mShowFps = local->get("mShowFps", mShowFps); - mShowGraphUI = local->get("mShowGraphUI", mShowGraphUI); - mShowConsole = local->get("mShowConsole", mShowConsole); - mShowTime = local->get("mShowTime", mShowTime); - mShowWinSize = local->get("mShowWinSize", mShowWinSize); - } + mAutoHideMenu = options.get("MogwaiSettings:mAutoHideMenu", mAutoHideMenu); + mShowFps = options.get("MogwaiSettings:mShowFps", mShowFps); + mShowGraphUI = options.get("MogwaiSettings:mShowGraphUI", mShowGraphUI); + mShowConsole = options.get("MogwaiSettings:mShowConsole", mShowConsole); + mShowTime = options.get("MogwaiSettings:mShowTime", mShowTime); + mShowWinSize = options.get("MogwaiSettings:mShowWinSize", mShowWinSize); } MogwaiSettings::UniquePtr MogwaiSettings::create(Renderer* pRenderer) diff --git a/Source/Mogwai/MogwaiSettings.h b/Source/Mogwai/MogwaiSettings.h index 6fd2ecba5..6fc1349cc 100644 --- a/Source/Mogwai/MogwaiSettings.h +++ b/Source/Mogwai/MogwaiSettings.h @@ -43,7 +43,7 @@ namespace Mogwai bool keyboardEvent(const KeyboardEvent& e) override; bool gamepadEvent(const GamepadEvent& e) override; - void onOptionsChange(const SettingsProperties& settings) override; + void onOptionsChange(const Settings::Options& settings) override; private: MogwaiSettings(Renderer* pRenderer) : Extension(pRenderer, "Settings") {} diff --git a/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp b/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp index 97c98b490..f2356afc7 100644 --- a/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp +++ b/Source/RenderPasses/BSDFOptimizer/BSDFOptimizer.cpp @@ -190,7 +190,9 @@ void BSDFOptimizer::setScene(RenderContext* pRenderContext, const ref& pS mpViewerPass = ComputePass::create(mpDevice, desc, defines); } - mpSceneGradients = std::make_unique(mpDevice, uint2(SerializedMaterialParams::kParamCount, 0), uint2(64, 0)); + mpSceneGradients = std::make_unique( + mpDevice, std::vector({{GradientType::Material, SerializedMaterialParams::kParamCount, 64}}) + ); // Prepare initial and reference BSDF parameters for optimization. mInitBSDFParams = mpScene->getMaterial(MaterialID(mParams.initMaterialID))->serializeParams(); diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp b/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp index 322959450..cfd145f8a 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.cpp @@ -37,6 +37,8 @@ std::string getModeDesc(SceneDebuggerMode mode) { switch (mode) { + case SceneDebuggerMode::FlatShaded: + return "Flat shaded"; // Geometry case SceneDebuggerMode::HitType: return "Hit type in pseudocolor"; @@ -70,12 +72,8 @@ std::string getModeDesc(SceneDebuggerMode mode) case SceneDebuggerMode::TexCoords: return "Texture coordinates in RG color wrapped to [0,1]"; // Material properties - case SceneDebuggerMode::GuideNormal: - return "Guide normal in RGB color"; - case SceneDebuggerMode::Roughness: - return "Material roughness estimate"; - case SceneDebuggerMode::FlatShaded: - return "Flat shaded"; + case SceneDebuggerMode::BSDFProperties: + return "BSDF properties"; default: FALCOR_UNREACHABLE(); return ""; @@ -238,11 +236,16 @@ void SceneDebugger::renderUI(Gui::Widgets& widget) widget.dropdown("Mode", reinterpret_cast(mParams.mode)); widget.tooltip("Selects visualization mode"); + if (mParams.mode == (uint32_t)SceneDebuggerMode::BSDFProperties) + { + widget.dropdown("BSDF property", reinterpret_cast(mParams.bsdfProperty)); + widget.var("BSDF index", mParams.bsdfIndex, 0u, 15u, 1u); + } + widget.checkbox("Clamp to [0,1]", mParams.clamp); widget.tooltip("Clamp pixel values to [0,1] before output."); if ((SceneDebuggerMode)mParams.mode == SceneDebuggerMode::FaceNormal || - (SceneDebuggerMode)mParams.mode == SceneDebuggerMode::GuideNormal || (SceneDebuggerMode)mParams.mode == SceneDebuggerMode::ShadingNormal || (SceneDebuggerMode)mParams.mode == SceneDebuggerMode::ShadingTangent || (SceneDebuggerMode)mParams.mode == SceneDebuggerMode::ShadingBitangent || diff --git a/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang b/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang index 6d0507158..4ccf82cc6 100644 --- a/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang +++ b/Source/RenderPasses/SceneDebugger/SceneDebugger.cs.slang @@ -181,10 +181,6 @@ struct SceneDebugger // Load shading data. const ShadingData sd = gScene.materials.prepareShadingData(v, materialID, -dir, lod); - // Create material instance and query its properties. - let mi = gScene.materials.getMaterialInstance(sd, lod); - let bsdfProperties = mi.getProperties(sd); - // Write pixel data for the selected pixel. if (all(pixel == params.selectedPixel)) { @@ -217,6 +213,14 @@ struct SceneDebugger 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); + } // Geometry case SceneDebuggerMode::HitType: return pseudocolor(uint(hit.getType())); @@ -267,18 +271,34 @@ struct SceneDebugger case SceneDebuggerMode::TexCoords: return float3(frac(remapVector(sd.uv)), 0.f); // Material properties - case SceneDebuggerMode::GuideNormal: - return remapVector(bsdfProperties.guideNormal); - case SceneDebuggerMode::Roughness: - return float3(bsdfProperties.roughness); - case SceneDebuggerMode::FlatShaded: + case SceneDebuggerMode::BSDFProperties: { - // 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); + // Create material instance and query its properties. + let mi = gScene.materials.getMaterialInstance(sd, lod); + let bsdfProperties = mi.getProperties(sd); + switch ((SceneDebuggerBSDFProperty)params.bsdfProperty) + { + case SceneDebuggerBSDFProperty::Emission: + return bsdfProperties.emission; + case SceneDebuggerBSDFProperty::Roughness: + return float3(bsdfProperties.roughness); + case SceneDebuggerBSDFProperty::GuideNormal: + return remapVector(bsdfProperties.guideNormal); + case SceneDebuggerBSDFProperty::DiffuseReflectionAlbedo: + return bsdfProperties.diffuseReflectionAlbedo; + case SceneDebuggerBSDFProperty::DiffuseTransmissionAlbedo: + return bsdfProperties.diffuseTransmissionAlbedo; + case SceneDebuggerBSDFProperty::SpecularReflectionAlbedo: + return bsdfProperties.specularReflectionAlbedo; + case SceneDebuggerBSDFProperty::SpecularTransmissionAlbedo: + return bsdfProperties.specularTransmissionAlbedo; + case SceneDebuggerBSDFProperty::SpecularReflectance: + return bsdfProperties.specularReflectance; + case SceneDebuggerBSDFProperty::IsTransmissive: + return bsdfProperties.isTransmissive ? float3(0, 1, 0) : float3(1, 0, 0); + } } + default: // Should not happen. return float3(1, 0, 0); diff --git a/Source/RenderPasses/SceneDebugger/SharedTypes.slang b/Source/RenderPasses/SceneDebugger/SharedTypes.slang index 42318edb6..b15d92396 100644 --- a/Source/RenderPasses/SceneDebugger/SharedTypes.slang +++ b/Source/RenderPasses/SceneDebugger/SharedTypes.slang @@ -32,6 +32,7 @@ BEGIN_NAMESPACE_FALCOR enum class SceneDebuggerMode : uint32_t { + FlatShaded, // Geometry HitType, InstanceID, @@ -49,14 +50,13 @@ enum class SceneDebuggerMode : uint32_t BackfacingShadingNormal, TexCoords, // Material properties - GuideNormal, - Roughness, - FlatShaded, + BSDFProperties, }; FALCOR_ENUM_INFO( SceneDebuggerMode, { + { SceneDebuggerMode::FlatShaded, "FlatShaded" }, // Geometry { SceneDebuggerMode::HitType, "HitType" }, { SceneDebuggerMode::InstanceID, "InstanceID" }, @@ -74,19 +74,49 @@ FALCOR_ENUM_INFO( { SceneDebuggerMode::BackfacingShadingNormal, "BackfacingShadingNormal" }, { SceneDebuggerMode::TexCoords, "TexCoords" }, // Material properties - { SceneDebuggerMode::GuideNormal, "GuideNormal" }, - { SceneDebuggerMode::Roughness, "Roughness" }, - { SceneDebuggerMode::FlatShaded, "FlatShaded" }, + { SceneDebuggerMode::BSDFProperties, "BSDFProperties" }, } ); FALCOR_ENUM_REGISTER(SceneDebuggerMode); +enum class SceneDebuggerBSDFProperty : uint32_t +{ + Emission, + Roughness, + GuideNormal, + DiffuseReflectionAlbedo, + DiffuseTransmissionAlbedo, + SpecularReflectionAlbedo, + SpecularTransmissionAlbedo, + SpecularReflectance, + IsTransmissive, +}; + +FALCOR_ENUM_INFO( + SceneDebuggerBSDFProperty, + { + { SceneDebuggerBSDFProperty::Emission, "Emission" }, + { SceneDebuggerBSDFProperty::Roughness, "Roughness" }, + { SceneDebuggerBSDFProperty::GuideNormal, "GuideNormal" }, + { SceneDebuggerBSDFProperty::DiffuseReflectionAlbedo, "DiffuseReflectionAlbedo" }, + { SceneDebuggerBSDFProperty::DiffuseTransmissionAlbedo, "DiffuseTransmissionAlbedo" }, + { SceneDebuggerBSDFProperty::SpecularReflectionAlbedo, "SpecularReflectionAlbedo" }, + { SceneDebuggerBSDFProperty::SpecularTransmissionAlbedo, "SpecularTransmissionAlbedo" }, + { SceneDebuggerBSDFProperty::SpecularReflectance, "SpecularReflectance" }, + { SceneDebuggerBSDFProperty::IsTransmissive, "IsTransmissive" }, + } +); +FALCOR_ENUM_REGISTER(SceneDebuggerBSDFProperty); + struct SceneDebuggerParams { uint mode = (uint)SceneDebuggerMode::FaceNormal; ///< Current visualization mode. See SceneDebuggerMode. uint2 frameDim = { 0, 0 }; uint frameCount = 0; + uint bsdfProperty = 0; ///< Current BSDF property. See SceneDebuggerBSDFProperty. + uint bsdfIndex = 0; ///< Current BSDF layer index. + uint2 selectedPixel = { 0, 0 }; ///< The currently selected pixel for readback. int flipSign = false; ///< Flip sign before visualization. int remapRange = true; ///< Remap valid range to [0,1] before output. diff --git a/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang b/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang index e3f82cf41..5d28c41b4 100644 --- a/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang +++ b/Source/RenderPasses/WARDiffPathTracer/PTUtils.slang @@ -119,10 +119,12 @@ ShadingData loadShadingData( // Overwrite some fields to enable auto-diff. sd.V = -rayDir; bool valid; - sd.frame = ShadingFrame::createSafe(isect.normalW, v.tangentW, valid); + float4 tangentW = float4(isect.tangentW, v.tangentW.w); + sd.frame = ShadingFrame::createSafe(isect.normalW, tangentW, valid); // Set offset value for the material gradient propagation. sd.materialGradOffset = materialID * DiffMaterialData::kMaterialParamCount; + sd.threadID = sceneQuery.gradInfo.pixelID; return sd; } @@ -152,7 +154,7 @@ bool generateScatterRay( { // Sample material. BSDFSample bsdfSample = {}; - if (mi.sample(sd, sg, bsdfSample, kUseBSDFSampling)) + if (mi.sample(sd, sg, bsdfSample, kUseBSDFSampling) && bsdfSample.pdf > 0.f) { ray.direction = bsdfSample.wo; ray.origin = computeRayOrigin(isect.posW, ((sd.frontFacing) ? sd.faceN : -sd.faceN)); @@ -163,14 +165,14 @@ bool generateScatterRay( pathData.thp *= weight; } - float3 weight = mi.evalAD(diffData, sd, ray.direction, sg) / bsdfSample.pdf; - pathData.thp *= weight; + float3 bsdfWeight = mi.evalAD(diffData, sd, ray.direction, sg) / bsdfSample.pdf; + pathData.thp *= bsdfWeight; // Save normal and pdf for MIS. pathData.normal = sd.getOrientedFaceNormal(); pathData.pdf = bsdfSample.pdf; - return any(weight > 0.f); + return any(pathData.thp > 0.f); } return false; @@ -276,10 +278,12 @@ void handleHit( // Create differentiable material instance. DiffMaterialData diffData; let mi = getDiffMaterialInstance(diffData, sd, lod); + BSDFProperties bsdfProperties = mi.getProperties(sd); // Add emitted light. const bool isPrimaryHit = pathData.length == 0; - if (isPrimaryHit || kUseEmissiveLights && (!kUseNEE || kUseMIS)) + bool computeEmissive = isPrimaryHit || kUseEmissiveLights && (!kUseNEE || kUseMIS); + if (computeEmissive && any(bsdfProperties.emission > 0.f)) { float misWeight = 1.f; @@ -302,7 +306,7 @@ void handleHit( } // TODO: support differentiable for the emission of lights. - pathData.radiance += pathData.thp * mi.getProperties(sd).emission * misWeight; + pathData.radiance += pathData.thp * bsdfProperties.emission * misWeight; } // Check whether to terminate based on max depth. diff --git a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp index fdcb2e931..d09b59d8a 100644 --- a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp +++ b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.cpp @@ -624,6 +624,7 @@ void WARDiffPathTracer::tracePass(RenderContext* pRenderContext, const RenderDat var["gDiffPTData"] = mpDiffPTBlock; var["gDiffDebug"].setBlob(mDiffDebugParams); + var["gInvOpt"].setBlob(mInvOptParams); var["dLdI"] = mpdLdI; if (mpSceneGradients) mpSceneGradients->bindShaderData(var["gSceneGradients"]); @@ -686,6 +687,14 @@ DefineList WARDiffPathTracer::StaticParams::getDefines(const WARDiffPathTracer& return defines; } +void WARDiffPathTracer::setDiffDebugParams(DiffVariableType varType, uint2 id, uint32_t offset, float4 grad) +{ + mDiffDebugParams.varType = varType; + mDiffDebugParams.id = id; + mDiffDebugParams.offset = offset; + mDiffDebugParams.grad = grad; +} + void WARDiffPathTracer::registerBindings(pybind11::module& m) { if (!pybind11::hasattr(m, "DiffMode")) @@ -701,12 +710,7 @@ void WARDiffPathTracer::registerBindings(pybind11::module& m) pass.def_property("scene_gradients", &WARDiffPathTracer::getSceneGradients, &WARDiffPathTracer::setSceneGradients); pass.def_property("run_backward", &WARDiffPathTracer::getRunBackward, &WARDiffPathTracer::setRunBackward); pass.def_property("dL_dI", &WARDiffPathTracer::getdLdI, &WARDiffPathTracer::setdLdI); -} -void WARDiffPathTracer::setDiffDebugParams(DiffVariableType varType, uint2 id, uint32_t offset, float4 grad) -{ - mDiffDebugParams.varType = varType; - mDiffDebugParams.id = id; - mDiffDebugParams.offset = offset; - mDiffDebugParams.grad = grad; + auto setMeshToOptimize = [&](WARDiffPathTracer& self, uint32_t meshID) { self.setInvOptParams(meshID); }; + pass.def("set_mesh_to_optimize", setMeshToOptimize); } diff --git a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.h b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.h index 939f6bc08..ecff97be5 100644 --- a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.h +++ b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.h @@ -78,6 +78,7 @@ class WARDiffPathTracer : public RenderPass void setdLdI(const ref& buf) { mpdLdI = buf; } void setDiffDebugParams(DiffVariableType varType, uint2 id, uint32_t offset, float4 grad); + void setInvOptParams(uint32_t meshID) { mInvOptParams.meshID = meshID; } private: struct TracePass @@ -172,6 +173,8 @@ class WARDiffPathTracer : public RenderPass StaticParams mStaticParams; /// Differentiable rendering debug parameters. DiffDebugParams mDiffDebugParams; + /// Inverse rendering optimization parameters. + InverseOptimizationParams mInvOptParams; /// Switch to enable/disable the path tracer. When disabled the pass outputs are cleared. bool mEnabled = true; diff --git a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.rt.slang b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.rt.slang index d92ac6ec2..fda8209c6 100644 --- a/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.rt.slang +++ b/Source/RenderPasses/WARDiffPathTracer/WARDiffPathTracer.rt.slang @@ -35,7 +35,8 @@ import Utils.Debug.PixelDebug; import DiffRendering.SharedTypes; import DiffRendering.DiffSceneIO; import DiffRendering.DiffSceneQuery; -import DiffRendering.DiffDebug; +import DiffRendering.DiffDebugParams; +import DiffRendering.InverseOptimizationParams; import PTUtils; import WarpedAreaReparam; @@ -145,10 +146,11 @@ float3 tracePaths(SceneQueryAD sceneQuery, uint2 pixel) void rayGen() { uint2 pixel = DispatchRaysIndex().xy; + uint pixelID = pixel.y * gDiffPTData.params.frameDim.x + pixel.x; printSetPixel(pixel); DiffSceneIO diffSceneIO; - SceneQueryAD sceneQuery = SceneQueryAD(diffSceneIO, SceneGradientInfo(SceneGradientFlag.make(GradientMode.Scene), pixel)); + SceneQueryAD sceneQuery = SceneQueryAD(diffSceneIO, SceneGradientInfo(SceneGradientFlag.make(GradientMode.Scene), pixel, pixelID)); if (kDiffMode == DiffMode::Primal) { diff --git a/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cpp b/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cpp index f3d9fca39..b663093ac 100644 --- a/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cpp +++ b/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cpp @@ -60,7 +60,10 @@ void testDiffPBRTDiffuse(GPUUnitTestContext& ctx, const BsdfConfig& bsdfConfig) // Create scene gradients. const uint32_t gradDim = 3; - std::unique_ptr pSceneGradients = std::make_unique(ctx.getDevice(), uint2(gradDim), uint2(1)); + std::unique_ptr pSceneGradients = std::make_unique( + ctx.getDevice(), + std::vector({{GradientType::Material, gradDim, 1}, {GradientType::MeshNormal, gradDim, 1}}) + ); // Create program. ProgramDesc desc; diff --git a/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cs.slang b/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cs.slang index 47c42f9e8..45e1524b6 100644 --- a/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cs.slang +++ b/Source/Tools/FalcorTest/Tests/DiffRendering/Material/DiffMaterialTests.cs.slang @@ -77,8 +77,8 @@ float3 evalAD_PBRTDiffuse(uint2 pixelID) UniformSampleGenerator sg = UniformSampleGenerator(pixelID, 0); // Setup differentiable wo. - uint hashIndex = hashFunction(sd.threadID, gSceneGradients.getHashSize(GradientType::Geometry)); - GradientIOWrapper gradIO = GradientIOWrapper(GradientType::Geometry, sd.geometryGradOffset, hashIndex); + uint hashIndex = hashFunction(sd.threadID, gSceneGradients.getHashSize(GradientType::MeshNormal)); + GradientIOWrapper gradIO = GradientIOWrapper(GradientType::MeshNormal, sd.geometryGradOffset, hashIndex); float3 wo = gradIO.getFloat(gWo, 0); float3 value = mi.evalAD(diffData, sd, wo, sg); @@ -94,6 +94,6 @@ void testDiffPBRTDiffuse(uint3 threadID: SV_DispatchThreadID) for (uint i = 0; i < 3; i++) { materialGrad[i] = gSceneGradients.getGrad(GradientType::Material, i, 0); - geometryGrad[i] = gSceneGradients.getGrad(GradientType::Geometry, i, 0); + geometryGrad[i] = gSceneGradients.getGrad(GradientType::MeshNormal, i, 0); } } diff --git a/Source/Tools/FalcorTest/Tests/DiffRendering/SceneGradientsTest.cpp b/Source/Tools/FalcorTest/Tests/DiffRendering/SceneGradientsTest.cpp index 1836f25d2..ef6cfe0d6 100644 --- a/Source/Tools/FalcorTest/Tests/DiffRendering/SceneGradientsTest.cpp +++ b/Source/Tools/FalcorTest/Tests/DiffRendering/SceneGradientsTest.cpp @@ -46,7 +46,8 @@ void testAggregateGradients(GPUUnitTestContext& ctx, const uint32_t hashSize) ref pDevice = ctx.getDevice(); RenderContext* pRenderContext = pDevice->getRenderContext(); - std::unique_ptr pSceneGradients = std::make_unique(pDevice, uint2(gradDim), uint2(hashSize)); + std::unique_ptr pSceneGradients = + std::make_unique(pDevice, std::vector({{GradientType::Material, gradDim, hashSize}})); pSceneGradients->clearGrads(pRenderContext, GradientType::Material); ctx.createProgram(kShaderFile, "atomicAdd"); diff --git a/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp b/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp index 70e746b74..0db21170a 100644 --- a/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp +++ b/Source/Tools/FalcorTest/Tests/Utils/SettingsTests.cpp @@ -26,7 +26,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **************************************************************************/ #include "Testing/UnitTest.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include "Utils/Scripting/ScriptBindings.h" #include "Utils/Scripting/Scripting.h" @@ -103,7 +103,7 @@ CPU_TEST(Settings_OptionsIntBool) Settings settings; settings.addOptions(pyDict); - SettingsProperties options = settings.getOptions(); + Settings::Options options = settings.getOptions(); EXPECT_EQ(options.get("TrueAsBool", false), true); EXPECT_EQ(options.get("TrueAsBool", 0), 1); @@ -124,24 +124,7 @@ CPU_TEST(Settings_OptionsNesting) Settings settings; settings.addOptions(pyDict); - SettingsProperties options = settings.getOptions(); - - { - SettingsProperties mogwai = options.get("mogwai", SettingsProperties()); - EXPECT_EQ(mogwai.get("value", 0), 17); - } - - EXPECT(options.get("mogwai")); - EXPECT(!options.get("user")); - - if (auto local = options.get("mogwai")) - { - EXPECT_EQ(local->get("value", 0), 17); - } - else - { - EXPECT(false); // never get here - } + const Settings::Options& options = settings.getOptions(); EXPECT_EQ(options.get("mogwai:value", 0), 17); } @@ -157,7 +140,7 @@ CPU_TEST(Settings_OptionsTypes) Settings settings; settings.addOptions(pyDict); - SettingsProperties options = settings.getOptions(); + const Settings::Options& options = settings.getOptions(); EXPECT_EQ(options.get("string", std::string()), "string"); EXPECT_EQ(options.get("float", 0.f), 1.f); @@ -172,9 +155,9 @@ CPU_TEST(Settings_OptionsTypes) EXPECT_EQ(result[0], validTuple[0]); EXPECT_EQ(result[1], validTuple[1]); - EXPECT_THROW_AS(options.get("string", int(3)), Falcor::SettingsProperties::TypeError); - EXPECT_THROW_AS(options.get("int", std::string("test")), Falcor::SettingsProperties::TypeError); - EXPECT_THROW_AS(options.get("int[2]", float(0.f)), Falcor::SettingsProperties::TypeError); + EXPECT_THROW_AS(options.get("string", int(3)), Falcor::Settings::TypeError); + EXPECT_THROW_AS(options.get("int", std::string("test")), Falcor::Settings::TypeError); + EXPECT_THROW_AS(options.get("int[2]", float(0.f)), Falcor::Settings::TypeError); } CPU_TEST(Settings_OptionsOverride) @@ -188,7 +171,7 @@ CPU_TEST(Settings_OptionsOverride) settings.addOptions(pyDict); } - SettingsProperties options = settings.getOptions(); + Settings::Options options = settings.getOptions(); EXPECT_EQ(options.get("mogwai:value", 0), 17); @@ -321,6 +304,33 @@ CPU_TEST(Settings_AttributeAssign) EXPECT_EQ(shapes[3].multiplyEmission, 3); } +CPU_TEST(Settings_AttributeFilters) +{ + pybind11::list pyList; + { + pybind11::dict pyDict; + pyDict["regex"] = ".*"; + pyDict["attributes"] = pybind11::dict(); + pyDict["attributes"]["curves"] = pybind11::dict(); + pyDict["attributes"]["curves"]["ShadingRate"] = 5.f; + pyList.append(pyDict); + } + { + pybind11::dict pyDict; + pyDict["regex"] = "/World/Tiger_Fur.*"; + pyDict["attributes"] = pybind11::dict(); + pyDict["attributes"]["curves"] = pybind11::dict(); + pyDict["attributes"]["curves"]["ShadingRate"] = 0.1f; + pyList.append(pyDict); + } + + Settings settings; + settings.addFilteredAttributes(pyList); + + EXPECT_EQ(settings.getAttribute("/World/Tiger_Mane/top", "curves:ShadingRate", 1.f), 5.f); + EXPECT_EQ(settings.getAttribute("/World/Tiger_Fur/back", "curves:ShadingRate", 1.f), 0.1f); +} + CPU_TEST(Settings_UpdatePathsColon) { Settings settings; diff --git a/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp b/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp index a8fa6f771..8681a4bac 100644 --- a/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp +++ b/Source/plugins/importers/PBRTImporter/PBRTImporter.cpp @@ -66,7 +66,7 @@ #include "EnvMapConverter.h" #include "Core/Error.h" #include "Core/API/Device.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include "Utils/Logger.h" #include "Utils/Timing/TimeReport.h" #include "Utils/Math/FalcorMath.h" diff --git a/Source/plugins/importers/USDImporter/ImporterContext.cpp b/Source/plugins/importers/USDImporter/ImporterContext.cpp index 037ffd006..0d14ebdcf 100644 --- a/Source/plugins/importers/USDImporter/ImporterContext.cpp +++ b/Source/plugins/importers/USDImporter/ImporterContext.cpp @@ -32,7 +32,7 @@ #include "Scene/Curves/CurveConfig.h" #include "Scene/Material/HairMaterial.h" #include "Scene/Material/StandardMaterial.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include "USDUtils/USDHelpers.h" #include "USDUtils/USDUtils.h" #include "USDUtils/USDScene1Utils.h" diff --git a/Source/plugins/importers/USDImporter/USDImporter.cpp b/Source/plugins/importers/USDImporter/USDImporter.cpp index 6b96393c6..91de8aa52 100644 --- a/Source/plugins/importers/USDImporter/USDImporter.cpp +++ b/Source/plugins/importers/USDImporter/USDImporter.cpp @@ -29,7 +29,7 @@ #include "ImporterContext.h" #include "Core/Platform/OS.h" #include "Utils/Timing/TimeReport.h" -#include "Utils/Settings.h" +#include "Utils/Settings/Settings.h" #include "Scene/Importer.h" #include "USDUtils/USDHelpers.h" diff --git a/dependencies.xml b/dependencies.xml index 6b3b00a2f..ecbfebe24 100644 --- a/dependencies.xml +++ b/dependencies.xml @@ -14,7 +14,7 @@ - + @@ -23,8 +23,8 @@ - - + + @@ -41,9 +41,6 @@ - - - diff --git a/environment.yml b/environment.yml index 8f8953593..b34d1c8f2 100644 --- a/environment.yml +++ b/environment.yml @@ -12,3 +12,4 @@ dependencies: - pip - pip: - pyexr + - largesteps diff --git a/scripts/inv-rendering/common.py b/scripts/inv-rendering/common.py index 6d4b8ddc5..972f2902e 100644 --- a/scripts/inv-rendering/common.py +++ b/scripts/inv-rendering/common.py @@ -79,7 +79,7 @@ def create_passes(testbed: falcor.Testbed, max_bounces: int, use_war: bool): def render_primal(spp: int, testbed: falcor.Testbed, passes): passes["war_diff_pt"].run_backward = 0 passes["primal_accumulate"].reset() - for i in range(spp): + for _ in range(spp): testbed.frame() img = testbed.render_graph.get_output("PrimalAccumulatePass.output").to_numpy() @@ -87,20 +87,28 @@ def render_primal(spp: int, testbed: falcor.Testbed, passes): return img -def render_grad(spp: int, testbed: falcor.Testbed, passes, dL_dI_buffer, grad_type): +def render_grad(spp: int, testbed: falcor.Testbed, passes, dL_dI_buffer): passes["war_diff_pt"].run_backward = 1 passes["war_diff_pt"].dL_dI = dL_dI_buffer scene_gradients = passes["war_diff_pt"].scene_gradients - scene_gradients.clear(testbed.device.render_context, grad_type) + scene_gradients.clear_all_grads(testbed.device.render_context) for _ in range(spp): testbed.frame() - scene_gradients.aggregate(testbed.device.render_context, grad_type) + scene_gradients.aggregate_all_grads(testbed.device.render_context) - grad_buffer = scene_gradients.get_grads_buffer(grad_type) - grad = torch.tensor([0] * (grad_buffer.size // 4), dtype=torch.float32) - grad_buffer.copy_to_torch(grad) + result = {} + grad_types = scene_gradients.get_grad_types() + for type in grad_types: + grad_buffer = scene_gradients.get_grads_buffer(type) + grad = torch.tensor([0] * (grad_buffer.size // 4), dtype=torch.float32) + grad_buffer.copy_to_torch(grad) + result[type] = grad testbed.device.render_context.wait_for_cuda() - return grad / float(spp) + + for type in result: + result[type] /= float(spp) + + return result diff --git a/scripts/inv-rendering/largesteps_optimizer.py b/scripts/inv-rendering/largesteps_optimizer.py new file mode 100644 index 000000000..fe8d0bc15 --- /dev/null +++ b/scripts/inv-rendering/largesteps_optimizer.py @@ -0,0 +1,38 @@ +import torch + +from largesteps.geometry import compute_matrix +from largesteps.optimize import AdamUniform +from largesteps.parameterize import from_differential, to_differential + + +class LargeSteps(torch.optim.Optimizer): + """ + V: A list of parameters to optimize. It contains a tensor of shape (N, 3) representing the mesh vertices. + F: An int32 tensor of shape (M, 3) representing the mesh indices. + """ + def __init__(self, V: torch.Tensor, F: torch.Tensor, lr=0.1, betas=(0.9, 0.999), lambda_value=0.1): + self.V = V[0] + self.F = F + self.M = compute_matrix(self.V, self.F, lambda_value) + self.u = to_differential(self.M, self.V.detach()).clone().detach().requires_grad_(True) + defaults = dict(F=self.F, lr=lr, betas=betas) + self.optimizer = AdamUniform([self.u], lr=lr, betas=betas) + super(LargeSteps, self).__init__(V, defaults) + + def step(self): + # Build compute graph from u to V_next + V_next = from_differential(self.M, self.u, 'Cholesky') + + # Propagate gradients from V_next to u + V_next.backward(self.V.grad) + + # Step u + self.optimizer.step() + + # Update V + V_next = from_differential(self.M, self.u, 'Cholesky') + self.V.data.copy_(V_next.data) + + def zero_grad(self): + super(LargeSteps, self).zero_grad() + self.optimizer.zero_grad() diff --git a/scripts/inv-rendering/material_optimization/diff_render_module.py b/scripts/inv-rendering/material_optimization/diff_render_module.py index a0f3c2244..91e17ad64 100644 --- a/scripts/inv-rendering/material_optimization/diff_render_module.py +++ b/scripts/inv-rendering/material_optimization/diff_render_module.py @@ -83,9 +83,12 @@ def backward(ctx, grad_output): testbed, passes, dL_dI_buffer, - falcor.GradientType.Material, ) - grad = material_utils.raw_params_to_dicts(testbed.scene, context["params"]["material_ids"], grad_raw) + grad = material_utils.raw_params_to_dicts( + testbed.scene, + context["params"]["material_ids"], + grad_raw[falcor.GradientType.Material], + ) return ( grad[falcor.MaterialType.Standard]["base_color"], diff --git a/scripts/inv-rendering/material_optimization/sphere_materials_example.py b/scripts/inv-rendering/material_optimization/sphere_materials_example.py index edcdfa50f..bd4a7ab2a 100644 --- a/scripts/inv-rendering/material_optimization/sphere_materials_example.py +++ b/scripts/inv-rendering/material_optimization/sphere_materials_example.py @@ -53,13 +53,16 @@ def __post_init__(self): self.width / self.height, ) - # Set up scene manager and gradients. + # Set up scene gradients. scene_gradients = falcor.SceneGradients.create( device=device, - grad_dim=falcor.uint2( - self.material_count * falcor.Material.PARAM_COUNT, 0 - ), - hash_size=falcor.uint2(256, 1), + grad_config_list=[ + falcor.GradConfig( + grad_type=falcor.GradientType.Material, + dim=self.material_count * falcor.Material.PARAM_COUNT, + hash_size=256, + ), + ], ) self.passes["war_diff_pt"].scene_gradients = scene_gradients diff --git a/scripts/inv-rendering/mesh_utils.py b/scripts/inv-rendering/mesh_utils.py index ef3d61ac3..6291e8101 100644 --- a/scripts/inv-rendering/mesh_utils.py +++ b/scripts/inv-rendering/mesh_utils.py @@ -47,7 +47,6 @@ def __init__( def init_falcor( self, device: falcor.Device, - scene: falcor.Scene, vertex_count: int, triangle_count: int, ): @@ -71,7 +70,7 @@ def load_from_falcor(self, testbed: falcor.Testbed, mesh_id: int): mesh = scene.get_mesh(mesh_id) self.init_falcor( - device, scene, mesh.vertex_count, mesh.triangle_count + device, mesh.vertex_count, mesh.triangle_count ) scene.get_mesh_vertices_and_indices(mesh_id, self.buffers) @@ -102,8 +101,9 @@ def update_to_falcor(self, testbed: falcor.Testbed, mesh_id: int): scene.set_mesh_vertices(mesh_id, self.buffers) def compute_shading_frame(self): - self.compute_normals() - self.compute_tangents() + normals = self.compute_normals() + tangents = self.compute_tangents() + return normals, tangents # From nvdiffrec. # Compute smooth vertex normals. @@ -134,6 +134,7 @@ def compute_normals(self): assert torch.all(torch.isfinite(v_normals)) self.v_norm = v_normals + return v_normals # From nvdiffrec. # Compute tangent space from texture map coordinates. @@ -194,3 +195,4 @@ def compute_tangents(self): assert torch.all(torch.isfinite(v_tangents)) self.v_tangent = v_tangents + return v_tangents diff --git a/scripts/inv-rendering/shape_optimization/diff_render_module.py b/scripts/inv-rendering/shape_optimization/diff_render_module.py new file mode 100644 index 000000000..c671f7b8f --- /dev/null +++ b/scripts/inv-rendering/shape_optimization/diff_render_module.py @@ -0,0 +1,100 @@ +import torch +import falcor + +import sys +import os +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import common +import mesh_utils + + +class DiffRenderFunction(torch.autograd.Function): + @staticmethod + def forward( + ctx, + mesh_positions, + mesh_normals, + mesh_tangents, + context, + ): + ctx.context = context + + testbed : falcor.Testbed = context["testbed"] + passes : dict[str, falcor.RenderPass] = context["passes"] + mesh : mesh_utils.Mesh = context["mesh"] + params = context["params"] + + # Update mesh. + mesh.v_pos = mesh_positions.detach() + mesh.v_normal = mesh_normals.detach() + mesh.v_tangent = mesh_tangents.detach() + mesh.update_to_falcor(testbed, params["mesh_id"]) + + # Falcor forward rendering. + img = common.render_primal(context["spp"]["primal"], testbed, passes) + return img.detach() + + @staticmethod + def backward(ctx, grad_output): + context = ctx.context + testbed : falcor.Testbed = context["testbed"] + passes : dict[str, falcor.RenderPass] = context["passes"] + dL_dI_buffer : falcor.Buffer = context["buffers"]["dL_dI"] + + # Set `dL_dI_buffer`. + dL_dI_buffer.from_torch(grad_output) + testbed.device.render_context.wait_for_cuda() + + # Falcor differentiable rendering. + grad_raw = common.render_grad( + context["spp"]["grad"], + testbed, + passes, + dL_dI_buffer, + ) + grad_position = grad_raw[falcor.GradientType.MeshPosition].view(-1, 3) + grad_normal = grad_raw[falcor.GradientType.MeshNormal].view(-1, 3) + grad_tangent = grad_raw[falcor.GradientType.MeshTangent].view(-1, 3) + + return ( + grad_position, + grad_normal, + grad_tangent, + None, + ) + + +class DiffRenderModule(torch.nn.Module): + def __init__( + self, + testbed, + passes, + scene, + mesh, + params, + buffers, + spp, + ): + super(DiffRenderModule, self).__init__() + self.context = dict( + testbed=testbed, + passes=passes, + scene=scene, + mesh=mesh, + params=params, + buffers=buffers, + spp=spp, + ) + + def forward( + self, + mesh_positions, + mesh_normals, + mesh_tangents, + ): + return DiffRenderFunction.apply( + mesh_positions, + mesh_normals, + mesh_tangents, + self.context, + ) diff --git a/scripts/inv-rendering/shape_optimization/run_shape_optimization.py b/scripts/inv-rendering/shape_optimization/run_shape_optimization.py new file mode 100644 index 000000000..062b125dc --- /dev/null +++ b/scripts/inv-rendering/shape_optimization/run_shape_optimization.py @@ -0,0 +1,91 @@ +import torch +import falcor +import time +import numpy as np +import pyexr as exr +import sys +import os +import dataclasses +import datetime +import glob +import argparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import common +from largesteps_optimizer import LargeSteps +from loss import compute_render_loss_L2 +from shape_example import ShapeExample + + +def main(args): + # Create torch CUDA device + device = torch.device("cuda:0") + print(device) + torch.set_default_tensor_type(torch.cuda.FloatTensor) + + # Create an inverse rendering example that optimizes sphere materials. + inv_ex = ShapeExample( + init_scene_filepath="inv_rendering_scenes/sphere_init.pyscene", + niter=1000, + max_bounces=2, + ) + + # Set up largesteps optimizer. + mesh_positions = inv_ex.params["mesh_positions"].clone().requires_grad_(True) + tri_idx = inv_ex.mesh.tri_idx + optimizer = LargeSteps([mesh_positions], tri_idx, inv_ex.lr, (0.9, 0.999), inv_ex.lambda_value) + + # Run optimization. + img_error = [] + for i_iter in range(inv_ex.niter): + optimizer.zero_grad() + loss = 0.0 + now = datetime.datetime.now() + + # Compute mesh positions, normals, and tangents. + inv_ex.mesh.v_pos = mesh_positions + mesh_normals, mesh_tangents = inv_ex.mesh.compute_shading_frame() + + # Render. + cur_img = inv_ex.diff_render( + mesh_positions, + mesh_normals, + mesh_tangents, + ) + cur_img[cur_img.isnan()] = 0.0 + + img_loss = compute_render_loss_L2(cur_img, inv_ex.ref_img) + loss += img_loss.item() + img_error.append(img_loss.item()) + + # Backpropagate gradients. + img_loss.backward() + + end = datetime.datetime.now() - now + time_iter = end.seconds + end.microseconds / 1e6 + + # Print stats + print( + "[INFO] iter = {:d}, image loss = {:.3f}, time = {:.3f}s".format( + i_iter, loss, time_iter + ) + ) + + optimizer.step() + + # Export images + if i_iter % 10 == 0 or i_iter == inv_ex.niter - 1: + if i_iter <= 50 or i_iter % 50 == 0 or i_iter == inv_ex.niter - 1: + cur_img = common.render_primal(256, inv_ex.testbed, inv_ex.passes) + exr.write( + os.path.join(inv_ex.output_dir, "iter_{:d}.exr".format(i_iter)), + cur_img.detach().cpu().numpy(), + ) + + np.savetxt(os.path.join(inv_ex.output_dir, "loss_image.txt"), img_error) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + args = parser.parse_args() + main(args) diff --git a/scripts/inv-rendering/shape_optimization/run_shape_optimization_simple.py b/scripts/inv-rendering/shape_optimization/run_shape_optimization_simple.py new file mode 100644 index 000000000..56cc13737 --- /dev/null +++ b/scripts/inv-rendering/shape_optimization/run_shape_optimization_simple.py @@ -0,0 +1,105 @@ +import torch +import falcor +import time +import numpy as np +import pyexr as exr +import sys +import os +import dataclasses +import datetime +import glob +import argparse + +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import common +from transform_utils import axis_angle_rotation +from loss import compute_render_loss_L2 +from shape_example import ShapeExample + + +def main(args): + # Create torch CUDA device + device = torch.device("cuda:0") + print(device) + torch.set_default_tensor_type(torch.cuda.FloatTensor) + + # Create an inverse rendering example that optimizes sphere materials. + inv_ex = ShapeExample( + max_bounces=2, + ) + + # Set up PyTorch optimizer. + mesh_translation = torch.zeros((3), requires_grad=True) + rotation_angle = torch.zeros((1), requires_grad=True) + optimizer = torch.optim.Adam([ + {"params": mesh_translation, "lr": 3e-4}, + {"params": rotation_angle, "lr": 3e-3}, + ], + eps=1e-6 + ) + + # Workaround to fix "If capturable=False, state_steps should not be CUDA tensors". + for param_group in optimizer.param_groups: + param_group["capturable"] = True + + # Run optimization. + img_error = [] + for i_iter in range(inv_ex.niter): + optimizer.zero_grad() + loss = 0.0 + now = datetime.datetime.now() + + # Compute mesh positions, normals, and tangents. + mesh_positions = inv_ex.params["mesh_positions"] + rotation_matrix = torch.transpose(axis_angle_rotation("Y", rotation_angle)[0], 0, 1) + mesh_positions = torch.matmul(mesh_positions, rotation_matrix) + mesh_positions = mesh_positions + mesh_translation + inv_ex.mesh.v_pos = mesh_positions + mesh_normals, mesh_tangents = inv_ex.mesh.compute_shading_frame() + + # Render. + cur_img = inv_ex.diff_render( + mesh_positions, + mesh_normals, + mesh_tangents, + ) + cur_img[cur_img.isnan()] = 0.0 + + img_loss = compute_render_loss_L2(cur_img, inv_ex.ref_img) + loss += img_loss.item() + img_error.append(img_loss.item()) + + # Backpropagate gradients. + img_loss.backward() + + print("translation:", mesh_translation) + print("rotation angle:", rotation_angle) + + end = datetime.datetime.now() - now + time_iter = end.seconds + end.microseconds / 1e6 + + # Print stats + print( + "[INFO] iter = {:d}, image loss = {:.3f}, time = {:.3f}s".format( + i_iter, loss, time_iter + ) + ) + + optimizer.step() + + # Export images + if i_iter % 10 == 0 or i_iter == inv_ex.niter - 1: + if i_iter <= 50 or i_iter % 50 == 0 or i_iter == inv_ex.niter - 1: + cur_img = common.render_primal(256, inv_ex.testbed, inv_ex.passes) + exr.write( + os.path.join(inv_ex.output_dir, "iter_{:d}.exr".format(i_iter)), + cur_img.detach().cpu().numpy(), + ) + + np.savetxt(os.path.join(inv_ex.output_dir, "loss_image.txt"), img_error) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + args = parser.parse_args() + main(args) diff --git a/scripts/inv-rendering/shape_optimization/shape_example.py b/scripts/inv-rendering/shape_optimization/shape_example.py new file mode 100644 index 000000000..2aaf7da73 --- /dev/null +++ b/scripts/inv-rendering/shape_optimization/shape_example.py @@ -0,0 +1,139 @@ +import torch +import falcor +import time +import numpy as np +import pyexr as exr +import sys +import os +import dataclasses + +CUR_DIR = os.path.dirname(__file__) +sys.path.append(os.path.join(CUR_DIR, "..")) +import common +import mesh_utils +from diff_render_module import DiffRenderModule + + +@dataclasses.dataclass +class ShapeExample: + width: int = 512 + height: int = 512 + + mesh_id: int = 6 # Indicate which mesh to optimize + max_bounces: int = 0 # Max number of indirect bounces + + niter: int = 400 # Number of iterations + lr: float = 0.01 # Learning rate + lambda_value: float = 60.0 # Lambda value for the largesteps optimizer + + spp_ref: int = 256 # Sample count for the reference images + spp_primal: int = 32 # Sample count for the primal (forward) pass + spp_grad: int = 32 # Sample count for the gradient (backward) pass + + output_dir: str = ( # Output directory + CUR_DIR + "/results/" + ) + + ref_scene_filepath: str = ( + "inv_rendering_scenes/bunny_ref.pyscene" + ) + + init_scene_filepath: str = ( + "inv_rendering_scenes/bunny_init.pyscene" + ) + + # Initialize material optimization. + def __post_init__(self): + # Set up Falcor-Python and render passes. + self.testbed = common.create_testbed([self.width, self.height]) + device = self.testbed.device + self.passes = common.create_passes(self.testbed, self.max_bounces, use_war=True) + + # Load the reference scene. + ref_scene = common.load_scene( + self.testbed, + self.ref_scene_filepath, + self.width / self.height, + ) + + # Render the reference image. + spp = { + "ref": self.spp_ref, + "primal": self.spp_primal, + "grad": self.spp_grad, + } + self.ref_img = common.render_primal(spp["ref"], self.testbed, self.passes) + print("Output ref image with shape", self.ref_img.shape) + if not os.path.exists(self.output_dir): + os.makedirs(self.output_dir) + exr.write(os.path.join(self.output_dir, "ref.exr"), self.ref_img.cpu().numpy()) + + # Load the initial scene. + init_scene = common.load_scene( + self.testbed, + self.init_scene_filepath, + self.width / self.height, + ) + + # Load the mesh. + self.mesh = mesh_utils.Mesh(); + self.mesh.load_from_falcor(self.testbed, self.mesh_id) + mesh_vertex_count = self.mesh.v_pos.shape[0] + + self.params = { + "mesh_id": self.mesh_id, + "mesh_positions": self.mesh.v_pos.detach().clone(), + } + + # Set up mesh to optimize. + self.passes["war_diff_pt"].set_mesh_to_optimize(self.mesh_id) + + # Set up scene gradients. + scene_gradients = falcor.SceneGradients.create( + device=device, + grad_config_list=[ + falcor.GradConfig( + grad_type=falcor.GradientType.MeshPosition, + dim=mesh_vertex_count * 3, + hash_size=128, + ), + falcor.GradConfig( + grad_type=falcor.GradientType.MeshNormal, + dim=mesh_vertex_count * 3, + hash_size=128, + ), + falcor.GradConfig( + grad_type=falcor.GradientType.MeshTangent, + dim=mesh_vertex_count * 3, + hash_size=128, + ), + ], + ) + self.passes["war_diff_pt"].scene_gradients = scene_gradients + + # Create Falcor buffers for Falcor-PyTorch communication. + dL_dI_buffer = device.create_structured_buffer( + struct_size=12, # float3 + element_count=self.width * self.height, + bind_flags=falcor.ResourceBindFlags.ShaderResource + | falcor.ResourceBindFlags.UnorderedAccess + | falcor.ResourceBindFlags.Shared, + ) + buffers = { + "dL_dI": dL_dI_buffer, + } + + init_img = common.render_primal(spp["ref"], self.testbed, self.passes) + print("Output init image with shape", init_img.shape) + exr.write(os.path.join(self.output_dir, "init.exr"), init_img.cpu().numpy()) + + # Set up differentiable render module. + self.diff_render = DiffRenderModule( + self.testbed, + self.passes, + init_scene, + self.mesh, + self.params, + buffers, + spp, + ) diff --git a/scripts/inv-rendering/transform_utils.py b/scripts/inv-rendering/transform_utils.py new file mode 100644 index 000000000..affd7450d --- /dev/null +++ b/scripts/inv-rendering/transform_utils.py @@ -0,0 +1,61 @@ +import torch + +# From PyTorch3D. + +def axis_angle_rotation(axis: str, angle: torch.Tensor) -> torch.Tensor: + """ + Return the rotation matrices for one of the rotations about an axis + of which Euler angles describe, for each value of the angle given. + + Args: + axis: Axis label "X" or "Y or "Z". + angle: any shape tensor of Euler angles in radians + + Returns: + Rotation matrices as tensor of shape (..., 3, 3). + """ + + cos = torch.cos(angle) + sin = torch.sin(angle) + one = torch.ones_like(angle) + zero = torch.zeros_like(angle) + + if axis == "X": + R_flat = (one, zero, zero, zero, cos, -sin, zero, sin, cos) + elif axis == "Y": + R_flat = (cos, zero, sin, zero, one, zero, -sin, zero, cos) + elif axis == "Z": + R_flat = (cos, -sin, zero, sin, cos, zero, zero, zero, one) + else: + raise ValueError("letter must be either X, Y or Z.") + + return torch.stack(R_flat, -1).reshape(angle.shape + (3, 3)) + + +def euler_angles_to_matrix(euler_angles: torch.Tensor, convention: str) -> torch.Tensor: + """ + Convert rotations given as Euler angles in radians to rotation matrices. + + Args: + euler_angles: Euler angles in radians as tensor of shape (..., 3). + convention: Convention string of three uppercase letters from + {"X", "Y", and "Z"}. + + Returns: + Rotation matrices as tensor of shape (..., 3, 3). + """ + if euler_angles.dim() == 0 or euler_angles.shape[-1] != 3: + raise ValueError("Invalid input euler angles.") + if len(convention) != 3: + raise ValueError("Convention must have 3 letters.") + if convention[1] in (convention[0], convention[2]): + raise ValueError(f"Invalid convention {convention}.") + for letter in convention: + if letter not in ("X", "Y", "Z"): + raise ValueError(f"Invalid letter {letter} in convention string.") + matrices = [ + axis_angle_rotation(c, e) + for c, e in zip(convention, torch.unbind(euler_angles, -1)) + ] + # return functools.reduce(torch.matmul, matrices) + return torch.matmul(torch.matmul(matrices[0], matrices[1]), matrices[2]) diff --git a/tests/image_tests/renderscripts/test_WARDiffPathTracer.py b/tests/image_tests/renderscripts/test_WARDiffPathTracer.py index 893601498..ef072db74 100644 --- a/tests/image_tests/renderscripts/test_WARDiffPathTracer.py +++ b/tests/image_tests/renderscripts/test_WARDiffPathTracer.py @@ -1,3 +1,7 @@ +IMAGE_TEST = { + 'tolerance': 1e-8 +} + import sys sys.path.append('..') from falcor import * diff --git a/tools/packman/bootstrap/configure.bat b/tools/packman/bootstrap/configure.bat index 6651e6c05..8f8aa66d6 100644 --- a/tools/packman/bootstrap/configure.bat +++ b/tools/packman/bootstrap/configure.bat @@ -1,4 +1,4 @@ -:: Copyright 2019 NVIDIA CORPORATION +:: Copyright 2019-2023 NVIDIA CORPORATION :: :: Licensed under the Apache License, Version 2.0 (the "License"); :: you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ :: See the License for the specific language governing permissions and :: limitations under the License. -set PM_PACKMAN_VERSION=6.42 +set PM_PACKMAN_VERSION=7.15.1 :: Specify where packman command is rooted set PM_INSTALL_PATH=%~dp0.. @@ -59,7 +59,7 @@ if defined PM_PYTHON_EXT ( goto PACKMAN ) -set PM_PYTHON_VERSION=3.7.12-windows-x86_64 +set PM_PYTHON_VERSION=3.10.5-1-windows-x86_64 set PM_PYTHON_BASE_DIR=%PM_PACKAGES_ROOT%\python set PM_PYTHON_DIR=%PM_PYTHON_BASE_DIR%\%PM_PYTHON_VERSION% set PM_PYTHON=%PM_PYTHON_DIR%\python.exe @@ -95,11 +95,16 @@ if exist "%PM_PYTHON%" ( if exist "%PM_PYTHON_DIR%" ( rd /s /q "%PM_PYTHON_DIR%" > nul ) ) -:: Perform atomic rename -rename "%TEMP_FOLDER_NAME%" "%PM_PYTHON_VERSION%" 1> nul -:: Failure during move, need to clean up and abort +:: Perform atomic move (allowing overwrite, /y) +move /y "%TEMP_FOLDER_NAME%" "%PM_PYTHON_DIR%" 1> nul +:: Verify that python.exe is now where we expect +if exist "%PM_PYTHON%" goto PACKMAN + +:: Wait a second and try again (can help with access denied weirdness) +timeout /t 1 /nobreak 1> nul +move /y "%TEMP_FOLDER_NAME%" "%PM_PYTHON_DIR%" 1> nul if %errorlevel% neq 0 ( - echo !!! Error renaming python !!! + echo !!! Error moving python %TEMP_FOLDER_NAME% -> %PM_PYTHON_DIR% !!! call :CLEAN_UP_TEMP_FOLDER goto ERROR ) @@ -114,7 +119,7 @@ if defined PM_MODULE_DIR_EXT ( set PM_MODULE=%PM_MODULE_DIR%\run.py -if exist "%PM_MODULE%" goto ENSURE_7ZA +if exist "%PM_MODULE%" goto END :: Clean out broken PM_MODULE_DIR if it exists if exist "%PM_MODULE_DIR%" ( rd /s /q "%PM_MODULE_DIR%" > nul ) @@ -137,19 +142,6 @@ if %errorlevel% neq 0 ( del "%TARGET%" -:ENSURE_7ZA -set PM_7Za_VERSION=16.02.4 -set PM_7Za_PATH=%PM_PACKAGES_ROOT%\7za\%PM_7ZA_VERSION% -if exist "%PM_7Za_PATH%" goto END -set PM_7Za_PATH=%PM_PACKAGES_ROOT%\chk\7za\%PM_7ZA_VERSION% -if exist "%PM_7Za_PATH%" goto END - -"%PM_PYTHON%" -S -s -u -E "%PM_MODULE%" pull "%PM_MODULE_DIR%\deps.packman.xml" -if %errorlevel% neq 0 ( - echo !!! Error fetching packman dependencies !!! - goto ERROR -) - goto END :ERROR_MKDIR_PACKAGES_ROOT diff --git a/tools/packman/bootstrap/install_package.py b/tools/packman/bootstrap/install_package.py index 14f8125fc..b8ae7f642 100644 --- a/tools/packman/bootstrap/install_package.py +++ b/tools/packman/bootstrap/install_package.py @@ -144,4 +144,11 @@ def install_package(package_path, install_path): if __name__ == "__main__": - install_package(sys.argv[1], sys.argv[2]) + executable_paths = os.getenv("PATH") + paths_list = executable_paths.split(os.path.pathsep) if executable_paths else [] + target_path_np = os.path.normpath(sys.argv[2]) + target_path_np_nc = os.path.normcase(target_path_np) + 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) diff --git a/tools/packman/packman b/tools/packman/packman index 2f99586c3..d93d80ebd 100755 --- a/tools/packman/packman +++ b/tools/packman/packman @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2019-2020 NVIDIA CORPORATION +# Copyright 2019-2023 NVIDIA CORPORATION # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,11 +18,13 @@ set -eu if echo ${PM_VERBOSITY-} | grep -i "debug" > /dev/null ; then set -x + PM_CURL_SILENT="" + PM_WGET_QUIET="" else PM_CURL_SILENT="-s -S" PM_WGET_QUIET="--quiet" fi -PM_PACKMAN_VERSION=6.42 +PM_PACKMAN_VERSION=7.15.1 # This is necessary for newer macOS if [ `uname` == 'Darwin' ]; then @@ -42,9 +44,13 @@ export PM_INSTALL_PATH="$(get_abs_filename "$(dirname "${BASH_SOURCE}")")" if [ -z "${PM_PACKAGES_ROOT:-}" ]; then # Set variable temporarily in this process so that the following execution will work if [ `uname` == 'Darwin' ]; then - export PM_PACKAGES_ROOT="/Library/Caches/packman" + export PM_PACKAGES_ROOT="${HOME}/Library/Application Support/packman-cache" else - export PM_PACKAGES_ROOT="/var/tmp/packman" + if [ -z "${XDG_CACHE_HOME:-}" ]; then + export PM_PACKAGES_ROOT="${HOME}/.cache/packman" + else + export PM_PACKAGES_ROOT="${XDG_CACHE_HOME}/packman" + fi fi fi @@ -81,7 +87,7 @@ install_python() { PLATFORM=`uname` PROCESSOR=`uname -m` - PYTHON_VERSION=3.7.12 + PYTHON_VERSION=3.10.5-1 if [ $PLATFORM == 'Darwin' ]; then PYTHON_PACKAGE=$PYTHON_VERSION-macos-x86_64 @@ -150,20 +156,6 @@ if [ ! -f "$PM_MODULE" ]; then fi fi -# Ensure 7za package exists: -PM_7za_VERSION=16.02.4 -export PM_7za_PATH="$PM_PACKAGES_ROOT/7za/$PM_7za_VERSION" -if [ ! -d "$PM_7za_PATH" ]; then - export PM_7za_PATH="$PM_PACKAGES_ROOT/chk/7za/$PM_7za_VERSION" - if [ ! -d "$PM_7za_PATH" ]; then - "$PM_PYTHON" -S -s -u -E "$PM_MODULE" pull "$PM_MODULE_DIR/deps.packman.xml" - if [ "$?" -ne 0 ]; then - echo "Failure while installing required 7za package" - exit 1 - fi - fi -fi - # Generate temporary file name for environment variables: PM_VAR_PATH=`mktemp -u -t tmp.$$.pmvars.XXXXXX` diff --git a/tools/packman/packman.cmd b/tools/packman/packman.cmd index c7bf3dcfa..e55b2f0e1 100644 --- a/tools/packman/packman.cmd +++ b/tools/packman/packman.cmd @@ -1,23 +1,22 @@ -:: Reset errorlevel status (don't inherit from caller) [xxxxxxxxxxx] +:: RUN_PM_MODULE must always be at the same spot for packman update to work (batch reloads file during update!) +:: [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] +:: Reset errorlevel status (don't inherit from caller) @call :ECHO_AND_RESET_ERROR -:: You can remove the call below if you do your own manual configuration of the dev machines -call "%~dp0\bootstrap\configure.bat" +:: You can remove this section if you do your own manual configuration of the dev machines +call :CONFIGURE if %errorlevel% neq 0 ( exit /b %errorlevel% ) + :: Everything below is mandatory if not defined PM_PYTHON goto :PYTHON_ENV_ERROR if not defined PM_MODULE goto :MODULE_ENV_ERROR -:: Generate temporary path for variable file -for /f "delims=" %%a in ('powershell -ExecutionPolicy ByPass -NoLogo -NoProfile ^ --File "%~dp0bootstrap\generate_temp_file_name.ps1"') do set PM_VAR_PATH=%%a +set PM_VAR_PATH_ARG= -if %1.==. ( - set PM_VAR_PATH_ARG= -) else ( - set PM_VAR_PATH_ARG=--var-path="%PM_VAR_PATH%" -) +if "%1"=="pull" goto :SET_VAR_PATH +if "%1"=="install" goto :SET_VAR_PATH +:RUN_PM_MODULE "%PM_PYTHON%" -S -s -u -E "%PM_MODULE%" %* %PM_VAR_PATH_ARG% if %errorlevel% neq 0 ( exit /b %errorlevel% ) @@ -48,9 +47,43 @@ exit /b 1 @echo Error while processing and setting environment variables! exit /b 1 +:: pad [xxxx] :ECHO_AND_RESET_ERROR @echo off if /I "%PM_VERBOSITY%"=="debug" ( @echo on ) exit /b 0 + +:SET_VAR_PATH +:: Generate temporary path for variable file +for /f "delims=" %%a in ('%PM_PYTHON% -S -s -u -E -c "import tempfile;file = tempfile.NamedTemporaryFile(mode='w+t', delete=False);print(file.name)"') do (set PM_VAR_PATH=%%a) +set PM_VAR_PATH_ARG=--var-path="%PM_VAR_PATH%" +goto :RUN_PM_MODULE + +:CONFIGURE +:: Must capture and set code page to work around issue #279, powershell invocation mutates console font +:: This issue only happens in Windows CMD shell when using 65001 code page. Some Git Bash implementations +:: don't support chcp so this workaround is a bit convoluted. +:: Test for chcp: +chcp > nul 2>&1 +if %errorlevel% equ 0 ( + for /f "tokens=2 delims=:" %%a in ('chcp') do (set PM_OLD_CODE_PAGE=%%a) +) else ( + call :ECHO_AND_RESET_ERROR +) +:: trim leading space (this is safe even when PM_OLD_CODE_PAGE has not been set) +set PM_OLD_CODE_PAGE=%PM_OLD_CODE_PAGE:~1% +if "%PM_OLD_CODE_PAGE%" equ "65001" ( + chcp 437 > nul + set PM_RESTORE_CODE_PAGE=1 +) +call "%~dp0\bootstrap\configure.bat" +set PM_CONFIG_ERRORLEVEL=%errorlevel% +if defined PM_RESTORE_CODE_PAGE ( + :: Restore code page + chcp %PM_OLD_CODE_PAGE% > nul +) +set PM_OLD_CODE_PAGE= +set PM_RESTORE_CODE_PAGE= +exit /b %PM_CONFIG_ERRORLEVEL% diff --git a/tools/packman/packmanconf.py b/tools/packman/packmanconf.py index 108a847aa..539d05625 100644 --- a/tools/packman/packmanconf.py +++ b/tools/packman/packmanconf.py @@ -34,9 +34,9 @@ def init(): """ major = sys.version_info[0] minor = sys.version_info[1] - if major != 3 or minor != 7: + if major != 3 or minor != 10: raise RuntimeError( - f"This version of packman requires Python 3.7.x, but {major}.{minor} was provided" + f"This version of packman requires Python 3.10.x, but {major}.{minor} was provided" ) conf_dir = os.path.dirname(os.path.abspath(__file__)) os.environ["PM_INSTALL_PATH"] = conf_dir @@ -55,9 +55,15 @@ def get_packages_root(conf_dir: str) -> str: root = os.path.join(drive, "packman-repo") elif platform_name == "Darwin": # macOS - root = "/Library/Caches/packman" + root = os.path.join( + os.path.expanduser("~"), "Library/Application Support/packman-cache" + ) elif platform_name == "Linux": - root = "/var/tmp/packman" + try: + cache_root = os.environ["XDG_HOME_CACHE"] + except KeyError: + cache_root = os.path.join(os.path.expanduser("~"), ".cache") + return os.path.join(cache_root, "packman") else: raise RuntimeError(f"Unsupported platform '{platform_name}'") # make sure the path exists: diff --git a/tools/packman/python.bat b/tools/packman/python.bat index ef37691b2..08df74f8a 100644 --- a/tools/packman/python.bat +++ b/tools/packman/python.bat @@ -13,9 +13,20 @@ :: limitations under the License. @echo off -setlocal +setlocal enableextensions call "%~dp0\packman" init set "PYTHONPATH=%PM_MODULE_DIR%;%PYTHONPATH%" -set PYTHONNOUSERSITE=1 -"%PM_PYTHON%" -u %* + +if not defined PYTHONNOUSERSITE ( + set PYTHONNOUSERSITE=1 +) + +REM For performance, default to unbuffered; however, allow overriding via +REM PYTHONUNBUFFERED=0 since PYTHONUNBUFFERED on windows can truncate output +REM when printing long strings +if not defined PYTHONUNBUFFERED ( + set PYTHONUNBUFFERED=1 +) + +"%PM_PYTHON%" %* \ No newline at end of file diff --git a/tools/packman/python.sh b/tools/packman/python.sh index 37c9f1b89..74328bf0d 100644 --- a/tools/packman/python.sh +++ b/tools/packman/python.sh @@ -22,11 +22,21 @@ if [ ! -f "$PACKMAN_CMD" ]; then fi source "$PACKMAN_CMD" init export PYTHONPATH="${PM_MODULE_DIR}:${PYTHONPATH}" -export PYTHONNOUSERSITE=1 + +if [ -z "${PYTHONNOUSERSITE:-}" ]; then + export PYTHONNOUSERSITE=1 +fi + +# For performance, default to unbuffered; however, allow overriding via +# PYTHONUNBUFFERED=0 since PYTHONUNBUFFERED on windows can truncate output +# when printing long strings +if [ -z "${PYTHONUNBUFFERED:-}" ]; then + export PYTHONUNBUFFERED=1 +fi # workaround for our python not shipping with certs if [[ -z ${SSL_CERT_DIR:-} ]]; then export SSL_CERT_DIR=/etc/ssl/certs/ fi -"${PM_PYTHON}" -u "$@" +"${PM_PYTHON}" "$@"