diff --git a/.gitignore b/.gitignore index cea5473..0aee38b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,13 @@ # Ignore build directories. Build/ +Build.*/ _build/ Externals/ _externals/ # Ignore script generated files Scripts/cmake/externalsConfig.cmake - +__pycache__ # Ignore IDE cache folders .vs/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..baf68c3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +# clang-format pre-commit hook configuration file. +# Should be activated before commiting code to the Aurora repository, see Doc\CodingStandards.md for more details. +repos: +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.6 + hooks: + - id: clang-format + exclude: Source/DirectX/DirectXHeaders/d3dx12.h + \ No newline at end of file diff --git a/Applications/Plasma/CMakeLists.txt b/Applications/Plasma/CMakeLists.txt index 1ba0279..0893842 100644 --- a/Applications/Plasma/CMakeLists.txt +++ b/Applications/Plasma/CMakeLists.txt @@ -30,7 +30,7 @@ add_executable(${PROJECT_NAME} "SceneContents.h" ) -# Set custom ouput properties. +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Applications" RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" diff --git a/Applications/Plasma/Camera.cpp b/Applications/Plasma/Camera.cpp index da59add..855a53b 100644 --- a/Applications/Plasma/Camera.cpp +++ b/Applications/Plasma/Camera.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/Camera.h b/Applications/Plasma/Camera.h index 3484aa4..4c8f109 100644 --- a/Applications/Plasma/Camera.h +++ b/Applications/Plasma/Camera.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/Libraries.cpp b/Applications/Plasma/Libraries.cpp index 3ca3304..58c6b23 100644 --- a/Applications/Plasma/Libraries.cpp +++ b/Applications/Plasma/Libraries.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/Loaders.h b/Applications/Plasma/Loaders.h index 2c56bc7..2231f5b 100644 --- a/Applications/Plasma/Loaders.h +++ b/Applications/Plasma/Loaders.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/OBJLoader.cpp b/Applications/Plasma/OBJLoader.cpp index c2f37aa..28261b7 100644 --- a/Applications/Plasma/OBJLoader.cpp +++ b/Applications/Plasma/OBJLoader.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,11 +13,14 @@ // limitations under the License. #include "pch.h" +#include "Aurora/Foundation/Geometry.h" #include "Loaders.h" #include "SceneContents.h" uint32_t ImageCache::whitePixels[1] = { 0xFFFFFFFF }; +#define PLASMA_HAS_TANGENTS 0 + // A simple structure describing an OBJ index, which has sub-indices for vertex channels: position, // normal, and texture coordinates. struct OBJIndex @@ -120,7 +123,8 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const { transmissionColor = vec3(1.0f); } - vec3 opacity = vec3(1.0f); // objMaterial.dissolve (see NOTE above) + vec3 emissionColor = ::sRGBToLinear(make_vec3(objMaterial.emission)); + vec3 opacity = vec3(1.0f); // objMaterial.dissolve (see NOTE above) // Load the base color image file from the "map_Kd" file path. // NOTE: Set the linearize flag, as these images typically use the sRGB color space. @@ -132,6 +136,11 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const Aurora::Path specularRoughnessImage = imageCache.getImage(objMaterial.roughness_texname, pScene, false); + // Load the emission color image file from the "map_Ke" file path. + // NOTE: Set the linearize flag, as these images typically use the sRGB color space. + Aurora::Path emissionColorImage = + imageCache.getImage(objMaterial.emissive_texname, pScene, true); + // Load the opacity image file from the "map_d" file path. // NOTE: Don't set the linearize flag, as these images typically use the linear color space. Aurora::Path opacityImage = imageCache.getImage(objMaterial.alpha_texname, pScene, false); @@ -144,17 +153,33 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const : objMaterial.normal_texname; Aurora::Path normalImage = imageCache.getImage(normalFilePath, pScene, false); - Aurora::Path materialPath = filePath + ":OBJFileMaterial-" + to_string(mtlCount++); + // Set emission (the actual light emitted) to 1.0 if there is an emission color image or the + // emission color has any positive components. + float emission = (!emissionColorImage.empty() || emissionColor.length()) ? 1.0f : 0.0f; // Create an Aurora material, assign the properties, and add the material to the list. - pScene->setMaterialProperties(materialPath, - { { "base_color", (baseColor) }, { "metalness", metalness }, - { "specular_color", (specularColor) }, { "specular_roughness", specularRoughness }, - { "specular_IOR", specularIOR }, { "transmission", transmission }, - { "transmission_color", (transmissionColor) }, { "opacity", (opacity) }, - { "base_color_image", baseColorImage }, - { "specular_roughness_image", specularRoughnessImage }, - { "opacity_image", opacityImage }, { "normal_image", normalImage } }); + // clang-format off + Aurora::Properties properties = + { + { "base_color", baseColor }, + { "metalness", metalness }, + { "specular_color", specularColor }, + { "specular_roughness", specularRoughness }, + { "specular_IOR", specularIOR }, + { "transmission", transmission }, + { "transmission_color", transmissionColor }, + { "emission", emission }, + { "emission_color", emissionColor }, + { "opacity", opacity }, + { "base_color_image", baseColorImage }, + { "specular_roughness_image", specularRoughnessImage }, + { "emission_color_image", emissionColorImage }, + { "opacity_image", opacityImage }, + { "normal_image", normalImage } + }; + // clang-format on + Aurora::Path materialPath = filePath + ":OBJFileMaterial-" + to_string(mtlCount++); + pScene->setMaterialProperties(materialPath, properties); lstMaterials.push_back(materialPath); }; @@ -178,8 +203,10 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const } hasMesh = true; - Aurora::Path sceneInstancePath = filePath + ":OBJFileInstance-" + to_string(objectCount); - Aurora::Path geomPath = filePath + ":OBJFileGeom-" + to_string(objectCount); + Aurora::Path sceneInstancePath = + filePath + "-" + shape.name + ":OBJFileInstance-" + to_string(objectCount); + Aurora::Path geomPath = + filePath + "-" + shape.name + ":OBJFileGeom-" + to_string(objectCount); objectCount++; SceneGeometryData& geometryData = sceneContents.addGeometry(geomPath); @@ -247,6 +274,32 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const } } + // Calculate the vertex count. + uint32_t vertexCount = static_cast(positions.size()) / 3; + + // Do we have tangents ? Default to false. + bool bHasTangents = false; + + // Create normals if they are not available. + if (!bHasNormals) + { + normals.resize(positions.size()); + Foundation::calculateNormals( + vertexCount, positions.data(), indexCount / 3, indices.data(), normals.data()); + } + + // Create tangents if texture coordinates are available. +#if PLASMA_HAS_TANGENTS + auto& tangents = geometryData.tangents; + if (bHasTexCoords) + { + tangents.resize(normals.size()); + Foundation::calculateTangents(vertexCount, positions.data(), normals.data(), + tex_coords.data(), indexCount / 3, indices.data(), tangents.data()); + bHasTangents = true; + } +#endif + Aurora::GeometryDescriptor& geomDesc = geometryData.descriptor; geomDesc.type = Aurora::PrimitiveType::Triangles; geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kPosition] = @@ -255,11 +308,15 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const Aurora::AttributeFormat::Float3; geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTexCoord0] = Aurora::AttributeFormat::Float2; - geomDesc.vertexDesc.count = static_cast(positions.size()) / 3; + if (bHasTangents) + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTangent] = + Aurora::AttributeFormat::Float3; + geomDesc.vertexDesc.count = vertexCount; geomDesc.indexCount = indexCount; - geomDesc.getAttributeData = [geomPath, &sceneContents](Aurora::AttributeDataMap& buffers, - size_t /* firstVertex*/, size_t /* vertexCount*/, - size_t /* firstIndex*/, size_t /* indexCount*/) { + geomDesc.getAttributeData = [geomPath, bHasTangents, &sceneContents]( + Aurora::AttributeDataMap& buffers, size_t /* firstVertex*/, + size_t /* vertexCount*/, size_t /* firstIndex*/, + size_t /* indexCount*/) { SceneGeometryData& geometryData = sceneContents.geometry[geomPath]; buffers[Aurora::Names::VertexAttributes::kPosition].address = @@ -276,6 +333,17 @@ bool loadOBJFile(Aurora::IRenderer* /*pRenderer*/, Aurora::IScene* pScene, const buffers[Aurora::Names::VertexAttributes::kTexCoord0].size = geometryData.texCoords.size() * sizeof(float); buffers[Aurora::Names::VertexAttributes::kTexCoord0].stride = sizeof(vec2); + + // Fill in the tangent data, if we have them. + if (bHasTangents) + { + buffers[Aurora::Names::VertexAttributes::kTangent].address = + geometryData.tangents.data(); + buffers[Aurora::Names::VertexAttributes::kTangent].size = + geometryData.tangents.size() * sizeof(float); + buffers[Aurora::Names::VertexAttributes::kTangent].stride = sizeof(vec3); + } + buffers[Aurora::Names::VertexAttributes::kIndices].address = geometryData.indices.data(); buffers[Aurora::Names::VertexAttributes::kIndices].size = diff --git a/Applications/Plasma/PerformanceMonitor.h b/Applications/Plasma/PerformanceMonitor.h index 4e4f425..2bbb4c3 100644 --- a/Applications/Plasma/PerformanceMonitor.h +++ b/Applications/Plasma/PerformanceMonitor.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/Plasma.cpp b/Applications/Plasma/Plasma.cpp index ad466ba..d72c26e 100644 --- a/Applications/Plasma/Plasma.cpp +++ b/Applications/Plasma/Plasma.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -64,7 +64,7 @@ Plasma::Plasma(HINSTANCE hInstance, unsigned int width, unsigned int height) ::SetWindowTextW(_hwnd, Foundation::s2w(report.str()).c_str()); }); } -#else //! INTERACTIVE_PLASMA +#else //! INTERACTIVE_PLASMA // Application constructor. Plasma::Plasma(unsigned int width, unsigned int height) { @@ -554,6 +554,8 @@ bool Plasma::initialize() _camera.fit(_sceneContents.bounds, kDefaultDirection); } + _pDistantLight = _pScene->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + // Get the environment map file path from the env argument. if (arguments.count("env")) { @@ -753,7 +755,11 @@ void Plasma::updateLighting() { Aurora::Names::EnvironmentProperties::kBackgroundTransform, transform } }); // Update the directional light. - _pScene->setLight(lightIntensity, value_ptr(lightColor), value_ptr(_lightDirection)); + _pDistantLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, lightIntensity); + _pDistantLight->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(lightColor)); + _pDistantLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(_lightDirection)); } void Plasma::updateGroundPlane() @@ -1647,7 +1653,7 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE /*hPrevInstance return result ? 0 : -1; } -#else //! INTERACTIVE_PLASMA +#else //! INTERACTIVE_PLASMA int main(int argc, char* argv[]) { // Create an application object on the stack, and run it. The run() function returns when diff --git a/Applications/Plasma/Plasma.h b/Applications/Plasma/Plasma.h index 7a3ee80..77aa014 100644 --- a/Applications/Plasma/Plasma.h +++ b/Applications/Plasma/Plasma.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -170,6 +170,7 @@ class Plasma Aurora::IRenderer::Backend _rendererType = Aurora::IRenderer::Backend::Default; Aurora::IRendererPtr _pRenderer; Aurora::IGroundPlanePtr _pGroundPlane; + Aurora::ILightPtr _pDistantLight; Aurora::IScenePtr _pScene; Aurora::IWindowPtr _pWindow; vector _assetPaths; diff --git a/Applications/Plasma/SceneContents.cpp b/Applications/Plasma/SceneContents.cpp index f2d7143..3dd7804 100644 --- a/Applications/Plasma/SceneContents.cpp +++ b/Applications/Plasma/SceneContents.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/SceneContents.h b/Applications/Plasma/SceneContents.h index 7ebd2b4..4e33416 100644 --- a/Applications/Plasma/SceneContents.h +++ b/Applications/Plasma/SceneContents.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ struct SceneGeometryData std::vector positions; std::vector normals; std::vector texCoords; + std::vector tangents; std::vector indices; Aurora::GeometryDescriptor descriptor; diff --git a/Applications/Plasma/glTFLoader.cpp b/Applications/Plasma/glTFLoader.cpp index b964d38..a58cf85 100644 --- a/Applications/Plasma/glTFLoader.cpp +++ b/Applications/Plasma/glTFLoader.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/pch.h b/Applications/Plasma/pch.h index 5b759e7..cfd481d 100644 --- a/Applications/Plasma/pch.h +++ b/Applications/Plasma/pch.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Applications/Plasma/resource.h b/Applications/Plasma/resource.h index 97cef29..47a7435 100644 --- a/Applications/Plasma/resource.h +++ b/Applications/Plasma/resource.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/AuroraVersion.h.in b/AuroraVersion.h.in new file mode 100644 index 0000000..9f8b6c5 --- /dev/null +++ b/AuroraVersion.h.in @@ -0,0 +1,49 @@ +// AuroraVersion.h.in is processed by cmake configure_file to produce AuroraVersion.h +// *** Do not edit AuroraVersion.h this is an autogenerated file *** + +#define AURORA_FILEVERSION_NUMBER @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@ +#define AURORA_FILEVERSION_STRING "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@PROJECT_VERSION_TWEAK@\0" +#define AURORA_COPYRIGHT_STRING "Copyright (C) @AURORA_BUILD_YEAR@ Autodesk, Inc.\0" +#define AURORA_PRODUCTVERSION_STRING "Autodesk Aurora @AURORA_BUILD_YEAR@\0" + +#ifdef _DEBUG + #define FILEFLAGS_VALUE @DEBUG_FLAG_VALUE@ + #define FILE_SUFFIX "@DEBUG_SUFFIX@" +#else + #define FILEFLAGS_VALUE 0x0L + #define FILE_SUFFIX "" +#endif + +#define AURORA_VERSION_INFO_2(name, extension) \ +VS_VERSION_INFO VERSIONINFO \ + FILEVERSION AURORA_FILEVERSION_NUMBER \ + PRODUCTVERSION 1,0,0,1 \ + FILEFLAGSMASK 0x3fL \ + FILEFLAGS FILEFLAGS_VALUE \ + FILEOS VOS_NT_WINDOWS32 \ + FILETYPE 0x2L \ + FILESUBTYPE 0x0L \ +BEGIN \ + BLOCK "StringFileInfo" \ + BEGIN \ + BLOCK "040904b0" \ + BEGIN \ + VALUE "Comments", "\0" \ + VALUE "CompanyName", "Autodesk, Inc.\0" \ + VALUE "FileDescription", #name \ + VALUE "FileVersion", AURORA_FILEVERSION_STRING \ + VALUE "InternalName", #name \ + VALUE "LegalCopyright", AURORA_COPYRIGHT_STRING \ + VALUE "LegalTrademarks", "\0" \ + VALUE "OriginalFilename", #name FILE_SUFFIX extension "\0" \ + VALUE "ProductName", "Autodesk, Inc. " #name \ + VALUE "ProductVersion", AURORA_PRODUCTVERSION_STRING \ + VALUE "SpecialBuild", "\0" \ + END \ + END \ + BLOCK "VarFileInfo" \ + BEGIN \ + VALUE "Translation", 0x409, 1200 \ + END \ +END +#define AURORA_VERSION_INFO(name) AURORA_VERSION_INFO_2(name, ".dll") diff --git a/CMakeLists.txt b/CMakeLists.txt index fcf5e6f..bfa3d99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,12 +4,23 @@ cmake_minimum_required(VERSION 3.24) # Forbid the in-source build if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) - message(FATAL_ERROR “In-source build is not allowed!”) + message(FATAL_ERROR "In-source build is not allowed!") endif() # Set the project name and project variables -project(Aurora VERSION 0.0.0.1) +project(Aurora VERSION 23.07.0.0) +# Create a folder for the version header files +set(VERSION_FOLDER "${PROJECT_BINARY_DIR}/VersionFiles") +file(MAKE_DIRECTORY "${VERSION_FOLDER}") + +# Configure version information in version header file (included by the .rc files that define the DLL version information for each library.) +set(DEBUG_FLAG_VALUE 0x0L) +set(DEBUG_SUFFIX ) +string(TIMESTAMP AURORA_BUILD_YEAR "%Y") +configure_file(${CMAKE_SOURCE_DIR}/AuroraVersion.h.in ${VERSION_FOLDER}/AuroraVersion.h) + +# Set the root folder. set(AURORA_ROOT_DIR ${PROJECT_SOURCE_DIR}) # install path if the user did not explicitly specify one. diff --git a/Doc/Build.md b/Doc/Build.md index 4990830..03d6fca 100644 --- a/Doc/Build.md +++ b/Doc/Build.md @@ -6,13 +6,14 @@ Several prerequisites must be installed before building Aurora. ### Windows On windows the following packages should be installed and added to the system PATH environment variable: -* Microsoft Visual Studio 2019 (https://my.visualstudio.com/Downloads?q=visual%20studio%202019) -* CMake 3.26.11 (https://github.com/Kitware/CMake/releases/download/v3.26.1/cmake-3.26.1-windows-x86_64.msi) -* Python 3.9.13 (https://www.python.org/downloads/release/python-3913/) -* PySide6 python package (install with `pip3 install pyside6`) -* PyOpenGL python package (install with `pip3 install PyOpenGL`) -* NASM 2.16 (https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-installer-x64.exe) -* Vulkan SDK (https://vulkan.lunarg.com/sdk/home#windows). Should be accessible via the VULKAN_SDK environment variable. +* [Microsoft Visual Studio 2019](https://my.visualstudio.com/Downloads?q=visual%20studio%202019) or [2022](https://my.visualstudio.com/Downloads?q=visual%20studio%202022). +* CMake 3.26.11 ([installer](https://github.com/Kitware/CMake/releases/download/v3.26.1/cmake-3.26.1-windows-x86_64.msi)). +* Python 3.9.13 ([installer](https://www.python.org/downloads/release/python-3913)) and the following Python packages: + * PySide6: install with `pip3 install pyside6`. + * PyOpenGL: install with `pip3 install PyOpenGL`. + +* NASM 2.16 ([installer](https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-installer-x64.exe)). +* [Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows). This should be accessible via the `VULKAN_SDK` environment variable. ### Linux The following dependencies are required on Linux. The versions listed are the recommended version for Ubuntu 20.04: @@ -37,7 +38,7 @@ Aurora includes a script that retrieves and builds dependencies ("externals") fr 1. **Download or clone** the contents of this repository to a location of your choice. Cloning with Git is not strictly necessary as Aurora does not currently use submodules. We refer to this location as *AURORA_DIR*. -2. **Start a command line** with access to your C++ compiler tools. When using Visual Studio, the "x64 Native Tools Command Prompt for VS 2019" shortcut will provide the proper environment. The CMake and Python executables must also be available, through the PATH environment variable. +2. **Start a command line** with access to your C++ compiler tools. When using Visual Studio, the "x64 Native Tools Command Prompt for VS 2019" (or 2022) shortcut will provide the proper environment. The CMake and Python executables must also be available, through the PATH environment variable. 3. **Installing externals:** Run *[Scripts/installExternals.py](Scripts/installExternals.py)* with Python in *AURORA_DIR* to build and install externals. @@ -56,7 +57,7 @@ Aurora includes a script that retrieves and builds dependencies ("externals") fr 4. **Generating projects:** Run CMake in *AURORA_DIR* to generate build projects, e.g. a Visual Studio solution. - - You may specify the externals installation directory (*EXTERNALS_ROOT*, above) as a CMake path variable called `EXTERNALS_ROOT`. If no `EXTERNALS_ROOT` is specified, the `EXTERNALS_ROOT` built by the latest run of *installExternals.py* will be used automatically. If you are using cmake-gui, you should specify this variable before generating. + - You may specify the externals installation directory (*EXTERNALS_ROOT*, above) as a CMake path variable called `EXTERNALS_ROOT`. This must be specified as an absolute path, e.g. with a drive letter on Windows. If no `EXTERNALS_ROOT` is specified, the `EXTERNALS_ROOT` built by the latest run of *installExternals.py* will be used automatically. If you are using cmake-gui, you should specify this variable before generating. - You must specify a build directory, and we refer to this location as *AURORA_BUILD_DIR*. The recommended build directory is *{AURORA_DIR}/Build*, which is filtered by [.gitignore](.gitignore) so it won't appear as local changes for Git. @@ -66,13 +67,15 @@ Aurora includes a script that retrieves and builds dependencies ("externals") fr cmake -S . -B {AURORA_BUILD_DIR} -D CMAKE_BUILD_TYPE={CONFIGURATION} -D EXTERNALS_ROOT={EXTERNALS_ROOT} ``` + As noted above, the value for `EXTERNALS_ROOT` must be specified as an absolute path. + - The *CONFIGURATION* value can be one of `Debug` or `Release` (default). - On Windows, `CMAKE_BUILD_TYPE` is ignored during the cmake configuration. You are required to specify the build configuration with `--config {CONFIGURATION}` during the cmake build. - You can optionally specify the desired graphics API backend as described below, e.g. `-D ENABLE_HGI_BACKEND=ON`. - - On Windows, you may need to specify the toolchain and architecture with `-G "Visual Studio 16 2019" -A x64`. + - On Windows, you may need to specify the toolchain and architecture with `-G "Visual Studio 16 2019" -A x64` or `-G "Visual Studio 17 2022" -A x64`. 5. **Building:** You can load the *Aurora.sln* Visual Studio solution file from the Aurora build directory, and build Aurora using the build configuration used with the *installExternals.py* script (see below), or use CMake. diff --git a/Doc/CodingStandards.md b/Doc/CodingStandards.md index b5e910a..a86f23b 100644 --- a/Doc/CodingStandards.md +++ b/Doc/CodingStandards.md @@ -132,9 +132,17 @@ If you encounter unexpected issues with automatic linting and formatting tools, ### clang-format We use [clang-format](https://clang.llvm.org/docs/ClangFormat.html) to automatically format code to be compliant with the standards. -- Formatting rules are defined by the `.clang-format` file in the root of each repository. +- Formatting rules are defined by the [.clang-format](/.clang-format) file in the repository root folder. +- clang-format operates on several file formats including (but not limited to): C, C++, C#, CUDA, JavaScript, JSON, and Objective-C. It does not currently operate on shader code (e.g. HLSL or Slang), so these must be formatted manually. +- Any lines of source code that should not be formatted by clang-format should be surrounded by `// clang-format off` and `// clang-format on` single-line comments. - To manually run clang-format on a file in Visual Studio, use _Edit | Advanced | Format Document_ (Ctrl-K+D). -- You can also use the [Format All Files extension](https://marketplace.visualstudio.com/items?itemName=munyabe.FormatAllFiles) to format all the files in a project. Make sure to undo changes to files that should not be formatted with clang-format, e.g. HLSL files. +- It is recommended to use the clang-format [pre-commit](https://pre-commit.com) hook, that is setup via the [YAML config file](/.pre-commit-config.yaml). Enable this with the following steps: + 1. Open a command prompt in the source root folder. + 2. Ensure the Python executable and `Scripts` folder is in your system path. + 3. Install pre-commit with the command: `python -m pip install pre-commit` + 4. Install the hooks with command: `pre-commit install` + 5. All subsequent Git commits will invoke clang-format on the changed files. If any changes are made by this process, it means that one or more files have incorrect formatting and the commit will fail. The developer should ensure the newly modified files are added to the commit and attempt to commit again. + 6. If needed clang-format can be invoked on all the files in the repository with the command: `pre-commit run --all-files` ## C++ Features diff --git a/Doc/ImageProcessingResolver.md b/Doc/ImageProcessingResolver.md new file mode 100644 index 0000000..e64e762 --- /dev/null +++ b/Doc/ImageProcessingResolver.md @@ -0,0 +1,42 @@ +# Image Processing Resolver + +The **ImageProcessingResolver** is a USD `ArResolver` plugin that allows image files to be pre-processed via an asset URI system prior to loading in Aurora and other parts of USD. + +If asset URI is added to a USDA file like this, in place of a filename, it will be parsed by the resolver to perform a environment map layout conversion on the file `c:/testart/HDRI/StandardCubeMap.hdr`. The results of the image processing operation will be passed to USD as if the processing has been carried out on an external file. + +``` +asset inputs:file = @imageProcessing://convertEnvMapLayout?file=C%3A%2Ftestart%2Fhdri%2FStandardCubeMap.hdr&sourceLayout=cube&targetLayout=latlon@ +``` + +## Asset URIs + +The image processing URI is an encoded description of the image processing operation required, encoded as a W3C [Universal Resource Identifier](https://www.w3.org/wiki/URI), e.g: + +``` +imageProcessing://linearize/?filename=Background.exr&exposure=2.5 +``` + +The URI components are interpreted as follows to perform the image processing operation: + +`Scheme`: The scheme (the part before colon) must be imageProcessing + +`Host`: The host (the part between the first // and the first / ) describes the image processing operation. See below for supported operations. + +`Query`: The query parameters (the first preceded by a ? the remaining parameters preceeded by a &) describes the parameters to the operations. The `file` query parameter is common to all operations the filename of the image file to be processed. Note this must be encoded so any invalid chars are represented as character codes(e.g. G%C3%BCnter). The filename itself could be an image processing URI to allow multiple image processing operations. + +## Supported operations + +### Linearize +If the host name in the URI is `linearize`, the image is linearized, that is an "inverse tonemapping" operation is carried out so the result after Canon tonemapping, with the provided exposure is the same in the original image file. + +Parameters: +* file: File to be linearized (must be RGB 32-bit floating point .EXR or .HDR image) +* exposure: For linearization the exposure used in the tone mapping operation to be reversed. + +### Convert environment map layout +If the host name in the URI is `convertEnvMapLayout`, the image is treated as an environment map and converted from one layout to another. Currently only conversion from a "cross" cube map layout to lat-long equirectangular environment map is supported. + +Parameters +* file: File to be converted (must be RGB 32-bit floating point .EXR or .HDR image) +* sourceLayout: Environment map layout to be converted from (currently must be `cube`) +* targetLayout: Environment map layout to be converted to (currently must be `latlon`) diff --git a/Jenkinsfile b/Jenkinsfile index 4aa802e..b6dc438 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,16 +5,22 @@ // Global constants COMMONSHELL = new ors.utils.CommonShell(steps, env) -node('forgeogs-win2019-gpu') { +node('OGS_Win_002') { try { stage ("Checkout Windows") { checkoutGit() } - stage ("Build Windows") { - windowsBuild() + stage ("Build Windows Release") { + windowsBuild("Release") } - stage ('Test Windows') { - windowsTest() + stage ("Build Windows Debug") { + windowsBuild("Debug") + } + stage ('Test Windows Release') { + windowsTest("Release") + } + stage ('Test Windows Debug') { + windowsTest("Debug") } } finally { @@ -27,23 +33,23 @@ node('forgeogs-win2019-gpu') { // Checkout Functions def checkoutGit() { - String branch = scm.branches[0].toString() - withGit { - COMMONSHELL.shell """ - git clone -b ${branch} --recurse-submodules https://git.autodesk.com/GFX/Aurora . - git lfs pull - """ - } + checkout([$class: 'GitSCM', + branches: scm.branches, + doGenerateSubmoduleConfigurations: false, + extensions: [[$class: 'GitLFSPull']], + submoduleCfg: [], + userRemoteConfigs: scm.userRemoteConfigs]) } /////////////////////////////////////////////////// // Build functions -def windowsBuild() { - def EXTERNALS_DIR="c:\\jenkins\\workspace\\AuroraExternals" +def windowsBuild(Config) { + def EXTERNALS_DIR="c:\\jenkins\\workspace\\AuroraExternals" + def EXTERNALS_DIR_AURORA="${EXTERNALS_DIR}\\vs2019\\${Config}" bat """ :: Set up EXTERNALS_DIR - if not exist ${EXTERNALS_DIR} call mkdir ${EXTERNALS_DIR} + if not exist ${EXTERNALS_DIR_AURORA} call mkdir ${EXTERNALS_DIR_AURORA} :: Set up nasm if not exist ${EXTERNALS_DIR}\\nasm-2.15.05-win64.zip call curl -o ${EXTERNALS_DIR}\\nasm-2.15.05-win64.zip https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/win64/nasm-2.15.05-win64.zip @@ -57,27 +63,28 @@ def windowsBuild() { :: Set up Vulkan SDK :: We need to install Vulkan SDK silently. For more details please refer to https://vulkan.lunarg.com/doc/view/latest/windows/getting_started.html - if not exist ${EXTERNALS_DIR}\\VulkanSDK-1.3.231.1-Installer.exe call curl -o ${EXTERNALS_DIR}\\VulkanSDK-1.3.231.1-Installer.exe https://sdk.lunarg.com/sdk/download/1.3.231.1/windows/VulkanSDK-1.3.231.1-Installer.exe - if not exist "C:\\VulkanSDK\\1.3.231.1\\bin" call ${EXTERNALS_DIR}\\VulkanSDK-1.3.231.1-Installer.exe --accept-licenses --default-answer --confirm-command install + if not exist "C:\\VulkanSDK\\1.3.231.1\\bin" if not exist ${EXTERNALS_DIR}\\VulkanSDK-1.3.231.1-Installer.exe call curl -o ${EXTERNALS_DIR}\\VulkanSDK-1.3.231.1-Installer.exe https://sdk.lunarg.com/sdk/download/1.3.231.1/windows/VulkanSDK-1.3.231.1-Installer.exe + if not exist "C:\\VulkanSDK\\1.3.231.1\\bin" call ${EXTERNALS_DIR}\\VulkanSDK-1.3.231.1-Installer.exe --accept-licenses --default-answer --confirm-command install com.lunarg.vulkan.debug call set PATH=C:\\VulkanSDK\\1.3.231.1\\bin;%PATH% call set VK_SDK_PATH=C:\\VulkanSDK\\1.3.231.1 call set VULKAN_SDK=C:\\VulkanSDK\\1.3.231.1 - + :: Set up Visual Studio 2019 Environment call C:\\"Program Files (x86)"\\"Microsoft Visual Studio"\\2019\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat - :: insatll externals - python -u Scripts\\installExternals.py ${EXTERNALS_DIR} + :: install externals + python -u Scripts\\installExternals.py ${EXTERNALS_DIR_AURORA} --build-variant=${Config} -v -v :: build Aurora echo Configure CMake project if exist Build\\CMakeCache.txt del /f Build\\CMakeCache.txt - :: Windows SDK version 10.0.22000.194 or later is required in https://git.autodesk.com/GFX/Aurora#windows. - :: We'd disable DX BACKEND as installing the windows sdk maybe have some bad effects on other jobs. - cmake -S . -B Build -D CMAKE_BUILD_TYPE=Release -D EXTERNALS_DIR=${EXTERNALS_DIR} -DENABLE_DIRECTX_BACKEND=OFF - cmake --build Build --config Release + + :: As the build machine is Windows Server 2019 while target running machine is Windows 10, + :: Use CMAKE_SYSTEM_VERSION to target the build for a different version of the host operating system than is actually running on the host + cmake -S . -B Build -D CMAKE_BUILD_TYPE=${Config} -D EXTERNALS_DIR=${EXTERNALS_DIR_AURORA} -DCMAKE_SYSTEM_VERSION=10.0.22000.0 -G "Visual Studio 16 2019" -A x64 + cmake --build Build --config ${Config} if not errorlevel 0 ( - echo ERROR: Failed to build the project for Release binaries, see console output for details + echo ERROR: Failed to build the project for ${Config} binaries, see console output for details exit /b %errorlevel% ) """ @@ -86,19 +93,19 @@ def windowsBuild() { /////////////////////////////////////////////////// // Test functions -def windowsTest() { +def windowsTest(Config) { bat """ :: Start unit test - echo Starting OGS modernization unit test for Release + echo Starting OGS modernization unit test for ${Config} setlocal EnableDelayedExpansion - pushd Build\\bin\\Release + pushd Build\\bin\\${Config} set exit_code=0 for %%i in (.\\*Tests.exe) do ( set exe=%%i set exe_name=!exe:~2,-4! !exe! - if not errorlevel 0 ( + if !errorlevel! neq 0 ( echo ERROR: Test !exe_name! exited with non-zero exit code, see console output for details set exit_code=1 ) @@ -106,7 +113,7 @@ def windowsTest() { popd :: Break further execution when test fails if %exit_code% equ 1 goto end - echo Done All tests for Release + echo Done All tests for ${Config} :end exit /b %exit_code% """ diff --git a/Libraries/Aurora/API/Aurora/Aurora.h b/Libraries/Aurora/API/Aurora/Aurora.h index 8b8024a..2cc5d85 100644 --- a/Libraries/Aurora/API/Aurora/Aurora.h +++ b/Libraries/Aurora/API/Aurora/Aurora.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -232,6 +232,55 @@ struct PropertyValue _strings.clear(); } + /// Compare property value for inequality, based on type. + bool operator!=(const PropertyValue& in) const { return !(*this == in); } + + /// Compare property value for equality, based on type. + bool operator==(const PropertyValue& in) const + { + // If types don't match never equal. + if (type != in.type) + return false; + + // Compare based on type value. + switch (type) + { + case Type::Bool: + return _bool == in._bool; + case Type::Int: + return _int == in._int; + case Type::Float: + return _float == in._float; + case Type::Float2: + return _float2 == in._float2; + case Type::Float3: + return _float3 == in._float3; + case Type::Float4: + return _float4 == in._float4; + case Type::Matrix4: + return _matrix4 == in._matrix4; + case Type::String: + return _string == in._string; + case Type::Strings: + // If string array lengths do not match, equality is false. + if (_strings.size() != in._strings.size()) + return false; + + // If any string does not match equality is false. + for (size_t i = 0; i < _strings.size(); i++) + { + if (_strings[i] != in._strings[i]) + return false; + } + + // Return true if all strings match. + return true; + default: + // Invalid values are always non-equal. + return false; + } + } + union { bool _bool; @@ -746,6 +795,20 @@ class AURORA_API IInstance }; MAKE_AURORA_PTR(IInstance); +/// A class representing a light. Which are added to the scene to provide direct illumination. +class AURORA_API ILight +{ +public: + /// Gets the lights's properties, which can be read and modified as needed. + /// + /// The properties are specific to the light type. + virtual IValues& values() = 0; + +protected: + virtual ~ILight() = default; // hidden destructor +}; +MAKE_AURORA_PTR(ILight); + /// The types of resources that can be associated with an Aurora scene (directly or indirectly). enum ResourceType { @@ -756,6 +819,7 @@ enum ResourceType Sampler, GroundPlane, Image, + Light, Invalid }; @@ -893,27 +957,6 @@ class AURORA_API IScene /// result in undefined behavior. This may be updated internally in the future. virtual void setBounds(const float* min, const float* max) = 0; - /// Sets the properties of the global directional light. - /// - /// \param intensity The light intensity. Set this to zero to disable the light. - /// \param color The light color, which is multiplied by the intensity. - /// \param direction The light direction, away from the light / toward the scene. - /// \param angularDiameter The light angular diameter in radians. For example, the angular - /// diameter of the sun is about 0.017 radians. - virtual void setLight( - float intensity, const rgb& color, const vec3& direction, float angularDiameter = 0.1f) = 0; - - /// Sets the properties of the global directional light. - /// - /// TODO: Remove. Replaced by GLM types. - /// \param intensity The light intensity. Set this to zero to disable the light. - /// \param color The light color, which is multiplied by the intensity. - /// \param direction The light direction, away from the light / toward the scene. - /// \param angularDiameter The light angular diameter in radians. For example, the angular - /// diameter of the sun is about 0.017 radians. - virtual void setLight(float intensity, const float* color, const float* direction, - float angularDiameter = 0.1f) = 0; - /// Sets the ground plane for the scene. virtual void setGroundPlanePointer(const IGroundPlanePtr& pGroundPlane) = 0; @@ -921,6 +964,14 @@ class AURORA_API IScene const IMaterialPtr& pMaterial, const mat4& pTransform, const LayerDefinitions& materialLayers = {}) = 0; + /// Add a new light to add illumination to the scene. + /// The light will be removed from the scene, when the shared pointer becomes unused. + /// + /// \param lightType Type of light (one of strings in + /// Aurora::Names::LightTypes). + /// \return A smart pointer to the new lights. + virtual ILightPtr addLightPointer(const string& lightType) = 0; + protected: virtual ~IScene() = default; // hidden destructor }; diff --git a/Libraries/Aurora/API/Aurora/AuroraNames.h b/Libraries/Aurora/API/Aurora/AuroraNames.h index 720d8ee..db8a3b9 100644 --- a/Libraries/Aurora/API/Aurora/AuroraNames.h +++ b/Libraries/Aurora/API/Aurora/AuroraNames.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -91,12 +91,12 @@ struct VertexAttributes { static AURORA_API const std::string kPosition; static AURORA_API const std::string kNormal; + static AURORA_API const std::string kTangent; static AURORA_API const std::string kTexCoord0; static AURORA_API const std::string kTexCoord1; static AURORA_API const std::string kTexCoord2; static AURORA_API const std::string kTexCoord3; static AURORA_API const std::string kTexCoord4; - static AURORA_API const std::string kTangent; static AURORA_API const std::string kIndices; }; @@ -118,6 +118,30 @@ struct MaterialTypes static AURORA_API const std::string kMaterialXPath; }; +/// Types of light. +struct LightTypes +{ + /// Distant (aka directional) light + static AURORA_API const std::string kDistantLight; +}; + +/// Light properties (only some will be valid properties for a given light type.) +struct LightProperties +{ + /// Light direction (only valid for LightTypes::kDistantLight.) + static AURORA_API const std::string kDirection; + /// The angular diameter of the light in radians (only valid for LightTypes::kDistantLight.) + static AURORA_API const std::string kAngularDiameter; + /// Light exposure (defines the power of the light with kIntensity.) + /// TODO: Implement a more physically accurate photometric model with correct units. + static AURORA_API const std::string kExposure; + /// Light intensity (defines the power of the light with kExposure.) + /// TODO: Implement a more physically accurate photometric model with correct units. + static AURORA_API const std::string kIntensity; + /// Light color as RGB. + static AURORA_API const std::string kColor; +}; + } // namespace Names } // namespace Aurora diff --git a/Libraries/Aurora/Aurora.rc b/Libraries/Aurora/Aurora.rc new file mode 100644 index 0000000..3747dfa --- /dev/null +++ b/Libraries/Aurora/Aurora.rc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d88ac77c265e46d863c96698610ff07223835808774864bc2101c15f33c2a7c +size 361 diff --git a/Libraries/Aurora/CMakeLists.txt b/Libraries/Aurora/CMakeLists.txt index cc358d5..38eb6ca 100644 --- a/Libraries/Aurora/CMakeLists.txt +++ b/Libraries/Aurora/CMakeLists.txt @@ -80,6 +80,11 @@ set(COMMON_SOURCE "Source/GeometryBase.h" "Source/MaterialBase.cpp" "Source/MaterialBase.h" + "Source/MaterialBase.cpp" + "Source/MaterialDefinition.h" + "Source/MaterialDefinition.cpp" + "Source/MaterialShader.h" + "Source/MaterialShader.cpp" "Source/pch.h" "Source/Properties.h" "Source/RendererBase.cpp" @@ -91,6 +96,8 @@ set(COMMON_SOURCE "Source/ResourceStub.h" "Source/SceneBase.cpp" "Source/SceneBase.h" + "Source/UniformBuffer.cpp" + "Source/UniformBuffer.h" "Source/Transpiler.h" "Source/Transpiler.cpp" "Source/WindowsHeaders.h" @@ -102,6 +109,7 @@ file(MAKE_DIRECTORY "${COMPILED_SHADERS_DIR}") # The slang shaders that are shared by DX and HGI backends. set(COMMON_SHADERS + "Source/Shaders/BackgroundMissShader.slang" "Source/Shaders/BSDF.slang" "Source/Shaders/BSDFCommon.slang" "Source/Shaders/Colors.slang" @@ -111,13 +119,11 @@ set(COMMON_SHADERS "Source/Shaders/Globals.slang" "Source/Shaders/GLSLToHLSL.slang" "Source/Shaders/GroundPlane.slang" - "Source/Shaders/ClosestHitEntryPointTemplate.slang" - "Source/Shaders/ShadowHitEntryPointTemplate.slang" - "Source/Shaders/LayerShaderEntryPointTemplate.slang" + "Source/Shaders/InitializeDefaultMaterialType.slang" + "Source/Shaders/MainEntryPoints.slang" "Source/Shaders/Material.slang" "Source/Shaders/MaterialXCommon.slang" "Source/Shaders/PathTracingCommon.slang" - "Source/Shaders/BackgroundMissShader.slang" "Source/Shaders/ShadowMissShader.slang" "Source/Shaders/RadianceMissShader.slang" "Source/Shaders/RayGenShader.slang" @@ -126,10 +132,17 @@ set(COMMON_SHADERS "Source/Shaders/RayTrace.slang" "Source/Shaders/ReferenceBSDF.slang" "Source/Shaders/StandardSurfaceBSDF.slang" - "Source/Shaders/InitializeDefaultMaterialType.slang" "Source/Shaders/Sampling.slang" ) +# On windows, the version resource and header files that add the version information to the DLL. +if(WIN32) +set(VERSION_FILES + ${VERSION_FOLDER}/AuroraVersion.h + Aurora.rc +) +endif() + # Set MINIFIED_SHADERS_HEADER to filename of auto-generated header file containing minified Slang shader strings. set(MINIFIED_COMMON_SHADERS_HEADER "${COMPILED_SHADERS_DIR}/CommonShaders.h") # Add commands to minifiy common shaders. @@ -155,6 +168,7 @@ if(ENABLE_DIRECTX_BACKEND) # DirectX backend source. set(DIRECTX_SOURCE + "Source/DirectX/MemoryPool.cpp" "Source/DirectX/MemoryPool.h" "Source/DirectX/PTDevice.cpp" "Source/DirectX/PTDevice.h" @@ -166,6 +180,8 @@ if(ENABLE_DIRECTX_BACKEND) "Source/DirectX/PTGroundPlane.h" "Source/DirectX/PTImage.cpp" "Source/DirectX/PTImage.h" + "Source/DirectX/PTLight.cpp" + "Source/DirectX/PTLight.h" "Source/DirectX/PTMaterial.cpp" "Source/DirectX/PTMaterial.h" "Source/DirectX/PTRenderer.cpp" @@ -202,6 +218,8 @@ if(ENABLE_HGI_BACKEND) "Source/HGI/HGIGeometry.h" "Source/HGI/HGIHandleWrapper.cpp" "Source/HGI/HGIHandleWrapper.h" + "Source/HGI/HGILight.cpp" + "Source/HGI/HGILight.h" "Source/HGI/HGIRenderBuffer.cpp" "Source/HGI/HGIRenderBuffer.h" "Source/HGI/HGIRenderer.cpp" @@ -235,6 +253,7 @@ add_library(${PROJECT_NAME} SHARED ${HGI_SOURCE} ${HGI_SHADERS} ${MINIFIED_HGI_SHADERS_HEADER} + ${VERSION_FILES} ) # Add to libraries folder @@ -316,6 +335,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy_directory ${MATERIALX_STDLIB_DIR} ${RUNTIME_OUTPUT_DIR}/MaterialX/libraries ) set_property(TARGET CopyMaterialXLibrary PROPERTY FOLDER "Deployment") + add_dependencies(CopyMaterialXLibrary MakeRuntimeDir) add_dependencies(${PROJECT_NAME} CopyMaterialXLibrary) endif() @@ -343,13 +363,15 @@ else() # Linux #TODO anything we need to set to support the Aurora execution? endif() -# Set custom ouput properties. +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Libraries" RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" - PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + # Follow the USD convention of lower-case initial on libraries. + OUTPUT_NAME "aurora" ) if(ENABLE_MATERIALX) @@ -369,16 +391,16 @@ if(ENABLE_HGI_BACKEND) endif() if(ENABLE_DIRECTX_BACKEND) - set(D2D12_TARGETS D3D12::D3D12 D3D12::compiler) + set(D3D12_TARGETS D3D12::D3D12 D3D12::compiler) else() - set(D2D12_TARGETS "") + set(D3D12_TARGETS "") endif() target_link_libraries(${PROJECT_NAME} PRIVATE Foundation ${MATERIALX_TARGETS} # MaterialX needs to be in front of USD due to the conflicts of MaterialX.h in USD - ${D2D12_TARGETS} + ${D3D12_TARGETS} ${USD_TARGETS} ${DENOISER_TARGETS} stb::stb @@ -392,6 +414,7 @@ PUBLIC PRIVATE "Source" # Source folder for PCH "${PROJECT_BINARY_DIR}" + ${VERSION_FOLDER} ) # Set the default compile definitions. diff --git a/Libraries/Aurora/Source/AliasMap.cpp b/Libraries/Aurora/Source/AliasMap.cpp index 8c83190..ef61f80 100644 --- a/Libraries/Aurora/Source/AliasMap.cpp +++ b/Libraries/Aurora/Source/AliasMap.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ void build(const float* pPixels, uvec2 dimensions, Entry* pOutputBuffer, size_t // Ensure output buffer size is correct (is being put in GPU texture so must match exactly). AU_ASSERT(outputBufferSize == bufferSize, - "Expected ouput buffer of size %d bytes, instead is %d bytes", pixelCount * sizeof(Entry), + "Expected output buffer of size %d bytes, instead is %d bytes", pixelCount * sizeof(Entry), outputBufferSize); // A pair of luminance-related values, for a single pixel. Pixel luminance is used to determine diff --git a/Libraries/Aurora/Source/AliasMap.h b/Libraries/Aurora/Source/AliasMap.h index e69ea49..5a0bb77 100644 --- a/Libraries/Aurora/Source/AliasMap.h +++ b/Libraries/Aurora/Source/AliasMap.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/AssetManager.cpp b/Libraries/Aurora/Source/AssetManager.cpp index 0f63462..f3ec9c4 100644 --- a/Libraries/Aurora/Source/AssetManager.cpp +++ b/Libraries/Aurora/Source/AssetManager.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ // TODO: Image loading will eventually be handled by clients. #pragma warning(push) // Disabe type conversion warnings intruduced from stb master. -// refer to the commit in stb https://github.com/nothings/stb/commit/b15b04321dfd8a2307c49ad9c5bf3c0c6bcc04cc +// refer to the commit in stb +// https://github.com/nothings/stb/commit/b15b04321dfd8a2307c49ad9c5bf3c0c6bcc04cc #pragma warning(disable : 4244) #define STB_IMAGE_IMPLEMENTATION #include diff --git a/Libraries/Aurora/Source/AssetManager.h b/Libraries/Aurora/Source/AssetManager.h index eec67db..907fc2d 100644 --- a/Libraries/Aurora/Source/AssetManager.h +++ b/Libraries/Aurora/Source/AssetManager.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Aurora.cpp b/Libraries/Aurora/Source/Aurora.cpp index 2b6204a..cc8465a 100644 --- a/Libraries/Aurora/Source/Aurora.cpp +++ b/Libraries/Aurora/Source/Aurora.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/AuroraNames.cpp b/Libraries/Aurora/Source/AuroraNames.cpp index af94ba0..7020462 100644 --- a/Libraries/Aurora/Source/AuroraNames.cpp +++ b/Libraries/Aurora/Source/AuroraNames.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -58,4 +58,12 @@ const string Names::AddressModes::kMirrorOnce("MirrorOnce"); const string Names::SamplerProperties::kAddressModeU("AddressModeU"); const string Names::SamplerProperties::kAddressModeV("AddressModeV"); +const string Names::LightTypes::kDistantLight("DistantLight"); + +const std::string Names::LightProperties::kDirection = "direction"; +const std::string Names::LightProperties::kAngularDiameter = "angular_diameter_radians"; +const std::string Names::LightProperties::kExposure = "exposure"; +const std::string Names::LightProperties::kIntensity = "intensity"; +const std::string Names::LightProperties::kColor = "color"; + END_AURORA diff --git a/Libraries/Aurora/Source/DLL.cpp b/Libraries/Aurora/Source/DLL.cpp index 7cdd832..4e9bcae 100644 --- a/Libraries/Aurora/Source/DLL.cpp +++ b/Libraries/Aurora/Source/DLL.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/MemoryPool.cpp b/Libraries/Aurora/Source/DirectX/MemoryPool.cpp new file mode 100644 index 0000000..dc6c052 --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/MemoryPool.cpp @@ -0,0 +1,54 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "MemoryPool.h" + +#include "PTRenderer.h" + +BEGIN_AURORA + +uint8_t* TransferBuffer::map(size_t sz, size_t offset) +{ + // Ensure buffer is not already mapped. + AU_ASSERT(mappedRange.Begin == SIZE_MAX, "Already mapped"); + + // Set the mapped range from size and offset. + mappedRange = { offset, sz }; + + // Map the DX buffer resource, call AU_FAIL if HRESULT indicates an error. + uint8_t* pMappedData = nullptr; + checkHR(pUploadBuffer->Map( + 0, sz == 0 ? nullptr : &mappedRange, reinterpret_cast(&pMappedData))); + + // Return mapped pointer. + return pMappedData; +} + +void TransferBuffer::unmap() +{ + // Ensure buffer is mapped. + AU_ASSERT(mappedRange.Begin != SIZE_MAX, "Not mapped"); + + // Unmap the DX buffer resource for the upload buffer. + pUploadBuffer->Unmap(0, mappedRange.End == 0 ? nullptr : &mappedRange); // no HRESULT + + // Pass this buffer to renderer to add to the pending upload list. + pRenderer->transferBufferUpdated(*this); + + // Reset the mapped range to invalid values. + mappedRange = { SIZE_MAX, SIZE_MAX }; +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/MemoryPool.h b/Libraries/Aurora/Source/DirectX/MemoryPool.h index ba7886b..4c3306f 100644 --- a/Libraries/Aurora/Source/DirectX/MemoryPool.h +++ b/Libraries/Aurora/Source/DirectX/MemoryPool.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -51,10 +51,10 @@ class ScratchBufferPool // add it the list of buffers for the current task. if (size > kBufferSize) { - ID3D12ResourcePtr pLargeBuffer = _funcCreateScratchBuffer(size); - _taskBuffers[_taskIndex].push_back(pLargeBuffer); + ID3D12ResourcePtr pTransferBuffer = _funcCreateScratchBuffer(size); + _taskBuffers[_taskIndex].push_back(pTransferBuffer); - return pLargeBuffer->GetGPUVirtualAddress(); + return pTransferBuffer->GetGPUVirtualAddress(); } // If the current buffer doesn't have enough space for the requested scratch buffer, add it @@ -103,53 +103,87 @@ class ScratchBufferPool size_t _bufferOffset = 0; }; +class PTRenderer; + +// A transfer buffer should be used whenever a buffer needs to be transferred to the GPU to be read +// from a ray tracing shader. +// NOTE: Failing to do so, and using an upload buffer (which is stored in CPU memory not VRAM) +// directly will result in a PCI copy every time a shader reads from the buffer. +struct TransferBuffer +{ + // The upload buffer is created on the UPLOAD heap, which is stored in CPU memory. This is + // mapped and filled with data on CPU. + ID3D12ResourcePtr pUploadBuffer; + // The GPU buffer is created on the DEFAULT heap, which is stored in VRAM. The renderer will + // copy the upload buffer to this buffer on the GPU. + // The calling code can optionally retain only this buffer and release the upload buffer safely + // (which will be deleted by the renderer once the upload is complete.) + ID3D12ResourcePtr pGPUBuffer; + // The currently mapped range in the buffer. + CD3DX12_RANGE mappedRange = { SIZE_MAX, SIZE_MAX }; + // The total size of the buffer in bytes. + size_t size = 0; + // The renderer pointer (which will upload the GPU buffers after unmap is called) + PTRenderer* pRenderer = nullptr; + // The final state the transition buffer will transition to after upload. + D3D12_RESOURCE_STATES finalState = D3D12_RESOURCE_STATE_GENERIC_READ; + // Has this buffer been uploaded ? + bool wasUploaded = false; + + // Is the buffer valid? + bool valid() { return pGPUBuffer != nullptr; } + // Reset and release the buffers. + void reset() + { + pUploadBuffer.Reset(); + pGPUBuffer.Reset(); + mappedRange = { SIZE_MAX, SIZE_MAX }; + size = 0; + pRenderer = nullptr; + } + // Map the upload buffer in CPU memory. + uint8_t* map(size_t size = 0, size_t offset = 0); + // Unmap the upload buffer in CPU memory. This will trigger an upload to the GPU buffer, in the + // renderer. + void unmap(); +}; + // A pool used to supply vertex buffers, e.g. index, position, normal, and tex coords. This improves // performance by collecting many small virtual allocations into fewer physical allocations of GPU // memory. class VertexBufferPool { public: - using FuncCreateVertexBuffer = function; + using FuncCreateVertexBuffer = function; - // Constructor. This accepts to create a single GPU vertex buffer. - VertexBufferPool(FuncCreateVertexBuffer funcCreateVertexBuffer) + // Constructor. This accepts to create a single transfer buffer for vertices. + VertexBufferPool(FuncCreateVertexBuffer funcCreateTransferBuffer) : + _funcCreateTransferBuffer(funcCreateTransferBuffer) { - assert(funcCreateVertexBuffer); - // Create the initial buffer, mapped for writing. - // NOTE: We deliberately do not specify a read range here, i.e. second parameter for Map(). - // Testing has shown that specifying the read range (zero start and end, to indicate that no - // reading will be performed), actually results in *slower* upload in practice. - _funcCreateVertexBuffer = funcCreateVertexBuffer; - _pBuffer = _funcCreateVertexBuffer(kBufferSize); - checkHR(_pBuffer->Map(0, nullptr, reinterpret_cast(&_pMappedData))); + allocateNewBuffer(); } // Gets a vertex buffer with the size specified in the provided vertex buffer description, and // fills it with the provided data. The resource pointer and buffer offset of the vertex buffer // description will be populated. - void get(VertexBuffer& vertexBuffer, void* pData) + void get(VertexBuffer& vertexBuffer, void* pData, size_t size) { - AU_ASSERT(vertexBuffer.size > 0 && pData, "Invalid vertex buffer"); - // If the requested size exceeds the total buffer size, just create a dedicated buffer and // and use it directly. - size_t size = vertexBuffer.size; if (size > kBufferSize) { // Create the (large) buffer. - ID3D12ResourcePtr pLargeBuffer = _funcCreateVertexBuffer(size); + TransferBuffer transferBuffer = _funcCreateTransferBuffer(size); // Map it for reading and copy the provided data. - CD3DX12_RANGE writeRange(0, size); - uint8_t* pMappedData = nullptr; - checkHR(pLargeBuffer->Map(0, nullptr, reinterpret_cast(&pMappedData))); + uint8_t* pMappedData = transferBuffer.map(); ::memcpy_s(pMappedData, size, pData, size); - pLargeBuffer->Unmap(0, &writeRange); // no HRESULT + transferBuffer.unmap(); - // Update the provided vertex buffer structure. - vertexBuffer.pBuffer = pLargeBuffer; - vertexBuffer.offset = 0; + // Update the provided vertex buffer structure. We only use the GPU buffer, the + // upload buffer will be released once upload complete. + vertexBuffer = VertexBuffer(transferBuffer.pGPUBuffer, size); return; } @@ -164,11 +198,11 @@ class VertexBufferPool // Copy the provided data to the mapped buffer. ::memcpy_s(_pMappedData + _bufferOffset, size, pData, size); - // Update the provided vertex buffer structure. By assigning the buffer resource pointer to - // the vertex buffer, the owner of the vertex buffer takes on shared ownership of the - // resource, so that it can safely be released by the pool later. - vertexBuffer.pBuffer = _pBuffer; - vertexBuffer.offset = _bufferOffset; + // Update the provided vertex buffer structure. By assigning the GPU buffer resource + // pointer to the vertex buffer, the owner of the vertex buffer takes on shared ownership of + // the resource, so that it can safely be released by the pool later. The upload buffer + // will be release by the renderer once it is uploaded. + vertexBuffer = VertexBuffer(_currentTransferBuffer.pGPUBuffer, size, _bufferOffset); // Increment the buffer offset, while taking the acceleration structure byte alignment (256) // into account. @@ -185,26 +219,34 @@ class VertexBufferPool return; } - // Unmap the (open) buffer, and release the buffer. - CD3DX12_RANGE writeRange(0, _bufferOffset - 1); - _pBuffer->Unmap(0, &writeRange); // no HRESULT - _pBuffer.Reset(); + // Unmap the (open) buffer. + _currentTransferBuffer.unmap(); _pMappedData = nullptr; // Create a new buffer, mapped for writing. - _pBuffer = _funcCreateVertexBuffer(kBufferSize); - _bufferOffset = 0; - checkHR(_pBuffer->Map(0, nullptr, reinterpret_cast(&_pMappedData))); + allocateNewBuffer(); } private: + void allocateNewBuffer() + { + // Allocate a new transfer buffer. + _currentTransferBuffer = _funcCreateTransferBuffer(kBufferSize); + + // Reset offset to zero. + _bufferOffset = 0; + + // Map entirety the current buffer. + _pMappedData = _currentTransferBuffer.map(); + } + // The buffer size is selected to support dozens of average vertex buffers. const size_t kBufferSize = 4 * 1024 * 1024; - FuncCreateVertexBuffer _funcCreateVertexBuffer; - ID3D12ResourcePtr _pBuffer; + FuncCreateVertexBuffer _funcCreateTransferBuffer; + TransferBuffer _currentTransferBuffer; uint8_t* _pMappedData = nullptr; size_t _bufferOffset = 0; }; -END_AURORA \ No newline at end of file +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTDevice.cpp b/Libraries/Aurora/Source/DirectX/PTDevice.cpp index 10bf90e..d7f55d4 100644 --- a/Libraries/Aurora/Source/DirectX/PTDevice.cpp +++ b/Libraries/Aurora/Source/DirectX/PTDevice.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,6 +17,12 @@ BEGIN_AURORA +// If true then DirectX debug layer is enabled (if available.) +// Will only be available if the Graphics Tools are installed via "Apps->Optional Features" in +// Windows Settings. +// https://learn.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features +#define AU_DEVICE_DEBUG_ENABLED defined(_DEBUG) && !defined(NODXDEBUG) + static bool checkDeviceFeatures(ID3D12Device5* pDevice, PTDevice::Features features) { // Check for low-power support. @@ -85,22 +91,27 @@ bool PTDevice::initialize(PTDevice::Features features, int sampleCount) // Enable the use of the debug layer on debug builds, and when NODXDEBUG is not defined. // NOTE: The debug layer requires installation of Graphics Tools for Windows 10, which may not // be desired by some clients. -#if defined(_DEBUG) && !defined(NODXDEBUG) +#if AU_DEVICE_DEBUG_ENABLED // Enable the D3D12 debug layer. + // D3D12GetDebugInterface return a null pointer if the Graphics Tools are not installed. ComPtr pDebugInterface; checkHR(::D3D12GetDebugInterface(IID_PPV_ARGS(&pDebugInterface))); - pDebugInterface->EnableDebugLayer(); - - // Break on DXGI errors. - ComPtr pDXGIInfoQueue; - checkHR(::DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDXGIInfoQueue))); - checkHR(pDXGIInfoQueue->SetBreakOnSeverity( - DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true)); - checkHR(pDXGIInfoQueue->SetBreakOnSeverity( - DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true)); - - // Create the factory with the debug flag. - dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; + if (pDebugInterface) + { + // Enable the debug layer. + pDebugInterface->EnableDebugLayer(); + + // Break on DXGI errors. + ComPtr pDXGIInfoQueue; + checkHR(::DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDXGIInfoQueue))); + checkHR(pDXGIInfoQueue->SetBreakOnSeverity( + DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true)); + checkHR(pDXGIInfoQueue->SetBreakOnSeverity( + DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true)); + + // Create the factory with the debug flag. + dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; + } #endif // Create the DXGI factory. @@ -140,11 +151,14 @@ bool PTDevice::initialize(PTDevice::Features features, int sampleCount) } // Enable breaking on DirectX errors on debug builds, and when NODXDEBUG is not defined. -#if defined(_DEBUG) && !defined(NODXDEBUG) +#if AU_DEVICE_DEBUG_ENABLED ComPtr pD3D12InfoQueue; pDevice.As(&pD3D12InfoQueue); - checkHR(pD3D12InfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true)); - checkHR(pD3D12InfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true)); + if (pD3D12InfoQueue) + { + checkHR(pD3D12InfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true)); + checkHR(pD3D12InfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true)); + } #endif // Record the factory, device, and name for the selected adapter. @@ -183,4 +197,4 @@ bool PTDevice::initialize(PTDevice::Features features, int sampleCount) return false; } -END_AURORA \ No newline at end of file +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTDevice.h b/Libraries/Aurora/Source/DirectX/PTDevice.h index b3a0a82..f1fe682 100644 --- a/Libraries/Aurora/Source/DirectX/PTDevice.h +++ b/Libraries/Aurora/Source/DirectX/PTDevice.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp b/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp index b4f129f..6bea55f 100644 --- a/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp +++ b/Libraries/Aurora/Source/DirectX/PTEnvironment.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ bool PTEnvironment::update() // Update the constant buffer with environment data, creating the buffer if needed. The lambda // here translates values to the data object for the constant buffer (GPU). _pRenderer->updateBuffer( - _pConstantBuffer, [this](EnvironmentData& data) { updateGPUStruct(data); }); + _constantBuffer, [this](EnvironmentData& data) { updateGPUStruct(data); }); _bIsDirty = false; diff --git a/Libraries/Aurora/Source/DirectX/PTEnvironment.h b/Libraries/Aurora/Source/DirectX/PTEnvironment.h index c86180a..71ddde3 100644 --- a/Libraries/Aurora/Source/DirectX/PTEnvironment.h +++ b/Libraries/Aurora/Source/DirectX/PTEnvironment.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ #pragma once #include "EnvironmentBase.h" +#include "MemoryPool.h" BEGIN_AURORA @@ -36,7 +37,7 @@ class PTEnvironment : public EnvironmentBase // Light texture and background texture. return 2; } - ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } + ID3D12Resource* buffer() const { return _constantBuffer.pGPUBuffer.Get(); } ID3D12Resource* aliasMap() const; bool update(); void createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const; @@ -47,7 +48,7 @@ class PTEnvironment : public EnvironmentBase /*** Private Variables ***/ PTRenderer* _pRenderer = nullptr; - ID3D12ResourcePtr _pConstantBuffer; + TransferBuffer _constantBuffer; }; MAKE_AURORA_PTR(PTEnvironment); diff --git a/Libraries/Aurora/Source/DirectX/PTGeometry.cpp b/Libraries/Aurora/Source/DirectX/PTGeometry.cpp index 3e25f99..6d587c0 100644 --- a/Libraries/Aurora/Source/DirectX/PTGeometry.cpp +++ b/Libraries/Aurora/Source/DirectX/PTGeometry.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ static void copyVertexChannelData(vector& dst, const AttributeDat ComponentType* pDstComp = dst.data(); for (size_t i = 0; i < vertexCount; i++) { - // Copy the individual element from the soure buffer to destination. + // Copy the individual element from the source buffer to destination. const ComponentType* pSrcComp = reinterpret_cast(pSrc); for (uint32_t j = 0; j < componentCount; j++) { @@ -116,6 +116,10 @@ bool PTGeometry::update() { createVertexBuffer(_normalBuffer, _normals.data(), sizeof(float) * _vertexCount * 3); } + if (!_tangents.empty()) + { + createVertexBuffer(_tangentBuffer, _tangents.data(), sizeof(float) * _vertexCount * 3); + } if (!_texCoords.empty()) { createVertexBuffer(_texCoordBuffer, _texCoords.data(), sizeof(float) * _vertexCount * 2); @@ -146,8 +150,7 @@ bool PTGeometry::updateBLAS() void PTGeometry::createVertexBuffer(VertexBuffer& vertexBuffer, void* pData, size_t dataSize) const { - vertexBuffer.size = dataSize; - _pRenderer->getVertexBuffer(vertexBuffer, pData); + _pRenderer->getVertexBuffer(vertexBuffer, pData, dataSize); } ID3D12ResourcePtr PTGeometry::buildBLAS() @@ -164,7 +167,7 @@ ID3D12ResourcePtr PTGeometry::buildBLAS() // Specify the vertex data. triangles.VertexCount = _vertexCount; triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; - triangles.VertexBuffer.StartAddress = _positionBuffer.address(); + triangles.VertexBuffer.StartAddress = _positionBuffer.gpuAddress(); triangles.VertexBuffer.StrideInBytes = sizeof(float) * 3; // Specify the index data, if any. @@ -172,7 +175,7 @@ ID3D12ResourcePtr PTGeometry::buildBLAS() { triangles.IndexCount = _indexCount; triangles.IndexFormat = DXGI_FORMAT_R32_UINT; - triangles.IndexBuffer = _indexBuffer.address(); + triangles.IndexBuffer = _indexBuffer.gpuAddress(); } // Describe the BLAS. diff --git a/Libraries/Aurora/Source/DirectX/PTGeometry.h b/Libraries/Aurora/Source/DirectX/PTGeometry.h index aecec60..e16fc07 100644 --- a/Libraries/Aurora/Source/DirectX/PTGeometry.h +++ b/Libraries/Aurora/Source/DirectX/PTGeometry.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,17 +20,25 @@ BEGIN_AURORA // Forward declarations. class PTRenderer; -// A structure describing a vertex buffer obtained from a vertex buffer pool. -struct VertexBuffer +// A class describing a vertex buffer obtained from a vertex buffer pool. +class VertexBuffer { - ID3D12ResourcePtr pBuffer; - size_t size = 0; - size_t offset = 0; +public: + VertexBuffer(ID3D12ResourcePtr buffer = nullptr, size_t size = 0, size_t offset = 0) : + _pGPUBuffer(buffer), _size(size), _offset(offset) + { + } - D3D12_GPU_VIRTUAL_ADDRESS address() const + D3D12_GPU_VIRTUAL_ADDRESS gpuAddress() const { - return pBuffer ? pBuffer->GetGPUVirtualAddress() + offset : 0; + return _pGPUBuffer ? _pGPUBuffer->GetGPUVirtualAddress() + _offset : 0; } + size_t size() { return _size; } + +private: + ID3D12ResourcePtr _pGPUBuffer; + size_t _size = 0; + size_t _offset = 0; }; // An internal implementation for IGeometry. @@ -44,6 +52,7 @@ class PTGeometry : public GeometryBase D3D12_GPU_VIRTUAL_ADDRESS IndexBuffer = 0; D3D12_GPU_VIRTUAL_ADDRESS PositionBuffer = 0; D3D12_GPU_VIRTUAL_ADDRESS NormalBuffer = 0; + D3D12_GPU_VIRTUAL_ADDRESS TangentBuffer = 0; D3D12_GPU_VIRTUAL_ADDRESS TexCoordBuffer = 0; }; @@ -57,10 +66,11 @@ class PTGeometry : public GeometryBase GeometryBuffers buffers() const { GeometryBuffers buffers; - buffers.IndexBuffer = _indexBuffer.address(); - buffers.PositionBuffer = _positionBuffer.address(); - buffers.NormalBuffer = _normalBuffer.address(); - buffers.TexCoordBuffer = _texCoordBuffer.address(); + buffers.IndexBuffer = _indexBuffer.gpuAddress(); + buffers.PositionBuffer = _positionBuffer.gpuAddress(); + buffers.NormalBuffer = _normalBuffer.gpuAddress(); + buffers.TangentBuffer = _tangentBuffer.gpuAddress(); + buffers.TexCoordBuffer = _texCoordBuffer.gpuAddress(); return buffers; } ID3D12Resource* blas() { return _pBLAS.Get(); } @@ -83,6 +93,7 @@ class PTGeometry : public GeometryBase VertexBuffer _indexBuffer; VertexBuffer _positionBuffer; VertexBuffer _normalBuffer; + VertexBuffer _tangentBuffer; VertexBuffer _texCoordBuffer; }; MAKE_AURORA_PTR(PTGeometry); diff --git a/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp b/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp index 7cff8ee..27f89ab 100644 --- a/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp +++ b/Libraries/Aurora/Source/DirectX/PTGroundPlane.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -69,7 +69,7 @@ void PTGroundPlane::update() // Update the constant buffer with ground plane data, creating the buffer if needed. The lambda // here translates values to the data object for the constant buffer (GPU). - _pRenderer->updateBuffer(_pConstantBuffer, [this](GroundPlaneData& data) { + _pRenderer->updateBuffer(_constantBuffer, [this](GroundPlaneData& data) { data.enabled = _values.asBoolean("enabled") ? 1 : 0; data.position = _values.asFloat3("position"); data.normal = _values.asFloat3("normal"); diff --git a/Libraries/Aurora/Source/DirectX/PTGroundPlane.h b/Libraries/Aurora/Source/DirectX/PTGroundPlane.h index 0a9b9ca..36add2b 100644 --- a/Libraries/Aurora/Source/DirectX/PTGroundPlane.h +++ b/Libraries/Aurora/Source/DirectX/PTGroundPlane.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ // limitations under the License. #pragma once +#include "MemoryPool.h" #include "Properties.h" BEGIN_AURORA @@ -34,7 +35,7 @@ class PTGroundPlane : public IGroundPlane, public FixedValues /*** Functions ***/ - ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } + ID3D12Resource* buffer() const { return _constantBuffer.pGPUBuffer.Get(); } void update(); private: @@ -59,7 +60,7 @@ class PTGroundPlane : public IGroundPlane, public FixedValues /*** Private Variables ***/ PTRenderer* _pRenderer; - ID3D12ResourcePtr _pConstantBuffer; + TransferBuffer _constantBuffer; }; MAKE_AURORA_PTR(PTGroundPlane); diff --git a/Libraries/Aurora/Source/DirectX/PTImage.cpp b/Libraries/Aurora/Source/DirectX/PTImage.cpp index 5b00d0b..7b7f378 100644 --- a/Libraries/Aurora/Source/DirectX/PTImage.cpp +++ b/Libraries/Aurora/Source/DirectX/PTImage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -39,14 +39,20 @@ PTImage::PTImage(PTRenderer* pRenderer, const IImage::InitData& initData) // sampling of that environment image. if (initData.isEnvironment) { - // Create a GPU buffer containing the alias map data. - size_t bufferSize = _dimensions.x * _dimensions.y * sizeof(AliasMap::Entry); - _pAliasMapBuffer = _pRenderer->createBuffer(bufferSize); - AliasMap::Entry* pMappedData = nullptr; - checkHR(_pAliasMapBuffer->Map(0, nullptr, reinterpret_cast(&pMappedData))); + // Create a transfer buffer for the alias map data. + size_t bufferSize = _dimensions.x * _dimensions.y * sizeof(AliasMap::Entry); + TransferBuffer transferBuffer = + _pRenderer->createTransferBuffer(bufferSize, _name + "AliasMap"); + + // Build the alias map directly in the mapped buffer. + AliasMap::Entry* pMappedData = reinterpret_cast(transferBuffer.map()); AliasMap::build(static_cast(initData.pImageData), _dimensions, pMappedData, bufferSize, _luminanceIntegral); - _pAliasMapBuffer->Unmap(0, nullptr); // no HRESULT + transferBuffer.unmap(); + + // Retain the GPU buffer pointer from the transfer buffer (upload buffer will be deleted + // after upload complete.) + _pAliasMapBuffer = transferBuffer.pGPUBuffer; } } diff --git a/Libraries/Aurora/Source/DirectX/PTImage.h b/Libraries/Aurora/Source/DirectX/PTImage.h index 1b5e477..72a38f7 100644 --- a/Libraries/Aurora/Source/DirectX/PTImage.h +++ b/Libraries/Aurora/Source/DirectX/PTImage.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/PTLight.cpp b/Libraries/Aurora/Source/DirectX/PTLight.cpp new file mode 100644 index 0000000..76e6abf --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTLight.cpp @@ -0,0 +1,58 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "PTLight.h" + +#include "PTRenderer.h" + +BEGIN_AURORA + +static PropertySetPtr g_pDistantLightPropertySet; + +static PropertySetPtr distantLightPropertySet() +{ + if (g_pDistantLightPropertySet) + { + return g_pDistantLightPropertySet; + } + + // Create properties and defaults for distant lights. + g_pDistantLightPropertySet = make_shared(); + g_pDistantLightPropertySet->add( + Names::LightProperties::kDirection, normalize(vec3(-1.0f, -0.5f, -1.0f))); + g_pDistantLightPropertySet->add(Names::LightProperties::kColor, vec3(1, 1, 1)); + g_pDistantLightPropertySet->add(Names::LightProperties::kAngularDiameter, 0.1f); + g_pDistantLightPropertySet->add(Names::LightProperties::kExposure, 0.0f); + g_pDistantLightPropertySet->add(Names::LightProperties::kIntensity, 1.0f); + + return g_pDistantLightPropertySet; +} + +// Return the property set for the provided light type. +static PropertySetPtr propertySet(const string& type) +{ + if (type.compare(Names::LightTypes::kDistantLight) == 0) + return distantLightPropertySet(); + + AU_FAIL("Unknown light type:%s", type.c_str()); + return nullptr; +} + +PTLight::PTLight(PTScene* pScene, const string& lightType, int index) : + FixedValues(propertySet(lightType)), _pScene(pScene), _index(index) +{ +} + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTLight.h b/Libraries/Aurora/Source/DirectX/PTLight.h new file mode 100644 index 0000000..f02218f --- /dev/null +++ b/Libraries/Aurora/Source/DirectX/PTLight.h @@ -0,0 +1,49 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +// Forward declarations. +class PTScene; + +// An internal implementation for ILight. +class PTLight : public ILight, public FixedValues +{ +public: + /*** Lifetime Management ***/ + + PTLight(PTScene* pScene, const string& lightType, int index); + ~PTLight() {}; + + /*** Functions ***/ + FixedValues& values() override { return *this; } + + int index() const { return _index; } + + bool isDirty() const { return _bIsDirty; } + void clearDirtyFlag() { _bIsDirty = false; } + +private: + /*** Private Variables ***/ + + PTScene* _pScene = nullptr; + int _index; +}; + +MAKE_AURORA_PTR(PTLight); + +END_AURORA diff --git a/Libraries/Aurora/Source/DirectX/PTMaterial.cpp b/Libraries/Aurora/Source/DirectX/PTMaterial.cpp index 1055d2f..e1f9807 100644 --- a/Libraries/Aurora/Source/DirectX/PTMaterial.cpp +++ b/Libraries/Aurora/Source/DirectX/PTMaterial.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,10 +24,10 @@ BEGIN_AURORA // Convenience macro to compare reflection data from shader to CPU offset // NOTE: Can only be used inside PTMaterial::validateOffsets(). #define VALIDATE_OFFSET(_member) \ - if (structDescriptions[#_member].Offset != offsetof(MaterialData, _member)) \ + if (structDescriptions[#_member].Offset != uniformBuffer.getOffsetForVariable(#_member)) \ { \ AU_ERROR("%s offset mismatch: %d!=%d\n", #_member, structDescriptions[#_member].Offset, \ - offsetof(MaterialData, _member)); \ + uniformBuffer.getOffsetForVariable(#_member)); \ isValid = false; \ } @@ -90,6 +90,8 @@ bool PTMaterial::validateOffsets(const PTShaderLibrary& pShaderLibrary) structDescriptions[pVarName] = varDesc; } + UniformBuffer uniformBuffer(StandardSurfaceUniforms, StandardSurfaceDefaults.properties); + // AU_INFO("%s", uniformBuffer.generateHLSLStruct().c_str()); // Validate each offset; isValid flag will get set to false if invalid. bool isValid = true; VALIDATE_OFFSET(base); @@ -123,27 +125,33 @@ bool PTMaterial::validateOffsets(const PTShaderLibrary& pShaderLibrary) VALIDATE_OFFSET(opacity); VALIDATE_OFFSET(thinWalled); VALIDATE_OFFSET(hasBaseColorTex); - VALIDATE_OFFSET(baseColorTexTransform); + VALIDATE_OFFSET(baseColorTexRotation); VALIDATE_OFFSET(hasSpecularRoughnessTex); - VALIDATE_OFFSET(specularRoughnessTexTransform); + VALIDATE_OFFSET(specularRoughnessTexOffset); + VALIDATE_OFFSET(hasEmissionColorTex); + VALIDATE_OFFSET(emissionColorTexOffset); + VALIDATE_OFFSET(emissionColorTexScale); + VALIDATE_OFFSET(emissionColorTexPivot); + VALIDATE_OFFSET(emissionColorTexRotation); VALIDATE_OFFSET(hasOpacityTex); - VALIDATE_OFFSET(opacityTexTransform); + VALIDATE_OFFSET(opacityTexPivot); VALIDATE_OFFSET(hasNormalTex); - VALIDATE_OFFSET(normalTexTransform); - VALIDATE_OFFSET(isOpaque); + VALIDATE_OFFSET(normalTexScale); + VALIDATE_OFFSET(normalTexRotation); // Validate structure size - if (sizeof(MaterialData) != cbDesc.Size) + if (cbDesc.Size != uniformBuffer.size()) { - AU_ERROR("%s struct sizes differ: %d!=%d", cbDesc.Name, sizeof(MaterialData), cbDesc.Size); + AU_ERROR("%s struct sizes differ: %d!=%d", cbDesc.Name, cbDesc.Size, uniformBuffer.size()); isValid = false; } return isValid; } -PTMaterial::PTMaterial(PTRenderer* pRenderer, shared_ptr pType) : - _pRenderer(pRenderer), _pType(pType) +PTMaterial::PTMaterial( + PTRenderer* pRenderer, MaterialShaderPtr pShader, shared_ptr pDef) : + MaterialBase(pShader, pDef), _pRenderer(pRenderer) { } @@ -155,11 +163,20 @@ bool PTMaterial::update() return false; } - // Update the constant buffer with material data, creating the buffer if needed. The lambda here - // translates values to the data object for the constant buffer (GPU). - // NOTE: The order here matches the order of the MaterialData structure. - _pRenderer->updateBuffer( - _pConstantBuffer, [this](MaterialData& data) { updateGPUStruct(data); }); + // Run the type-specific update function on this material. + definition()->updateFunction()(*this); + + // Create a transfer buffer for the material data if it doesn't already exist. + if (!_constantBuffer.size) + { + _constantBuffer = _pRenderer->createTransferBuffer( + uniformBuffer().size(), std::to_string(uint64_t(this)) + "MaterialBuffer"); + } + + // Copy the data to the transfer buffer. + void* pMappedData = _constantBuffer.map(uniformBuffer().size()); + ::memcpy_s(pMappedData, uniformBuffer().size(), uniformBuffer().data(), uniformBuffer().size()); + _constantBuffer.unmap(); _bIsDirty = false; @@ -211,29 +228,26 @@ void PTMaterial::createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT i // Create a SRV (descriptor) on the descriptor heap for the base color image, if any. PTImagePtr pImage = dynamic_pointer_cast(_values.asImage("base_color_image")); PTImage::createSRV(*_pRenderer, pImage.get(), handle); - - // Increment the heap location pointed to by the handle past the base color image SRV. handle.Offset(increment); // Create a SRV (descriptor) on the descriptor heap for the specular roughness image, if any. pImage = dynamic_pointer_cast(_values.asImage("specular_roughness_image")); PTImage::createSRV(*_pRenderer, pImage.get(), handle); - - // Increment the heap location pointed to by the handle past the specular roughness image SRV. handle.Offset(increment); // Create a SRV (descriptor) on the descriptor heap for the normal image, if any. pImage = dynamic_pointer_cast(_values.asImage("normal_image")); PTImage::createSRV(*_pRenderer, pImage.get(), handle); + handle.Offset(increment); - // Increment the heap location pointed to by the handle past the normal image SRV. + // Create a SRV (descriptor) on the descriptor heap for the emission color image, if any. + pImage = dynamic_pointer_cast(_values.asImage("emission_color_image")); + PTImage::createSRV(*_pRenderer, pImage.get(), handle); handle.Offset(increment); - // Create a SRV (descriptor) on the descriptor heap for the normal image, if any. + // Create a SRV (descriptor) on the descriptor heap for the opacity image, if any. pImage = dynamic_pointer_cast(_values.asImage("opacity_image")); PTImage::createSRV(*_pRenderer, pImage.get(), handle); - - // Increment the heap location pointed to by the handle past the opacity image SRV. handle.Offset(increment); } diff --git a/Libraries/Aurora/Source/DirectX/PTMaterial.h b/Libraries/Aurora/Source/DirectX/PTMaterial.h index 2c12380..06b4881 100644 --- a/Libraries/Aurora/Source/DirectX/PTMaterial.h +++ b/Libraries/Aurora/Source/DirectX/PTMaterial.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,13 +14,23 @@ #pragma once #include "MaterialBase.h" +#include "MemoryPool.h" BEGIN_AURORA // Forward declarations. class PTRenderer; -class PTMaterialType; +class MaterialShader; class PTShaderLibrary; +struct MaterialDefaultValues; + +struct TextureTransform +{ + vec2 pivot; + vec2 scale; + vec2 offset; + float rotation = 0.0f; +}; // An internal implementation for IMaterial. class PTMaterial : public MaterialBase @@ -35,7 +45,8 @@ class PTMaterial : public MaterialBase /*** Lifetime Management ***/ - PTMaterial(PTRenderer* pRenderer, shared_ptr pType); + PTMaterial( + PTRenderer* pRenderer, MaterialShaderPtr pShader, shared_ptr pDef); ~PTMaterial() {}; /*** Functions ***/ @@ -43,22 +54,20 @@ class PTMaterial : public MaterialBase // The total number of texture descriptors for each material instance. static uint32_t descriptorCount() { - // Base color, specular roughness, normal and opacity textures. - return 4; + // Base color, specular roughness, normal, emission color, and opacity textures. + return 5; } // The total number of sampler descriptors for each material instance. static uint32_t samplerDescriptorCount() { // Base color + opacity only for now. + // TODO: Support samplers for other textures. return 2; } // Gets the constant buffer for this material. - ID3D12Resource* buffer() const { return _pConstantBuffer.Get(); } - - // Gets the material type for this material. - shared_ptr materialType() const { return _pType; } + ID3D12Resource* buffer() const { return _constantBuffer.pGPUBuffer.Get(); } bool update(); void createDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE& handle, UINT increment) const; @@ -70,11 +79,12 @@ class PTMaterial : public MaterialBase /*** Private Functions ***/ + bool computeIsOpaque() const; + /*** Private Variables ***/ PTRenderer* _pRenderer = nullptr; - ID3D12ResourcePtr _pConstantBuffer; - shared_ptr _pType; + TransferBuffer _constantBuffer; }; MAKE_AURORA_PTR(PTMaterial); diff --git a/Libraries/Aurora/Source/DirectX/PTRenderer.cpp b/Libraries/Aurora/Source/DirectX/PTRenderer.cpp index 237d158..53e7131 100644 --- a/Libraries/Aurora/Source/DirectX/PTRenderer.cpp +++ b/Libraries/Aurora/Source/DirectX/PTRenderer.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,10 +24,10 @@ #include "PTGeometry.h" #include "PTGroundPlane.h" #include "PTImage.h" +#include "PTLight.h" #include "PTMaterial.h" #include "PTScene.h" #include "PTShaderLibrary.h" -#include "PTTarget.h" #if ENABLE_MATERIALX #include "MaterialX/MaterialGenerator.h" @@ -78,17 +78,14 @@ PTRenderer::PTRenderer(uint32_t taskCount) : RendererBase(taskCount) // TODO: Should be per-scene not per-renderer. _pShaderLibrary = make_unique(_pDXDevice); - // Enable layer shaders on all platforms except AMD GPUs. - // TODO: There is a driver bug that causes a failure on AMD, when attempting to trace a ray with - // a null acceleration structure. This capability is needed for material layers. Once this - // driver bug is fixed, this can either be removed, or check for a specific driver version. - _pShaderLibrary->setOption("ENABLE_LAYERS", _pDevice->vendor() != PTDevice::Vendor::kAMD); + // Enable layer shaders. + _pShaderLibrary->setOption("ENABLE_LAYERS", true); #if ENABLE_MATERIALX // Get the materialX folder relative to the module path. string mtlxFolder = Foundation::getModulePath() + "MaterialX"; // Initialize the MaterialX code generator. - _pMaterialXGenerator = make_unique(this, mtlxFolder); + _pMaterialXGenerator = make_unique(mtlxFolder); // Default to MaterialX distance unit to centimeters. _pShaderLibrary->setOption( @@ -115,7 +112,7 @@ PTRenderer::PTRenderer(uint32_t taskCount) : RendererBase(taskCount) D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); }); _pVertexBufferPool = make_unique( - [&](size_t size) { return createBuffer(size, "Vertex Buffer Pool"); }); + [&](size_t size) { return createTransferBuffer(size, "VertexBufferPool"); }); } PTRenderer::~PTRenderer() @@ -172,9 +169,9 @@ IMaterialPtr PTRenderer::createMaterialPointer( // This has no overhead, so just do it each time. _pAssetMgr->enableVerticalFlipOnImageLoad(_values.asBoolean(kLabelIsFlipImageYEnabled)); - // The material type and default values for this material. - PTMaterialTypePtr pMtlType; - map defaultValues; + // The material shader and definition for this material. + MaterialShaderPtr pShader; + shared_ptr pDef; // Create a material type based on the material type name provided. if (materialType.compare(Names::MaterialTypes::kBuiltIn) == 0) @@ -182,32 +179,33 @@ IMaterialPtr PTRenderer::createMaterialPointer( // Work out built-in type. string builtInType = document; - // Get the built-in material type from the shader library. - pMtlType = _pShaderLibrary->getBuiltInMaterialType(builtInType); + // Get the built-in material type and definition for built-in. + pShader = _pShaderLibrary->getBuiltInShader(builtInType); + pDef = _pShaderLibrary->getBuiltInMaterialDefinition(builtInType); - // Print error and provide null material type if built-in not found. + // Print error and provide null material shader if built-in not found. // TODO: Proper error handling for this case. - if (!pMtlType) + if (!pShader) { AU_ERROR("Unknown built-in material type %s for material %s", document.c_str(), name.c_str()); - pMtlType = nullptr; + pShader = nullptr; } } else if (materialType.compare(Names::MaterialTypes::kMaterialX) == 0) { - // Generate a material type from the materialX document. - pMtlType = generateMaterialX(document, &defaultValues); + // Generate a material shader and definition from the materialX document. + pShader = generateMaterialX(document, &pDef); // If flag is set dump the document to disk for development purposes. if (AU_DEV_DUMP_MATERIALX_DOCUMENTS) { - string mltxPath = Foundation::getModulePath() + name + "Dumped.mtlx"; - AU_INFO("Dumping MTLX document to:%s", mltxPath.c_str()); - ofstream outputFile; - outputFile.open(mltxPath); - outputFile << document; - outputFile.close(); + string mltxPath = name + "Dumped.mtlx"; + Foundation::sanitizeFileName(mltxPath); + if (Foundation::writeStringToFile(document, mltxPath)) + AU_INFO("Dumping MTLX document to:%s", mltxPath.c_str()); + else + AU_WARN("Failed to dump MTLX document to:%s", mltxPath.c_str()); } } else if (materialType.compare(Names::MaterialTypes::kMaterialXPath) == 0) @@ -221,66 +219,40 @@ IMaterialPtr PTRenderer::createMaterialPointer( { AU_ERROR("Failed to load MaterialX document %s for material %s", document.c_str(), name.c_str()); - pMtlType = nullptr; + pShader = nullptr; } else { - // If Material XML document loaded, use it to generate material type. - pMtlType = generateMaterialX(*pMtlxDocument, &defaultValues); + // If Material XML document loaded, use it to generate the material shader and + // definition. + pShader = generateMaterialX(*pMtlxDocument, &pDef); } } else { - // Print error and return null material type if material type not found. + // Print error and return null material shader if material type not found. // TODO: Proper error handling for this case. - AU_ERROR("Unrecognized material type %s for material %s, using Default built-in instead.", - materialType.c_str(), name.c_str()); - pMtlType = nullptr; + AU_ERROR( + "Unrecognized material type %s for material %s.", materialType.c_str(), name.c_str()); + pShader = nullptr; } // Error case, just return null material. - if (!pMtlType) + if (!pShader || !pDef) return nullptr; - // Create the material object with the material type. - auto pNewMtl = make_shared(this, pMtlType); + // Create the material object with the material shader and definition. + auto pNewMtl = make_shared(this, pShader, pDef); - // Set the default values on the new material. - for (auto valIter : defaultValues) + // Set the default textures on the new material. + for (int i = 0; i < pDef->defaults().textures.size(); i++) { - auto defaultVal = valIter.second; - switch (defaultVal.type()) - { - case Aurora::IValues::Type::Float: - // Set float default value. - pNewMtl->values().setFloat(valIter.first, defaultVal.asFloat()); - break; - case Aurora::IValues::Type::Int: - // Set int default value. - pNewMtl->values().setInt(valIter.first, defaultVal.asInt()); - break; - case Aurora::IValues::Type::Boolean: - // Set bool default value. - pNewMtl->values().setBoolean(valIter.first, defaultVal.asBoolean()); - break; - case Aurora::IValues::Type::Float2: - // Set 2d vector default value. - pNewMtl->values().setFloat2(valIter.first, &defaultVal.asFloat2().x); - break; - case Aurora::IValues::Type::Float3: - // Set 3d vector default value. - pNewMtl->values().setFloat3(valIter.first, &defaultVal.asFloat3().x); - break; - case Aurora::IValues::Type::Sampler: - { - pNewMtl->values().setSampler(valIter.first, defaultVal.asSampler()); - } - break; - case Aurora::IValues::Type::String: - { - // Image default values are provided as strings and must be loaded. - auto textureFilename = defaultVal.asString(); + auto txtDef = pDef->defaults().textures[i]; + // Image default values are provided as strings and must be loaded. + auto textureFilename = txtDef.defaultFilename; + if (!textureFilename.empty()) + { // Load the pixels for the image using asset manager. auto pImageData = _pAssetMgr->acquireImage(textureFilename); if (!pImageData) @@ -292,7 +264,11 @@ IMaterialPtr PTRenderer::createMaterialPointer( } else { - // Create Aurora image from the loaded pixels. + // Set the linearize flag. + // TODO: Should effect caching. + pImageData->data.linearize = txtDef.linearize; + + // Create Ultra image from the loaded pixels. // TODO: This should be cached by filename. auto pImage = createImagePointer(pImageData->data); if (!pImage) @@ -305,13 +281,41 @@ IMaterialPtr PTRenderer::createMaterialPointer( else { // Set the default image. - pNewMtl->setImage(valIter.first, pImage); + pNewMtl->setImage(txtDef.name, pImage); } } } - break; - default: - break; + + // If we have an address mode, create a sampler for texture. + // Currently only the first two hardcoded textures have samplers, so only do this for first + // two textures. + // TODO: Move to fully data driven textures and samplers. + if (i < 2 && (!txtDef.addressModeU.empty() || !txtDef.addressModeV.empty())) + { + Properties samplerProps; + + // Set U address mode. + if (txtDef.addressModeU.compare("periodic") == 0) + samplerProps[Names::SamplerProperties::kAddressModeU] = Names::AddressModes::kWrap; + else if (txtDef.addressModeU.compare("clamp") == 0) + samplerProps[Names::SamplerProperties::kAddressModeU] = Names::AddressModes::kClamp; + else if (txtDef.addressModeU.compare("mirror") == 0) + samplerProps[Names::SamplerProperties::kAddressModeU] = + Names::AddressModes::kMirror; + + // Set V address mode. + if (txtDef.addressModeV.compare("periodic") == 0) + samplerProps[Names::SamplerProperties::kAddressModeV] = Names::AddressModes::kWrap; + else if (txtDef.addressModeV.compare("clamp") == 0) + samplerProps[Names::SamplerProperties::kAddressModeV] = Names::AddressModes::kClamp; + else if (txtDef.addressModeV.compare("mirror") == 0) + samplerProps[Names::SamplerProperties::kAddressModeV] = + Names::AddressModes::kMirror; + + // Create a sampler and set in the material. + // TODO: Don't assume hardcoded _sampler prefix. + auto pSampler = createSamplerPointer(samplerProps); + pNewMtl->setSampler(txtDef.name + "_sampler", pSampler); } } @@ -464,6 +468,26 @@ ID3D12ResourcePtr PTRenderer::createBuffer(size_t size, const string& name, return pResource; } +TransferBuffer PTRenderer::createTransferBuffer(size_t sz, const string& name, + D3D12_RESOURCE_FLAGS gpuBufferFlags, D3D12_RESOURCE_STATES gpuBufferState, + D3D12_RESOURCE_STATES gpuBufferFinalState) +{ + TransferBuffer buffer; + // Create the upload buffer in the UPLOAD heap (which will mean it is in CPU memory not VRAM). + buffer.pUploadBuffer = createBuffer(sz, name + ":Upload"); + // Create the GPU buffer in the DEFAULT heap, with the flags and state provided (these will + // default to D3D12_RESOURCE_FLAG_NONE and D3D12_RESOURCE_STATE_COPY_DEST). + buffer.pGPUBuffer = + createBuffer(sz, name + ":GPU", D3D12_HEAP_TYPE_DEFAULT, gpuBufferFlags, gpuBufferState); + // Set the size. + buffer.size = sz; + // Set the renderer to this (will call transferBufferUpdated from unmap.) + buffer.pRenderer = this; + // Set the final state for the buffer, which it will transition to after uploading. + buffer.finalState = gpuBufferFinalState; + return buffer; +} + ID3D12ResourcePtr PTRenderer::createTexture(uvec2 dimensions, DXGI_FORMAT format, const string& name, bool isUnorderedAccess, bool shareable) { @@ -504,9 +528,30 @@ D3D12_GPU_VIRTUAL_ADDRESS PTRenderer::getScratchBuffer(size_t size) return _pScratchBufferCache->get(size); } -void PTRenderer::getVertexBuffer(VertexBuffer& vertexBuffer, void* pData) +void PTRenderer::transferBufferUpdated(const TransferBuffer& buffer) { - _pVertexBufferPool->get(vertexBuffer, pData); + // Get the mapped range for buffer (set the end to buffer size, in the case where end==0.) + size_t beginMap = buffer.mappedRange.Begin; + size_t endMap = buffer.mappedRange.End == 0 ? buffer.size : buffer.mappedRange.End; + + // See if this buffer already exists in pending list. + auto iter = _pendingTransferBuffers.find(buffer.pGPUBuffer.Get()); + if (iter != _pendingTransferBuffers.end()) + { + // Just update the mapped range in the existing pending buffer. + iter->second.mappedRange.Begin = std::min(iter->second.mappedRange.Begin, beginMap); + iter->second.mappedRange.End = std::max(iter->second.mappedRange.End, endMap); + return; + } + + // Add the buffer in the pending list, updating the end range if needed. + _pendingTransferBuffers[buffer.pGPUBuffer.Get()] = buffer; + _pendingTransferBuffers[buffer.pGPUBuffer.Get()].mappedRange.End = endMap; +} + +void PTRenderer::getVertexBuffer(VertexBuffer& vertexBuffer, void* pData, size_t size) +{ + _pVertexBufferPool->get(vertexBuffer, pData, size); } void PTRenderer::flushVertexBufferPool() @@ -514,6 +559,66 @@ void PTRenderer::flushVertexBufferPool() _pVertexBufferPool->flush(); } +void PTRenderer::deleteUploadedTransferBuffers() +{ + // Do nothing if no buffers to delete. + if (_transferBuffersToDelete.empty()) + return; + + // Clear the list, so any upload or GPU buffer without a reference to it will be deleted. + _transferBuffersToDelete.clear(); +} + +void PTRenderer::uploadTransferBuffers() +{ + // If there are no transfer buffers pending, do nothing. + if (_pendingTransferBuffers.empty()) + return; + + // Begin a command list. + ID3D12GraphicsCommandList4Ptr pCommandList = beginCommandList(); + + // Iterate through all pending buffers. + for (auto iter = _pendingTransferBuffers.begin(); iter != _pendingTransferBuffers.end(); iter++) + { + // Get pending buffer. + auto& buffer = iter->second; + + // Calculate the byte count from the mapped range (which will be the maximum range mapped + // this frame.) + size_t bytesToCopy = buffer.mappedRange.End - buffer.mappedRange.Begin; + + // If this buffer was previously uploaded we need transition back to + // D3D12_RESOURCE_STATE_COPY_DEST before copying. + if (buffer.wasUploaded) + addTransitionBarrier( + buffer.pGPUBuffer.Get(), buffer.finalState, D3D12_RESOURCE_STATE_COPY_DEST); + + // Submit buffer copy command for mapped range from the upload buffer to the GPU buffer. + pCommandList->CopyBufferRegion(buffer.pGPUBuffer.Get(), buffer.mappedRange.Begin, + buffer.pUploadBuffer.Get(), buffer.mappedRange.Begin, bytesToCopy); + + // Transition the buffer to its final state. + // Note in practice the Nvidia driver seems to do this implicitly without any problems, + // though the spec says this explicit transition is required. + addTransitionBarrier( + buffer.pGPUBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, buffer.finalState); + + // Set the uploaded flag. + buffer.wasUploaded = true; + + // Add the buffer to the list to be deleted (if nothing has kept a reference to it) next + // frame. + _transferBuffersToDelete[iter->first] = iter->second; + } + + // Submit the command list. + submitCommandList(); + + // Clear the pending list. + _pendingTransferBuffers.clear(); +} + ID3D12CommandAllocator* PTRenderer::getCommandAllocator() { return _commandAllocators[_taskIndex].Get(); @@ -698,7 +803,7 @@ void PTRenderer::initFrameData() // Create a buffer to store frame data for each buffer. A single buffer can be used for this, // as long as each section of the buffer is not written to while it is being used by the GPU. size_t frameDataBufferSize = _FRAME_DATA_SIZE * _taskCount; - _pFrameDataBuffer = createBuffer(frameDataBufferSize, "FrameData"); + _frameDataBuffer = createTransferBuffer(frameDataBufferSize, "FrameData"); } void PTRenderer::initRayGenShaderTable() @@ -710,22 +815,21 @@ void PTRenderer::initRayGenShaderTable() rayGenShaderRecordStride = ALIGNED_SIZE(rayGenShaderRecordStride, SHADER_RECORD_ALIGNMENT); _rayGenShaderTableSize = rayGenShaderRecordStride; // just one shader record - // Create a buffer for the shader table and map it for writing. - _pRayGenShaderTable = createBuffer(_rayGenShaderTableSize); + // Create a transfer buffer for the shader table. + _rayGenShaderTable = createTransferBuffer(_rayGenShaderTableSize, "RayGenShaderTable"); } void PTRenderer::updateRayGenShaderTable() { - uint8_t* pShaderTableMappedData = nullptr; - checkHR( - _pRayGenShaderTable->Map(0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + // Map the ray gen shader table. + uint8_t* pShaderTableMappedData = _rayGenShaderTable.map(); // Write the shader identifier for the ray gen shader. - ::memcpy_s(pShaderTableMappedData, _rayGenShaderTableSize, _pShaderLibrary->getRayGenShaderID(), - SHADER_ID_SIZE); + ::memcpy_s(pShaderTableMappedData, _rayGenShaderTableSize, + _pShaderLibrary->getSharedEntryPointShaderID(EntryPointTypes::kRayGen), SHADER_ID_SIZE); - // Close the shader table buffer. - _pRayGenShaderTable->Unmap(0, nullptr); // no HRESULT + // Unmap the shader table buffer. + _rayGenShaderTable.unmap(); } void PTRenderer::initAccumulation() @@ -774,13 +878,16 @@ void PTRenderer::renderInternal(uint32_t sampleStart, uint32_t sampleCount) // Retain certain option state for the frame. bool isResetHistoryEnabled = _values.asBoolean(kLabelIsResetHistoryEnabled); + // Call preUpdate function (will create any resources before the scene and shaders are rebuilt). + dxScene()->preUpdate(); + // Have any options changed? if (_bIsDirty) { #if ENABLE_MATERIALX // Get the units option. string unit = _values.asString(kLabelUnits); - // Lookup the unit in in the code generator, and ensure it is valid. + // Lookup the unit in the code generator, and ensure it is valid. auto unitIter = _pMaterialXGenerator->codeGenerator().units().indices.find(unit); if (unitIter == _pMaterialXGenerator->codeGenerator().units().indices.end()) { @@ -925,12 +1032,14 @@ void PTRenderer::updateFrameData() uint8_t* pFrameDataBufferMappedData = nullptr; size_t start = _FRAME_DATA_SIZE * _taskIndex; size_t end = start + _FRAME_DATA_SIZE; - D3D12_RANGE range = { start, end }; - checkHR( - _pFrameDataBuffer->Map(0, &range, reinterpret_cast(&pFrameDataBufferMappedData))); + pFrameDataBufferMappedData = _frameDataBuffer.map(end, start); ::memcpy_s( pFrameDataBufferMappedData + start, _FRAME_DATA_SIZE, &_frameData, sizeof(_frameData)); - _pFrameDataBuffer->Unmap(0, nullptr); // no HRESULT + _frameDataBuffer.unmap(); + + // Upload the transfer buffers after changing the frame data, so the new frame data is available + // on the GPU while rendering frame. + uploadTransferBuffers(); } void PTRenderer::updateSceneResources() @@ -944,14 +1053,12 @@ void PTRenderer::updateSceneResources() // Set the descriptor heap on the ray gen shader table, for descriptors needed from the heap for // the ray gen shader. The ray gen shader expects the first entry to be the direct texture. - uint8_t* pShaderTableMappedData = nullptr; - checkHR( - _pRayGenShaderTable->Map(0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + uint8_t* pShaderTableMappedData = _rayGenShaderTable.map(); CD3DX12_GPU_DESCRIPTOR_HANDLE handle(_pDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), kDirectDescriptorOffset, _handleIncrementSize); ::memcpy_s(pShaderTableMappedData + SHADER_ID_SIZE, _rayGenShaderTableSize, &handle, SHADER_RECORD_DESCRIPTOR_SIZE); - _pRayGenShaderTable->Unmap(0, nullptr); // no HRESULT + _rayGenShaderTable.unmap(); } void PTRenderer::updateOutputResources() @@ -1170,7 +1277,7 @@ void PTRenderer::prepareRayDispatch(D3D12_DISPATCH_RAYS_DESC& dispatchRaysDesc) dispatchRaysDesc.Depth = 1; // ... the shader table for the ray generation shader... - D3D12_GPU_VIRTUAL_ADDRESS address = _pRayGenShaderTable->GetGPUVirtualAddress(); + D3D12_GPU_VIRTUAL_ADDRESS address = _rayGenShaderTable.pGPUBuffer->GetGPUVirtualAddress(); dispatchRaysDesc.RayGenerationShaderRecord.StartAddress = address; dispatchRaysDesc.RayGenerationShaderRecord.SizeInBytes = _rayGenShaderTableSize; @@ -1224,7 +1331,7 @@ void PTRenderer::submitRayDispatch( // 2) The frame data constant buffer, for the current task index. D3D12_GPU_VIRTUAL_ADDRESS frameDataBufferAddress = - _pFrameDataBuffer->GetGPUVirtualAddress() + _FRAME_DATA_SIZE * _taskIndex; + _frameDataBuffer.pGPUBuffer->GetGPUVirtualAddress() + _FRAME_DATA_SIZE * _taskIndex; pCommandList->SetComputeRootConstantBufferView(2, frameDataBufferAddress); // 3) The environment data constant buffer. @@ -1413,42 +1520,28 @@ bool PTRenderer::isDenoisingAOVsEnabled() const _values.asInt(kLabelDebugMode) > kDebugModeErrors; } -shared_ptr PTRenderer::generateMaterialX( - [[maybe_unused]] const string& document, [[maybe_unused]] map* pDefaultValuesOut) +shared_ptr PTRenderer::generateMaterialX([[maybe_unused]] const string& document, + [[maybe_unused]] shared_ptr* pDefOut) { #if ENABLE_MATERIALX - // Generate the shader for the materialX document, along with its unique material name. - MaterialTypeSource materialTypeSource; - if (!_pMaterialXGenerator->generate(document, pDefaultValuesOut, materialTypeSource)) + // Generate the material definition for the materialX document, this contains the source code, + // default values, and a unique name. + shared_ptr pDef = _pMaterialXGenerator->generate(document); + if (!pDef) { return nullptr; } - // Set the shared definitions, this will only change anything if string is different to current - // one. - string definitions; - _pMaterialXGenerator->generateDefinitions(definitions); - _pShaderLibrary->setDefinitionsHLSL(definitions); - - // Acquire a material type for this shader. + // Acquire a material shader for the definition. // This will create a new one if needed (and trigger a rebuild), otherwise will it will return // existing one. - bool typeCreated; - auto pType = _pShaderLibrary->acquireMaterialType(materialTypeSource, &typeCreated); - if (typeCreated) - { - if (AU_DEV_DUMP_PROCESSED_MATERIALX_DOCUMENTS) - { - string mltxPath = Foundation::getModulePath() + materialTypeSource.name + "Dumped.mtlx"; - AU_INFO("Dumping processed MTLX document to:%s", mltxPath.c_str()); - ofstream outputFile; - outputFile.open(mltxPath); - outputFile << document; - outputFile.close(); - } - } + auto pShader = _pShaderLibrary->acquireShader(*pDef); + + // Output the definition pointer. + if (pDefOut) + *pDefOut = pDef; - return pType; + return pShader; #else return nullptr; #endif diff --git a/Libraries/Aurora/Source/DirectX/PTRenderer.h b/Libraries/Aurora/Source/DirectX/PTRenderer.h index e68e442..6d9ae30 100644 --- a/Libraries/Aurora/Source/DirectX/PTRenderer.h +++ b/Libraries/Aurora/Source/DirectX/PTRenderer.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -37,10 +37,11 @@ class MaterialGenerator; // Forward references. class AssetManager; class PTDevice; -class PTMaterialType; +class MaterialShader; class PTShaderLibrary; class ScratchBufferPool; class VertexBufferPool; +struct TransferBuffer; // An path tracing (PT) implementation for IRenderer. class PTRenderer : public RendererBase @@ -85,17 +86,27 @@ class PTRenderer : public RendererBase ID3D12Device5* dxDevice() const { return _pDXDevice.Get(); } IDXGIFactory4* dxFactory() const { return _pDXFactory.Get(); } ID3D12CommandQueue* commandQueue() const { return _pCommandQueue.Get(); } + // Create a transfer buffer. GPU buffer state defaults to D3D12_RESOURCE_STATE_COPY_DEST as it + // is used as a target for a resource copy command. The final GPU buffer state defaults to + // D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE. + TransferBuffer createTransferBuffer(size_t sz, const string& name = "", + D3D12_RESOURCE_FLAGS gpuBufferFlags = D3D12_RESOURCE_FLAG_NONE, + D3D12_RESOURCE_STATES gpuBufferState = D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATES gpuBufferFinalState = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); ID3D12ResourcePtr createBuffer(size_t size, const string& name = "", D3D12_HEAP_TYPE heapType = D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_GENERIC_READ); template - void updateBuffer(ID3D12ResourcePtr& pBuffer, FillDataFunction fillDataFunction); + void updateBuffer(TransferBuffer& bufferOut, FillDataFunction fillDataFunction); ID3D12ResourcePtr createTexture(uvec2 dimensions, DXGI_FORMAT format, const string& name = "", bool isUnorderedAccess = false, bool shareable = false); D3D12_GPU_VIRTUAL_ADDRESS getScratchBuffer(size_t size); - void getVertexBuffer(VertexBuffer& vertexBuffer, void* pData); + void getVertexBuffer(VertexBuffer& vertexBuffer, void* pData, size_t size); + void transferBufferUpdated(const TransferBuffer& buffer); void flushVertexBufferPool(); + void uploadTransferBuffers(); + void deleteUploadedTransferBuffers(); ID3D12CommandAllocator* getCommandAllocator(); ID3D12GraphicsCommandList4* beginCommandList(); void submitCommandList(); @@ -134,15 +145,15 @@ class PTRenderer : public RendererBase void createUAV(ID3D12Resource* pTexture, CD3DX12_CPU_DESCRIPTOR_HANDLE& handle); void copyTextureToTarget(ID3D12Resource* pTexture, PTTarget* pTarget); bool isDenoisingAOVsEnabled() const; - shared_ptr generateMaterialX( - const string& document, map* pDefaultValuesOut); + shared_ptr generateMaterialX( + const string& document, shared_ptr* pDefOut); PTScenePtr dxScene() { return static_pointer_cast(_pScene); } /*** Private Variables ***/ unique_ptr _pDevice; bool _isCommandListOpen = false; - ID3D12ResourcePtr _pFrameDataBuffer; + TransferBuffer _frameDataBuffer; PTEnvironmentPtr _pEnvironment; uvec2 _outputDimensions; bool _isDimensionsChanged = true; @@ -166,6 +177,8 @@ class PTRenderer : public RendererBase ID3D12Device5Ptr _pDXDevice; IDXGIFactory4Ptr _pDXFactory; ID3D12CommandQueuePtr _pCommandQueue; + map _pendingTransferBuffers; + map _transferBuffersToDelete; vector _commandAllocators; ID3D12GraphicsCommandList4Ptr _pCommandList; ID3D12FencePtr _pTaskFence; @@ -182,7 +195,7 @@ class PTRenderer : public RendererBase ID3D12ResourcePtr _pTexAccumulation; // for accumulation (HDR) ID3D12ResourcePtr _pTexDirect; // for path tracing or direct lighting (HDR) DXGI_FORMAT _finalFormat = DXGI_FORMAT_R8G8B8A8_UNORM; - ID3D12ResourcePtr _pRayGenShaderTable; + TransferBuffer _rayGenShaderTable; size_t _rayGenShaderTableSize = 0; unique_ptr _pScratchBufferCache; unique_ptr _pVertexBufferPool; @@ -205,14 +218,14 @@ class PTRenderer : public RendererBase // Creates (if needed) and an updates a GPU constant buffer with data supplied by the caller. template -void PTRenderer::updateBuffer( - ID3D12ResourcePtr& pBuffer, FillDataFunction fillDataFunction) +void PTRenderer::updateBuffer(TransferBuffer& buffer, FillDataFunction fillDataFunction) { - // Create a constant buffer for the data if it doesn't already exist. + // Create a transfer buffer for the data if it doesn't already exist. static const size_t BUFFER_SIZE = sizeof(DataType); - if (!pBuffer) + if (!buffer.valid()) { - pBuffer = createBuffer(BUFFER_SIZE); + buffer = createTransferBuffer( + BUFFER_SIZE, "updateBuffer:" + to_string(uint64(&fillDataFunction))); } // Fill the data using the callback function. @@ -220,10 +233,9 @@ void PTRenderer::updateBuffer( fillDataFunction(data); // Copy the data to the constant buffer. - void* pMappedData = nullptr; - checkHR(pBuffer->Map(0, nullptr, &pMappedData)); + void* pMappedData = buffer.map(); ::memcpy_s(pMappedData, BUFFER_SIZE, &data, BUFFER_SIZE); - pBuffer->Unmap(0, nullptr); // no HRESULT + buffer.unmap(); } MAKE_AURORA_PTR(PTRenderer); diff --git a/Libraries/Aurora/Source/DirectX/PTSampler.cpp b/Libraries/Aurora/Source/DirectX/PTSampler.cpp index ff61f62..601bce3 100644 --- a/Libraries/Aurora/Source/DirectX/PTSampler.cpp +++ b/Libraries/Aurora/Source/DirectX/PTSampler.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/PTSampler.h b/Libraries/Aurora/Source/DirectX/PTSampler.h index 6a13e7d..264c86b 100644 --- a/Libraries/Aurora/Source/DirectX/PTSampler.h +++ b/Libraries/Aurora/Source/DirectX/PTSampler.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/PTScene.cpp b/Libraries/Aurora/Source/DirectX/PTScene.cpp index 0ab2dbf..654f279 100644 --- a/Libraries/Aurora/Source/DirectX/PTScene.cpp +++ b/Libraries/Aurora/Source/DirectX/PTScene.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ #include "PTEnvironment.h" #include "PTGeometry.h" #include "PTGroundPlane.h" +#include "PTLight.h" #include "PTMaterial.h" #include "PTRenderer.h" #include "PTShaderLibrary.h" @@ -71,8 +72,11 @@ struct HitGroupShaderRecord PositionBufferAddress = geometry.PositionBuffer; HasNormals = geometry.NormalBuffer ? 1 : 0; NormalBufferAddress = HasNormals ? geometry.NormalBuffer : 0; - HasTexCoords = geometry.TexCoordBuffer ? 1 : 0; + HasTangents = geometry.TangentBuffer != 0; + TangentBufferAddress = HasTangents ? geometry.TangentBuffer : 0; + HasTexCoords = geometry.TexCoordBuffer != 0; TexCoordBufferAddress = HasTexCoords ? geometry.TexCoordBuffer : 0; + IsOpaque = material.isOpaque(); MaterialLayerCount = materialLayerCount; MaterialBufferAddress = material.buffer()->GetGPUVirtualAddress(); MaterialLayerIndexTableAddress = pMaterialLayerIndexBuffer @@ -101,10 +105,13 @@ struct HitGroupShaderRecord D3D12_GPU_VIRTUAL_ADDRESS IndexBufferAddress; D3D12_GPU_VIRTUAL_ADDRESS PositionBufferAddress; D3D12_GPU_VIRTUAL_ADDRESS NormalBufferAddress; + D3D12_GPU_VIRTUAL_ADDRESS TangentBufferAddress; D3D12_GPU_VIRTUAL_ADDRESS TexCoordBufferAddress; - int HasNormals; - int HasTexCoords; + uint32_t HasNormals; + uint32_t HasTangents; + uint32_t HasTexCoords; uint32_t MaterialLayerCount; + uint32_t IsOpaque; D3D12_GPU_VIRTUAL_ADDRESS MaterialBufferAddress; D3D12_GPU_VIRTUAL_ADDRESS MaterialLayerIndexTableAddress; D3D12_GPU_DESCRIPTOR_HANDLE TexturesHeapAddress; @@ -127,25 +134,29 @@ PTInstance::PTInstance(PTScene* pScene, const PTGeometryPtr& pGeometry, _layers.push_back(make_pair(dynamic_pointer_cast(layers[i].first), dynamic_pointer_cast(layers[i].second))); - _layers[i].first->materialType()->incrementRefCount(PTMaterialType::EntryPoint::kLayerMiss); + _layers[i].first->shader()->incrementRefCount(EntryPointTypes::kLayerMiss); } } PTInstance::~PTInstance() { if (_pMaterial) - _pMaterial->materialType()->decrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + { + _pMaterial->shader()->decrementRefCount(EntryPointTypes::kRadianceHit); + // The shadow anyhit is always attached. + _pMaterial->shader()->decrementRefCount(EntryPointTypes::kShadowAnyHit); + } for (size_t i = 0; i < _layers.size(); i++) { - _layers[i].first->materialType()->decrementRefCount(PTMaterialType::EntryPoint::kLayerMiss); + _layers[i].first->shader()->decrementRefCount(EntryPointTypes::kLayerMiss); } } void PTInstance::setMaterial(const IMaterialPtr& pMaterial) { if (_pMaterial) - _pMaterial->materialType()->decrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + _pMaterial->shader()->decrementRefCount(EntryPointTypes::kRadianceHit); // Cast the (optional) material to the renderer implementation. Use the default material if one // is / not specified. @@ -153,7 +164,11 @@ void PTInstance::setMaterial(const IMaterialPtr& pMaterial) ? dynamic_pointer_cast(pMaterial) : dynamic_pointer_cast(_pScene->defaultMaterialResource()->resource()); - _pMaterial->materialType()->incrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); + _pMaterial->shader()->incrementRefCount(EntryPointTypes::kRadianceHit); + // The shadow anyhit is always attached. This is needed as the ordering is arbitrary for anyhit + // shader invocations, so we cannot mix shaders with and without anyhit shadow shaders in the + // same scene. + _pMaterial->shader()->incrementRefCount(EntryPointTypes::kShadowAnyHit); // Set the instance as dirty. _bIsDirty = true; @@ -171,7 +186,7 @@ void PTInstance::setTransform(const mat4& transform) void PTInstance::setObjectIdentifier(int /*objectId*/) { - // TODO: implement object id setting for AuroraPT + // TODO: implement object id setting for Aurora } bool PTInstance::update() @@ -215,6 +230,28 @@ PTScene::PTScene( createDefaultResources(); } +ILightPtr PTScene::addLightPointer(const string& lightType) +{ + // Only distant lights are currently supported. + AU_ASSERT(lightType.compare(Names::LightTypes::kDistantLight) == 0, + "Only distant lights currently supported"); + + // The remaining operations are not yet thread safe. + std::lock_guard lock(_mutex); + + // Assign arbritary index to ensure deterministic ordering. + int index = _currentLightIndex++; + + // Create the light object. + PTLightPtr pLight = make_shared(this, lightType, index); + + // Add weak pointer to distant light map. + _distantLights[index] = pLight; + + // Return the new light. + return pLight; +} + IInstancePtr PTScene::addInstancePointer(const Path& /* path*/, const IGeometryPtr& pGeom, const IMaterialPtr& pMaterial, const mat4& transform, const LayerDefinitions& materialLayers) { @@ -261,13 +298,24 @@ ID3D12Resource* PTScene::getHitGroupShaderTable(size_t& recordStride, uint32_t& return _pHitGroupShaderTable.Get(); } +// Sort function, used to sort lights to ensure deterministic ordering. +bool compareLights(PTLight* first, PTLight* second) +{ + return (first->index() < second->index()); +} + void PTScene::update() { // Update base class. SceneBase::update(); + // Delete the transfer buffers that were uploaded last frame. + _pRenderer->deleteUploadedTransferBuffers(); + // Update the environment. - if (_environments.active().modified()) + // This is called if *any* active environment objects have changed, as that will almost always + // be the only active one. + if (_environments.changedThisFrame()) { _pEnvironment = static_pointer_cast(_pEnvironmentResource->resource()); _pEnvironment->update(); @@ -277,26 +325,85 @@ void PTScene::update() // Update the ground plane. _pGroundPlane->update(); + // See if any distant lights have been changed this frame, and build vector of active lights. + bool distantLightsUpdated = false; + vector currLights; + for (auto iter = _distantLights.begin(); iter != _distantLights.end(); iter++) + { + // Get the light and ensure weak pointer still valid. + PTLightPtr pLight = iter->second.lock(); + if (pLight) + { + // Add to currently active light vector. + currLights.push_back(pLight.get()); + + // If the dirty flag is set, GPU data must be updated. + if (pLight->isDirty()) + { + distantLightsUpdated = true; + pLight->clearDirtyFlag(); + } + } + else + { + // If the weak pointer is not valid remove it from the map, and ensure GPU data is + // updated (as a light has been removed.) + _distantLights.erase(iter->first); + distantLightsUpdated = true; + } + } + + // If distant lights have changed update the LightData struct that is passed to the GPU. + if (distantLightsUpdated) + { + // Sort the lights by index, to ensure deterministic ordering. + sort(currLights.begin(), currLights.end(), compareLights); + + // Set the distant light count to minimum of current light vector size and the max distant + // light limit. Lights in the sorted array past LightLimits::kMaxDistantLights are ignored. + _lights.distantLightCount = + std::min(int(currLights.size()), int(LightLimits::kMaxDistantLights)); + + // Add to the light data buffer that is copied to the frame data for this frame. + for (int i = 0; i < _lights.distantLightCount; i++) + { + // Store the cosine of the radius for use in the shader. + _lights.distantLights[i].cosRadius = + cos(0.5f * currLights[i]->asFloat(Names::LightProperties::kAngularDiameter)); + + // Invert the direction for use in the shader. + _lights.distantLights[i].direction = + -currLights[i]->asFloat3(Names::LightProperties::kDirection); + + // Store color in RGB and intensity in alpha. + _lights.distantLights[i].colorAndIntensity = + vec4(currLights[i]->asFloat3(Names::LightProperties::kColor), + currLights[i]->asFloat(Names::LightProperties::kIntensity)); + } + } + // If any active geometry resources have been modified, flush the vertex buffer pool in case // there are any pending vertex buffers that are required to update the geometry, and then - // update the geometry (and update BLAS for "complete" geometry that has position data). - if (_geometry.changed()) + // update the geometry. + if (_geometry.changedThisFrame()) { - _pRenderer->flushVertexBufferPool(); - for (PTGeometry& geom : _geometry.active().resources()) + // Iterate through the modified geometry (which will include the newly active ones.) + for (PTGeometry& geom : _geometry.modified().resources()) { geom.update(); - if (!geom.isIncomplete()) - geom.updateBLAS(); } + + // Flush the vertex buffer pool. + _pRenderer->flushVertexBufferPool(); } // If any active material resources have been modified update them and build a list of unique // samplers for all the active materials. - if (_materials.changed()) + if (_materials.changedThisFrame()) { map materialSamplerIndicesMap; _samplerLookup.clear(); + // Iterate through the all the active materials, even ones that have not changed. for (PTMaterial& mtl : _materials.active().resources()) { mtl.update(); @@ -311,8 +418,21 @@ void PTScene::update() clearDesciptorHeap(); } - // If any active instances have been modified, update all the active instances. - if (_instances.changed()) + // Upload any transfer buffers that have been updated this frame. + _pRenderer->uploadTransferBuffers(); + + // Update the geometry BLAS (after any transfer buffers have been uploaded.) + if (_geometry.changedThisFrame()) + { + for (PTGeometry& geom : _geometry.modified().resources()) + { + if (!geom.isIncomplete()) + geom.updateBLAS(); + } + } + + // If any active instances have been modified or activated, update all the active instances. + if (_instances.changedThisFrame()) { for (PTInstance& instance : _instances.active().resources()) { @@ -321,7 +441,7 @@ void PTScene::update() } // Update the acceleration structure if any geometry or instances have been modified. - if (_instances.active().modified() || _geometry.active().modified()) + if (_instances.changedThisFrame() || _geometry.changedThisFrame()) { // Ensure the acceleration structure is no longer being accessed. @@ -333,7 +453,7 @@ void PTScene::update() } // Update the scene resources: the acceleration structure, the descriptor heap, and the shader - // tables. Will only doanything if the relevant pointers have been cleared. + // tables. Will only do anything if the relevant pointers have been cleared. updateAccelerationStructure(); updateDescriptorHeap(); updateShaderTables(); @@ -599,23 +719,26 @@ void PTScene::updateShaderTables() size_t recordStride = HitGroupShaderRecord::stride(); - // Create a buffer for the shader table, and map it for writing. - size_t shaderTableSize = recordStride * instanceCount; - _pHitGroupShaderTable = _pRenderer->createBuffer(shaderTableSize); - uint8_t* pShaderTableMappedData = nullptr; - checkHR(_pHitGroupShaderTable->Map( - 0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + // Create a transfer buffer for the shader table, and map it for writing. + size_t shaderTableSize = recordStride * instanceCount; + TransferBuffer hitGroupTransferBuffer = + _pRenderer->createTransferBuffer(shaderTableSize, "HitGroupShaderTable"); + uint8_t* pShaderTableMappedData = hitGroupTransferBuffer.map(); + + // Retain the GPU buffer from the transfer buffer, the upload buffer will be deleted by the + // renderer once upload complete. + _pHitGroupShaderTable = hitGroupTransferBuffer.pGPUBuffer; // Iterate the instance data objects, creating a hit group shader record for each one, and // copying the shader record data to the shader table. for (int i = 0; i < _lstInstanceData.size(); i++) { const auto& instanceData = _lstInstanceData[i]; - // Get the hit group shader ID from the material type, which will change if the shader + // Get the hit group shader ID from the material shader, which will change if the shader // library is rebuilt. PTMaterial& instanceMtl = activeMaterialResources[instanceData.mtlIndex]; const DirectXShaderIdentifier hitGroupShaderID = - instanceMtl.materialType()->getShaderID(); + _pShaderLibrary->getShaderID(instanceMtl.shader()); // Lookup texture and sampler handle for instance. CD3DX12_GPU_DESCRIPTOR_HANDLE mtlTextureHandle = @@ -638,7 +761,7 @@ void PTScene::updateShaderTables() } // Close the shader table buffer. - _pHitGroupShaderTable->Unmap(0, nullptr); // no HRESULT + hitGroupTransferBuffer.unmap(); } // Create and populate the miss shader table if necessary. if (!_pMissShaderTable) @@ -649,11 +772,15 @@ void PTScene::updateShaderTables() // Create a buffer for the shader table and write the shader identifiers for the miss // shaders. // NOTE: There are no arguments (and indeed no local root signatures) for these shaders. - size_t shaderTableSize = _missShaderRecordStride * _missShaderRecordCount; - _pMissShaderTable = _pRenderer->createBuffer(shaderTableSize); - uint8_t* pShaderTableMappedData = nullptr; - checkHR( - _pMissShaderTable->Map(0, nullptr, reinterpret_cast(&pShaderTableMappedData))); + size_t shaderTableSize = _missShaderRecordStride * _missShaderRecordCount; + + // Create the transfer buffer. + TransferBuffer missShaderTableTransferBuffer = + _pRenderer->createTransferBuffer(shaderTableSize, "MissShaderTable"); + _pMissShaderTable = missShaderTableTransferBuffer.pGPUBuffer; + + // Map the shader table buffer. + uint8_t* pShaderTableMappedData = missShaderTableTransferBuffer.map(); uint8_t* pEndOfShaderTableMappedData = pShaderTableMappedData + shaderTableSize; // Get the shader identifiers from the shader library, which will change if the library is @@ -663,12 +790,15 @@ void PTScene::updateShaderTables() ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, kNullShaderID.data(), SHADER_ID_SIZE); pShaderTableMappedData += _missShaderRecordStride; ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, - _pShaderLibrary->getBackgroundMissShaderID(), SHADER_ID_SIZE); + _pShaderLibrary->getSharedEntryPointShaderID(EntryPointTypes::kBackgroundMiss), + SHADER_ID_SIZE); pShaderTableMappedData += _missShaderRecordStride; ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, - _pShaderLibrary->getRadianceMissShaderID(), SHADER_ID_SIZE); + _pShaderLibrary->getSharedEntryPointShaderID(EntryPointTypes::kRadianceMiss), + SHADER_ID_SIZE); pShaderTableMappedData += _missShaderRecordStride; - ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, _pShaderLibrary->getShadowMissShaderID(), + ::memcpy_s(pShaderTableMappedData, SHADER_ID_SIZE, + _pShaderLibrary->getSharedEntryPointShaderID(EntryPointTypes::kShadowMiss), SHADER_ID_SIZE); pShaderTableMappedData += _missShaderRecordStride; @@ -680,8 +810,8 @@ void PTScene::updateShaderTables() auto& parentInstanceData = _lstInstanceData[layerData.index]; PTMaterial& layerMtl = activeMaterialResources[layerData.instanceData.mtlIndex]; - // Get the material type - auto pMtlType = layerMtl.materialType(); + // Get the material shader + auto pShader = layerMtl.shader(); // Create the geometry by merging the layer geometry with parent instance's geometry. PTGeometry::GeometryBuffers geometryLayerBuffers = @@ -733,8 +863,8 @@ void PTScene::updateShaderTables() // Create hit group (as layer miss shader has same layout as luminance closest hit // shader) - HitGroupShaderRecord layerRecord(pMtlType->getLayerShaderID(), geometryLayerBuffers, - layerMtl, nullptr, mtlTextureHandle, mtlSamplerHandle, 0); + HitGroupShaderRecord layerRecord(_pShaderLibrary->getLayerShaderID(pShader), + geometryLayerBuffers, layerMtl, nullptr, mtlTextureHandle, mtlSamplerHandle, 0); layerRecord.copyTo(pShaderTableMappedData); pShaderTableMappedData += _missShaderRecordStride; } @@ -743,7 +873,7 @@ void PTScene::updateShaderTables() AU_ASSERT(pShaderTableMappedData == pEndOfShaderTableMappedData, "Shader table overrun"); // Unmap the table. - _pMissShaderTable->Unmap(0, nullptr); // no HRESULT + missShaderTableTransferBuffer.unmap(); // no HRESULT } } diff --git a/Libraries/Aurora/Source/DirectX/PTScene.h b/Libraries/Aurora/Source/DirectX/PTScene.h index 2ba0c1a..6550120 100644 --- a/Libraries/Aurora/Source/DirectX/PTScene.h +++ b/Libraries/Aurora/Source/DirectX/PTScene.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ #include "PTEnvironment.h" #include "PTGeometry.h" #include "PTGroundPlane.h" +#include "PTLight.h" #include "PTMaterial.h" #include "SceneBase.h" @@ -108,6 +109,7 @@ class PTScene : public SceneBase IInstancePtr addInstancePointer(const Path& path, const IGeometryPtr& geom, const IMaterialPtr& pMaterial, const mat4& transform, const LayerDefinitions& materialLayers) override; + ILightPtr addLightPointer(const string& lightType) override; void update(); @@ -204,6 +206,9 @@ class PTScene : public SceneBase PTGroundPlanePtr _pGroundPlane; PTEnvironmentPtr _pEnvironment; uint32_t _numRendererDescriptors = 0; + map> _distantLights; + int _currentLightIndex = 0; + /*** DirectX 12 Objects ***/ ID3D12ResourcePtr _pAccelStructure; diff --git a/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp index 5b15273..a50a42b 100644 --- a/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp +++ b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,19 +26,34 @@ #include "Transpiler.h" // Development flag to enable/disable multithreaded compilation. -#define AU_DEV_MULTITHREAD_COMPILATION 0 +#define AU_DEV_MULTITHREAD_COMPILATION 1 #if AU_DEV_MULTITHREAD_COMPILATION -#include -#include +#include #endif BEGIN_AURORA +// Input to thread used to compile shaders. +struct CompileJob +{ + CompileJob(int ji) : jobIndex(ji) {} + + string code; + string libName; + map includes; + vector> entryPoints; + int index; + int jobIndex; +}; // Development flag to entire HLSL library to disk. // NOTE: This should never be enabled in committed code; it is only for local development. #define AU_DEV_DUMP_SHADER_CODE 0 +// Development flag to transpiled HLSL library to disk. +// NOTE: This should never be enabled in committed code; it is only for local development. +#define AU_DEV_DUMP_TRANSPILED_CODE 0 + // Pack four bytes into a single unsigned int. // Based on version in DirectX toolkit. #define MAKEFOURCC(ch0, ch1, ch2, ch3) \ @@ -47,14 +62,21 @@ BEGIN_AURORA (static_cast(static_cast(ch2)) << 16) | \ (static_cast(static_cast(ch3)) << 24)) -// Strings for shader entry points. -static const wchar_t* gRayGenEntryPoint = L"RayGenShader"; -static const wchar_t* gBackgroundMissEntryPoint = L"BackgroundMissShader"; -static const wchar_t* gRadianceMissEntryPoint = L"RadianceMissShader"; -static const wchar_t* gShadowMissEntryPoint = L"ShadowMissShader"; +// Define the entry point type strings. +const string EntryPointTypes::kRadianceHit = "RADIANCE_HIT"; +const string EntryPointTypes::kLayerMiss = "LAYER_MISS"; +const string EntryPointTypes::kShadowAnyHit = "SHADOW_ANY_HIT"; +const string EntryPointTypes::kRayGen = "RAY_GEN"; +const string EntryPointTypes::kBackgroundMiss = "BACKGROUND_MISS"; +const string EntryPointTypes::kRadianceMiss = "RADIANCE_MISS"; +const string EntryPointTypes::kShadowMiss = "SHADOW_MISS"; + +// Array of entry point names. +const vector PTShaderLibrary::DefaultEntryPoints = { EntryPointTypes::kRadianceHit, + EntryPointTypes::kShadowAnyHit, EntryPointTypes::kLayerMiss }; // Combine the source code for the reference and Standard Surface BSDF to produce the default built -// in material type. Use USE_REFERENCE_BSDF ifdef so that the material's BSDF can be selected via an +// in shader. Use USE_REFERENCE_BSDF ifdef so that the material's BSDF can be selected via an // option. // DXC include handler, used by PTShaderLibrary::compileLibrary @@ -114,66 +136,6 @@ class IncludeHandler : public IDxcIncludeHandler const map& _includes; }; -PTMaterialType::PTMaterialType( - PTShaderLibrary* pShaderLibrary, int sourceIndex, const string& typeName) : - _pShaderLibrary(pShaderLibrary), _sourceIndex(sourceIndex), _name(typeName) -{ - // Set the closest hit, any hit, and layer miss hit entry point names, converted to wide string. - _closestHitEntryPoint = Foundation::s2w(typeName + "RadianceHitShader"); - _shadowAnyHitEntryPoint = Foundation::s2w(typeName + "ShadowAnyHitShader"); - _materialLayerMissEntryPoint = Foundation::s2w(typeName + "LayerMissShader"); - - // Set the hit group export name from the entry point name, converted to wide string. - _exportName = _closestHitEntryPoint + Foundation::s2w("Group"); - - // Initialize ref. counts to zero. - for (int i = 0; i < EntryPoint::kNumEntryPoints; i++) - _entryPointRefCount[i] = 0; -} - -PTMaterialType::~PTMaterialType() -{ - // If this material type is valid, when its destroyed (which will happen when no material holds - // a shared pointer to it) remove it source code from the library. - if (isValid()) - { - _pShaderLibrary->removeSource(_sourceIndex); - } -} - -void PTMaterialType::incrementRefCount(EntryPoint entryPoint) -{ - // Increment the ref. count and trigger rebuild if this is the flip case (that causes the count - // to go from zero to non-zero.) A shader rebuild is required as this will change the HLSL code. - _entryPointRefCount[entryPoint]++; - if (_pShaderLibrary && _entryPointRefCount[entryPoint] == 1) - _pShaderLibrary->triggerRebuild(); -} - -void PTMaterialType::decrementRefCount(EntryPoint entryPoint) -{ - // Ensure ref. count is non-zero. - AU_ASSERT(_entryPointRefCount[entryPoint] > 0, "Invalid ref count"); - - // Decrement the ref. count and trigger rebuild if this is the flip case (that causes the count - // to go from non-zero to zero). A shader rebuild is required as this will change the HLSL code. - _entryPointRefCount[entryPoint]--; - if (_pShaderLibrary && _entryPointRefCount[entryPoint] == 0) - _pShaderLibrary->triggerRebuild(); -} - -DirectXShaderIdentifier PTMaterialType::getShaderID() -{ - // Get the shader ID from the library. - return _pShaderLibrary->getShaderID(_exportName.c_str()); -} - -DirectXShaderIdentifier PTMaterialType::getLayerShaderID() -{ - // Get the shader ID from the library. - return _pShaderLibrary->getShaderID(_materialLayerMissEntryPoint.c_str()); -} - bool PTShaderOptions::set(const string& name, int val) { // Find name in lookup map. @@ -229,19 +191,6 @@ string PTShaderOptions::toHLSL() const return hlslStr; } -PTShaderLibrary::~PTShaderLibrary() -{ - // Invalidate any remaining valid material types, to avoid zombies. - for (auto pWeakMaterialType : _materialTypes) - { - PTMaterialTypePtr pMaterialType = pWeakMaterialType.second.lock(); - if (pMaterialType) - { - pMaterialType->invalidate(); - } - } -} - bool PTShaderLibrary::compileLibrary(const ComPtr& pDXCLibrary, const string source, const string& name, const string& target, const string& entryPoint, const vector>& defines, bool debug, ComPtr& pOutput, @@ -438,9 +387,9 @@ void PTShaderLibrary::initRootSignatures() CD3DX12_DESCRIPTOR_RANGE texRange; CD3DX12_DESCRIPTOR_RANGE samplerRange; - CD3DX12_ROOT_PARAMETER globalRootParameters[8] = {}; // NOLINT(modernize-avoid-c-arrays) - globalRootParameters[0].InitAsShaderResourceView(0); // gScene: acceleration structure - globalRootParameters[1].InitAsConstants(2, 0); // sampleIndex + seedOffset + array globalRootParameters = {}; // NOLINT(modernize-avoid-c-arrays) + globalRootParameters[0].InitAsShaderResourceView(0); // gScene: acceleration structure + globalRootParameters[1].InitAsConstants(2, 0); // sampleIndex + seedOffset globalRootParameters[2].InitAsConstantBufferView(1); // gFrameData: per-frame constant buffer globalRootParameters[3].InitAsConstantBufferView(2); // gEnvironmentConstants globalRootParameters[4].InitAsShaderResourceView(1); // gEnvironmentAliasMap @@ -451,8 +400,8 @@ void PTShaderLibrary::initRootSignatures() globalRootParameters[6].InitAsConstantBufferView(3); // gGroundPlane globalRootParameters[7].InitAsShaderResourceView(4); // gNullScene: null acceleration structure CD3DX12_STATIC_SAMPLER_DESC samplerDesc(0, D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR); - CD3DX12_ROOT_SIGNATURE_DESC globalDesc( - _countof(globalRootParameters), globalRootParameters, 1, &samplerDesc); + CD3DX12_ROOT_SIGNATURE_DESC globalDesc(static_cast(globalRootParameters.size()), + globalRootParameters.data(), 1, &samplerDesc); _pGlobalRootSignature = createRootSignature(globalDesc); _pGlobalRootSignature->SetName(L"Global Root Signature"); @@ -462,62 +411,84 @@ void PTShaderLibrary::initRootSignatures() CD3DX12_DESCRIPTOR_RANGE uavRange; uavRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 7, 0); // Output images (for AOV data) CD3DX12_DESCRIPTOR_RANGE rayGenRanges[] = { uavRange }; // NOLINT(modernize-avoid-c-arrays) - CD3DX12_ROOT_PARAMETER rayGenRootParameters[1] = {}; // NOLINT(modernize-avoid-c-arrays) + array rayGenRootParameters = {}; rayGenRootParameters[0].InitAsDescriptorTable(_countof(rayGenRanges), rayGenRanges); - CD3DX12_ROOT_SIGNATURE_DESC rayGenDesc(_countof(rayGenRootParameters), rayGenRootParameters); + CD3DX12_ROOT_SIGNATURE_DESC rayGenDesc( + static_cast(rayGenRootParameters.size()), rayGenRootParameters.data()); rayGenDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; _pRayGenRootSignature = createRootSignature(rayGenDesc); _pRayGenRootSignature->SetName(L"Ray Gen Local Root Signature"); - // Specify a local root signature for the radiance hit group. + // Start a local root signature for the radiance hit group. // NOTE: All shaders in the hit group must have the same local root signature. - CD3DX12_ROOT_PARAMETER radianceHitParameters[9] = {}; // NOLINT(modernize-avoid-c-arrays) - radianceHitParameters[0].InitAsShaderResourceView(0, 1); // gIndices: indices - radianceHitParameters[1].InitAsShaderResourceView(1, 1); // gPositions: positions - radianceHitParameters[2].InitAsShaderResourceView(2, 1); // gNormals: normals - radianceHitParameters[3].InitAsShaderResourceView(3, 1); // gTexCoords: texture coordinates - radianceHitParameters[4].InitAsConstants( - 3, 0, 1); // gHasNormals, gHasTexCoords, and gLayerMissShaderIndex - radianceHitParameters[5].InitAsConstantBufferView(1, 1); // gMaterial: material data - radianceHitParameters[6].InitAsConstantBufferView( - 2, 1); // gMaterialLayerIDs: indices for layer material shaders - - // Texture descriptors starting at register(t4, space1) - texRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, PTMaterial::descriptorCount(), 4, 1); + array radianceHitParameters = {}; + + // Geometry buffers: indices, positions, normals, tangents, and texture coordinates. + const int kGeometryBufferCount = 5; + radianceHitParameters[0].InitAsShaderResourceView(0, 1); // gIndices + radianceHitParameters[1].InitAsShaderResourceView(1, 1); // gPositions + radianceHitParameters[2].InitAsShaderResourceView(2, 1); // gNormals + radianceHitParameters[3].InitAsShaderResourceView(3, 1); // gTangents + radianceHitParameters[4].InitAsShaderResourceView(4, 1); // gTexCoords + + // Constants: gHasNormals, gHasTangents gHasTexCoords, gLayerMissShaderIndex, and gIsOpaque. + radianceHitParameters[5].InitAsConstants(5, 0, 1); + + // gMaterialConstants: material data (stored after geometry data and textures). + radianceHitParameters[6].InitAsShaderResourceView( + kGeometryBufferCount + PTMaterial::descriptorCount(), 1); + + // gMaterialLayerIDs: indices for layer material shaders. + radianceHitParameters[7].InitAsConstantBufferView(2, 1); + + // Texture descriptors starting at register(tX, space1), where X is the number of byte address + // buffers for geometry data. + texRange.Init( + D3D12_DESCRIPTOR_RANGE_TYPE_SRV, PTMaterial::descriptorCount(), kGeometryBufferCount, 1); CD3DX12_DESCRIPTOR_RANGE radianceHitRanges[] = { texRange }; // NOLINT(modernize-avoid-c-arrays) - radianceHitParameters[7].InitAsDescriptorTable(_countof(radianceHitRanges), radianceHitRanges); + radianceHitParameters[8].InitAsDescriptorTable(_countof(radianceHitRanges), radianceHitRanges); // Sampler descriptors starting at register(s1) samplerRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, PTMaterial::samplerDescriptorCount(), 1); CD3DX12_DESCRIPTOR_RANGE radianceHitSamplerRanges[] = { samplerRange }; // NOLINT(modernize-avoid-c-arrays) - radianceHitParameters[8].InitAsDescriptorTable( + radianceHitParameters[9].InitAsDescriptorTable( _countof(radianceHitSamplerRanges), radianceHitSamplerRanges); CD3DX12_ROOT_SIGNATURE_DESC radianceHitDesc( - _countof(radianceHitParameters), radianceHitParameters); + static_cast(radianceHitParameters.size()), radianceHitParameters.data()); radianceHitDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; _pRadianceHitRootSignature = createRootSignature(radianceHitDesc); _pRadianceHitRootSignature->SetName(L"Radiance Hit Group Local Root Signature"); - // Specify a local root signature for the layer miss shader. + // Start a local root signature for the layer miss shader. // NOTE: All shaders in the hit group must have the same local root signature. - CD3DX12_ROOT_PARAMETER layerMissParameters[9] = {}; // NOLINT(modernize-avoid-c-arrays) + array layerMissParameters = {}; + + // Geometry buffers. layerMissParameters[0].InitAsShaderResourceView(0, 1); // gIndices: indices layerMissParameters[1].InitAsShaderResourceView(1, 1); // gPositions: positions layerMissParameters[2].InitAsShaderResourceView(2, 1); // gNormals: normals - layerMissParameters[3].InitAsShaderResourceView(3, 1); // gTexCoords: texture coordinates - layerMissParameters[4].InitAsConstants( - 3, 0, 1); // gHasNormals, gHasTexCoords, and gLayerMissShaderIndex - layerMissParameters[5].InitAsConstantBufferView(1, 1); // gMaterial: material data - layerMissParameters[6].InitAsConstantBufferView( - 2, 1); // gMaterialLayerIDs: indices for layer material shaders - - // Texture descriptors starting at register(t4, space1) - texRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, PTMaterial::descriptorCount(), 4, 1); // Textures + layerMissParameters[3].InitAsShaderResourceView(3, 1); // gTangents: tangents + layerMissParameters[4].InitAsShaderResourceView(4, 1); // gTexCoords: texture coordinates + + // Constants: gHasNormals, gHasTangents gHasTexCoords, gLayerMissShaderIndex, and gIsOpaque. + layerMissParameters[5].InitAsConstants(5, 0, 1); + + // gMaterialConstants: material data (stored after geometry data and textures). + layerMissParameters[6].InitAsShaderResourceView( + kGeometryBufferCount + PTMaterial::descriptorCount(), 1); + + // gMaterialLayerIDs: indices for layer material shaders. + layerMissParameters[7].InitAsConstantBufferView(2, 1); + + // Texture descriptors starting at register(tX, space1), where X is the number of byte address + // buffers for geometry data. + texRange.Init( + D3D12_DESCRIPTOR_RANGE_TYPE_SRV, PTMaterial::descriptorCount(), kGeometryBufferCount, 1); CD3DX12_DESCRIPTOR_RANGE layerMissRanges[] = { texRange }; // NOLINT(modernize-avoid-c-arrays) - layerMissParameters[7].InitAsDescriptorTable(_countof(layerMissRanges), layerMissRanges); + layerMissParameters[8].InitAsDescriptorTable(_countof(layerMissRanges), layerMissRanges); // Sampler descriptors starting at register(s1) samplerRange.Init( @@ -525,11 +496,12 @@ void PTShaderLibrary::initRootSignatures() CD3DX12_DESCRIPTOR_RANGE layerMissSamplerRanges[] = { samplerRange }; // NOLINT(modernize-avoid-c-arrays) - layerMissParameters[8].InitAsDescriptorTable( + layerMissParameters[9].InitAsDescriptorTable( _countof(layerMissSamplerRanges), layerMissSamplerRanges); // Create layer miss root signature. - CD3DX12_ROOT_SIGNATURE_DESC layerMissDesc(_countof(layerMissParameters), layerMissParameters); + CD3DX12_ROOT_SIGNATURE_DESC layerMissDesc( + static_cast(layerMissParameters.size()), layerMissParameters.data()); layerMissDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; _pLayerMissRootSignature = createRootSignature(layerMissDesc); _pLayerMissRootSignature->SetName(L"Layer Miss Local Root Signature"); @@ -538,7 +510,7 @@ void PTShaderLibrary::initRootSignatures() DirectXShaderIdentifier PTShaderLibrary::getShaderID(const wchar_t* entryPoint) { // Assert if a rebuild is required, as the pipeline state will be invalid. - AU_ASSERT(!_rebuildRequired, + AU_ASSERT(!rebuildRequired(), "Shader Library rebuild required, call rebuild() before accessing shaders"); // Get the shader ID from the pipeline state. @@ -547,148 +519,70 @@ DirectXShaderIdentifier PTShaderLibrary::getShaderID(const wchar_t* entryPoint) return stateObjectProps->GetShaderIdentifier(entryPoint); } -DirectXShaderIdentifier PTShaderLibrary::getBackgroundMissShaderID() +DirectXShaderIdentifier PTShaderLibrary::getShaderID(MaterialShaderPtr pShader) { - // Get the shader ID for the HLSL function name. - return getShaderID(gBackgroundMissEntryPoint); -} + auto& compiledShader = _compiledShaders[pShader->libraryIndex()]; -DirectXShaderIdentifier PTShaderLibrary::getRadianceMissShaderID() -{ - // Get the shader ID for the HLSL function name. - return getShaderID(gRadianceMissEntryPoint); + return getShaderID(Foundation::s2w(compiledShader.exportName).c_str()); } -DirectXShaderIdentifier PTShaderLibrary::getShadowMissShaderID() +DirectXShaderIdentifier PTShaderLibrary::getLayerShaderID(MaterialShaderPtr pShader) { - // Get the shader ID for the HLSL function name. - return getShaderID(gShadowMissEntryPoint); -} + auto& compiledShader = _compiledShaders[pShader->libraryIndex()]; -DirectXShaderIdentifier PTShaderLibrary::getRayGenShaderID() -{ - // Get the shader ID for the HLSL function name. - return getShaderID(gRayGenEntryPoint); + return getShaderID( + Foundation::s2w(compiledShader.entryPoints[EntryPointTypes::kLayerMiss]).c_str()); } -vector PTShaderLibrary::getActiveTypeNames() +DirectXShaderIdentifier PTShaderLibrary::getSharedEntryPointShaderID(const string& entryPoint) { - vector res; - for (auto hgIter = _materialTypes.begin(); hgIter != _materialTypes.end(); hgIter++) - { - // Weak pointer is stored in shader library, ensure it has not been deleted. - PTMaterialTypePtr pMtlType = hgIter->second.lock(); - if (pMtlType) - { - res.push_back(pMtlType->name()); - } - } - return res; -} - -PTMaterialTypePtr PTShaderLibrary::getType(const string& name) -{ - return _materialTypes[name].lock(); -} + // Get the default compiled shader, which contains all the shared entry points. + const auto& defaultCompiledShader = getDefaultShader(); -void PTShaderLibrary::removeSource(int sourceIndex) -{ - // Push index in to vector, the actual remove only happens when library rebuilt. - _sourceToRemove.push_back(sourceIndex); -} -PTMaterialTypePtr PTShaderLibrary::acquireMaterialType( - const MaterialTypeSource& source, bool* pCreatedType) -{ - // The shared pointer to material type. - PTMaterialTypePtr pMtlType; - - // First see if this entry point already exists. - map>::iterator hgIter = _materialTypes.find(source.name); - if (hgIter != _materialTypes.end()) - { - // Weak pointer is stored in shader library, ensure it has not been deleted. - pMtlType = hgIter->second.lock(); - if (pMtlType) - { - // If the entry point exists, in debug mode do a string comparison to ensure the source - // also matches. - AU_ASSERT_DEBUG( - source.compareSource(_compiledMaterialTypes[pMtlType->_sourceIndex].source), - "Source mis-match for material type %s.", source.name.c_str()); - - // No type created. - if (pCreatedType) - *pCreatedType = false; - - // Return the existing material type. - return pMtlType; - } - } - - // Append the new source to the source vector, and calculate source index. - int sourceIdx = static_cast(_compiledMaterialTypes.size()); - _compiledMaterialTypes.push_back({ source, nullptr }); - - // Trigger rebuild. - _rebuildRequired = true; - - // Create new material type. - pMtlType = make_shared(this, sourceIdx, source.name); - - // Add weak reference to map. - _materialTypes[source.name] = weak_ptr(pMtlType); - - // New type created. - if (pCreatedType) - *pCreatedType = true; - - // Return the new material type. - return pMtlType; -} - -PTMaterialTypePtr PTShaderLibrary::getBuiltInMaterialType(const string& name) -{ - return _builtInMaterialTypes[name]; + // Get the shader ID for entry point. + return getShaderID(Foundation::s2w(defaultCompiledShader.entryPoints.at(entryPoint)).c_str()); } void PTShaderLibrary::initialize() { - _pTranspiler = make_shared(CommonShaders::g_sDirectory); + // Create an emptry array of Slang transpilers. + _transpilerArray = {}; - // Initialize root signatures (these are shared by all material types, and don't change.) + // Initialize root signatures (these are shared by all shaders, and don't change.) initRootSignatures(); // Clear the source and built ins vector. Not strictly needed, but this function could be called // repeatedly in the future. - _compiledMaterialTypes.clear(); + _compiledShaders.clear(); _builtInMaterialNames = {}; - // Create the default material type. - MaterialTypeSource defaultMaterialSource( + // Create source code for the default shader. + MaterialShaderSource defaultMaterialSource( "Default", CommonShaders::g_sInitializeDefaultMaterialType); - PTMaterialTypePtr pDefaultMaterialType = acquireMaterialType(defaultMaterialSource); - - // Ensure the radiance hit entry point is compiled for default material type. - pDefaultMaterialType->incrementRefCount(PTMaterialType::EntryPoint::kRadianceHit); - - // Add default material type to the built-in array. - _builtInMaterialNames.push_back(defaultMaterialSource.name); - _builtInMaterialTypes[defaultMaterialSource.name] = - pDefaultMaterialType; // Stores strong reference to the built-in's material type. -} - -bool PTShaderLibrary::setDefinitionsHLSL(const string& definitions) -{ - // If the MaterialX definitions HLSL has not changed do nothing. - if (_materialXDefinitionsSource.compare(definitions) == 0) - { - return false; - } - // Otherwise set definitions and trigger rebuild. - _materialXDefinitionsSource = definitions; - _rebuildRequired = true; - return true; + // Add the shared entry points to the default shader's definitions source. + defaultMaterialSource.definitions = "#include \"BackgroundMissShader.slang\"\n"; + defaultMaterialSource.definitions += "#include \"RadianceMissShader.slang\"\n"; + defaultMaterialSource.definitions += "#include \"ShadowMissShader.slang\"\n"; + defaultMaterialSource.definitions += "#include \"RayGenShader.slang\"\n"; + + // Create the material definition for default shader. + _builtInMaterialDefinitions[defaultMaterialSource.uniqueId] = + make_shared(defaultMaterialSource, + MaterialBase::StandardSurfaceDefaults, MaterialBase::updateBuiltInMaterial, false); + + // Create shader from the definition. + MaterialShaderDefinition shaderDef; + _builtInMaterialDefinitions[defaultMaterialSource.uniqueId]->getShaderDefinition(shaderDef); + MaterialShaderPtr pDefaultShader = _shaderLibrary.acquire(shaderDef); + + // Ensure the radiance hit entry point is compiled for default shader. + pDefaultShader->incrementRefCount(EntryPointTypes::kRadianceHit); + + // Add default shader to the built-in array. + _builtInMaterialNames.push_back(defaultMaterialSource.uniqueId); + _builtInShaders[defaultMaterialSource.uniqueId] = + pDefaultShader; // Stores strong reference to the built-in's shader. } bool PTShaderLibrary::setOption(const string& name, int value) @@ -699,68 +593,159 @@ bool PTShaderLibrary::setOption(const string& name, int value) // If the option changed set the HLSL and trigger rebuild. if (changed) { - _optionsSource = _options.toHLSL(); - _rebuildRequired = true; + _optionsSource = _options.toHLSL(); + _shaderLibrary.forceRebuildAll(); } return changed; } -void PTShaderLibrary::assembleShadersForMaterialType(const MaterialTypeSource& source, - const map& entryPoints, vector& shadersOut) +MaterialShaderPtr PTShaderLibrary::getBuiltInShader(const string& name) { - // Add shared common code. - string shaderSource = _optionsSource; - shaderSource += CommonShaders::g_sGLSLToHLSL; - shaderSource += CommonShaders::g_sMaterialXCommon; - shaderSource += _materialXDefinitionsSource; - - // Clear the output shaders vector. - shadersOut.clear(); + return _builtInShaders[name]; +} - // Add the shaders for the radiance hit entry point, if needed. - if (entryPoints.at("RADIANCE_HIT")) - { +shared_ptr PTShaderLibrary::getBuiltInMaterialDefinition(const string& name) +{ + return _builtInMaterialDefinitions[name]; +} - // Create radiance hit entry point, by replacing template tags with the material type name. - string radianceHitEntryPointSource = regex_replace( - CommonShaders::g_sClosestHitEntryPointTemplate, regex("@MATERIAL_TYPE@"), source.name); - shadersOut.push_back(shaderSource + radianceHitEntryPointSource); +void PTShaderLibrary::setupCompileJobForShader(const MaterialShader& shader, CompileJob& jobOut) +{ + // Add shared common code. + auto& source = shader.definition().source; + + // Setup the preprocessor defines to enable the entry points in the source code, based on shader + // ref-counts. + jobOut.code = "#define RADIANCE_HIT " + + to_string(shader.hasEntryPoint(EntryPointTypes::kRadianceHit)) + "\n"; + jobOut.code += "#define LAYER_MISS " + + to_string(shader.hasEntryPoint(EntryPointTypes::kLayerMiss)) + "\n\n"; + jobOut.code += "#define SHADOW_ANYHIT " + + to_string(shader.hasEntryPoint(EntryPointTypes::kShadowAnyHit)) + "\n\n"; + jobOut.code += "#define SHADOW_ANYHIT_ALWAYS_OPAQUE " + + to_string(shader.definition().isAlwaysOpaque) + "\n\n"; + + // Create the shader entry points, by replacing template tags with the shader name. + string entryPointSource = + regex_replace(CommonShaders::g_sMainEntryPoints, regex("___Material___"), source.uniqueId); + jobOut.code += entryPointSource; + + // Get the compiled shader for this material shader. + auto& compiledShader = _compiledShaders[shader.libraryIndex()]; + + // Set the library name to the shader name. + jobOut.libName = compiledShader.hlslFilename; + + // Set the includes from the shader's source code and the options source. + jobOut.includes = { + { "InitializeMaterial.slang", source.setup }, + { "Options.slang", _optionsSource }, + { "Definitions.slang", source.definitions }, + }; - // Create shadow hit entry point, by replacing template tags with the material type name. - string shadowHitEntryPointSource = regex_replace( - CommonShaders::g_sShadowHitEntryPointTemplate, regex("@MATERIAL_TYPE@"), source.name); - shadersOut.push_back(shaderSource + shadowHitEntryPointSource); - } + // Set the entry points to be post-processed (Slang will remove the [shader] tags). + jobOut.entryPoints = { { "closesthit", + compiledShader.entryPoints[EntryPointTypes::kRadianceHit] }, + { "anyhit", compiledShader.entryPoints[EntryPointTypes::kShadowAnyHit] }, + { "miss", compiledShader.entryPoints[EntryPointTypes::kLayerMiss] } }; - // Add the shaders for the layer miss entry point, if needed. - if (entryPoints.at("LAYER_MISS")) - { - // Create layer miss entry point, by replacing template tags with the material type name. - string layerMissShaderSource = regex_replace( - CommonShaders::g_sLayerShaderEntryPointTemplate, regex("@MATERIAL_TYPE@"), source.name); - shadersOut.push_back(shaderSource + layerMissShaderSource); - } + // Set the index to map back to the compiled shader array. + jobOut.index = shader.libraryIndex(); } -// Input to thread used to compile shaders. -struct CompileJob -{ - string code; - string libName; - ComPtr pBlob; - map includes; -}; - void PTShaderLibrary::rebuild() { // Start timer. _timer.reset(); // Should only be called if required (rebuilding requires stalling the GPU pipeline.) - AU_ASSERT(_rebuildRequired, "Rebuild not needed"); + AU_ASSERT(rebuildRequired(), "Rebuild not needed"); + + // Build vector of compile jobs to execute in parallel. + vector compileJobs; + + // Compile function is executed by MaterialShaderLibrary::update for any shaders that need + // recompiling. + auto compileShaderFunction = [this, &compileJobs](const MaterialShader& shader) { + // If there is no compiled shader object in the array for this shader, then create one. + if (_compiledShaders.size() <= shader.libraryIndex()) + { + // Resize array of compiled shaders. + _compiledShaders.resize(shader.libraryIndex() + 1); + } + + // Get the compiled shader object for this shader, and destroy any existing compiled shader + // binary. + auto& compiledShader = _compiledShaders[shader.libraryIndex()]; + + // If the compiled shader object is empty, fill in the entry points, etc. + if (compiledShader.id.empty()) + { + // Create entry points + _compiledShaders[shader.libraryIndex()].entryPoints = { + { EntryPointTypes::kRadianceHit, shader.id() + "RadianceHitShader" }, + { EntryPointTypes::kLayerMiss, shader.id() + "LayerMissShader" }, + { EntryPointTypes::kShadowAnyHit, shader.id() + "ShadowAnyHitShader" }, + }; + + // Default shader (at index 0) has all the shared entry points. + if (shader.libraryIndex() == 0) + { + _compiledShaders[shader.libraryIndex()] + .entryPoints[EntryPointTypes::kRadianceMiss] = "RadianceMissShader"; + _compiledShaders[shader.libraryIndex()].entryPoints[EntryPointTypes::kRayGen] = + "RayGenShader"; + _compiledShaders[shader.libraryIndex()].entryPoints[EntryPointTypes::kShadowMiss] = + "ShadowMissShader"; + _compiledShaders[shader.libraryIndex()] + .entryPoints[EntryPointTypes::kBackgroundMiss] = "BackgroundMissShader"; + } + + // Set the hit group export name from the entry point name. + _compiledShaders[shader.libraryIndex()].exportName = + _compiledShaders[shader.libraryIndex()].entryPoints[EntryPointTypes::kRadianceHit] + + "Group"; + + // Set the ID from the shader ID. + _compiledShaders[shader.libraryIndex()].id = shader.id(); + _compiledShaders[shader.libraryIndex()].hlslFilename = shader.id() + ".hlsl"; + Foundation::sanitizeFileName(_compiledShaders[shader.libraryIndex()].hlslFilename); + } + + // Destroy any existing compiled binary. + compiledShader.destroyBinary(); + + // Ensure ID matches shader. + AU_ASSERT(compiledShader.id.compare(shader.id()) == 0, "Compiled shader mismatch"); + + // Setup the compile job for this shader. + compileJobs.push_back(CompileJob(int(compileJobs.size()))); + setupCompileJobForShader(shader, compileJobs.back()); + + // If this is the default shader (which always library index 0), add the shared entry points + // (used by all shaders) + if (shader.libraryIndex() == 0) + { + compileJobs.back().entryPoints.push_back({ "miss", "BackgroundMissShader" }); + compileJobs.back().entryPoints.push_back({ "miss", "RadianceMissShader" }); + compileJobs.back().entryPoints.push_back({ "miss", "ShadowMissShader" }); + compileJobs.back().entryPoints.push_back({ "raygeneration", "RayGenShader" }); + } + + return true; + }; + + // Destroy function is executed by MaterialShaderLibrary::update for any shaders that need + // releasing. + auto destroyShaderFunction = [this](int index) { _compiledShaders[index].reset(); }; - // This creates the following subjects for a pipeline state object: + // Run the MaterialShaderLibrary update function and return if shaders were compiled. + if (!_shaderLibrary.update(compileShaderFunction, destroyShaderFunction)) + return; + + // If shaders were rebuild we must completely rebuild the pipeline state with the following + // subjects: // - Pipeline configuration: the max trace recursion depth. // - DXIL library: the compiled shaders in a bundle. // - Shader configurations: the ray payload and intersection attributes sizes. @@ -794,74 +779,38 @@ void PTShaderLibrary::rebuild() // exported, then DefineExport() on the subobject should be used. auto* pLibrarySubObject = pipelineStateDesc.CreateSubobject(); - // Clear any source that has been removed. - // TODO: Allow re-use of source indices. - for (auto sourceIndex : _sourceToRemove) + // As the transpiler is not thread safe we must create one for each thread. + // TODO: These are around 30-40Mb each so we could look at cleaning them up once a certain + // number are allocated. + for (size_t i = _transpilerArray.size(); i < compileJobs.size(); i++) { - _compiledMaterialTypes[sourceIndex].reset(); + _transpilerArray.push_back(make_shared(CommonShaders::g_sDirectory)); } - _sourceToRemove.clear(); - - // Build vector of compile jobs to execute in paralell. - vector compileJobs; - - // Add code to be compiled for the common shared entry points. - // TODO: Just compile once at the start of day. - compileJobs.push_back({ CommonShaders::g_sBackgroundMissShader, "BackgroundMiss", nullptr }); - compileJobs.push_back({ CommonShaders::g_sRadianceMissShader, "RadianceMiss", nullptr }); - compileJobs.push_back({ CommonShaders::g_sShadowMissShader, "ShadowMiss", nullptr }); - compileJobs.push_back({ CommonShaders::g_sRayGenShader, "RayGen", nullptr }); - - // Assemble the shader code for for each material type. - for (int i = 0; i < _compiledMaterialTypes.size(); i++) - { - auto& compiledMtlType = _compiledMaterialTypes[i]; - if (!compiledMtlType.source.empty() && !compiledMtlType.binary) - { - // Get the material type. - PTMaterialType* pMtlType = _materialTypes[compiledMtlType.source.name].lock().get(); - - // Get entry points for material type (this may have changed since last rebuild) - map entryPoints; - pMtlType->getEntryPoints(entryPoints); - // Assemble the shaders for all the entry points. - vector shaderCode; - assembleShadersForMaterialType(compiledMtlType.source, entryPoints, shaderCode); - - // Add a compile job for each entry point. - for (int j = 0; j < shaderCode.size(); j++) - { - // Add to compile jobs to be built, adding the material type source as includes map. - compileJobs.push_back({ shaderCode[j], compiledMtlType.source.name + to_string(j), - nullptr, { { "InitializeMaterial.slang", compiledMtlType.source.setup } } }); - } - } - } - - // Compile function called from parallel thread. + // Transpilation and DXC Compile function is called from parallel threads. auto compileFunc = [this](CompileJob& job) { + // Get the transpiler for this thread + auto pTranspiler = _transpilerArray[job.jobIndex]; + // If development flag set dump HLSL library to a file. if (AU_DEV_DUMP_SHADER_CODE) { - string entryPointPath = Foundation::getModulePath() + job.libName + ".txt"; - AU_INFO("Dumping shader code to:%s", entryPointPath.c_str()); - ofstream outputFile; - outputFile.open(entryPointPath); - outputFile << job.code; - outputFile.close(); + if (Foundation::writeStringToFile(job.code, job.libName)) + AU_INFO("Dumping shader code to:%s", job.libName.c_str()); + else + AU_WARN("Failed to write shader code to:%s", job.libName.c_str()); } - // Set the material type source as source code available as via #include in the compiler. + // Set the shader source as source code available as via #include in the compiler. for (auto iter = job.includes.begin(); iter != job.includes.end(); iter++) { - _pTranspiler->setSource(iter->first, iter->second); + pTranspiler->setSource(iter->first, iter->second); } - // Transpile the source. - string transpiledHLSL; + // Run the transpiler string transpilerErrors; - if (!_pTranspiler->transpileCode( + string transpiledHLSL; + if (!pTranspiler->transpileCode( job.code, transpiledHLSL, transpilerErrors, Transpiler::Language::HLSL)) { AU_ERROR("Slang transpiling error log:\n%s", transpilerErrors.c_str()); @@ -869,13 +818,36 @@ void PTShaderLibrary::rebuild() AU_FAIL("Slang transpiling failed, see log in console for details."); } - // Compile the HLSL source containing all the material types. + // Fix up the entry points (Slang will remove all the [shader] tags except one. So we need + // to re-add them. + for (int i = 0; i < job.entryPoints.size(); i++) + { + // Build the code to search for and version with [shader] prefixed. + string entryPointCode = "void " + job.entryPoints[i].second; + string entryPointCodeWithTag = + "[shader(\"" + job.entryPoints[i].first + "\")] " + entryPointCode; + + // Run the regex to replace add the tag to the transpiled HLSL source. + transpiledHLSL = regex_replace( + transpiledHLSL, regex("\n" + entryPointCode), "\n" + entryPointCodeWithTag); + } + + // If development flag set dump transpiled library to a file. + if (AU_DEV_DUMP_TRANSPILED_CODE) + { + if (Foundation::writeStringToFile(transpiledHLSL, job.libName)) + AU_INFO("Dumping transpiled code to:%s", job.libName.c_str()); + else + AU_WARN("Failed to write transpiled code to:%s", job.libName.c_str()); + } + + // Compile the HLSL source for this shader. ComPtr compiledShader; vector> defines = { { L"DIRECTX", "1" } }; string errorMessage; if (!compileLibrary(_pDXCLibrary, transpiledHLSL.c_str(), // Source code string. - "AuroraShaderLibrary", // Arbitrary shader name + job.libName, // Arbitrary shader name "lib_6_3", // Used DXIL 6.3 shader target "", // DXIL has empty entry point. defines, // Defines (currently empty vector). @@ -888,40 +860,45 @@ void PTShaderLibrary::rebuild() // developer a chance to handle HLSL programming errors as early as possible. AU_ERROR("HLSL compilation error log:\n%s", errorMessage.c_str()); AU_DEBUG_BREAK(); - AU_FAIL("HLSL compilation failed, see log in console for details."); + AU_FAIL("HLSL compilation failed for %s, see log in console for details.", + job.libName.c_str()); } - job.pBlob = compiledShader; + + // Set the compiled binary in the compiled shader obect for this shader. + _compiledShaders[job.index].binary = compiledShader; }; - // Compile all the material types. - vector> compiledShaders; - vector compiledShaderNames; + // Compile all the shaders in parallel (if AU_DEV_MULTITHREAD_COMPILATION is set.) float compStart = _timer.elapsed(); -#if AU_DEV_MULTITHREAD_COMPILATION // Set to 1 to force single threaded. - tbb::parallel_for(tbb::blocked_range(0, (int)compileJobs.size()), - [&compileJobs, compileFunc](tbb::blocked_range r) { - for (int i = r.begin(); i < r.end(); ++i) - { - compileFunc(compileJobs[i]); - } - }); +#if AU_DEV_MULTITHREAD_COMPILATION // Set to 0 to force single threaded. + for_each(execution::par, compileJobs.begin(), compileJobs.end(), + [compileFunc](CompileJob& job) { compileFunc(job); }); #else + // Otherwise run in single thread. for (auto& job : compileJobs) { compileFunc(job); } #endif - float compEnd = _timer.elapsed(); - // Build array of shader binaries and names for linking. - for (auto& job : compileJobs) + // Build array of all the shader binaries (not just the ones compiled this frame) and names for + // linking. + vector> compiledShaders; + vector compiledShaderNames; + for (int i = 0; i < _compiledShaders.size(); i++) { - compiledShaders.push_back(job.pBlob); - compiledShaderNames.push_back(job.libName); + auto& compiledShader = _compiledShaders[i]; + if (compiledShader.binary) + { + compiledShaders.push_back(compiledShader.binary); + string libName = compiledShader.id + ".hlsl"; + Foundation::sanitizeFileName(libName); + compiledShaderNames.push_back(libName); + } } - // Link the compiled shaders into single library. + // Link the compiled shaders into a single library blob. float linkStart = _timer.elapsed(); ComPtr linkedShader; string linkErrorMessage; @@ -981,37 +958,44 @@ void PTShaderLibrary::rebuild() pipelineStateDesc.CreateSubobject(); pGlobalRootSignatureSubobject->SetRootSignature(_pGlobalRootSignature.Get()); - // Create the local root signature subobject associated with the ray generation shader. + // Create the local root signature subobject associated with the ray generation shader (which is + // compiled with the default builtin shader) auto* pRayGenRootSignatureSubobject = pipelineStateDesc.CreateSubobject(); pRayGenRootSignatureSubobject->SetRootSignature(_pRayGenRootSignature.Get()); auto* pAssociationSubobject = pipelineStateDesc.CreateSubobject(); pAssociationSubobject->SetSubobjectToAssociate(*pRayGenRootSignatureSubobject); - pAssociationSubobject->AddExport(gRayGenEntryPoint); + pAssociationSubobject->AddExport( + Foundation::s2w(getDefaultShader().entryPoints[EntryPointTypes::kRayGen]).c_str()); - // Keep track of number of active material types. - int activeMaterialTypes = 0; + // Keep track of number of active shaders. + int activeShaders = 0; - // Create a DXR hit group for each material type. - for (auto pWeakMaterialType : _materialTypes) + // Create a DXR hit group for each shader. + for (int i = 0; i < _compiledShaders.size(); i++) { - PTMaterialTypePtr pMaterialType = pWeakMaterialType.second.lock(); - if (pMaterialType) + // Get the shader. + MaterialShaderPtr pShader = _shaderLibrary.get(i); + if (pShader) { + // Get the compiled shader object for this shader. + auto& compiledShader = _compiledShaders[i]; - if (pMaterialType->refCount(PTMaterialType::EntryPoint::kRadianceHit) == 0 && - pMaterialType->refCount(PTMaterialType::EntryPoint::kLayerMiss) == 0) + // Ensure some of the hit points are active. + if (!pShader->hasEntryPoint(EntryPointTypes::kRadianceHit) && + !pShader->hasEntryPoint(EntryPointTypes::kShadowAnyHit) && + !pShader->hasEntryPoint(EntryPointTypes::kLayerMiss)) { - AU_WARN("Invalid material type %s: all entry point reference counts are zero!", - pMaterialType->name().c_str()); + AU_WARN("Invalid shader %s: all entry point reference counts are zero!", + pShader->id().c_str()); } - // Increment active material type. - activeMaterialTypes++; + // Increment active shader. + activeShaders++; // Create the local root signature subobject associated with the hit group. - // All material types are based on the same radiance hit root signature currently. + // All shaders are based on the same radiance hit root signature currently. auto* pRadianceHitRootSignatureSubobject = pipelineStateDesc.CreateSubobject(); pRadianceHitRootSignatureSubobject->SetRootSignature(_pRadianceHitRootSignature.Get()); @@ -1028,27 +1012,36 @@ void PTShaderLibrary::rebuild() // an any hit shader for radiance rays, then a separate hit group for shadow rays would // have to be created, and referenced with an offset in the related TraceRay() calls. - // Create hit group (required even if only has miss shader.) - auto* pMaterialTypeSubobject = + // Create hit group (required even if only has miss shader.) + auto* pShaderSubobject = pipelineStateDesc.CreateSubobject(); - pMaterialTypeSubobject->SetHitGroupExport(pMaterialType->exportName().c_str()); - pAssociationSubobject->AddExport(pMaterialType->exportName().c_str()); + pShaderSubobject->SetHitGroupExport(Foundation::s2w(compiledShader.exportName).c_str()); + pAssociationSubobject->AddExport(Foundation::s2w(compiledShader.exportName).c_str()); - if (pMaterialType->refCount(PTMaterialType::EntryPoint::kRadianceHit) > 0) + // Setup the ClosestHitShaderImport (for radiance hit entry point) and, if needed, + // AnyHitShaderImport (for shadow hit entry point) on the hit group if + // the radiance hit reference count is non-zero. + if (pShader->refCount(EntryPointTypes::kRadianceHit) > 0) { + pShaderSubobject->SetClosestHitShaderImport( + Foundation::s2w(compiledShader.entryPoints[EntryPointTypes::kRadianceHit]) + .c_str()); + // Only add the shadow anyhit sub-object if needed (as indicated by refcount) + if (pShader->refCount(EntryPointTypes::kShadowAnyHit) > 0) + { + pShaderSubobject->SetAnyHitShaderImport( + Foundation::s2w(compiledShader.entryPoints[EntryPointTypes::kShadowAnyHit]) + .c_str()); + } + pShaderSubobject->SetHitGroupType(D3D12_HIT_GROUP_TYPE_TRIANGLES); - pMaterialTypeSubobject->SetClosestHitShaderImport( - pMaterialType->closestHitEntryPoint().c_str()); - pMaterialTypeSubobject->SetAnyHitShaderImport( - pMaterialType->shadowAnyHitEntryPoint().c_str()); - pMaterialTypeSubobject->SetHitGroupType(D3D12_HIT_GROUP_TYPE_TRIANGLES); - - pAssociationSubobject->AddExport(pMaterialType->exportName().c_str()); + pAssociationSubobject->AddExport( + Foundation::s2w(compiledShader.exportName).c_str()); } - if (pMaterialType->refCount(PTMaterialType::EntryPoint::kLayerMiss) > 0) + // Setup layer miss subobject is the layer miss reference count is non-zero. + if (pShader->refCount(EntryPointTypes::kLayerMiss) > 0) { - auto* pLayerMissRootSignatureSubobject = pipelineStateDesc.CreateSubobject(); pLayerMissRootSignatureSubobject->SetRootSignature(_pLayerMissRootSignature.Get()); @@ -1057,7 +1050,8 @@ void PTShaderLibrary::rebuild() .CreateSubobject(); pAssociationSubobject->SetSubobjectToAssociate(*pLayerMissRootSignatureSubobject); pAssociationSubobject->AddExport( - pMaterialType->materialLayerMissEntryPoint().c_str()); + Foundation::s2w(compiledShader.entryPoints[EntryPointTypes::kLayerMiss]) + .c_str()); } } } @@ -1071,16 +1065,14 @@ void PTShaderLibrary::rebuild() AU_ASSERT( PTMaterial::validateOffsets(*this), "Mismatch between GPU and CPU material structure"); - // Rebuild is no longer required. - _rebuildRequired = false; - - // Get time taken to rebuild. + // Get the total time taken to rebuild. // TODO: This should go into a stats property set and exposed to client properly. float elapsedMillisec = _timer.elapsed(); - AU_INFO("Compiled %d material types in %d ms", activeMaterialTypes, + // Dump breakdown of rebuild timing. + AU_INFO("Compiled %d shaders and linked %d in %d ms", compileJobs.size(), activeShaders, static_cast(elapsedMillisec)); - AU_INFO(" - DXC compile took %d ms", static_cast(compEnd - compStart)); + AU_INFO(" - Transpilation and DXC compile took %d ms", static_cast(compEnd - compStart)); AU_INFO(" - DXC link took %d ms", static_cast(linkEnd - linkStart)); AU_INFO(" - Pipeline creation took %d ms", static_cast(plEnd - plStart)); } diff --git a/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h index c121dec..9573510 100644 --- a/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h +++ b/Libraries/Aurora/Source/DirectX/PTShaderLibrary.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,126 +14,35 @@ #pragma once #include "MaterialBase.h" +#include "MaterialShader.h" +#include "Transpiler.h" BEGIN_AURORA -class Transpiler; - // Alias for DirectX shader ID, used to access the shader functions in the library. using DirectXShaderIdentifier = void*; -/** - * \desc Class representing a material type, which maps directly to a DirectX hit group and a hit - * shader in the HLSL shader library. Material types are managed by the shader library, and delete - * via shared pointer when no longer referenced by any materials. - */ -class PTMaterialType -{ - // Shader library manages the material type, and accesses private properties. - friend class PTShaderLibrary; - -public: - /** - * \param pShaderLibrary The shader library this type is part of. - * \param sourceIndex The index for this type's shader code within the library's HLSL source. - * \param hitEntryPoint The entry point for this type's hit shader. - */ - PTMaterialType(PTShaderLibrary* pShaderLibrary, int sourceIndex, const string& typeName); - ~PTMaterialType(); - - // Entry point types. - enum EntryPoint - { - kRadianceHit, - kLayerMiss, - kNumEntryPoints - }; - - // Array of entry point names. - static constexpr string_view EntryPointNames[EntryPoint::kNumEntryPoints] = { "RADIANCE_HIT", - "LAYER_MISS" }; - - // Gets the DX shader identifier for this type's hit group. - DirectXShaderIdentifier getShaderID(); - - // Gets the DX shader identifier for this type's layer miss shader. - DirectXShaderIdentifier getLayerShaderID(); - - // Gets the unique material type name. - const string& name() { return _name; } - - // Gets the export name for this type's hit group. - const wstring& exportName() const { return _exportName; } - - // Gets the closest hit HLSL function entry point name for this type. - const wstring& closestHitEntryPoint() const { return _closestHitEntryPoint; } - - // Gets the shadow any hit HLSL function entry point name for this type. - const wstring& shadowAnyHitEntryPoint() const { return _shadowAnyHitEntryPoint; } - - // Gets the layer material miss shader HLSL function entry point name for this type. - const wstring& materialLayerMissEntryPoint() const { return _materialLayerMissEntryPoint; } - - // Gets the index for this type's hit shader within the library's HLSL source. - int sourceIndex() const { return _sourceIndex; } - - void incrementRefCount(EntryPoint entryPoint); - - void decrementRefCount(EntryPoint entryPoint); - - int refCount(EntryPoint entryPoint) const { return _entryPointRefCount[entryPoint]; } - - void getEntryPoints(map& entryPoints) - { - for (int i = 0; i < kNumEntryPoints; i++) - { - entryPoints[EntryPointNames[i].data()] = (refCount((EntryPoint)i) > 0); - } - } - -protected: - // Invalidate this type, by setting its shader library to null pointer. - // Called by shader library in its destructor to avoid orphaned material types. - void invalidate() { _pShaderLibrary = nullptr; } - - // Is this type still valid? - bool isValid() { return _pShaderLibrary != nullptr; } - - array _entryPointRefCount; - - // The shader library this type is part of. - PTShaderLibrary* _pShaderLibrary; - - // The index for this type's shader code within the library's HLSL source. - int _sourceIndex; - - // Hit group export name. - wstring _exportName; - - // The closest hit function entry point name for this type. - wstring _closestHitEntryPoint; - - // The shadow any hit function entry point name for this type. - wstring _shadowAnyHitEntryPoint; +class MaterialBase; +struct MaterialDefaultValues; +struct CompileJob; - // The layer material miss shader entry for for this type. - // TODO: Should only exist if used as layer material. - wstring _materialLayerMissEntryPoint; - - // The unique material type name. - string _name; -}; - -// Shared pointer type for material types. -using PTMaterialTypePtr = shared_ptr; -struct CompiledMaterialType +// The DX data for a compiled. +struct CompiledShader { - MaterialTypeSource source; ComPtr binary = nullptr; + string exportName; + + string id; + string hlslFilename; + map entryPoints; + void destroyBinary() { binary = nullptr; } + // Reset the struct (as indices are re-used.) void reset() { - binary = nullptr; - source.reset(); + destroyBinary(); + entryPoints.clear(); + exportName.clear(); + id.clear(); } }; @@ -161,70 +70,82 @@ class PTShaderOptions vector> _data; }; +// Types of shader entry point used in the library. +struct EntryPointTypes +{ + static const string kRadianceHit; + static const string kLayerMiss; + static const string kShadowAnyHit; + static const string kRayGen; + static const string kBackgroundMiss; + static const string kRadianceMiss; + static const string kShadowMiss; +}; + /** - * \desc Class representing a DXIL shader library, and its HLSL source code. Manages the material - * types that implement Aurora materials using the compiled code in the library. The DXIL library - * and its associated pipeline state must be rebuilt as the source code for the library changes. + * \desc Class representing a DXIL shader library, and its HLSL source code. The DXIL library and + * its associated pipeline state must be rebuilt and linked as the source code for the shaders in + * the library changes. Uses the platform-indpendent class MaterialShaderLibrary to manage the + * shaders themselves. */ class PTShaderLibrary { - // Shader library manages the material type, and accesses private properties. - friend class PTMaterialType; + // Shader library manages the material shader, and accesses private properties. + friend class MaterialShader; public: + // Array of entry point names. + static const vector DefaultEntryPoints; + /*** Lifetime Management ***/ /** * \param pDevice DirectX12 device used by the library. */ - PTShaderLibrary(ID3D12Device5Ptr device) : _pDXDevice(device) { initialize(); } - ~PTShaderLibrary(); + PTShaderLibrary(ID3D12Device5Ptr device) : + _pDXDevice(device), _shaderLibrary(DefaultEntryPoints) + { + initialize(); + } + ~PTShaderLibrary() {} - /// Get the named built-in material type. These are hand-coded and do not require runtime code - /// generation, so this will never trigger a rebuild. - PTMaterialTypePtr getBuiltInMaterialType(const string& name); + /// Get the named built-in material shader. These are hand-coded and do not require runtime + /// code generation, so this will never trigger a rebuild. + MaterialShaderPtr getBuiltInShader(const string& name); - void assembleShadersForMaterialType(const MaterialTypeSource& source, - const map& entryPoints, vector& hlslOut); + /// Get the definition of the named built-in material shader. + shared_ptr getBuiltInMaterialDefinition(const string& name); /** - * \desc Acquire a material type for the provided source and type name. This - * will create new material type (and trigger a rebuild) if the named function does not already - * exist.. + * \desc Acquire a material shader for the provided definition. This + * will create new material shader (and trigger a rebuild) if a shader with the definition's ID + * does not already exist. * - * \param entryPoint HLSL function name of closest hit shader for this material type, if type - * with this name already exists will return that. - * \param hlslSource HLSL source code that implements this material type. If an existing - * material type for the entry point exists, the source code must match or this will trigger - * assert. + * \param def the definition of the material to acquire shader for. */ - PTMaterialTypePtr acquireMaterialType( - const MaterialTypeSource& source, bool* pCreateNewType = nullptr); + MaterialShaderPtr acquireShader(const MaterialDefinition& def) + { + // Get the shader definition from the material defintion. + // The shader definition is a subset of the material definition. + MaterialShaderDefinition shaderDef; + def.getShaderDefinition(shaderDef); + + // Acquire from the MaterialShaderLibrary object. + return _shaderLibrary.acquire(shaderDef); + } /// Get the DX pipeline state for this library. This will assert if the library requires a /// rebuild. ID3D12StateObjectPtr pipelineState() { - AU_ASSERT(!_rebuildRequired, + AU_ASSERT(!rebuildRequired(), "Shader Library rebuild required, call rebuild() before accessing pipeline state."); return _pPipelineState; } - /// Get the DX background miss shader identifier, that is shared by all material types.This will - /// assert if the library requires a rebuild. - DirectXShaderIdentifier getBackgroundMissShaderID(); - - /// Get the DX radiance miss shader identifier, that is shared by all material types.This will - /// assert if the library requires a rebuild. - DirectXShaderIdentifier getRadianceMissShaderID(); - - /// Get the DX shadow miss shader identifier, that is shared by all material types.This will - /// assert if the library requires a rebuild. - DirectXShaderIdentifier getShadowMissShaderID(); - - /// Get the DX ray generation shader identifier, that is shared by all material types.This will - /// assert if the library requires a rebuild. - DirectXShaderIdentifier getRayGenShaderID(); + // Get the DX shader identifier for one of the shared entry points (that are used by all + // materials). entryPointType must be one of the strings in EntryPointTypes. + DirectXShaderIdentifier getSharedEntryPointShaderID(const string& entryPointType); /// Get the names of the built-in material types. const vector& builtInMaterials() const { return _builtInMaterialNames; } @@ -232,16 +153,9 @@ class PTShaderLibrary /// Get the global root signature used by all shaders. ID3D12RootSignaturePtr globalRootSignature() const { return _pGlobalRootSignature; } - /// Is a rebuild of the shader library and its pipeline state required? - bool rebuildRequired() { return _rebuildRequired; } - /// Rebuild the shader library and its pipeline state. GPU must be idle before calling this. void rebuild(); - /// Set the MaterialX definition HLSL source. This will trigger rebuild if the new source is - /// different to the current definitions source. - bool setDefinitionsHLSL(const string& definitions); - // Get the DirectX shader reflection for library. ID3D12LibraryReflection* reflection() const { return _pShaderLibraryReflection; } @@ -249,15 +163,18 @@ class PTShaderLibrary // Will be added to shader library as a #define statement. bool setOption(const string& name, int value); - PTMaterialTypePtr getType(const string& name); + // Get the DirectX shader ID for the provided shader. + DirectXShaderIdentifier getShaderID(MaterialShaderPtr pShader); + // Get the DirectX shader ID for the layer shader for provided shader. + DirectXShaderIdentifier getLayerShaderID(MaterialShaderPtr pShader); - vector getActiveTypeNames(); - - void triggerRebuild() { _rebuildRequired = true; } + bool rebuildRequired() { return _shaderLibrary.rebuildRequired(); } private: /*** Private Functions ***/ + void setupCompileJobForShader(const MaterialShader& shader, CompileJob& shadersOut); + // Initialize the library. void initialize(); @@ -271,19 +188,26 @@ class PTShaderLibrary const string& target, const string& entryPoint, bool debug, ComPtr& pOutput, string* pErrorMessage); + void handleError(const string& errorMessage); + + void dumpShaderSource(const string& source, const string& name); + // Create a DirectX root signature. ID3D12RootSignaturePtr createRootSignature(const D3D12_ROOT_SIGNATURE_DESC& desc); - // Get the shader identifier for an entry point. - DirectXShaderIdentifier getShaderID(const wchar_t* entryPoint); - // Initialize the shared root signatures. void initRootSignatures(); - // Remove the HLSL source for the associated index. Called by friend class PTMaterialType. + // Remove the HLSL source for the associated index. Called by friend class MaterialShader. void removeSource(int sourceIndex); + CompiledShader& getDefaultShader() { return _compiledShaders[0]; } + /*** Private Variables ***/ + + // Get the shader identifier for an entry point. + DirectXShaderIdentifier getShaderID(const wchar_t* entryPoint); + vector _builtInMaterialNames; ID3D12Device5Ptr _pDXDevice; @@ -292,7 +216,6 @@ class PTShaderLibrary ID3D12RootSignaturePtr _pRayGenRootSignature; ID3D12RootSignaturePtr _pRadianceHitRootSignature; ID3D12RootSignaturePtr _pLayerMissRootSignature; - ; ID3D12StateObjectPtr _pPipelineState; @@ -301,18 +224,14 @@ class PTShaderLibrary ComPtr _pDXCompiler; ComPtr _pDXLinker; - vector _sourceToRemove; - - map> _materialTypes; - map _builtInMaterialTypes; - - bool _rebuildRequired = true; + MaterialShaderLibrary _shaderLibrary; + map> _builtInMaterialDefinitions; + map> _builtInShaders; - vector _compiledMaterialTypes; - string _materialXDefinitionsSource; + vector _compiledShaders; string _optionsSource; PTShaderOptions _options; - shared_ptr _pTranspiler; + vector> _transpilerArray; Foundation::CPUTimer _timer; }; diff --git a/Libraries/Aurora/Source/DirectX/PTTarget.cpp b/Libraries/Aurora/Source/DirectX/PTTarget.cpp index 3952fc0..60ed1e4 100644 --- a/Libraries/Aurora/Source/DirectX/PTTarget.cpp +++ b/Libraries/Aurora/Source/DirectX/PTTarget.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/PTTarget.h b/Libraries/Aurora/Source/DirectX/PTTarget.h index c85089c..5f80148 100644 --- a/Libraries/Aurora/Source/DirectX/PTTarget.h +++ b/Libraries/Aurora/Source/DirectX/PTTarget.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl b/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl index 3955108..87f3076 100644 --- a/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl +++ b/Libraries/Aurora/Source/DirectX/Shaders/Accumulation.hlsl @@ -1,7 +1,7 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. +// you may not use this file except in compBliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 @@ -22,7 +22,7 @@ // Source (input) and destination (output) textures. RWTexture2D gAccumulation : register(u0); -RWTexture2D gDirect : register(u1); +RWTexture2D gResult : register(u1); RWTexture2D gDepthNDC : register(u2); RWTexture2D gDepthView : register(u3); RWTexture2D gNormalRoughness : register(u4); @@ -43,18 +43,19 @@ struct Accumulation ConstantBuffer gSettings : register(b0); // A compute shader that accumulates path tracing results, optionally with denoising. -[RootSignature(ROOT_SIGNATURE)][numthreads(1, 1, 1)] void Accumulation(uint3 threadID - : SV_DispatchThreadID) +[RootSignature(ROOT_SIGNATURE)] +[numthreads(1, 1, 1)] +void Accumulation(uint3 threadID : SV_DispatchThreadID) { uint sampleIndex = gSettings.sampleIndex; - // Get the screen coordinates (2D) from the thread ID, and the color / alpha as the result. - // Treat the color as the direct lighting value, optionally used below. + // Get the screen coordinates (2D) from the thread ID, and the color / alpha from the result of + // the most recent sample. Treat the result as the "extra" shading value, optionally used below. float2 screenCoords = threadID.xy; - float4 result = gDirect[screenCoords]; - float3 direct = result.rgb; + float4 result = gResult[screenCoords]; + float3 extra = result.rgb; - // Combine data from textures if denoising is enabled. Otherwise the "direct" texture has the + // Combine data from textures if denoising is enabled. Otherwise the "result" value has the // complete path tracing output. if (gSettings.isDenoisingEnabled) { @@ -67,10 +68,10 @@ ConstantBuffer gSettings : register(b0); float3 denoisedGlossy = gGlossyDenoised[screenCoords].rgb * hitFactor; // Combine the following: - // - Direct lighting. This also includes the environment background. + // - Extra: shading that is not denoised. See RadianceRayPayload for more information. // - The denoised diffuse radiance, modulated by the base color (albedo). // - The denoised glossy radiance. - result.rgb = direct + denoisedDiffuse * baseColor + denoisedGlossy; + result.rgb = extra + (denoisedDiffuse * baseColor) + denoisedGlossy; } // If the sample index is greater than zero, blend the new result color with the previous diff --git a/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl b/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl index ad83972..953f66a 100644 --- a/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl +++ b/Libraries/Aurora/Source/DirectX/Shaders/PostProcessing.hlsl @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ // Source (input) and destination (output) textures. RWTexture2D gFinal : register(u0); RWTexture2D gAccumulation : register(u1); -RWTexture2D gDirect : register(u2); +RWTexture2D gResult : register(u2); RWTexture2D gDepthNDC : register(u3); RWTexture2D gDepthView : register(u4); RWTexture2D gNormalRoughness : register(u5); @@ -70,8 +70,9 @@ float normalizeDepthView(float depthView) } // A compute shader that applies post-processing to the source texture. -[RootSignature(ROOT_SIGNATURE)][numthreads(1, 1, 1)] void PostProcessing(uint3 threadID - : SV_DispatchThreadID) +[RootSignature(ROOT_SIGNATURE)] +[numthreads(1, 1, 1)] +void PostProcessing(uint3 threadID : SV_DispatchThreadID) { // Get the screen coordinates (2D) from the thread ID. float2 coords = threadID.xy; diff --git a/Libraries/Aurora/Source/EnvironmentBase.cpp b/Libraries/Aurora/Source/EnvironmentBase.cpp index d10dace..0763238 100644 --- a/Libraries/Aurora/Source/EnvironmentBase.cpp +++ b/Libraries/Aurora/Source/EnvironmentBase.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/EnvironmentBase.h b/Libraries/Aurora/Source/EnvironmentBase.h index 2768ea9..c87b294 100644 --- a/Libraries/Aurora/Source/EnvironmentBase.h +++ b/Libraries/Aurora/Source/EnvironmentBase.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/GeometryBase.cpp b/Libraries/Aurora/Source/GeometryBase.cpp index 5194bd7..3782b3a 100644 --- a/Libraries/Aurora/Source/GeometryBase.cpp +++ b/Libraries/Aurora/Source/GeometryBase.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ static void copyVertexChannelData(vector& dst, const AttributeDat ComponentType* pDstComp = dst.data(); for (size_t i = 0; i < vertexCount; i++) { - // Copy the individual element from the soure buffer to destination. + // Copy the individual element from the source buffer to destination. const ComponentType* pSrcComp = reinterpret_cast(pSrc); for (uint32_t j = 0; j < componentCount; j++) { @@ -100,16 +100,18 @@ GeometryBase::GeometryBase(const std::string& name, const GeometryDescriptor& de _positions, vertexBuffers[Names::VertexAttributes::kPosition], _vertexCount, 3); copyVertexChannelData( _normals, vertexBuffers[Names::VertexAttributes::kNormal], _vertexCount, 3); + copyVertexChannelData( + _tangents, vertexBuffers[Names::VertexAttributes::kTangent], _vertexCount, 3); copyVertexChannelData( _texCoords, vertexBuffers[Names::VertexAttributes::kTexCoord0], _vertexCount, 2); copyVertexChannelData(_indices, vertexBuffers[Names::VertexAttributes::kIndices], _indexCount); - // Run the optional attributeUpdateComplete functoin to free any buffers being held by the + // Run the optional attributeUpdateComplete function to free any buffers being held by the // client. if (descriptor.attributeUpdateComplete) descriptor.attributeUpdateComplete(vertexBuffers, 0, _vertexCount, 0, _indexCount); - // TODO: We need a better UV generator, esspecially if we need tangents generated from them. + // TODO: We need a better UV generator, especially if we need tangents generated from them. if (_texCoords.size() == 0) { _texCoords.resize(_vertexCount * 2); diff --git a/Libraries/Aurora/Source/GeometryBase.h b/Libraries/Aurora/Source/GeometryBase.h index cd66357..1b102cd 100644 --- a/Libraries/Aurora/Source/GeometryBase.h +++ b/Libraries/Aurora/Source/GeometryBase.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -44,6 +44,7 @@ class GeometryBase : public IGeometry uint32_t _vertexCount = 0; vector _positions; vector _normals; + vector _tangents; vector _texCoords; uint32_t _indexCount = 0; vector _indices; diff --git a/Libraries/Aurora/Source/HGI/HGIGeometry.h b/Libraries/Aurora/Source/HGI/HGIGeometry.h index 8c5fc9f..da639df 100644 --- a/Libraries/Aurora/Source/HGI/HGIGeometry.h +++ b/Libraries/Aurora/Source/HGI/HGIGeometry.h @@ -21,11 +21,14 @@ BEGIN_AURORA // Forward declarations. class HGIRenderer; +// The geometry buffer structure, must match GPU layout in +// Libraries\Aurora\Source\HGI\Shaders\InstanceData.glsl struct HGIGeometryBuffers { uint64_t indexBufferDeviceAddress = 0ull; uint64_t vertexBufferDeviceAddress = 0ull; uint64_t normalBufferDeviceAddress = 0ull; + uint64_t tangentBufferDeviceAddress = 0ull; uint64_t texCoordBufferDeviceAddress = 0ull; }; diff --git a/Libraries/Aurora/Source/HGI/HGILight.cpp b/Libraries/Aurora/Source/HGI/HGILight.cpp new file mode 100644 index 0000000..da9067e --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGILight.cpp @@ -0,0 +1,58 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "HGILight.h" + +#include "HGIRenderer.h" + +BEGIN_AURORA + +static PropertySetPtr g_pDistantLightPropertySet; + +static PropertySetPtr distantLightPropertySet() +{ + if (g_pDistantLightPropertySet) + { + return g_pDistantLightPropertySet; + } + + // Create properties and defaults for distant lights. + g_pDistantLightPropertySet = make_shared(); + g_pDistantLightPropertySet->add( + Names::LightProperties::kDirection, normalize(vec3(-1.0f, -0.5f, -1.0f))); + g_pDistantLightPropertySet->add(Names::LightProperties::kColor, vec3(1, 1, 1)); + g_pDistantLightPropertySet->add(Names::LightProperties::kAngularDiameter, 0.1f); + g_pDistantLightPropertySet->add(Names::LightProperties::kExposure, 0.0f); + g_pDistantLightPropertySet->add(Names::LightProperties::kIntensity, 1.0f); + + return g_pDistantLightPropertySet; +} + +// Return the property set for the provided light type. +static PropertySetPtr propertySet(const string& type) +{ + if (type.compare(Names::LightTypes::kDistantLight) == 0) + return distantLightPropertySet(); + + AU_FAIL("Unknown light type:%s", type.c_str()); + return nullptr; +} + +HGILight::HGILight(HGIScene* pScene, const string& lightType, int index) : + FixedValues(propertySet(lightType)), _pScene(pScene), _index(index) +{ +} + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGILight.h b/Libraries/Aurora/Source/HGI/HGILight.h new file mode 100644 index 0000000..99d2dcc --- /dev/null +++ b/Libraries/Aurora/Source/HGI/HGILight.h @@ -0,0 +1,49 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +// Forward declarations. +class HGIScene; + +// An internal implementation for ILight. +class HGILight : public ILight, public FixedValues +{ +public: + /*** Lifetime Management ***/ + + HGILight(HGIScene* pScene, const string& lightType, int index); + ~HGILight() {}; + + /*** Functions ***/ + FixedValues& values() override { return *this; } + + int index() const { return _index; } + + bool isDirty() const { return _bIsDirty; } + void clearDirtyFlag() { _bIsDirty = false; } + +private: + /*** Private Variables ***/ + + HGIScene* _pScene = nullptr; + int _index; +}; + +MAKE_AURORA_PTR(HGILight); + +END_AURORA diff --git a/Libraries/Aurora/Source/HGI/HGIMaterial.cpp b/Libraries/Aurora/Source/HGI/HGIMaterial.cpp index 18499ba..438c0c9 100644 --- a/Libraries/Aurora/Source/HGI/HGIMaterial.cpp +++ b/Libraries/Aurora/Source/HGI/HGIMaterial.cpp @@ -20,14 +20,16 @@ using namespace pxr; BEGIN_AURORA -HGIMaterial::HGIMaterial(HGIRenderer* pRenderer) : _pRenderer(pRenderer) +HGIMaterial::HGIMaterial( + HGIRenderer* pRenderer, MaterialShaderPtr pShader, shared_ptr pDef) : + MaterialBase(pShader, pDef), _pRenderer(pRenderer) { // Create buffer descriptor, passing material as initial data. HgiBufferDesc uboDesc; uboDesc.debugName = "Material UBO"; uboDesc.usage = HgiBufferUsageUniform | HgiBufferUsageRayTracingExtensions | HgiBufferUsageShaderDeviceAddress; - uboDesc.byteSize = sizeof(MaterialData); + uboDesc.byteSize = uniformBuffer().size(); // Create UBO. _ubo = @@ -37,13 +39,13 @@ HGIMaterial::HGIMaterial(HGIRenderer* pRenderer) : _pRenderer(pRenderer) void HGIMaterial::update() { // Build a structure from values map into staging buffer. - MaterialData* pStaging = getStagingAddress(_ubo); - updateGPUStruct(*pStaging); + void* pStaging = _ubo->handle()->GetCPUStagingAddress(); + ::memcpy_s(pStaging, uniformBuffer().size(), uniformBuffer().data(), uniformBuffer().size()); // Transfer staging buffer to GPU. pxr::HgiBlitCmdsUniquePtr blitCmds = _pRenderer->hgi()->CreateBlitCmds(); pxr::HgiBufferCpuToGpuOp blitOp; - blitOp.byteSize = sizeof(MaterialData); + blitOp.byteSize = uniformBuffer().size(); blitOp.cpuSourceBuffer = pStaging; blitOp.sourceByteOffset = 0; blitOp.gpuDestinationBuffer = _ubo->handle(); diff --git a/Libraries/Aurora/Source/HGI/HGIMaterial.h b/Libraries/Aurora/Source/HGI/HGIMaterial.h index d490994..8578be3 100644 --- a/Libraries/Aurora/Source/HGI/HGIMaterial.h +++ b/Libraries/Aurora/Source/HGI/HGIMaterial.h @@ -20,12 +20,14 @@ BEGIN_AURORA // Forward declarations. class HGIRenderer; +struct MaterialData; // An internal implementation for IMaterial. class HGIMaterial : public MaterialBase { public: - HGIMaterial(HGIRenderer* pRenderer); + HGIMaterial( + HGIRenderer* pRenderer, MaterialShaderPtr pShader, shared_ptr pDef); ~HGIMaterial() {}; void update(); @@ -33,6 +35,7 @@ class HGIMaterial : public MaterialBase pxr::HgiBufferHandle ubo() { return _ubo->handle(); } private: + void updateGPUStruct(MaterialData& data); HGIRenderer* _pRenderer; HgiBufferHandleWrapper::Pointer _ubo; }; diff --git a/Libraries/Aurora/Source/HGI/HGIRenderer.cpp b/Libraries/Aurora/Source/HGI/HGIRenderer.cpp index 52666c1..fef6c7c 100644 --- a/Libraries/Aurora/Source/HGI/HGIRenderer.cpp +++ b/Libraries/Aurora/Source/HGI/HGIRenderer.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "pch.h" +#include "CompiledShaders/CommonShaders.h" #include "CompiledShaders/HGIShaders.h" #include "HGIEnvironment.h" #include "HGIGroundPlane.h" @@ -27,7 +28,10 @@ using namespace pxr; BEGIN_AURORA -HGIRenderer::HGIRenderer(uint32_t activeFrameCount) : RendererBase(activeFrameCount) +const vector DefaultEntryPoints = { "RADIANCE_HIT" }; + +HGIRenderer::HGIRenderer(uint32_t activeFrameCount) : + RendererBase(activeFrameCount), _shaderLibrary(DefaultEntryPoints) { _isValid = true; @@ -38,6 +42,21 @@ HGIRenderer::HGIRenderer(uint32_t activeFrameCount) : RendererBase(activeFrameCo // Vulkan texture coordinates are upside down compared to DX. _pAssetMgr->enableVerticalFlipOnImageLoad(!_values.asBoolean(kLabelIsFlipImageYEnabled)); + + MaterialShaderSource defaultMaterialSource( + "Default", CommonShaders::g_sInitializeDefaultMaterialType); + + // Create the material definition for default shader. + _pDefaultMaterialDefinition = make_shared(defaultMaterialSource, + MaterialBase::StandardSurfaceDefaults, MaterialBase::updateBuiltInMaterial, false); + + // Create shader from the definition. + MaterialShaderDefinition shaderDef; + _pDefaultMaterialDefinition->getShaderDefinition(shaderDef); + _pDefaultMaterialShader = _shaderLibrary.acquire(shaderDef); + + // Ensure the radiance hit entry point is compiled for default shader. + _pDefaultMaterialShader->incrementRefCount("RADIANCE_HIT"); } HGIRenderer::~HGIRenderer() { @@ -309,7 +328,7 @@ IMaterialPtr HGIRenderer::createMaterialPointer( } // Create and return a new material object. - return make_shared(this); + return make_shared(this, _pDefaultMaterialShader, _pDefaultMaterialDefinition); } IGeometryPtr HGIRenderer::createGeometryPointer( diff --git a/Libraries/Aurora/Source/HGI/HGIRenderer.h b/Libraries/Aurora/Source/HGI/HGIRenderer.h index 57f6aaa..522f76b 100644 --- a/Libraries/Aurora/Source/HGI/HGIRenderer.h +++ b/Libraries/Aurora/Source/HGI/HGIRenderer.h @@ -100,6 +100,10 @@ class HGIRenderer : public RendererBase HgiSamplerHandleWrapper::Pointer _sampler; HgiTextureHandleWrapper::Pointer _pDirectTex; HgiTextureHandleWrapper::Pointer _pAccumulationTex; + + MaterialShaderLibrary _shaderLibrary; + shared_ptr _pDefaultMaterialDefinition; + shared_ptr _pDefaultMaterialShader; }; MAKE_AURORA_PTR(HGIRenderer); diff --git a/Libraries/Aurora/Source/HGI/HGIScene.cpp b/Libraries/Aurora/Source/HGI/HGIScene.cpp index fb32703..b33d7df 100644 --- a/Libraries/Aurora/Source/HGI/HGIScene.cpp +++ b/Libraries/Aurora/Source/HGI/HGIScene.cpp @@ -52,7 +52,7 @@ bool HGIScene::update() SceneBase::update(); // Update the environment. - if (_environments.changed()) + if (_environments.changedThisFrame()) { HGIEnvironmentPtr pEnvironment = static_pointer_cast(_pEnvironmentResource->resource()); @@ -62,7 +62,7 @@ bool HGIScene::update() // If any active geometry resources have been modified, flush the vertex buffer pool in case // there are any pending vertex buffers that are required to update the geometry, and then // update the geometry (and update BLAS for "complete" geometry that has position data). - if (_geometry.changed()) + if (_geometry.changedThisFrame()) { for (HGIGeometry& geom : _geometry.modified().resources()) { @@ -72,7 +72,7 @@ bool HGIScene::update() // If any active material resources have been modified update them and build a list of unique // samplers for all the active materials. - if (_materials.changed()) + if (_materials.changedThisFrame()) { for (HGIMaterial& mtl : _materials.modified().resources()) { @@ -81,7 +81,7 @@ bool HGIScene::update() } // Update the acceleration structure if any geometry or instances have been modified. - if (_instances.changed() || _geometry.changed()) + if (_instances.changedThisFrame() || _geometry.changedThisFrame()) { _rayTracingPipeline.reset(); } @@ -92,6 +92,7 @@ bool HGIScene::update() { rebuildInstanceList(); rebuildPipeline(); + // TODO: Should upload lights here too. rebuildAccelerationStructure(); rebuildResourceBindings(); @@ -482,14 +483,17 @@ void HGIScene::createResources() } // Create the closest hit shader from template text. - string closestHitEntryPointSource = regex_replace( - CommonShaders::g_sClosestHitEntryPointTemplate, regex("@MATERIAL_TYPE@"), "Default"); + string mainEntryPointSource = "#define RADIANCE_HIT 1\n" + + regex_replace(CommonShaders::g_sMainEntryPoints, regex("___Material___"), "Default"); + _transpiler->setSource( "InitializeMaterial.slang", CommonShaders::g_sInitializeDefaultMaterialType); + _transpiler->setSource("Options.slang", ""); + _transpiler->setSource("Definitions.slang", ""); // Transpaile the closest hit shader. - if (!_transpiler->transpileCode(closestHitEntryPointSource, transpiledGLSL, transpilerErrors, - Transpiler::Language::GLSL)) + if (!_transpiler->transpileCode( + mainEntryPointSource, transpiledGLSL, transpilerErrors, Transpiler::Language::GLSL)) { AU_ERROR("Slang transpiling error on closest hit shdaer:\n%s", transpilerErrors.c_str()); AU_DEBUG_BREAK(); @@ -640,6 +644,25 @@ void HGIScene::rebuildPipeline() hgi->CreateRayTracingPipeline(pipelineDesc), hgi); } +ILightPtr HGIScene::addLightPointer(const string& lightType) +{ + // Only distant lights are currently supported. + AU_ASSERT(lightType.compare(Names::LightTypes::kDistantLight) == 0, + "Only distant lights currently supported"); + + // Assign arbritary index to ensure deterministic ordering. + int index = _currentLightIndex++; + + // Create the light object. + HGILightPtr pLight = make_shared(this, lightType, index); + + // Add weak pointer to distant light map. + _distantLights[index] = pLight; + + // Return the new light. + return pLight; +} + IInstancePtr HGIScene::addInstancePointer(const Path& /* path*/, const IGeometryPtr& pGeom, const IMaterialPtr& pMaterial, const mat4& transform, const LayerDefinitions& /* materialLayers*/) diff --git a/Libraries/Aurora/Source/HGI/HGIScene.h b/Libraries/Aurora/Source/HGI/HGIScene.h index 0f81e2d..1cd4e5c 100644 --- a/Libraries/Aurora/Source/HGI/HGIScene.h +++ b/Libraries/Aurora/Source/HGI/HGIScene.h @@ -16,6 +16,7 @@ #include "HGIEnvironment.h" #include "HGIGeometry.h" #include "HGIImage.h" +#include "HGILight.h" #include "HGIMaterial.h" #include "SceneBase.h" @@ -80,6 +81,7 @@ struct InstanceShaderRecord // Geometry flags. unsigned int hasNormals = true; + unsigned int hasTangents = false; unsigned int hasTexCoords = true; }; @@ -116,8 +118,12 @@ class HGIScene : public SceneBase IInstancePtr addInstancePointer(const Path& /* path*/, const IGeometryPtr& pGeom, const IMaterialPtr& pMaterial, const mat4& transform, const LayerDefinitions& materialLayers) override; + ILightPtr addLightPointer(const string& lightType) override; private: + map> _distantLights; + int _currentLightIndex = 0; + HGIRenderer* _pRenderer = nullptr; shared_ptr _transpiler; HgiRayTracingPipelineHandleWrapper::Pointer _rayTracingPipeline; diff --git a/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl b/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl index 19b7b0c..08136b8 100644 --- a/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl +++ b/Libraries/Aurora/Source/HGI/Shaders/InstanceData.glsl @@ -10,6 +10,7 @@ layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Indices { uint i[]; }; layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Positions { vec3 v[]; }; layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Normals { vec3 n[]; }; +layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer Tangents { vec3 tn[]; }; layout(buffer_reference, std430, buffer_reference_align=4, scalar) buffer TexCoords { vec2 t[]; }; // Buffer type for material. @@ -25,6 +26,7 @@ layout(shaderRecordEXT, std430) buffer InstanceShaderRecord Indices indices; Positions positions; Normals normals; + Tangents tangents; TexCoords texcoords; // Material data. @@ -38,6 +40,7 @@ layout(shaderRecordEXT, std430) buffer InstanceShaderRecord // Geometry flags. uint hasNormals; + uint hasTangents; uint hasTexCoords; } instance; @@ -61,6 +64,11 @@ vec3 getNormalForVertex_0(int vertexIndex) { return instance.normals.n[vertexIndex]; } +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +vec3 getTangentForVertex_0(int vertexIndex) { + return instance.tangents.tn[vertexIndex]; +} + // Implementation for forward declared geometry accessor function in PathTracingCommon.slang. vec2 getTexCoordForVertex_0(int vertexIndex) { return instance.texcoords.t[vertexIndex]; @@ -68,12 +76,17 @@ vec2 getTexCoordForVertex_0(int vertexIndex) { // Implementation for forward declared geometry accessor function in PathTracingCommon.slang. bool instanceHasNormals_0() { - return true;//instance.hasNormals; + return instance.hasNormals!=0; +} + +// Implementation for forward declared geometry accessor function in PathTracingCommon.slang. +bool instanceHasTangents_0() { + return instance.hasTangents!=0; } // Implementation for forward declared geometry accessor function in PathTracingCommon.slang. bool instanceHasTexCoords_0() { - return true;//instance.hasTexCoords; + return instance.hasTexCoords!=0; } // Implementation for forward declared material accessor function in Material.hlsli. @@ -91,6 +104,12 @@ vec4 sampleSpecularRoughnessTexture_0(vec2 uv, float level) { return texture(textureSamplers[nonuniformEXT(instance.specularRoughnessTextureIndex)], uv); } +// Implementation for forward declared texture sample function in Material.hlsli. +vec4 sampleEmissionColorTexture_0(vec2 uv, float level) { + // TODO: Implement emission color in HGI backend. + return vec4(0,0,0,0); +} + // Implementation for forward declared texture sample function in Material.hlsli. vec4 sampleNormalTexture_0(vec2 uv, float level) { return texture(textureSamplers[nonuniformEXT(instance.normalTextureIndex)], uv); diff --git a/Libraries/Aurora/Source/MaterialBase.cpp b/Libraries/Aurora/Source/MaterialBase.cpp index 071547d..2016ec9 100644 --- a/Libraries/Aurora/Source/MaterialBase.cpp +++ b/Libraries/Aurora/Source/MaterialBase.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,49 +28,6 @@ static PropertySetPtr propertySet() g_pPropertySet = make_shared(); - // Constants. - // NOTE: Default values and order come from the Standard surface reference document: - // https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx - g_pPropertySet->add("base", 0.8f); - g_pPropertySet->add("base_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("diffuse_roughness", 0.0f); - g_pPropertySet->add("metalness", 0.0f); - g_pPropertySet->add("specular", 1.0f); - g_pPropertySet->add("specular_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("specular_roughness", 0.2f); - g_pPropertySet->add("specular_IOR", 1.5f); - g_pPropertySet->add("specular_anisotropy", 0.0f); - g_pPropertySet->add("specular_rotation", 0.0f); - g_pPropertySet->add("transmission", 0.0f); - g_pPropertySet->add("transmission_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("transmission_depth", 0.0f); - g_pPropertySet->add("transmission_scatter", vec3(0.0f, 1.0f, 1.0f)); - g_pPropertySet->add("transmission_scatter_anisotropy", 0.0f); - g_pPropertySet->add("transmission_dispersion", 0.0f); - g_pPropertySet->add("transmission_extra_roughness", 0.0f); - g_pPropertySet->add("subsurface", 0.0f); - g_pPropertySet->add("subsurface_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("subsurface_radius", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("subsurface_scale", 1.0f); - g_pPropertySet->add("subsurface_anisotropy", 0.0f); - g_pPropertySet->add("sheen", 0.0f); - g_pPropertySet->add("sheen_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("sheen_roughness", 0.3f); - g_pPropertySet->add("coat", 0.0f); - g_pPropertySet->add("coat_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("coat_roughness", 0.1f); - g_pPropertySet->add("coat_anisotropy", 0.0f); - g_pPropertySet->add("coat_rotation", 0.0f); - g_pPropertySet->add("coat_IOR", 1.5f); - g_pPropertySet->add("coat_affect_color", 0.0f); - g_pPropertySet->add("coat_affect_roughness", 0.0f); - g_pPropertySet->add("thin_film_thickness", 0.0f); - g_pPropertySet->add("thin_film_IOR", 1.5f); - g_pPropertySet->add("emission", 0.0f); - g_pPropertySet->add("emission_color", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("opacity", vec3(1.0f, 1.0f, 1.0f)); - g_pPropertySet->add("thin_walled", false); - // Images (textures) and associated transforms. // NOTE: Default values must be nullptr, as the property set has a lifetime that could exceed // the renderer, and images can't be retained outside their renderer. @@ -91,6 +48,11 @@ static PropertySetPtr propertySet() g_pPropertySet->add("coat_color_image_transform", mat4()); g_pPropertySet->add("coat_roughness_image", IImagePtr()); g_pPropertySet->add("coat_roughness_image_transform", mat4()); + g_pPropertySet->add("emission_color_image", IImagePtr()); + g_pPropertySet->add("emission_color_image_offset", vec2()); + g_pPropertySet->add("emission_color_image_scale", vec2(1, 1)); + g_pPropertySet->add("emission_color_image_pivot", vec2()); + g_pPropertySet->add("emission_color_image_rotation", 0.0f); g_pPropertySet->add("opacity_image", IImagePtr()); g_pPropertySet->add("opacity_image_offset", vec2()); g_pPropertySet->add("opacity_image_scale", vec2(1, 1)); @@ -107,125 +69,184 @@ static PropertySetPtr propertySet() return g_pPropertySet; } -void MaterialBase::updateGPUStruct(MaterialData& data) +// A shortcut macro for defining a uniform buffer property definition. +#define PROPERTY_DEF(NAME1, NAME2, TYPE) \ + UniformBufferPropertyDefinition(NAME1, NAME2, PropertyValue::Type::TYPE) + +// Material properties used by the built-in Standard Surface material type. +// NOTE: Default values and order come from the Standard surface reference document: +// https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx +// clang-format off +UniformBufferDefinition MaterialBase::StandardSurfaceUniforms = { + PROPERTY_DEF("base", "base", Float), + PROPERTY_DEF("base_color", "baseColor", Float3), + PROPERTY_DEF("diffuse_roughness", "diffuseRoughness", Float), + PROPERTY_DEF("metalness", "metalness", Float), + PROPERTY_DEF("specular", "specular", Float), + PROPERTY_DEF("specular_color", "specularColor", Float3), + PROPERTY_DEF("specular_roughness", "specularRoughness", Float), + PROPERTY_DEF("specular_IOR", "specularIOR", Float), + PROPERTY_DEF("specular_anisotropy", "specularAnisotropy", Float), + PROPERTY_DEF("specular_rotation", "specularRotation", Float), + PROPERTY_DEF("transmission", "transmission", Float), + PROPERTY_DEF("transmission_color", "transmissionColor", Float3), + PROPERTY_DEF("subsurface", "subsurface", Float), + PROPERTY_DEF("subsurface_color", "subsurfaceColor", Float3), + PROPERTY_DEF("subsurface_radius", "subsurfaceRadius", Float3), + PROPERTY_DEF("subsurface_scale", "subsurfaceScale", Float), + PROPERTY_DEF("subsurface_anisotropy", "subsurfaceAnisotropy", Float), + PROPERTY_DEF("sheen", "sheen", Float), + PROPERTY_DEF("sheen_color", "sheenColor", Float3), + PROPERTY_DEF("sheen_roughness", "sheenRoughness", Float), + PROPERTY_DEF("coat", "coat", Float), + PROPERTY_DEF("coat_color", "coatColor", Float3), + PROPERTY_DEF("coat_roughness", "coatRoughness", Float), + PROPERTY_DEF("coat_anisotropy", "coatAnisotropy", Float), + PROPERTY_DEF("coat_rotation", "coatRotation", Float), + PROPERTY_DEF("coat_IOR", "coatIOR", Float), + PROPERTY_DEF("coat_affect_color", "coatAffectColor", Float), + PROPERTY_DEF("coat_affect_roughness", "coatAffectRoughness", Float), + PROPERTY_DEF("emission", "emission", Float), + PROPERTY_DEF("emission_color", "emissionColor", Float3), + PROPERTY_DEF("opacity", "opacity", Float3), + PROPERTY_DEF("thin_walled", "thinWalled", Bool), + PROPERTY_DEF("has_base_color_image", "hasBaseColorTex", Bool), + PROPERTY_DEF("base_color_image_offset", "baseColorTexOffset", Float2), + PROPERTY_DEF("base_color_image_scale", "baseColorTexScale", Float2), + PROPERTY_DEF("base_color_image_pivot", "baseColorTexPivot", Float2), + PROPERTY_DEF("base_color_image_rotation", "baseColorTexRotation", Float), + PROPERTY_DEF("has_specular_roughness_image", "hasSpecularRoughnessTex", Bool), + PROPERTY_DEF("specular_roughness_image_offset", "specularRoughnessTexOffset", Float2), + PROPERTY_DEF("specular_roughness_image_scale", "specularRoughnessTexScale", Float2), + PROPERTY_DEF("specular_roughness_image_pivot", "specularRoughnessTexPivot", Float2), + PROPERTY_DEF("specular_roughness_image_rotation", "specularRoughnessTexRotation", Float), + PROPERTY_DEF("has_emission_color_image", "hasEmissionColorTex", Bool), + PROPERTY_DEF("emission_color_image_offset", "emissionColorTexOffset", Float2), + PROPERTY_DEF("emission_color_image_scale", "emissionColorTexScale", Float2), + PROPERTY_DEF("emission_color_image_pivot", "emissionColorTexPivot", Float2), + PROPERTY_DEF("emission_color_image_rotation", "emissionColorTexRotation", Float), + PROPERTY_DEF("has_opacity_image", "hasOpacityTex", Bool), + PROPERTY_DEF("opacity_image_offset", "opacityTexOffset", Float2), + PROPERTY_DEF("opacity_image_scale", "opacityTexScale", Float2), + PROPERTY_DEF("opacity_image_pivot", "opacityTexPivot", Float2), + PROPERTY_DEF("opacity_image_rotation", "opacityTexRotation", Float), + PROPERTY_DEF("has_normal_image", "hasNormalTex", Bool), + PROPERTY_DEF("normal_image_offset", "normalTexOffset", Float2), + PROPERTY_DEF("normal_image_scale", "normalTexScale", Float2), + PROPERTY_DEF("normal_image_pivot", "normalTexPivot", Float2), + PROPERTY_DEF("normal_image_rotation", "normalTexRotation", Float) +}; +// clang-format on + +// Textures used by the built-in Standard Surface material type. +vector MaterialBase::StandardSurfaceTextures = { + "base_color_image", + "specular_roughness_image", + "opacity_image", + "normal_image", +}; + +// Default values for textures used in Standard Surface material type. +vector StandardSurfaceDefaultTextures = { + { "base_color_image", false }, + { "specular_roughness_image", true }, + { "opacity_image", true }, + { "normal_image", true }, +}; + +// Default values for Standard Surface properties. +// NOTE: These must align with the StandardSurfaceUniforms array above. +vector StandardSurfaceDefaultProperties = { + 0.8f, // base + vec3(1.0f, 1.0f, 1.0f), // base_color + 0.0f, // diffuse_roughness + 0.0f, // metalness + 1.0f, // specular + vec3(1.0f, 1.0f, 1.0f), // specular_color + 0.2f, // specular_roughness + 1.5f, // specular_IOR + 0.0f, // specular_anisotropy + 0.0f, // specular_rotation + 0.0f, // transmission + vec3(1.0f, 1.0f, 1.0f), // transmission_color + 0.0f, // subsurface + vec3(1.0f, 1.0f, 1.0f), // subsurface_color + vec3(1.0f, 1.0f, 1.0f), // subsurface_radius + 1.0f, // subsurface_scale + 0.0f, // subsurface_anisotropy + 0.0f, // sheen + vec3(1.0f, 1.0f, 1.0f), // sheen_color + 0.3f, // sheen_roughness + 0.0f, // coat + vec3(1.0f, 1.0f, 1.0f), // coat_color + 0.1f, // coat_roughness + 0.0f, // coat_anisotropy + 0.0f, // coat_rotation + 1.5f, // coat_IOR + 0.0f, // coat_affect_roughness + 0.0f, // coat_affect_color + 0.0f, // emission + vec3(1.0f, 1.0f, 1.0f), // emission_color + vec3(1.0f, 1.0f, 1.0f), // opacity + false, // thin_walled + false, // has_base_color_image + vec2(0.0f, 0.0f), // base_color_image_offset + vec2(1.0f, 1.0f), // base_color_image_scale + vec2(0.0f, 0.0f), // base_color_image_pivot + 0.0f, // base_color_image_rotation + false, // has_specular_roughness_image + vec2(0.0f, 0.0f), // specular_roughness_image_offset + vec2(1.0f, 1.0f), // specular_roughness_image_scale + vec2(0.0f, 0.0f), // specular_roughness_image_pivot + 0.0f, // specular_roughness_image_rotation + false, // has_emission_color_image + vec2(0.0f, 0.0f), // emission_color_image_offset + vec2(1.0f, 1.0f), // emission_color_image_scale + vec2(0.0f, 0.0f), // emission_color_image_pivot + 0.0f, // emission_color_image_rotation + false, // has_opacity_image + vec2(0.0f, 0.0f), // opacity_image_offset + vec2(1.0f, 1.0f), // opacity_image_scale + vec2(0.0f, 0.0f), // opacity_image_pivot + 0.0f, // opacity_image_rotation + false, // has_normal_image + vec2(0.0f, 0.0f), // normal_image_offset + vec2(1.0f, 1.0f), // normal_image_scale + vec2(0.0f, 0.0f), // normal_image_pivot + 0.0f, // normal_image_rotation +}; + +MaterialDefaultValues MaterialBase::StandardSurfaceDefaults( + StandardSurfaceUniforms, StandardSurfaceDefaultProperties, StandardSurfaceDefaultTextures); + +MaterialBase::MaterialBase(MaterialShaderPtr pShader, MaterialDefinitionPtr pDef) : + FixedValues(propertySet()), + _pDef(pDef), + _pShader(pShader), + _uniformBuffer(pDef->defaults().propertyDefinitions, pDef->defaults().properties) { - // Get print offsets into struct for debugging purposes. -#if 0 - int id = 0; - AU_INFO("Offset %02d - base:%d", id++, offsetof(MaterialData, base)); - AU_INFO("Offset %02d - baseColor:%d", id++, offsetof(MaterialData, baseColor)); - AU_INFO("Offset %02d - diffuseRoughness:%d", id++, offsetof(MaterialData, diffuseRoughness)); - AU_INFO("Offset %02d - metalness:%d", id++, offsetof(MaterialData, metalness)); - AU_INFO("Offset %02d - specular:%d", id++, offsetof(MaterialData, specular)); - AU_INFO("Offset %02d - specularColor:%d", id++, offsetof(MaterialData, specularColor)); - AU_INFO("Offset %02d - specularRoughness:%d", id++, offsetof(MaterialData, specularRoughness)); - AU_INFO("Offset %02d - specularIOR:%d", id++, offsetof(MaterialData, specularIOR)); - AU_INFO("Offset %02d - specularAnisotropy:%d", id++, offsetof(MaterialData, specularAnisotropy)); - AU_INFO("Offset %02d - specularRotation:%d", id++, offsetof(MaterialData, specularRotation)); - AU_INFO("Offset %02d - transmission:%d", id++, offsetof(MaterialData, transmission)); - AU_INFO("Offset %02d - transmissionColor:%d", id++, offsetof(MaterialData, transmissionColor)); - AU_INFO("Offset %02d - subsurface:%d", id++, offsetof(MaterialData, subsurface)); - AU_INFO("Offset %02d - subsurfaceColor:%d", id++, offsetof(MaterialData, subsurfaceColor)); - AU_INFO("Offset %02d - subsurfaceRadius:%d", id++, offsetof(MaterialData, subsurfaceRadius)); - AU_INFO("Offset %02d - subsurfaceScale:%d", id++, offsetof(MaterialData, subsurfaceScale)); - AU_INFO("Offset %02d - subsurfaceAnisotropy:%d", id++, offsetof(MaterialData, subsurfaceAnisotropy)); - AU_INFO("Offset %02d - sheen:%d", id++, offsetof(MaterialData, sheen)); - AU_INFO("Offset %02d - sheenColor:%d", id++, offsetof(MaterialData, sheenColor)); - AU_INFO("Offset %02d - sheenRoughness:%d", id++, offsetof(MaterialData, sheenRoughness)); - AU_INFO("Offset %02d - coat:%d", id++, offsetof(MaterialData, coat)); - AU_INFO("Offset %02d - coatColor:%d", id++, offsetof(MaterialData, coatColor)); - AU_INFO("Offset %02d - coatRoughness:%d", id++, offsetof(MaterialData, coatRoughness)); - AU_INFO("Offset %02d - coatAnisotropy:%d", id++, offsetof(MaterialData, coatAnisotropy)); - AU_INFO("Offset %02d - coatRotation:%d", id++, offsetof(MaterialData, coatRotation)); - AU_INFO("Offset %02d - coatIOR:%d", id++, offsetof(MaterialData, coatIOR)); - AU_INFO("Offset %02d - coatAffectColor:%d", id++, offsetof(MaterialData, coatAffectColor)); - AU_INFO( - "Offset %02d - coatAffectRoughness:%d", id++, offsetof(MaterialData, coatAffectRoughness)); - AU_INFO("Offset %02d - _padding4:%d", id++, offsetof(MaterialData, _padding4)); - AU_INFO("Offset %02d - opacity:%d", id++, offsetof(MaterialData, opacity)); - AU_INFO("Offset %02d - thinWalled:%d", id++, offsetof(MaterialData, thinWalled)); - AU_INFO("Offset %02d - hasBaseColorTex:%d", id++, offsetof(MaterialData, hasBaseColorTex)); - AU_INFO("Offset %02d - baseColorTexTransform:%d", id++, offsetof(MaterialData, baseColorTexTransform)); - AU_INFO("Offset %02d - hasSpecularRoughnessTex:%d", id++, offsetof(MaterialData, hasSpecularRoughnessTex)); - AU_INFO("Offset %02d - specularRoughnessTexTransform:%d", id++, offsetof(MaterialData, specularRoughnessTexTransform)); - AU_INFO("Offset %02d - hasOpacityTex:%d", id++, offsetof(MaterialData, hasOpacityTex)); - AU_INFO("Offset %02d - opacityTexTransform:%d", id++, offsetof(MaterialData, opacityTexTransform)); - AU_INFO("Offset %02d - hasNormalTex:%d", id++, offsetof(MaterialData, hasNormalTex)); - AU_INFO("Offset %02d - normalTexTransform:%d", id++, offsetof(MaterialData, normalTexTransform)); - AU_INFO("Offset %02d - isOpaque:%d", id++, offsetof(MaterialData, isOpaque)); -#endif - - // Update the GPU struct from the values map. - data.base = _values.asFloat("base"); - data.baseColor = _values.asFloat3("base_color"); - data.diffuseRoughness = _values.asFloat("diffuse_roughness"); - data.metalness = _values.asFloat("metalness"); - data.specular = _values.asFloat("specular"); - data.specularColor = _values.asFloat3("specular_color"); - data.specularRoughness = _values.asFloat("specular_roughness"); - data.specularIOR = _values.asFloat("specular_IOR"); - data.specularAnisotropy = _values.asFloat("specular_anisotropy"); - data.specularRotation = _values.asFloat("specular_rotation"); - data.transmission = _values.asFloat("transmission"); - data.transmissionColor = _values.asFloat3("transmission_color"); - data.subsurface = _values.asFloat("subsurface"); - data.subsurfaceColor = _values.asFloat3("subsurface_color"); - data.subsurfaceRadius = _values.asFloat3("subsurface_radius"); - data.subsurfaceScale = _values.asFloat("subsurface_scale"); - data.subsurfaceAnisotropy = _values.asFloat("subsurface_anisotropy"); - data.sheen = _values.asFloat("sheen"); - data.sheenColor = _values.asFloat3("sheen_color"); - data.sheenRoughness = _values.asFloat("sheen_roughness"); - data.coat = _values.asFloat("coat"); - data.coatColor = _values.asFloat3("coat_color"); - data.coatRoughness = _values.asFloat("coat_roughness"); - data.coatAnisotropy = _values.asFloat("coat_anisotropy"); - data.coatRotation = _values.asFloat("coat_rotation"); - data.coatIOR = _values.asFloat("coat_IOR"); - data.coatAffectColor = _values.asFloat("coat_affect_color"); - data.coatAffectRoughness = _values.asFloat("coat_affect_roughness"); - data.opacity = _values.asFloat3("opacity"); - data.thinWalled = _values.asBoolean("thin_walled") ? 1 : 0; - data.hasBaseColorTex = _values.asImage("base_color_image") ? 1 : 0; - data.baseColorTexTransform.offset = _values.asFloat2("base_color_image_offset"); - data.baseColorTexTransform.scale = _values.asFloat2("base_color_image_scale"); - data.baseColorTexTransform.pivot = _values.asFloat2("base_color_image_pivot"); - data.baseColorTexTransform.rotation = _values.asFloat("base_color_image_rotation"); - data.hasSpecularRoughnessTex = _values.asImage("specular_roughness_image") ? 1 : 0; - data.specularRoughnessTexTransform.offset = _values.asFloat2("specular_roughness_image_offset"); - data.specularRoughnessTexTransform.scale = _values.asFloat2("specular_roughness_image_scale"); - data.specularRoughnessTexTransform.pivot = _values.asFloat2("specular_roughness_image_pivot"); - data.specularRoughnessTexTransform.rotation = - _values.asFloat("specular_roughness_image_rotation"); - data.hasOpacityTex = _values.asImage("opacity_image") ? 1 : 0; - data.opacityTexTransform.offset = _values.asFloat2("opacity_image_offset"); - data.opacityTexTransform.scale = _values.asFloat2("opacity_image_scale"); - data.opacityTexTransform.pivot = _values.asFloat2("opacity_image_pivot"); - data.opacityTexTransform.rotation = _values.asFloat("opacity_image_rotation"); - data.hasNormalTex = _values.asImage("normal_image") ? 1 : 0; - data.normalTexTransform.offset = _values.asFloat2("normal_image_offset"); - data.normalTexTransform.scale = _values.asFloat2("normal_image_scale"); - data.normalTexTransform.pivot = _values.asFloat2("normal_image_pivot"); - data.normalTexTransform.rotation = _values.asFloat("normal_image_rotation"); - - // Record whether the material is opaque with its current values. - data.isOpaque = computeIsOpaque(); } -MaterialBase::MaterialBase() : FixedValues(propertySet()) {} - -bool MaterialBase::computeIsOpaque() const +void MaterialBase::updateBuiltInMaterial(MaterialBase& mtl) { - // A material is considered opaque if all the opacity components are 1.0, there is no opacity - // image, and transmission is zero. - // TODO: This should also consider whether the material type has overridden opacity or - // transmission, likely with a shader graph attached to that input. + // A built-in material is considered opaque if all the opacity components are 1.0, there is no + // opacity image, and transmission is zero. static vec3 kOpaque(1.0f); - vec3 opacity = _values.asFloat3("opacity"); - bool hasOpacityImage = _values.asImage("opacity_image") ? 1 : 0; - float transmission = _values.asFloat("transmission"); + vec3 opacity = mtl.uniformBuffer().get("opacity"); + bool hasOpacityImage = mtl._values.asImage("opacity_image") ? 1 : 0; + float transmission = mtl.uniformBuffer().get("transmission"); + mtl.setIsOpaque(opacity == kOpaque && !hasOpacityImage && transmission == 0.0f); - return opacity == kOpaque && !hasOpacityImage && transmission == 0.0f; + // Set the image flags used by built-in materials. + mtl.uniformBuffer().set( + "has_base_color_image", mtl._values.asImage("base_color_image") ? true : false); + mtl.uniformBuffer().set("has_specular_roughness_image", + mtl._values.asImage("specular_roughness_image") ? true : false); + mtl.uniformBuffer().set( + "has_emission_color_image", mtl._values.asImage("emission_color_image") ? true : false); + mtl.uniformBuffer().set( + "has_opacity_image", mtl._values.asImage("opacity_image") ? true : false); + mtl.uniformBuffer().set("has_normal_image", mtl._values.asImage("normal_image") ? true : false); } END_AURORA diff --git a/Libraries/Aurora/Source/MaterialBase.h b/Libraries/Aurora/Source/MaterialBase.h index 98a4c51..13ed000 100644 --- a/Libraries/Aurora/Source/MaterialBase.h +++ b/Libraries/Aurora/Source/MaterialBase.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,128 +13,123 @@ // limitations under the License. #pragma once +#include "MaterialDefinition.h" #include "Properties.h" +#include "UniformBuffer.h" BEGIN_AURORA -// Texture UV transform. -struct TextureTransform +// A base class for implementations of IMaterial. +class MaterialBase : public IMaterial, public FixedValues { - vec2 pivot; - vec2 scale; - vec2 offset; - float rotation; -}; +public: + /*** Lifetime Management ***/ -// Representation of the shader source for a material type. -struct MaterialTypeSource -{ - MaterialTypeSource(const string& typeName = "", const string& setupSource = "", - const string& bsdfSource = "") : - name(typeName), setup(setupSource), bsdf(bsdfSource) + MaterialBase(MaterialShaderPtr pShader, MaterialDefinitionPtr pDef); + + /*** IMaterial Functions ***/ + + // Gets the material shader for this material. + MaterialShaderPtr shader() const { return _pShader; } + + IValues& values() override { return *this; } + + // Override default setter. + void setBoolean(const string& name, bool value) override { + _uniformBuffer.set(name, value); + _bIsDirty = true; } - // Compare the source itself. - bool compareSource(const MaterialTypeSource& other) const + // Override default setter. + void setInt(const string& name, int value) override { - return (setup.compare(other.setup) == 0 && bsdf.compare(other.bsdf) == 0); + _uniformBuffer.set(name, value); + _bIsDirty = true; } - // Compare the name. - bool compare(const MaterialTypeSource& other) const { return (name.compare(other.name) == 0); } + // Override default setter. + void setFloat(const string& name, float value) override + { + _uniformBuffer.set(name, value); + _bIsDirty = true; + } - // Reset the contents of the - void reset() + // Override default setter. + void setFloat2(const string& name, const float* value) override { - name = ""; - setup = ""; - bsdf = ""; + _uniformBuffer.set(name, glm::make_vec2(value)); + _bIsDirty = true; } - // Is there actually source associated with this material type? - bool empty() const { return name.empty(); } + // Override default setter. + void setFloat3(const string& name, const float* value) override + { + _uniformBuffer.set(name, glm::make_vec3(value)); + _bIsDirty = true; + } - // Unique name. - string name; + // Override default setter. + void setMatrix(const string& name, const float* value) override + { + _uniformBuffer.set(name, glm::make_mat4(value)); + _bIsDirty = true; + } - // Shader source for material setup. - string setup; + // Override default setter. + // TODO: Correctly map arbritrary texture names. + void setImage(const string& name, const IImagePtr& value) override + { + FixedValues::setImage(name, value); + } - // Optional shader source for bsdf. - string bsdf; -}; + // Override default setter. + // TODO: Correctly map arbritrary texture names. + void setSampler(const string& name, const ISamplerPtr& value) override + { + FixedValues::setSampler(name, value); + } -// A base class for implementations of IMaterial. -class MaterialBase : public IMaterial, public FixedValues -{ -public: - /*** Lifetime Management ***/ + // Override default clear function. + void clearValue(const string& name) override + { + if (_uniformBuffer.contains(name)) + { + _uniformBuffer.reset(name); + _bIsDirty = true; + return; + } + FixedValues::clearValue(name); + } - MaterialBase(); + // Is this material opaque? + bool isOpaque() const { return _isOpaque; } - /*** IMaterial Functions ***/ + // Set the opaque flag. + void setIsOpaque(bool val) { _isOpaque = val; } - IValues& values() override { return *this; } + // Get the uniform buffer for this material. + UniformBuffer& uniformBuffer() { return _uniformBuffer; } + const UniformBuffer& uniformBuffer() const { return _uniformBuffer; } + + // Hard-coded Standard Surface properties, textures and defaults used by built-in materials. + static UniformBufferDefinition StandardSurfaceUniforms; + static vector StandardSurfaceTextures; + static MaterialDefaultValues StandardSurfaceDefaults; + + // Update function for built-in materials. + static void updateBuiltInMaterial(MaterialBase& mtl); + + shared_ptr definition() { return _pDef; } + const shared_ptr definition() const { return _pDef; } protected: - // The CPU representation of material constant buffer. The layout of this struct must match the - // MaterialConstants struct in the shader. - // NOTE: Members should be kept in the same order as the Standard Surface reference document. - // https://github.com/Autodesk/standard-surface/blob/master/reference/standard_surface.mtlx - // Because it is a CPU representation of a GPU constant buffer, it must be padded to conform to - // DirectX packing conventions: - // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules - struct MaterialData - { - float base; - vec3 baseColor; - float diffuseRoughness; - float metalness; - float specular; - float _padding1; - vec3 specularColor; - float specularRoughness; - float specularIOR; - float specularAnisotropy; - float specularRotation; - float transmission; - vec3 transmissionColor; - float subsurface; - vec3 subsurfaceColor; - float _padding2; - vec3 subsurfaceRadius; - float subsurfaceScale; - float subsurfaceAnisotropy; - float sheen; - vec2 _padding3; - vec3 sheenColor; - float sheenRoughness; - float coat; - vec3 coatColor; - float coatRoughness; - float coatAnisotropy; - float coatRotation; - float coatIOR; - float coatAffectColor; - float coatAffectRoughness; - vec2 _padding4; - vec3 opacity; - int thinWalled; - int hasBaseColorTex; - vec3 _padding5; - TextureTransform baseColorTexTransform; - int hasSpecularRoughnessTex; - TextureTransform specularRoughnessTexTransform; - int hasOpacityTex; - TextureTransform opacityTexTransform; - int hasNormalTex; - TextureTransform normalTexTransform; - int isOpaque; - }; - - void updateGPUStruct(MaterialData& data); - bool computeIsOpaque() const; + bool _isOpaque = true; + +private: + MaterialDefinitionPtr _pDef; + MaterialShaderPtr _pShader; + UniformBuffer _uniformBuffer; }; -END_AURORA \ No newline at end of file +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialDefinition.cpp b/Libraries/Aurora/Source/MaterialDefinition.cpp new file mode 100644 index 0000000..cb4c509 --- /dev/null +++ b/Libraries/Aurora/Source/MaterialDefinition.cpp @@ -0,0 +1,28 @@ +// +// Copyright 2023 by Autodesk, Inc. All rights reserved. +// +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. +// +#include "pch.h" + +#include "MaterialDefinition.h" + +BEGIN_AURORA + +MaterialDefaultValues::MaterialDefaultValues(const UniformBufferDefinition& propertyDefs, + const vector& defaultProps, const vector& defaultTxt) : + propertyDefinitions(propertyDefs), properties(defaultProps), textures(defaultTxt) +{ + AU_ASSERT( + defaultProps.size() == propertyDefs.size(), "Default properties do not match definition"); + for (int i = 0; i < defaultTxt.size(); i++) + { + textureNames.push_back(defaultTxt[i].name); + } +} + +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialDefinition.h b/Libraries/Aurora/Source/MaterialDefinition.h new file mode 100644 index 0000000..37b6da3 --- /dev/null +++ b/Libraries/Aurora/Source/MaterialDefinition.h @@ -0,0 +1,99 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "MaterialShader.h" + +BEGIN_AURORA + +class MaterialBase; + +// The default values for the properties and textures contained in a material. +struct MaterialDefaultValues +{ + MaterialDefaultValues() {} + MaterialDefaultValues(const UniformBufferDefinition& propertyDefs, + const vector& defaultProps, const vector& defaultTxt); + + // The names of the textures defined for this material. + vector textureNames; + + // The definitions of the properties defined for this material. + UniformBufferDefinition propertyDefinitions; + + // Default values for properties, must match order and size of propertyDefinitions. + vector properties; + + // Default values (include texture filename and sampler properties) for textures, must match + // match order and size of textureNames. + vector textures; +}; + +// The definition of a material. All the data need to create a material: the shader source code, a +// set of default values, and an update callback function. +class MaterialDefinition +{ +public: + MaterialDefinition(const MaterialShaderSource& source, const MaterialDefaultValues& defaults, + function updateFunc, bool isAlwaysOpaque) : + _source(source), + _defaults(defaults), + _updateFunc(updateFunc), + _isAlwaysOpaque(isAlwaysOpaque) + { + } + MaterialDefinition() {} + + // Create a shader definition from this material definition. + // A shader definition is a subset of the material definition. + // Material definitions with the same property definitions, but different default values, will + // produce the same shader definition. + void getShaderDefinition(MaterialShaderDefinition& defOut) const + { + defOut.source = _source; + defOut.textureNames = defaults().textureNames; + defOut.propertyDefinitions = defaults().propertyDefinitions; + defOut.isAlwaysOpaque = _isAlwaysOpaque; + } + + // Gets the default values. + const MaterialDefaultValues& defaults() const { return _defaults; } + + // Gets the source code. + const MaterialShaderSource& source() const { return _source; } + + // Gets the update the function, that is invoked when the material is updated. + function updateFunction() const { return _updateFunc; } + + // Returns whether the material is always opaque, regardless of the property values. + bool isAlwaysOpaque() const { return _isAlwaysOpaque; } + +private: + // The source code (and unique shader ID) for this material. + MaterialShaderSource _source; + + // The default values for the material. + MaterialDefaultValues _defaults; + + // The update function, called when material is changed. + function _updateFunc; + + // Whether this material is guaranteed to be always opaque, regardless of the property values. + bool _isAlwaysOpaque; +}; + +// Shared point to material definition. +using MaterialDefinitionPtr = shared_ptr; + +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialShader.cpp b/Libraries/Aurora/Source/MaterialShader.cpp new file mode 100644 index 0000000..e88ff5a --- /dev/null +++ b/Libraries/Aurora/Source/MaterialShader.cpp @@ -0,0 +1,263 @@ +// +// Copyright 2023 by Autodesk, Inc. All rights reserved. +// +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. +// +#include "pch.h" + +#include "MaterialShader.h" + +BEGIN_AURORA + +MaterialShader::MaterialShader(MaterialShaderLibrary* pShaderLibrary, int libraryIndex, + const MaterialShaderDefinition& def, const vector entryPoints) : + _pShaderLibrary(pShaderLibrary), + _libraryIndex(libraryIndex), + _entryPointsTypes(entryPoints), + _def(def) +{ + + // Initialize ref. counts to zero. + for (int i = 0; i < entryPoints.size(); i++) + { + _entryPointNameLookup[entryPoints[i]] = i; + _entryPoints.push_back({ entryPoints[i], 0 }); + } +} + +MaterialShader::~MaterialShader() +{ + // If this material shader is valid, when its destroyed (which will happen when no material + // holds a shared pointer to it) remove it source code from the library. + if (isValid()) + { + _pShaderLibrary->destructionRequired(*this); + } +} + +void MaterialShader::incrementRefCount(const string& entryPoint) +{ + EntryPoint* pEP = getEntryPoint(entryPoint); + if (!pEP) + { + AU_ERROR("Unknown entry point %s", entryPoint.c_str()); + return; + } + pEP->refCount++; + if (_pShaderLibrary && pEP->refCount == 1) + _pShaderLibrary->compilationRequired(*this); +} + +void MaterialShader::decrementRefCount(const string& entryPoint) +{ + EntryPoint* pEP = getEntryPoint(entryPoint); + if (!pEP) + { + AU_ERROR("Unknown entry point %s", entryPoint.c_str()); + return; + } + AU_ASSERT(pEP->refCount > 0, "Invalid ref count"); + pEP->refCount--; + if (_pShaderLibrary && pEP->refCount == 0) + _pShaderLibrary->compilationRequired(*this); +} + +int MaterialShader::refCount(const string& entryPoint) const +{ + const EntryPoint* pEP = getEntryPoint(entryPoint); + if (!pEP) + { + AU_ERROR("Unknown entry point %s", entryPoint.c_str()); + return 0; + } + return pEP->refCount; +} + +MaterialShaderLibrary::~MaterialShaderLibrary() +{ + // Invalidate any remaining valid material types, to avoid zombies. + for (auto pWeakShaderPtr : _shaders) + { + MaterialShaderPtr pShader = pWeakShaderPtr.second.lock(); + if (pShader) + { + pShader->invalidate(); + } + } +} + +MaterialShaderPtr MaterialShaderLibrary::get(const string& id) +{ + return _shaders[id].lock(); +} + +MaterialShaderPtr MaterialShaderLibrary::get(int index) +{ + return _shaderState[index].first.lock(); +} + +vector MaterialShaderLibrary::getActiveShaderIDs() +{ + vector res; + for (auto hgIter = _shaders.begin(); hgIter != _shaders.end(); hgIter++) + { + // Weak pointer is stored in shader library, ensure it has not been deleted. + MaterialShaderPtr pShader = hgIter->second.lock(); + if (pShader) + { + res.push_back(pShader->id()); + } + } + return res; +} + +void MaterialShaderLibrary::destructionRequired(const MaterialShader& shader) +{ + // Push index into pending removal list, the actual remove only happens when library rebuilt. + int index = shader.libraryIndex(); + _shadersToRemove.insert(index); + + // Update state. + _shaderState[index].second = CompileState::PendingRemoval; +} + +void MaterialShaderLibrary::compilationRequired(const MaterialShader& shader) +{ + int index = shader.libraryIndex(); + // Push index in to vector, the actual remove only happens when library rebuilt. + _shadersToCompile.insert(index); + _shaderState[index].second = CompileState::PendingCompilation; +} + +MaterialShaderPtr MaterialShaderLibrary::acquire( + const MaterialShaderDefinition& def, const vector& entryPoints) +{ + // The shared pointer to material shader. + MaterialShaderPtr pShader; + + // Get the entry points (or the default entry points, if none provided) + const vector& ep = entryPoints.empty() ? _defaultEntryPoints : entryPoints; + + // Get the unique name from the source object. + string id = def.source.uniqueId; + + // First see a material shader already exists. + map>::iterator hgIter = _shaders.find(id); + if (hgIter != _shaders.end()) + { + // Weak pointer is stored in shader library, ensure it has not been deleted. + pShader = hgIter->second.lock(); + if (pShader) + { + // If the entry point exists, in debug mode do a string comparison to ensure the source + // also matches. + AU_ASSERT_DEBUG(def.compareSource(pShader->definition()), + "Source mis-match for material shader %s.", pShader->id().c_str()); + + // Ensure definitions match. + AU_ASSERT(def.compare(pShader->definition()), + "Definition mis-match for material shader %s.", pShader->id().c_str()); + + // Ensure the entry points match. + AU_ASSERT(pShader->entryPoints().size() == ep.size(), "Material entry points mismatch"); + + // Return the existing material shader. + return pShader; + } + } + + // Get a library index for new shader. + int libIndex; + if (!_indexFreeList.empty()) + { + // Use the free list to reuse an index from a destroyed shader, if not empty. + libIndex = _indexFreeList.back(); + _indexFreeList.pop_back(); + } + else + { + // Add a new state entry if nothing in free list. + libIndex = static_cast(_shaderState.size()); + _shaderState.push_back({ weak_ptr(), CompileState::Invalid }); + } + + // Create new material shader. + pShader = make_shared(this, libIndex, def, ep); + + // Fill in the shader state for the new shader. + auto weakPtr = weak_ptr(pShader); + _shaderState[libIndex].first = weakPtr; + _shaderState[libIndex].second = CompileState::Invalid; + + // Trigger compilation for the new shader. + compilationRequired(*pShader.get()); + + // Add weak reference to map. + _shaders[def.source.uniqueId] = weakPtr; + + // Return the new material shader. + return pShader; +} + +bool MaterialShaderLibrary::update(CompileShader compileFunction, DestroyShader destroyFunction) +{ + // Destroy all shaders pending removal. + for (auto iter = _shadersToRemove.begin(); iter != _shadersToRemove.end(); iter++) + { + // Run the provided callback function to destroy device-specific shader binary. + int idx = *iter; + destroyFunction(*iter); + + // Update the shader state, and add to free list. + _shaderState[idx] = { weak_ptr(), CompileState::Invalid }; + _indexFreeList.push_back(idx); + } + + // Clear pending removals. + _shadersToRemove.clear(); + + // Return value is true if there are shaders to compile. + bool shadersCompiled = !_shadersToCompile.empty(); + + // Compile all shaders pending compilation. + for (auto iter = _shadersToCompile.begin(); iter != _shadersToCompile.end(); iter++) + { + // Get the shader, ensure there is still valid shader for this index. + int idx = *iter; + auto pShader = _shaderState[idx].first.lock(); + + if (pShader) + { + // Run the device-specific compilation callback function and update state based on + // result. + // TODO: Better handling of shader errors, currently will assert before reaching this + // code on failure. + bool res = compileFunction(*pShader.get()); + _shaderState[idx].second = + res ? CompileState::CompiledSuccessfully : CompileState::CompilationFailed; + } + } + + // Clear pending compilations. + _shadersToCompile.clear(); + + // Return true if some shaders compiled. + return shadersCompiled; +} + +void MaterialShaderLibrary::forceRebuildAll() +{ + // Add all active shaders to compilation pending list. + for (int i = 0; i < _shaderState.size(); i++) + { + MaterialShaderPtr pShader = _shaderState[i].first.lock(); + if (pShader) + compilationRequired(*pShader.get()); + } +} + +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialShader.h b/Libraries/Aurora/Source/MaterialShader.h new file mode 100644 index 0000000..60eb10a --- /dev/null +++ b/Libraries/Aurora/Source/MaterialShader.h @@ -0,0 +1,285 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "Aurora/Foundation/Log.h" +#include "Properties.h" +#include "UniformBuffer.h" + +BEGIN_AURORA + +class MaterialShader; +class MaterialShaderLibrary; + +// Representation of the shader source for a material shader. Contains source code itself and a +// unique ID string. +struct MaterialShaderSource +{ + MaterialShaderSource(const string& id = "", const string& setupSource = "", + const string& definitionsSource = "", const string& bsdfSource = "") : + uniqueId(id), setup(setupSource), definitions(definitionsSource), bsdf(bsdfSource) + { + } + + // Compare the source itself. + bool compareSource(const MaterialShaderSource& other) const + { + return (setup.compare(other.setup) == 0 && bsdf.compare(other.bsdf) == 0 && + definitions.compare(other.definitions) == 0); + } + + // Compare the unique IDs. + bool compare(const MaterialShaderSource& other) const + { + return (uniqueId.compare(other.uniqueId) == 0); + } + + // Reset the contents to empty strings. + void reset() + { + uniqueId = ""; + setup = ""; + bsdf = ""; + definitions = ""; + } + + // Is there actually source associated with this material shader? + bool empty() const { return uniqueId.empty(); } + + // Unique name (assigning different source the same name will result in errors). + string uniqueId; + + // Shader source for material setup. + string setup; + + // Optional shader source for bsdf. + string bsdf; + + // Optional function definitions. + string definitions; +}; + +// The definitions of a material shader. +// This is a subset of the MaterialDefinition, containing just the source, the property definitions +// (not the values), and the texture names. +struct MaterialShaderDefinition +{ + // Shader source + MaterialShaderSource source; + // Definitions of properties for this shader (without the default values.) + UniformBufferDefinition propertyDefinitions; + // Names of the textures defined by this shader. + vector textureNames; + // Is this shader always opaque regardless of properties. + bool isAlwaysOpaque; + + bool compare(const MaterialShaderDefinition& other) const + { + if (!other.source.compare(source)) + return false; + if (textureNames.size() != other.textureNames.size()) + return false; + if (propertyDefinitions.size() != other.propertyDefinitions.size()) + return false; + if (isAlwaysOpaque != other.isAlwaysOpaque) + return false; + return true; + // TODO also compare contents of arrays. + } + bool compareSource(const MaterialShaderDefinition& other) const + { + if (!compare(other)) + return false; + if (!other.source.compareSource(source)) + return false; + return true; + } +}; + +/** + * \desc Class representing a material shader, which maps directly to a DirectX hit group and a hit + * shader in the HLSL shader library. Material types are managed by the MaterialShaderLibrary class, + * and deletes via shared pointer when no longer referenced by any materials. + */ +class MaterialShader +{ + friend class MaterialShaderLibrary; + +public: + /*** Types ***/ + + /*** Lifetime Management ***/ + + /** + * NOTE: Do not call ctor directly, instead create via MaterialShaderLibrary. + * \param pShaderLibrary The shader library this type is part of. + * \param libraryIndex The index for this type's shader code within the library object. + * \param def The definition of this shader. + * \param entryPoints The entry points for this shader. + */ + MaterialShader(MaterialShaderLibrary* pShaderLibrary, int libraryIndex, + const MaterialShaderDefinition& def, const vector entryPoints); + ~MaterialShader(); + + // Gets the index for this type's hit shader within the library object. + int libraryIndex() const { return _libraryIndex; } + + // Decrement the reference count for the given entry point. + void incrementRefCount(const string& entryPoint); + + // Decrement the reference count for the given entry point. + // Will trigger rebuild if the entry point reference count goes from zero to non-zero. + void decrementRefCount(const string& entryPoint); + + // Get the current reference count for this entry point. + int refCount(const string& entryPoint) const; + + // Does this entry point exist (true if the entry point has non-zero ref count.) + bool hasEntryPoint(const string& entryPoint) const { return refCount(entryPoint) > 0; } + + const vector& entryPoints() const { return _entryPointsTypes; } + + // Get the unique ID for the + const string& id() const { return _def.source.uniqueId; } + + // Get the definition of this shader. + const MaterialShaderDefinition& definition() const { return _def; } + +protected: + struct EntryPoint + { + string name; + int refCount = 0; + }; + + EntryPoint* getEntryPoint(const string& name) + { + auto iter = _entryPointNameLookup.find(name); + if (iter == _entryPointNameLookup.end()) + { + return nullptr; + } + return &_entryPoints[iter->second]; + } + const EntryPoint* getEntryPoint(const string& name) const + { + auto iter = _entryPointNameLookup.find(name); + if (iter == _entryPointNameLookup.end()) + { + return nullptr; + } + return &_entryPoints[iter->second]; + } + + // Invalidate this type, by setting its shader library to null pointer. + // Called by shader library in its destructor to avoid orphaned material types. + void invalidate() { _pShaderLibrary = nullptr; } + + // Is this type still valid? + bool isValid() { return _pShaderLibrary != nullptr; } + + vector _entryPoints; + map _entryPointNameLookup; + vector _entryPointsTypes; + + // The shader library this type is part of. + MaterialShaderLibrary* _pShaderLibrary; + + // The index for this type's shader code within the library. + int _libraryIndex; + + // Definition of this shader. + MaterialShaderDefinition _def; +}; + +// Shared point to material shader. +using MaterialShaderPtr = shared_ptr; + +// Library of material shaders that manages shader lifetime. +class MaterialShaderLibrary +{ + friend class MaterialShader; + +public: + // State of a shader in this library + enum CompileState + { + Invalid, // Shader is invalid, no source code associated with it. + CompiledSuccessfully, // Shader has valid source code and is compiled. + CompilationFailed, // Shader has valid source code but compilation failed. + PendingRemoval, // This shader is to be removed during next update call. + PendingCompilation // This shader is to be compiled during the next update call. + }; + + // Compilation callback function, called by platform specific code to actually compile ths + // shader on device. + using CompileShader = function; + + // Destruction callback function, called by platform specific code to release compiled shader + // binaries. By the time this is called the MaterialShader object is deleted so only index + // within library is passed. + using DestroyShader = function; + + // Ctor takes the default set of entry points for the shaders in library. + MaterialShaderLibrary(const vector& defaultEntryPoints = {}) : + _defaultEntryPoints(defaultEntryPoints) + { + } + + ~MaterialShaderLibrary(); + + // Get IDs for all the active shaders in the library. + vector getActiveShaderIDs(); + + // Get shared pointer to the shader with the given ID, or nullptr if none. + MaterialShaderPtr get(const string& id); + + // Get shared pointer to the shader with the given libray index, or nullptr if none. + MaterialShaderPtr get(int index); + + // Acquire a shader for the provided definition and set of entry points. + // Will create a new one if none found with this definition's ID, otherwise will return a + // previously created shader. If the entry point array is empty the default entry points for + // this library are used. + MaterialShaderPtr acquire( + const MaterialShaderDefinition& def, const vector& entryPoints = {}); + + // Update the library and execute the provided compilation and destruction functions as + // required, for shared pending recompilation or destruction. Will return false if no shaders + // require compilation. Upon return from this function the lists of shaders pending compilation + // or destruction + bool update(CompileShader compileFunction, DestroyShader destroyFunction); + + // Force a rebuild of all the shaders in the library. + void forceRebuildAll(); + + // Returns true if there are shaders pending recompilation. + bool rebuildRequired() { return !_shadersToCompile.empty(); } + +protected: + void destructionRequired(const MaterialShader& shader); + void compilationRequired(const MaterialShader& shader); + + using WeakShaderPtr = weak_ptr; + map _shaders; + vector> _shaderState; + set _shadersToRemove; + set _shadersToCompile; + + vector _defaultEntryPoints; + + list _indexFreeList; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp index 4b313fb..16940f4 100644 --- a/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp +++ b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.cpp @@ -1,16 +1,12 @@ -// Copyright 2022 Autodesk, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Copyright 2023 by Autodesk, Inc. All rights reserved. // -// http://www.apache.org/licenses/LICENSE-2.0 +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. #include "pch.h" #include @@ -67,13 +63,14 @@ class BSDFCodeGeneratorShader : public MaterialX::Shader // TODO: These are just educated guesses. How do we work out which attributes are used? auto tc0 = vd->add(MaterialX::TypeDesc::get("vector2"), MaterialX::HW::T_TEXCOORD + "_0"); tc0->setVariable("texCoord"); + auto no = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_NORMAL_OBJECT); + no->setVariable("objectNormal"); auto nw = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_NORMAL_WORLD); nw->setVariable("normal"); auto tw = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_TANGENT_WORLD); tw->setVariable("tangent"); - // TODO: Should absolutely not just map world position to object position. auto po = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_POSITION_OBJECT); - po->setVariable("position"); + po->setVariable("objectPosition"); auto pw = vd->add(MaterialX::TypeDesc::get("vector3"), MaterialX::HW::T_POSITION_WORLD); pw->setVariable("position"); } @@ -217,6 +214,13 @@ class BSDFShaderGenerator : public MaterialX::GlslShaderGenerator // Clear all the temp variables. void clearTempVariables() { _tempVariables.clear(); } + // Clear the generated definitions and includes. + void clearGeneratedDefinitions() + { + _pGeneratedDefinitions->clear(); + _pGeneratedIncludes->clear(); + } + protected: map _tempVariables; unique_ptr> _pGeneratedDefinitions; @@ -224,7 +228,7 @@ class BSDFShaderGenerator : public MaterialX::GlslShaderGenerator string _mtlxLibPath; // Map of include paths. - // TOOO: How to know which of these we should have? + // TODO: How to know which of these we should have? map _includeFilePaths = { { "lib/$fileTransformUv", "stdlib/genglsl/lib/mx_transform_uv.glsl" } }; }; @@ -302,26 +306,87 @@ BSDFCodeGenerator::BSDFCodeGenerator( MaterialX::FileSearchPath searchPath; searchPath.append(_mtlxLibPath.c_str()); _pGeneratorContext->registerSourceCodeSearchPath(searchPath); +} + +// Convert path name (e.g. foo/bar/value) to a C-like variable name (e.g. foo_bar_value). +string pathToVariableName(const string& propPath) +{ + + string invalidChars = " .\\/:~()*+$#@"; + string varName; + + varName.reserve(propPath.length()); + + for (size_t i = 0; i < propPath.length(); i++) + { + bool valid = true; + for (size_t j = 0; j < invalidChars.length(); j++) + { + if (propPath[i] == invalidChars[j]) + { + valid = false; + break; + } + } + if (valid) + varName += propPath[i]; + else + varName += "_"; + } + + return varName; +} + +// Process path and remove first (and, optionally, last) section. +string processPath(const string& propPath, bool removeLastSection) +{ + string res; + size_t firstSlash = propPath.find("/"); + if (firstSlash == string::npos) + res = propPath; + else + res = propPath.substr(firstSlash + 1); - _defaultOutputParamMapping = { { "base", "base" }, { "base_color", "baseColor" }, - { "diffuse_roughness", "diffuseRoughness" }, { "specular_roughness", "specularRoughness" }, - { "specular", "specular" }, { "sheen", "sheen" }, { "sheen_roughness", "sheenRoughness" }, - { "sheen_color", "sheenColor" }, { "specular_color", "specularColor" }, - { "metalness", "metalness" } }; - _defaultOutputMapper = [&](const string& name, Aurora::IValues::Type /*type*/, - const string& /*topLevelShaderName*/) { - auto iter = _defaultOutputParamMapping.find(name); - if (iter != _defaultOutputParamMapping.end()) - return iter->second; - return string(""); - }; + if (removeLastSection) + { + size_t lastSlash = res.rfind("/"); + if (lastSlash != string::npos) + res = res.substr(0, lastSlash); + } + + return res; +} + +// Convert property type to GLSL string type name. +string getGLSLStringFromType(PropertyValue::Type type) +{ + switch (type) + { + case PropertyValue::Type::Bool: + return "int"; + case PropertyValue::Type::Int: + return "int"; + case PropertyValue::Type::Float: + return "float"; + case PropertyValue::Type::Float2: + return "vec2"; + case PropertyValue::Type::Float3: + return "vec3"; + case PropertyValue::Type::Float4: + return "vec4"; + case PropertyValue::Type::Matrix4: + return "mat4"; + default: + AU_FAIL("Unsupported type for uniform block:%x", type); + return 0; + } } // Empty dtor in C++ file, to avoid issues with forward declaring MaterialX types. BSDFCodeGenerator::~BSDFCodeGenerator() {} void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, - shared_ptr pBSDFGenShader, const string& outputVariable, + shared_ptr pBSDFGenShader, const string& bsdfInputVariable, string* pSourceOut) { // Do we need local scope to avoid name collisions? @@ -347,39 +412,55 @@ void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, { // If this a connection from the high-level shader graph, its a material input. auto inputName = input->getFullName(); + string path = input->getPath(); - IValues::Type auroraInputType = - glslTypeToAuroraType(pBSDFGenShader->syntax()->getTypeName(input->getType())); + // Special case for inputs that are units, represent as built-in "distance_unit" parameter. + // TODO: Can we infer this from the mtlx API? If not we should add this, to avoid + // hard-coded string searches. + bool isUnit = false; + if (inputName.find("unit_unit_to") != string::npos) + { + _hasUnits = true; + isUnit = true; + if (_builtInIndexLookup.find("distance_unit") == _builtInIndexLookup.end()) + { + _builtInIndexLookup["distance_unit"] = (int)_builtIns.size(); + _builtIns = { { "int", "distance_unit" } }; + } + } - string mappedName = - _currentInputParameterMapper(inputName, auroraInputType, _topLevelShaderNodeName); - pSourceOut->append("\t// Graph input " + inputName + "\n"); - if (mappedName.empty()) + // Process the graph input. + pSourceOut->append("\t// Graph input " + path + "\n"); + auto paramIter = _parameterIndexLookup.find(path); + if (!isUnit && paramIter == _parameterIndexLookup.end()) { // MtlX code gen requires a scope, but we comment it out so variable scope not effected. pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) .beginScope(MaterialX::Syntax::CURLY_BRACKETS); - // If this is not one of the material inputs from _materialInputs emit with hard-coded - // default value. + // If this is not one of the parameters to the setup function write the default value as + // GLSL. _pGenerator->emitVariableDeclaration(input, "", *_pGeneratorContext, ps, true); // End the scope for declaration. pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) .endScope(MaterialX::Syntax::CURLY_BRACKETS); + + // Append source for this input. pSourceOut->append("\t" + pBSDFGenShader->getNewSource() + ";\n"); } else { + // MtlX code gen requires a scope, but we comment it out so variable scope not effected. pBSDFGenShader->getStage(MaterialX::Stage::PIXEL).addValue("//"); pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) .beginScope(MaterialX::Syntax::CURLY_BRACKETS); - // If this is one of the material inputs from _materialInputs, set it using the function - // parameter for that input. + // If this is one of the parameters to the setup function don't write the default value + // in GLSL. _pGenerator->emitVariableDeclaration(input, "", *_pGeneratorContext, ps, false); // End the declaration scope. @@ -387,15 +468,24 @@ void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, pBSDFGenShader->getStage(MaterialX::Stage::PIXEL) .endScope(MaterialX::Syntax::CURLY_BRACKETS); + // Append declaration to source. pSourceOut->append("\t" + pBSDFGenShader->getNewSource()); - pSourceOut->append(" = " + mappedName + _materialInputSuffix + ";\n"); - // Mark this input as active. - if (_activeInputs.find(mappedName) == _activeInputs.end()) - _activeInputNames.push_back(mappedName); - - _activeInputs[mappedName] = make_pair( - pBSDFGenShader->syntax()->getTypeName(input->getType()), input->getValue()); + if (isUnit) + { + // the unit parameter is always first built-in. + pSourceOut->append(" = " + _builtIns[0].second + ";\n"); + } + else + { + // Append the parameter name. + auto& param = _parameters[paramIter->second]; + if (param.paramType == ParameterType::MaterialProperty) + pSourceOut->append(" = material." + param.variableName + ";\n"); + else if (param.paramType == ParameterType::Texture) + pSourceOut->append(" = " + param.variableName + "_image_parameter;\n"); + param.glslType = pBSDFGenShader->syntax()->getTypeName(input->getType()); + } } // Set the variable name that will used to generate the output.result. @@ -407,7 +497,7 @@ void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, auto node = connection->getNode(); string implName = node->getImplementation().getName(); - // Create a temp variable to store the node ouput, if this node has already been generated + // Create a temp variable to store the node output, if this node has already been generated // then this will be reused without generating again. // TODO: Handle multiple outputs. string outputTempVariable = @@ -516,7 +606,6 @@ void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, pSourceOut->append("\t" + pBSDFGenShader->getNewSource()); - // pSourceOut->append("\t" + outputTempVariable); pSourceOut->append(" = " + variableName + ";// Output connection\n"); } @@ -527,9 +616,9 @@ void BSDFCodeGenerator::processInput(MaterialX::ShaderInput* input, } // Copy this output to the provided - if (outputVariable.size()) + if (bsdfInputVariable.size()) { - pSourceOut->append("\t" + outputVariable); + pSourceOut->append("\t" + bsdfInputVariable); pSourceOut->append(" = " + variableName + ";// Output connection\n"); } @@ -545,6 +634,9 @@ void BSDFCodeGenerator::clearDefinitions() // Clear the definitions, which are accumulated after each generate call. _definitions.clear(); _definitionMap.clear(); + + // Clear the includes and defs in the shader generator. + _pGenerator->clearGeneratedDefinitions(); } int BSDFCodeGenerator::generateDefinitions(string* pResultOut) @@ -606,30 +698,85 @@ bool BSDFCodeGenerator::materialXValueToAuroraValue( return false; } -IValues::Type BSDFCodeGenerator::glslTypeToAuroraType(const string glslType) +bool BSDFCodeGenerator::materialXValueToAuroraPropertyValue( + PropertyValue* pValueOut, shared_ptr pMtlXValue) { - // Set the aurora type based on GLSL type string. + *pValueOut = PropertyValue(); + + string glslType = pMtlXValue->getTypeString(); + if (glslType.compare("color3") == 0) + { + MaterialX::Color3 valData = pMtlXValue->asA(); + *pValueOut = glm::vec3(valData[0], valData[1], valData[2]); + return true; + } + if (glslType.compare("vector2") == 0) + { + MaterialX::Vector2 valData = pMtlXValue->asA(); + *pValueOut = glm::vec2(valData[0], valData[1]); + return true; + } + if (glslType.compare("vector3") == 0) + { + MaterialX::Vector3 valData = pMtlXValue->asA(); + *pValueOut = glm::vec3(valData[0], valData[1], valData[2]); + return true; + } + if (glslType.compare("vector4") == 0) + { + MaterialX::Vector4 valData = pMtlXValue->asA(); + *pValueOut = glm::vec4(valData[0], valData[1], valData[2], valData[3]); + return true; + } + if (glslType.compare("boolean") == 0) + { + bool valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + if (glslType.compare("integer") == 0) + { + int valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + if (glslType.compare("float") == 0) + { + float valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + if (glslType.compare("string") == 0) + { + string valData = pMtlXValue->asA(); + *pValueOut = valData; + return true; + } + AU_FAIL("Unrecognized MateriaLX value type:%s", glslType.c_str()); + return false; +} + +PropertyValue::Type BSDFCodeGenerator::glslTypeToAuroraType(const string glslType) +{ + // Set the Aurora type based on GLSL type string. if (glslType.compare("vec3") == 0) - return IValues::Type::Float3; + return PropertyValue::Type::Float3; if (glslType.compare("vec2") == 0) - return IValues::Type::Float2; + return PropertyValue::Type::Float2; if (glslType.compare("float") == 0) - return IValues::Type::Float; - if (glslType.compare("sampler2D") == 0) - return IValues::Type::Image; + return PropertyValue::Type::Float; if (glslType.compare("int") == 0) - return IValues::Type::Int; + return PropertyValue::Type::Int; if (glslType.compare("bool") == 0) - return IValues::Type::Boolean; + return PropertyValue::Type::Bool; // Fail if no valid mapping found. AU_FAIL("Unsupported GLSL type %s", glslType.c_str()); - return IValues::Type::Undefined; + return PropertyValue::Type::Undefined; } bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Result* pResultOut, - ParameterMappingFunction inputParameterMapper, ParameterMappingFunction outputParameterMapper, - const string& overrideDocumentName) + const set supportedBSDFInputs, const string& overrideDocumentName) { // Processed MaterialX document string. string processedMtlXDocument; @@ -667,13 +814,17 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu // Get reference to original or processed materialX document string. const string& mtlXDocument = processedMtlXDocument.empty() ? document : processedMtlXDocument; - // Set the current input an output mappers to provided values. - _currentInputParameterMapper = inputParameterMapper; - _currentOutputParameterMapper = - outputParameterMapper ? outputParameterMapper : _defaultOutputMapper; - + // Clear any previous data. _pGenerator->clearTempVariables(); _processedNodes.clear(); + _parameters.clear(); + _parameterIndexLookup.clear(); + _builtInIndexLookup.clear(); + _materialProperties.clear(); + _materialPropertyDefaults.clear(); + _textures.clear(); + _builtIns.clear(); + _hasUnits = false; // Clear the setup code string. pResultOut->materialSetupCode = ""; @@ -681,7 +832,7 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu // Create new document object. MaterialX::DocumentPtr mtlxDocument = MaterialX::createDocument(); - // Read the XML into the document. + // Read the XML into a document. try { MaterialX::readFromXmlString(mtlxDocument, mtlXDocument); @@ -691,9 +842,12 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu AU_ERROR("Failed to read MaterialX from document:\n%s\n", exception.what()); return false; } + MaterialX::DocumentPtr doc = mtlxDocument; + // Write unit registry. _unitRegistry->write(mtlxDocument); + // Return false and print error message, if any. string errorMessage; if (!mtlxDocument->validate(&errorMessage)) { @@ -701,6 +855,153 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu return false; } + // Find all the terminal input values that will become the setup function parameters. + vector stringValues; + map textureIndexLookup; + for (MaterialX::ElementPtr elem : doc->traverseTree()) + { + // Get full path to node. + string path = elem->getNamePath(); + + // Initialize parameter struct to be filled in if parameter is found. + Parameter param; + param.parameterIndex = (int)_parameters.size(); + + // Have we found a parameter? + bool foundParam = false; + + MaterialX::ValueElementPtr valueElem = elem->asA(); + if (valueElem && valueElem->isA() && valueElem->hasValue()) + { + // All ValueElement's are parameters. + foundParam = true; + + // Create a parameter object from the ValueElement's input value (adsk texture node + // values will be represented this way). + auto type = valueElem->getType(); + + // Fill in parameter based on type. + if (type.compare("filename") == 0) + { + // Create texture parameter from the file ValueElement. + param.path = processPath(path, true); // Remove the last and first part of path. + param.variableName = pathToVariableName(param.path); + param.index = (int)_textures.size(); + param.paramType = ParameterType::Texture; + param.type = "sampler2D"; + + // Create the texture definition for this parameter. + TextureDefinition tex; + tex.name = param.path; + tex.defaultFilename = valueElem->getValue()->asA(); + + // Set linearize to if no color space or srgb_texture color space. + string colorSpace = valueElem->getColorSpace(); + tex.linearize = colorSpace.empty() || colorSpace.compare("srgb_texture") == 0; + + _textures.push_back(tex); + textureIndexLookup[param.path] = param.index; + } + else if (type.compare("string") == 0) + { + // String parameters are hardcoded texture properties (e.g. wrap mode), must be + // post-processed. + stringValues.push_back(elem); + } + else + { + // All other ValueElement types become material properties. + param.path = processPath(path, false); // Remove only first section of path. + param.variableName = pathToVariableName(param.path); + param.index = (int)_materialProperties.size(); + param.paramType = ParameterType::MaterialProperty; + param.type = valueElem->getType(); + + // Create uniform block property definition for parameter. + PropertyValue propVal; + materialXValueToAuroraPropertyValue(&propVal, valueElem->getValue()); + UniformBufferPropertyDefinition propDef( + param.path, param.variableName, propVal.type); + _materialProperties.push_back(propDef); + _materialPropertyDefaults.push_back(propVal); + } + } + else + { + + // image nodes and their children are not represented by ValueElement for some reason, + // so get the type as attribute. + string type = elem->getAttribute("type"); + if (!type.empty()) + { + // Process string and filename nodes. + if (type.compare("filename") == 0) + { + // Found a filename parameter. + foundParam = true; + + // Create texture parameter. + param.path = processPath(path, true); // Remove the last and first part of path. + param.variableName = pathToVariableName(param.path); + param.index = (int)_textures.size(); + param.paramType = ParameterType::Texture; + param.type = "sampler2D"; + + // Create the texture definition for this parameter. + TextureDefinition tex; + tex.name = param.path; + tex.defaultFilename = elem->getAttribute("value"); + + // Set linearize to if no color space or srgb_texture color space. + string colorSpace = elem->getAttribute("colorspace"); + tex.linearize = colorSpace.empty() || colorSpace.compare("srgb_texture") == 0; + + _textures.push_back(tex); + textureIndexLookup[param.path] = param.index; + } + else if (type.compare("string") == 0) + { + // Found a string parameter. + foundParam = true; + + // String parameters are hardcoded texture properties (e.g. wrap mode), must be + // post-processed. + stringValues.push_back(elem); + } + } + } + + // Add to setup function parameters. + if (foundParam && !param.variableName.empty()) + { + _parameterIndexLookup[path] = (int)_parameters.size(); + _parameters.push_back(param); + } + } + + // Process the hardcoded texture properties that will become sampler settings. + // TODO: Fix this in MaterialX, so we have actual sampler objects, not error prone hard coded + // magic property names. + for (MaterialX::ElementPtr elem : stringValues) + { + string path = elem->getNamePath(); + string txtPath = processPath(path, true); + string valData = elem->getAttribute("value"); + auto iter = textureIndexLookup.find(txtPath); + if (iter != textureIndexLookup.end()) + { + auto& texture = _textures[iter->second]; + if (elem->getName().compare("uaddressmode") == 0) + { + texture.addressModeU = valData; + } + else if (elem->getName().compare("vaddressmode") == 0) + { + texture.addressModeV = valData; + } + } + } + // Import the standard library. mtlxDocument->importLibrary(s_pStdLib); @@ -763,20 +1064,14 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu } // Find the surface node for shader. - MaterialX::TypedElementPtr shaderNodeElement = nullptr; - MaterialX::InterfaceElementPtr shaderNodeDefImpl = nullptr; + MaterialX::TypedElementPtr shaderNodeElement = nullptr; for (MaterialX::NodePtr shaderNode : shaderNodes) { if ((shaderNode->getType() == MaterialX::SURFACE_SHADER_TYPE_STRING) && (shaderNode->getCategory() == _surfaceShaderNodeCategory)) { shaderNodeElement = shaderNode; - - // Find the nodegraph implementation of the surface node. - shaderNodeDefImpl = shaderNodeElement->asA()->getImplementation(); - - if (shaderNodeDefImpl) - break; + break; } } if (!shaderNodeElement) @@ -784,17 +1079,43 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu AU_ERROR("Failed to find surface shader in material"); return false; } + + // Get the node for materialX shader. + MaterialX::NodePtr shaderNode = shaderNodeElement->asA(); + + // Build a set of the BSDF inputs used by the shader nodes. These will be the outputs to the + // setup function. + auto graphInputs = shaderNode->getInputs(); + set bsdfInputSet; + for (auto gi : graphInputs) + { + string bsdfInputName = gi->getName(); + if (supportedBSDFInputs.empty() || + supportedBSDFInputs.find(bsdfInputName) != supportedBSDFInputs.end()) + bsdfInputSet.insert(bsdfInputName); + } + + // Get the implementation for the node. + MaterialX::InterfaceElementPtr shaderNodeDefImpl = shaderNode->getImplementation(); if (!shaderNodeDefImpl) { AU_ERROR("Failed to find nodegraph implementation for the surface shader"); return false; } - string surfaceShaderNodeType = shaderNodeDefImpl->getName(); // Create a shader graph from the surface node. - MaterialX::ShaderGraphPtr graph = MaterialX::ShaderGraph::create( - nullptr, "BSDFCodeGeneratorShaderGraph", shaderNodeElement, *_pGeneratorContext); + MaterialX::ShaderGraphPtr graph; + try + { + graph = MaterialX::ShaderGraph::create( + nullptr, "BSDFCodeGeneratorShaderGraph", shaderNodeElement, *_pGeneratorContext); + } + catch (MaterialX::ExceptionShaderGenError& ex) + { + AU_ERROR("Exception in MaterialX code:%s", ex.what()); + return false; + } shared_ptr pBSDFGenShader = make_shared("BSDFCodeGeneratorShader", graph); MaterialX::ShaderPtr shader = dynamic_pointer_cast(pBSDFGenShader); @@ -818,115 +1139,130 @@ bool BSDFCodeGenerator::generate(const string& document, BSDFCodeGenerator::Resu _topLevelShaderNodeName = surfaceShaderNode->getName(); // Create the active inputs and outputs. - _activeOutputs.clear(); - _activeInputs.clear(); - _activeInputNames.clear(); - _activeOutputNames.clear(); + _activeBSDFInputs.clear(); + _activeBSDFInputNames.clear(); // Build the body of the setup function from the surface shader node inputs. string functionBody = ""; auto surfaceShaderInputs = surfaceShaderNode->getInputs(); for (int ssi = 0; ssi < surfaceShaderInputs.size(); ssi++) { - // Is this shader input one of the outputs we are interested in ? auto surfaceShaderInput = surfaceShaderInputs[ssi]; + string inputName = surfaceShaderInput->getName(); - IValues::Type auroraoOutputType = glslTypeToAuroraType( - pBSDFGenShader->syntax()->getTypeName(surfaceShaderInput->getType())); - string outputVariable = _currentOutputParameterMapper( - surfaceShaderInput->getName(), auroraoOutputType, _topLevelShaderNodeName); - - if (!outputVariable.empty()) + // Process inputs that are in the set of BSDF inputs for this material. + string bsdfInputVariable; + if (bsdfInputSet.find(inputName) != bsdfInputSet.end()) + bsdfInputVariable = inputName; + if (!bsdfInputVariable.empty()) { // Process the shader input. - processInput(surfaceShaderInput, pBSDFGenShader, outputVariable, &functionBody); + processInput(surfaceShaderInput, pBSDFGenShader, bsdfInputVariable, &functionBody); - // Add type to active outputs. - _activeOutputNames.push_back(outputVariable); - _activeOutputs[outputVariable] = + // Add to list of active BSDF inputs. + _activeBSDFInputNames.push_back(bsdfInputVariable); + _activeBSDFInputs[bsdfInputVariable] = pBSDFGenShader->syntax()->getTypeName(surfaceShaderInput->getType()); }; } - // Create a hash from the contents of the setup function. This is used to uniquely identify the - // shader code generated for this material, without any of the parameters or other surrounding - // data. + // Set the material properties and textures in result. + pResultOut->materialProperties = _materialProperties; + pResultOut->materialPropertyDefaults = _materialPropertyDefaults; + pResultOut->textureDefaults = _textures; + pResultOut->hasUnits = _hasUnits; + + // Set the texture names in the result. + pResultOut->textures.clear(); + for (int i = 0; i < pResultOut->textureDefaults.size(); i++) + { + pResultOut->textures.push_back(pResultOut->textureDefaults[i].name); + } + + // Create the contents of the material struct. + string structProperties; + for (int i = 0; i < pResultOut->materialProperties.size(); i++) + { + string glslType = getGLSLStringFromType(pResultOut->materialProperties[i].type); + + structProperties.append( + "\t" + glslType + " " + pResultOut->materialProperties[i].variableName + ";\n"); + } + + // Create a hash from the contents of the setup function and the material struct. This is used + // to uniquely identify the shader code generated for this material. It is possible to have + // identical functions with different material structs (as struct properties can be unused.) + // TODO: Strip out unused material parameters. + size_t structHash = hash {}(structProperties); pResultOut->functionHash = hash {}(functionBody); + Foundation::hashCombine(pResultOut->functionHash, structHash); // Create setup function name from hash. stringstream sstream; sstream << "setupMaterial_" << Foundation::sHash(pResultOut->functionHash); pResultOut->setupFunctionName = sstream.str(); + // Create material struct type name from hash. + sstream = stringstream(); + sstream << "Material_" << Foundation::sHash(pResultOut->functionHash); + pResultOut->materialStructName = sstream.str(); + + // Create the GLSL code for the material struct (containing all material properties). + pResultOut->materialStructCode.append("struct " + pResultOut->materialStructName + " {\n"); + pResultOut->materialStructCode.append(structProperties); + pResultOut->materialStructCode.append("};\n"); + // Create the function prototype from the function name, active inputs and outputs. pResultOut->materialSetupCode = "void " + pResultOut->setupFunctionName + "(\n"; // Keep track of total input and output parameter count. int numParams = 0; - // Clear the argument and default values vectors. - pResultOut->argumentsUsed.clear(); - pResultOut->defaultInputValues.clear(); - pResultOut->defaultInputValues.resize(_activeInputNames.size()); + // The first argument is always the material struct. + pResultOut->materialSetupCode.append("\t" + pResultOut->materialStructName + " material"); - // Add the material inputs to the function prototype. - for (int i = 0; i < _activeInputNames.size(); i++) + // Add the texture inputs to the function prototype. + for (int i = 0; i < _textures.size(); i++) { - auto inputVar = _activeInputNames[i]; + pResultOut->materialSetupCode.append(",\n"); - auto activeTypeIter = _activeInputs.find(inputVar); - if (activeTypeIter != _activeInputs.end()) - { - auto activeInput = activeTypeIter->second; - string activeInputType = activeInput.first; + // Add the code for this input to the prototype. + pResultOut->materialSetupCode.append( + "\tsampler2D " + _textures[i].name + "_image_parameter"); - // Create variable name using input name and suffix. - string inputVarName = inputVar + _materialInputSuffix; - - // Append comma to previous line, if any. - if (numParams) - pResultOut->materialSetupCode.append(",\n"); - - // Convert GLSL type to Aurora type enum. - IValues::Type auroraType = glslTypeToAuroraType(activeInputType); - - // Convert MaterialX value for input into Aurora Value. - if (activeInput.second) - { - materialXValueToAuroraValue(&pResultOut->defaultInputValues[i], activeInput.second); - } - Value* pDefaultValue = &pResultOut->defaultInputValues[i]; + numParams++; + } - // Add this input to the results. - pResultOut->argumentsUsed.push_back({ inputVar, auroraType, false, pDefaultValue }); + // The distance unit (if used) is last input parameter in setup function prototype. + if (_hasUnits) + { + pResultOut->materialSetupCode.append(",\n"); - // Add the code for this input to the prototype. - pResultOut->materialSetupCode.append("\t" + activeInputType + " " + inputVarName); + // Add the code for the distance unit. + pResultOut->materialSetupCode.append("\tint " + _builtIns[0].second); - // Increment parameter count. - numParams++; - } + numParams++; } - // Add the material outputs to the function prototype. - for (auto outputVar : _activeOutputNames) + // The active BSDF inputs become output parameters to the function prototype. + pResultOut->bsdfInputs.clear(); + for (auto bsdfInputName : _activeBSDFInputNames) { - auto activeTypeIter = _activeOutputs.find(outputVar); - if (activeTypeIter != _activeOutputs.end()) + auto activeTypeIter = _activeBSDFInputs.find(bsdfInputName); + if (activeTypeIter != _activeBSDFInputs.end()) { // Create variable name using output name and suffix. - string outputVarName = outputVar + _materialOutputSuffix; + string outputVarName = bsdfInputName; // Append comma to previous line, if any. - if (numParams) - pResultOut->materialSetupCode.append(",\n"); + pResultOut->materialSetupCode.append(",\n"); // Convert GLSL type to Aurora type enum. - IValues::Type auroraType = glslTypeToAuroraType(activeTypeIter->second); + PropertyValue::Type auroraType = glslTypeToAuroraType(activeTypeIter->second); // Add this output to the results. - pResultOut->argumentsUsed.push_back({ outputVar, auroraType, true }); + pResultOut->bsdfInputs.push_back({ bsdfInputName, auroraType }); // Add the code for this output to the prototype. pResultOut->materialSetupCode.append( diff --git a/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h index 8b5e409..ec8ad7f 100644 --- a/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h +++ b/Libraries/Aurora/Source/MaterialX/BSDFCodeGenerator.h @@ -1,23 +1,20 @@ -// Copyright 2022 Autodesk, Inc. // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Copyright 2023 by Autodesk, Inc. All rights reserved. // -// http://www.apache.org/licenses/LICENSE-2.0 +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. #pragma once +#include "UniformBuffer.h" #include // Forward declare MaterialX types. -MATERIALX_NAMESPACE_BEGIN - +namespace MaterialX_v1_38_5 +{ class Document; class FileSearchPath; class ShaderGenerator; @@ -28,8 +25,8 @@ class Value; class UnitConverterRegistry; class UnitSystem; class ShaderNode; - -MATERIALX_NAMESPACE_END +class TypeDesc; +} // namespace MaterialX_v1_38_5 #include "Properties.h" @@ -57,17 +54,13 @@ class BSDFCodeGenerator std::map indices; }; - /// Material argument details. - struct MaterialArgument + // The BSDF inputs that are produced by the setup function. + struct BSDFInput { - /// Argument name + /// Input name string name; - /// Argument type as GLSL string - IValues::Type type; - /// True if output argument. - bool isOutput; - /// Pointer to default value (nullptr for output values) - Value* pDefaultValue; + /// Input type as PropertyValue type enum + PropertyValue::Type type = PropertyValue::Type::Undefined; }; /// Code generation result. @@ -79,19 +72,28 @@ class BSDFCodeGenerator /// \desc The setup function takes a set of material inputs from CPU and outputs the /// parameters of the Standard Surface material, based on a MaterialX network. string materialSetupCode; + /// \desc The GLSL structure for the material object used as an input to this material. + string materialStructCode; /// \desc The name of the setup function . string setupFunctionName; - /// \brief Vector of input and output arguments used by the setup function. - vector argumentsUsed; - /// \brief Vector of default values for the material inputs. - vector defaultInputValues; + /// \desc The name of the material structure type. + string materialStructName; + /// \brief Vector of material properties in material struct that is the input to the setup + /// function. + vector materialProperties; + /// \brief Vector of textures used by this material in the setup function. + vector textures; + /// \brief Vector of BSDF inputs output by the setup function. + vector bsdfInputs; + /// \desc True if the setup fuction takes an integer unit parameter (index into unit names + /// in the Units struct). + bool hasUnits = false; + /// Default values for material properties. + vector materialPropertyDefaults; + /// \brief Vector of textures used by this material in the setup function. + vector textureDefaults; }; - /// Maps a MaterialX parameter (either input or output) to an argument in the generated setup - /// function. Returns empty string to indicate parameter is not mapped. - using ParameterMappingFunction = function; - /// \param surfaceShaderNodeCategory The surface shader category to code generate material /// inputs for. \param mtlxPath The search path for the materialX assets, including the library /// assets in the 'libraries' folder. @@ -103,12 +105,10 @@ class BSDFCodeGenerator /// \param.document MaterialX XML document string. /// \param inputParameterMapper Function used to map a parameter in the MaterialX network an /// input parameter in the setup function. \param outputParameterMapper Function used to map a - /// parameter in the MaterialX network an ouput parameter in the setup function. \param + /// parameter in the MaterialX network an output parameter in the setup function. \param /// pResultOut Code generation result output. bool generate(const string& document, Result* pResultOut, - ParameterMappingFunction inputParameterMapper, - ParameterMappingFunction outputParameterMapper = nullptr, - const string& overrideDocumentName = ""); + const set supportedBSDFInputs = {}, const string& overrideDocumentName = ""); /// \desc Generate the shared GLSL definitions for all the documents generated by this /// generator. Will clear the shared definitions. @@ -122,12 +122,33 @@ class BSDFCodeGenerator const Units& units() { return _units; } protected: + enum ParameterType + { + MaterialProperty, + Texture, + BuiltIn + }; + + struct Parameter + { + string variableName; + string path; + string type; + string glslType; + int index = 0; + int parameterIndex = 0; + ParameterType paramType = MaterialProperty; + }; + // Convert GLSL type string to Aurora type (asserts if conversion fails.) - IValues::Type glslTypeToAuroraType(const string glslType); + PropertyValue::Type glslTypeToAuroraType(const string glslType); // Convert materialX value to Aurora value (asserts if conversion fails.) bool materialXValueToAuroraValue(Value* pValueOut, shared_ptr pMtlXValue); + bool materialXValueToAuroraPropertyValue( + PropertyValue* pValueOut, shared_ptr pMtlXValue); + // Process a MaterialX shader input. void processInput(MaterialX::ShaderInput* input, shared_ptr pBSDFGenShader, const string& outputVariable, @@ -146,19 +167,20 @@ class BSDFCodeGenerator unique_ptr _pGeneratorContext; // Current active material inputs and outputs. - map _activeOutputs; - map>> _activeInputs; - vector _activeInputNames; - vector _activeOutputNames; + map _activeBSDFInputs; + map _parameterIndexLookup; + map _builtInIndexLookup; + vector _parameters; + vector _materialProperties; + vector _materialPropertyDefaults; + vector _textures; + vector> _builtIns; + vector _activeBSDFInputNames; // Definition look-up. map _definitionMap; vector _definitions; - // Suffix added to material inputs. - string _materialInputSuffix = "In"; - string _materialOutputSuffix = ""; - // The surface shader category to code generate inputs for. string _surfaceShaderNodeCategory; @@ -168,13 +190,9 @@ class BSDFCodeGenerator shared_ptr _unitSystem; shared_ptr _unitRegistry; set _processedNodes; - ParameterMappingFunction _defaultOutputMapper; - map _defaultOutputParamMapping; string _topLevelShaderNodeName; string _mtlxLibPath; - - ParameterMappingFunction _currentInputParameterMapper; - ParameterMappingFunction _currentOutputParameterMapper; + bool _hasUnits = false; }; } // namespace MaterialXCodeGen diff --git a/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp index 21fea75..4fe9913 100644 --- a/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp +++ b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,45 +25,6 @@ namespace Aurora namespace MaterialXCodeGen { -static string mapMaterialXTextureName(const string& name) -{ - string mappedName = ""; - - // Any image input containing string 'diffuse' or 'base' is mapped to the - // base_color_image texture (e.g. "generic_diffuse_file" or "base_color_image".). - if (name.find("diffuse") != string::npos || name.find("base") != string::npos) - { - mappedName = "base_color_image"; - } - // Any image input containing string 'roughness' or 'specular' is mapped to the - // specular_roughness_image texture. - else if (name.find("roughness") != string::npos || name.find("specular") != string::npos) - { - mappedName = "specular_roughness_image"; - } - // Any image input containing string 'normal' or 'bump' is mapped to the - // normal_image texture. - else if (name.find("normal") != string::npos || name.find("bump") != string::npos) - { - mappedName = "normal_image"; - } - // Any image input containing string 'opacity', 'alpha', 'cutout', or 'transmission' - // is mapped to the opacity_image texture. - else if (name.find("opacity") != string::npos || name.find("alpha") != string::npos || - name.find("cutout") != string::npos || name.find("transmission") != string::npos) - { - mappedName = "opacity_image"; - } - // If nothing else matches, then map any image containing color to base_color_image - // texture. - else if (name.find("color") != string::npos) - { - mappedName = "base_color_image"; - } - - return mappedName; -} - // Extremely primitive GLSL-to-HLSL conversion function, that handles cases not dealt with in shader // code prefix. // TODO: This is a very basic placeholder, we need a better solution for this. @@ -80,8 +41,7 @@ static string GLSLToHLSL(const string& glslStr) return hlslStr; } -MaterialGenerator::MaterialGenerator(IRenderer* pRenderer, const string& mtlxFolder) : - _pRenderer(pRenderer) +MaterialGenerator::MaterialGenerator(const string& mtlxFolder) { // Create code generator. _pCodeGenerator = make_unique(mtlxFolder); @@ -89,7 +49,7 @@ MaterialGenerator::MaterialGenerator(IRenderer* pRenderer, const string& mtlxFol // Map of MaterialX output parameters to Aurora material property. // Used by PTRenderer::generateMaterialX. // clang-format off - _materialXOutputParamMapping = + _bsdfInputParamMapping = { { "base", "base" }, { "base_color", "baseColor" }, @@ -124,10 +84,15 @@ MaterialGenerator::MaterialGenerator(IRenderer* pRenderer, const string& mtlxFol { "coat_IOR", "coatIOR" }, { "coat_affect_color", "coatAffectColor" }, { "coat_affect_roughness", "coatAffectRoughness" }, + { "emission", "emission" }, + { "emission_color", "emissionColor" }, { "opacity", "opacity" }, { "base_color_image_scale", "baseColorTexTransform.scale" }, { "base_color_image_offset", "baseColorTexTransform.offset" }, { "base_color_image_rotation", "baseColorTexTransform.rotation" }, + { "emission_color_image_scale", "emissionColorTexTransform.scale" }, + { "emission_color_image_offset", "emissionColorTexTransform.offset" }, + { "emission_color_image_rotation", "emissionColorTexTransform.rotation" }, { "opacity_image_scale", "opacityTexTransform.scale" }, { "opacity_image_offset", "opacityTexTransform.offset" }, { "opacity_image_rotation", "opacityTexTransform.rotation" }, @@ -137,272 +102,99 @@ MaterialGenerator::MaterialGenerator(IRenderer* pRenderer, const string& mtlxFol { "specular_roughness_image_scale", "specularRoughnessTexTransform.scale" }, { "specular_roughness_image_offset", "specularRoughnessTexTransform.offset" }, { "specular_roughness_image_rotation", "specularRoughnessTexTransform.rotation" }, - { "thin_walled", "thinWalled" } + { "thin_walled", "thinWalled" }, + { "normal", "normal" } }; // clang-format on } -bool MaterialGenerator::generate( - const string& document, map* pDefaultValuesOut, MaterialTypeSource& sourceOut) +shared_ptr MaterialGenerator::generate(const string& document) { + shared_ptr pDef; + + // If we have already generated this material document, then just used cached material type. + auto mtliter = _definitions.find(document); + if (mtliter != _definitions.end()) + { + pDef = mtliter->second.lock(); + if (pDef) + { + return pDef; + } + else + { + _definitions.erase(document); + } + } + + // Currently every material has its own definitions. + _pCodeGenerator->clearDefinitions(); + // Create code generator result struct. MaterialXCodeGen::BSDFCodeGenerator::Result res; + // Strings for generated HLSL and entry point. + string generatedMtlxSetupFunction; + // The hardcoded inputs are added manually to the generated HLSL and passed to the - // code-generated set up function. This is filled in by the inputMapper lamdba when the code is + // code-generated set up function. This is filled in by the inputMapper lambda when the code is // generated. set hardcodedInputs; - // Callback function to map MaterialX inputs to Aurora uniforms and textures. - // This function will be called for each input parameter in the MaterialX document that is - // connected to a mapped output. Each "top level" input parameter will be prefixed with the top - // level shader name for the MaterialX document, other input parameters will be named for the - // node graph they are part of (e.g. generic_diffuse) - MaterialXCodeGen::BSDFCodeGenerator::ParameterMappingFunction inputMapper = - [&](const string& name, Aurora::IValues::Type type, const string& topLevelShaderName) { - // Top-level inputs are prefixed with the top level material name. - if (name.size() > topLevelShaderName.size()) - { - // Strip the top level material name (and following underscore) from the input name. - string nameStripped = name.substr(topLevelShaderName.size() + 1); - - // If this is one of the Standard Surface inputs then map to that. - auto iter = _materialXOutputParamMapping.find(nameStripped); - if (iter != _materialXOutputParamMapping.end()) - return iter->second; - } - - // By default return an empty string (which will leave input unmapped.) - string mappedName = ""; - - // Map anything containing the string 'unit_unit_to' to the distance unit. - if (name.find("unit_unit_to") != string::npos) - { - mappedName = "distanceUnit"; - } - - // Map any image inputs using a heuristic that should guess the right texture mapping - // for any textures we care about. - if (type == Aurora::IValues::Type::Image) - { - mappedName = mapMaterialXTextureName(name); - if (mappedName.empty()) - { - // Leaving a texture unassigned will result in a HLSL compilation error, so map - // unrecognized textures to gBaseColorTexture and print a warning. This avoids - // the shader compile error, but will render incorrectly. - AU_WARN("Unrecognized texture variable %s, setting to gBaseColorTexture.", - name.c_str()); - mappedName = "base_color_image"; - } - } - - // Map any int parameters as sampler properties. - if (type == Aurora::IValues::Type::Int) - { - string mappedTextureName = mapMaterialXTextureName(name); - - // Any int input that can be mapped to hardcoded texture name is treated as address - // mode. - // TODO: Only base color and opacity image currently supports samplers. - if (!mappedTextureName.empty() && - (mappedTextureName.compare("base_color_image") == 0 || - mappedTextureName.compare("opacity_image") == 0)) - { - // Map uaddressmode to the image's AddressModeU property. - if (name.find("uaddressmode") != string::npos) - mappedName = mappedTextureName + "_sampler_uaddressmode"; - // Map uaddressmode to the image's AddressModeV property. - else if (name.find("vaddressmode") != string::npos) - mappedName = mappedTextureName + "_sampler_vaddressmode"; - } - } - if (type == Aurora::IValues::Type::Float2) - { - string mappedTextureName = mapMaterialXTextureName(name); - - // Any vec2 input that can be mapped to hardcoded texture name is treated as texture - // transform property. - if (!mappedTextureName.empty()) - { - // Map uv_offset to the image transform's offset property. - if (name.find("uv_offset") != string::npos) - mappedName = mappedTextureName + "_offset"; - // Map uv_scale to the image transform's scale property. - else if (name.find("uv_scale") != string::npos) - mappedName = mappedTextureName + "_scale"; - } - } - if (type == Aurora::IValues::Type::Float) - { - string mappedTextureName = mapMaterialXTextureName(name); - - // Any float input that can be mapped to hardcoded texture name as texture rotation. - if (!mappedTextureName.empty()) - { - // Map uv_scale to the image transform's rotaiton property. - if (name.find("rotation_angle") != string::npos) - mappedName = mappedTextureName + "_rotation"; - } - } - - // Add to the hardcoded inputs. - if (!mappedName.empty()) - hardcodedInputs.insert(mappedName); - - // Return the mapped name - return mappedName; - }; - - // Set to true by lambda if the normal has been modified in code-generated material setup. - bool modifiedNormal = false; - - // Callback function to map MaterialX setup outputs to the Aurora material parameter. - MaterialXCodeGen::BSDFCodeGenerator::ParameterMappingFunction outputMapper = - [&](const string& name, Aurora::IValues::Type /*type*/, - const string& /*topLevelShaderName*/) { - // If there is direct mapping to aurora material use that. - auto iter = _materialXOutputParamMapping.find(name); - if (iter != _materialXOutputParamMapping.end()) - return iter->second; - - // If the setup code has generated a normal, set the modified flag. - if (name.compare("normal") == 0) - { - modifiedNormal = true; - return name; - } - - // Otherwise don't code generate this output. - return string(""); - }; - - // Remap the parameters *back* to the materialX name, as the material inputs are in snake case, - // not camel case. - // TODO: We should unify camel-vs-snake case for material parameters. - map remappedInputs; - for (auto iter : _materialXOutputParamMapping) + // Create set of supported BSDF inputs. + set supportedBSDFInputs; + for (auto iter : _bsdfInputParamMapping) { - remappedInputs[iter.second] = iter.first; + supportedBSDFInputs.insert(iter.first); } // Run the code generator overriding materialX document name, so that regardless of the name in // the document, the generated HLSL is based on a hardcoded name string for caching purposes. // NOTE: This will immediate run the code generator and invoke the inputMapper and outputMapper // function populate hardcodedInputs and set modifiedNormal. - if (!_pCodeGenerator->generate(document, &res, inputMapper, outputMapper, "MaterialXDocument")) + if (!_pCodeGenerator->generate(document, &res, supportedBSDFInputs, "MaterialXDocument")) { // Fail if code generation fails. // TODO: Proper error handling here. AU_ERROR("Failed to generate MaterialX code."); - return false; + return nullptr; } - // Map of samplers to collate sampler properties. - map samplerProperties; - - // Get the default values. - pDefaultValuesOut->clear(); - for (int i = 0; i < res.argumentsUsed.size(); i++) + // Create set of the generated BSDF inputs. + set bsdfInputs; + for (int i = 0; i < res.bsdfInputs.size(); i++) { - auto arg = res.argumentsUsed[i]; - // Code generator returns input and output values, we only care about inputs. - if (!arg.isOutput) - { - // For all Standard Surface inputs, remap back to snake case. - string remappedName = arg.name; - - // We need to collate the seperate sampler properties (vaddressmode or uaddressmode) - // into sampler object. - bool isSamplerParam = false; - if (arg.name.find("uaddressmode") != string::npos || - arg.name.find("vaddressmode") != string::npos) - { - isSamplerParam = true; - - // Compute sampler name from property name. - string samplerName = arg.name; - samplerName.erase(samplerName.length() - string("_uaddressmode").length()); - - // If no sampler properties, create one. - if (samplerProperties.find(samplerName) == samplerProperties.end()) - { - samplerProperties[samplerName] = {}; - } - - // Map int value to address mode. - // These int values are abrirarily defined in MaterialX source: - // source\MaterialXRender\ImageHandler.h - // TODO: Make this less error prone. - string addressMode = Names::AddressModes::kWrap; - switch (arg.pDefaultValue->asInt()) - { - case 1: - addressMode = Names::AddressModes::kClamp; - break; - case 2: - addressMode = Names::AddressModes::kWrap; - break; - case 3: - addressMode = Names::AddressModes::kMirror; - break; - default: - break; - } - - // Set the sampler properties for U/V address mode. - if (arg.name.find("uaddressmode") != string::npos) - { - samplerProperties[samplerName][Names::SamplerProperties::kAddressModeU] = - addressMode; - } - else if (arg.name.find("vaddressmode") != string::npos) - { - samplerProperties[samplerName][Names::SamplerProperties::kAddressModeV] = - addressMode; - } - } - - // Don't map individual sampler parameters to default values, as they are added as - // sampler objects below. - if (!isSamplerParam) - { - // Remap if needed. - auto hardcodedIter = hardcodedInputs.find(arg.name); - if (hardcodedIter != hardcodedInputs.end()) - { - // If this is a hard-oded input, use the hardcoded name directly. - remappedName = arg.name; - } - else - { - // For Standard Surface inputs, remap back to snake case. - remappedName = remappedInputs[arg.name]; - } - - // Add the default value to the output map. - pDefaultValuesOut->insert(make_pair(remappedName, *arg.pDefaultValue)); - } - } + bsdfInputs.insert(res.bsdfInputs[i].name); } - // Create sampler objects for the sampler properties. - // TODO: Cache these to avoid unnessacary sampler objects. - for (auto iter = samplerProperties.begin(); iter != samplerProperties.end(); iter++) + // Create set of the textures used by the generated setup function. + map textures; + for (int i = 0; i < res.textures.size(); i++) { - pDefaultValuesOut->insert( - make_pair(iter->first, _pRenderer->createSamplerPointer(iter->second))); + textures[res.textures[i]] = i; } + // Normal modified if the normal BSDF input is active. + bool modifiedNormal = bsdfInputs.find("normal") != bsdfInputs.end(); + // Create material name from the hash provided by code generator. string materialName = "MaterialX_" + Foundation::sHash(res.functionHash); - // Begin with the setup code (converted to HLSL) - string generatedMtlxSetupFunction = GLSLToHLSL(res.materialSetupCode); + // Append the material accessor functions used to read material properties from + // ByteAddressBuffer. + UniformBuffer mtlConstantsBuffer(res.materialProperties, res.materialPropertyDefaults); + generatedMtlxSetupFunction += "struct " + res.materialStructName + "\n"; + generatedMtlxSetupFunction += mtlConstantsBuffer.generateHLSLStruct(); + generatedMtlxSetupFunction += ";\n\n"; + generatedMtlxSetupFunction += + mtlConstantsBuffer.generateByteAddressBufferAccessors(res.materialStructName + "_"); + generatedMtlxSetupFunction += "\n"; + generatedMtlxSetupFunction += GLSLToHLSL(res.materialSetupCode); // Create a wrapper function that is called by the ray hit entry point to initialize material. generatedMtlxSetupFunction += - "\nMaterial initializeMaterial(ShadingData shading, float3x4 objToWorld, out float3 " + "\nMaterial initializeMaterial" + "(ShadingData shading, float3x4 objToWorld, out float3 " "materialNormal, out bool " "isGeneratedNormal) {\n"; @@ -413,125 +205,155 @@ bool MaterialGenerator::generate( // generated a normal) generatedMtlxSetupFunction += "\tisGeneratedNormal = " + to_string(modifiedNormal) + ";\n"; - // The hardcoded initializeDefaultMaterial() function requires a normal parameter, this should - // never be used here. - generatedMtlxSetupFunction += - "\nfloat3 unusedObjectSpaceNormal = shading.normal; bool unusedIsGeneratedNormal;\n"; + // Reset material to default values. + generatedMtlxSetupFunction += "\tMaterial material = defaultMaterial();\n"; - // Add code to call default initialize material function. - // NOTE: Most of this will get overwritten, but the compiler should be clever enough to know - // that and remove dead code. - generatedMtlxSetupFunction += - "\tMaterial material = initializeDefaultMaterial(shading, ObjectToWorld3x4(), " - "unusedObjectSpaceNormal, " - "unusedIsGeneratedNormal);\n"; - - // MaterialX accepts addresmode parameters as dummy integers that are passed down to the shader - // code, but then never used. So we need to define them as arbritary ints to avoid compile - // errors. - // TODO: Less hacky way to do this. - if (hardcodedInputs.find("base_color_image_sampler_uaddressmode") != hardcodedInputs.end()) - { - generatedMtlxSetupFunction += "\tint base_color_image_sampler_uaddressmode = 0;\n"; - } - if (hardcodedInputs.find("base_color_image_sampler_vaddressmode") != hardcodedInputs.end()) - { - generatedMtlxSetupFunction += "\tint base_color_image_sampler_vaddressmode = 0;\n"; - } - if (hardcodedInputs.find("opacity_image_sampler_uaddressmode") != hardcodedInputs.end()) + // Add code to create local texture variables for the textures used by generated code, based on + // hard-code texture names from Standard Surface. + // TODO: Data-driven textures that are not mapped to hard coded texture names. + vector textureVars; + if (res.textures.size() >= 1) { - generatedMtlxSetupFunction += "\tint opacity_image_sampler_uaddressmode = 0;\n"; + // Map first texture to the base_color_image and sampler. + generatedMtlxSetupFunction += + "\tsampler2D base_color_image = createSampler2D(gBaseColorTexture, " + "gBaseColorSampler);\n"; + + // Add to the texture array. + TextureDefinition txtDef = res.textureDefaults[0]; + txtDef.name = "base_color_image"; + textureVars.push_back(txtDef); } - if (hardcodedInputs.find("opacity_image_sampler_vaddressmode") != hardcodedInputs.end()) + + if (res.textures.size() >= 2) { - generatedMtlxSetupFunction += "\tint opacity_image_sampler_vaddressmode = 0;\n"; + // Map second texture to the opacity_image and sampler. + generatedMtlxSetupFunction += + "\tsampler2D opacity_image = createSampler2D(gOpacityTexture, " + "gOpacitySampler);\n"; + + // Add to the texture array. + TextureDefinition txtDef = res.textureDefaults[1]; + txtDef.name = "opacity_image"; + textureVars.push_back(txtDef); } - // Add code to create local texture variables for the textures used by generated code. - // TODO: Add more and be more rigorous about finding which textures are used. - if (hardcodedInputs.find("base_color_image") != hardcodedInputs.end()) + if (res.textures.size() >= 3) { - // Ensure the correct sampler is used for the base color sampler (not default sampler). + // Map third texture to the normal_image and the default sampler. generatedMtlxSetupFunction += - "\tsampler2D base_color_image = createSampler2D(gBaseColorTexture, " - "gBaseColorSampler);\n"; + "\tsampler2D normal_image = createSampler2D(gNormalTexture, " + "gDefaultSampler);\n"; + + // Add to the texture array. + TextureDefinition txtDef = res.textureDefaults[2]; + txtDef.name = "normal_image"; + textureVars.push_back(txtDef); } - if (hardcodedInputs.find("specular_roughness_image") != hardcodedInputs.end()) + + if (res.textures.size() >= 4) { + // Map fourth texture to the specular_roughness_image and the default sampler. generatedMtlxSetupFunction += "\tsampler2D specular_roughness_image = createSampler2D(gSpecularRoughnessTexture, " "gDefaultSampler);\n"; + + // Add to the texture array. + TextureDefinition txtDef = res.textureDefaults[3]; + txtDef.name = "specular_roughness_image"; + textureVars.push_back(txtDef); } - if (hardcodedInputs.find("normal_image") != hardcodedInputs.end()) + + if (res.textures.size() >= 5) { + // Map fifth texture to the emission_color_image and the default sampler. generatedMtlxSetupFunction += - "\tsampler2D normal_image = createSampler2D(gNormalTexture, " + "\tsampler2D emission_color_image = createSampler2D(gEmissionColorTexture, " "gDefaultSampler);\n"; + + // Add to the texture array. + TextureDefinition txtDef = res.textureDefaults[4]; + txtDef.name = "emission_color_image"; + textureVars.push_back(txtDef); } - if (hardcodedInputs.find("opacity_image") != hardcodedInputs.end()) + + // Map any additional textures to base color image. + for (size_t i = textureVars.size(); i < res.textures.size(); i++) { - // Ensure the correct sampler is used for the opacity sampler (not default sampler). - generatedMtlxSetupFunction += - "\tsampler2D opacity_image = createSampler2D(gOpacityTexture, " - "gOpacitySampler);\n"; + TextureDefinition txtDef = textureVars[0]; + txtDef.name = "base_color_image"; + textureVars.push_back(txtDef); } // Use the define DISTANCE_UNIT which is set in the shader library options. generatedMtlxSetupFunction += "\tint distanceUnit = DISTANCE_UNIT;\n"; + // Create temporary material struct + generatedMtlxSetupFunction += "\t" + res.materialStructName + " setupMaterialStruct;\n"; + + // Fill struct using the byte address buffer accessors. + for (int i = 0; i < res.materialProperties.size(); i++) + { + generatedMtlxSetupFunction += "\tsetupMaterialStruct." + + res.materialProperties[i].variableName + " = " + res.materialStructName + "_" + + res.materialProperties[i].variableName + "(gMaterialConstants);\n"; + ; + } + // Add code to call the generate setup function. generatedMtlxSetupFunction += "\t" + res.setupFunctionName + "(\n"; - // Add code for all the required arguments. - for (int i = 0; i < res.argumentsUsed.size(); i++) + // First argument is material struct. + generatedMtlxSetupFunction += "setupMaterialStruct"; + + // Add code for all the texture arguments. + for (int i = 0; i < textureVars.size(); i++) + { + // Add texture name. + generatedMtlxSetupFunction += ",\n"; + + // Add texture name. + generatedMtlxSetupFunction += textureVars[i].name; + } + + // Add distance unit parameter, if used. + if (res.hasUnits) { - // Current argument. - auto arg = res.argumentsUsed[i]; + // Add texture name. + generatedMtlxSetupFunction += ",\n"; - // Append comma if needed. - if (i > 0) - generatedMtlxSetupFunction += ",\n"; + // Add distance unit variable. + generatedMtlxSetupFunction += "distanceUnit"; + } - if (arg.isOutput) + // Add the BSDF inputs that will be output from setup function. + for (int i = 0; i < res.bsdfInputs.size(); i++) + { + generatedMtlxSetupFunction += ",\n"; + if (res.bsdfInputs[i].name.compare("normal") == 0) { - if (arg.name.compare("normal") == 0) - { - // Output the generated normal straight into the materialNormal output parameter. - generatedMtlxSetupFunction += "\t\tmaterialNormal"; - } - else - { - // Output arguments are written to the material struct. - generatedMtlxSetupFunction += "\t\tmaterial." + arg.name; - } + // Output the generated normal straight into the materialNormal output parameter. + generatedMtlxSetupFunction += "\t\tmaterialNormal"; } else { - if (hardcodedInputs.find(arg.name) != hardcodedInputs.end()) - { - if (_materialXOutputParamMapping.find(arg.name) != - _materialXOutputParamMapping.end()) - { - generatedMtlxSetupFunction += - "\t\tgMaterialConstants." + _materialXOutputParamMapping[arg.name]; - } - else - { - // Hardcoded inputs are specified in the HLSL above and passed in directly. - generatedMtlxSetupFunction += "\t\t" + arg.name; - } - } - else - { - // Other arguments are read from constant buffer transfered from CPU. - generatedMtlxSetupFunction += "\t\tgMaterialConstants." + arg.name; - } + // Output into the material struct to be returned. + string mappedBSDFInput = _bsdfInputParamMapping[res.bsdfInputs[i].name]; + AU_ASSERT( + !mappedBSDFInput.empty(), "Invalid BSDF input:%s", res.bsdfInputs[i].name.c_str()); + // Output arguments are written to the material struct. + generatedMtlxSetupFunction += "\t\tmaterial." + mappedBSDFInput; } } // Finish function call. generatedMtlxSetupFunction += ");\n"; + // Replace overwrite metal color with base color. + // TODO: Metal color is not supported by materialX and not in Standard Surface definition. So + // maybe remove it? + generatedMtlxSetupFunction += "material.metalColor = material.baseColor;"; + // Return the generated material struct. generatedMtlxSetupFunction += "\treturn material;\n"; @@ -539,22 +361,37 @@ bool MaterialGenerator::generate( generatedMtlxSetupFunction += "}\n"; // Output the material name - sourceOut.bsdf = ""; - sourceOut.setup = generatedMtlxSetupFunction; - sourceOut.name = materialName; - - return true; -} - -void MaterialGenerator::generateDefinitions(string& definitionHLSLOut) -{ + MaterialShaderSource source(materialName, generatedMtlxSetupFunction); - // Generate the shared definitions used by all MaterialX material types. + // Generate the function definitions used by the MaterialX code. string definitionGLSL; _pCodeGenerator->generateDefinitions(&definitionGLSL); - // Set the shared definitions; - definitionHLSLOut = (GLSLToHLSL(definitionGLSL)); + // Convert the definitions to HLSL and add to definitions string of the source object. + source.definitions = "#include \"GLSLToHLSL.slang\"\n"; + source.definitions += "#include \"MaterialXCommon.slang\"\n"; + source.definitions += (GLSLToHLSL(definitionGLSL)); + + // Create the default values object from the generated properties and textures. + MaterialDefaultValues defaults( + res.materialProperties, res.materialPropertyDefaults, textureVars); + + // Material is opaque if neither opacity or transmission input is used. + bool isOpaque = bsdfInputs.find("opacity") == bsdfInputs.end() && + bsdfInputs.find("transmission") == bsdfInputs.end(); + + // Create update function which just sets opacity flag based on materialX inputs. + function updateFunc = [isOpaque](MaterialBase& mtl) { + mtl.setIsOpaque(isOpaque); + }; + + // Create the material definition. + pDef = make_shared(source, defaults, updateFunc, isOpaque); + + // Set in the cache. + _definitions[document] = pDef; + + return pDef; } } // namespace MaterialXCodeGen diff --git a/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h index e41f7dc..927d27f 100644 --- a/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h +++ b/Libraries/Aurora/Source/MaterialX/MaterialGenerator.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,10 +14,11 @@ #pragma once #include "BSDFCodeGenerator.h" +#include "MaterialBase.h" BEGIN_AURORA -struct MaterialTypeSource; +struct MaterialShaderSource; namespace MaterialXCodeGen { @@ -25,26 +26,22 @@ namespace MaterialXCodeGen class MaterialGenerator { public: - MaterialGenerator(IRenderer* pRenderer, const string& mtlxFolder); + MaterialGenerator(const string& mtlxFolder); /// Generate shader code for material. - bool generate(const string& document, map* pDefaultValuesOut, - MaterialTypeSource& sourceOut); - - // Generate shared definition functions. - void generateDefinitions(string& definitionHLSLOut); + shared_ptr generate(const string& document); // Get the code generator used to generate material shader code. - MaterialXCodeGen::BSDFCodeGenerator& codeGenerator() { return *_pCodeGenerator; } + BSDFCodeGenerator& codeGenerator() { return *_pCodeGenerator; } private: // Code generator used to generate MaterialX files. unique_ptr _pCodeGenerator; // Mapping from a MaterialX output property to a Standard Surface property. - map _materialXOutputParamMapping; + map _bsdfInputParamMapping; - IRenderer* _pRenderer; + map> _definitions; }; } // namespace MaterialXCodeGen diff --git a/Libraries/Aurora/Source/Properties.h b/Libraries/Aurora/Source/Properties.h index f467e0d..ddde423 100644 --- a/Libraries/Aurora/Source/Properties.h +++ b/Libraries/Aurora/Source/Properties.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/RendererBase.cpp b/Libraries/Aurora/Source/RendererBase.cpp index 87561e3..bb36954 100644 --- a/Libraries/Aurora/Source/RendererBase.cpp +++ b/Libraries/Aurora/Source/RendererBase.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -157,13 +157,8 @@ bool RendererBase::updateFrameDataGPUStruct(FrameData* pStaging) const Foundation::BoundingBox& bounds = _pScene->bounds(); frameData.sceneSize = glm::length(bounds.max() - bounds.min()); - // Get the light properties. - // NOTE: The light direction is inverted, as expected by the shaders. The light size is - // converted from a diameter in radians to the cosine of the radius. - frameData.lightDir = -_pScene->lightDirection(); - frameData.lightColorAndIntensity = make_vec4(_pScene->lightColor()); - frameData.lightColorAndIntensity.w = _pScene->lightIntensity(); - frameData.lightCosRadius = cos(0.5f * _pScene->lightAngularDiameter()); + // Copy the current light buffer for the scene to this frame's light data. + memcpy(&frameData.lights, &_pScene->lights(), sizeof(frameData.lights)); int debugMode = _values.asInt(kLabelDebugMode); int traceDepth = _values.asInt(kLabelTraceDepth); diff --git a/Libraries/Aurora/Source/RendererBase.h b/Libraries/Aurora/Source/RendererBase.h index 2481a70..26cbb0e 100644 --- a/Libraries/Aurora/Source/RendererBase.h +++ b/Libraries/Aurora/Source/RendererBase.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ #include "AssetManager.h" #include "Properties.h" +#include "SceneBase.h" BEGIN_AURORA @@ -96,29 +97,67 @@ class RendererBase : public IRenderer, public FixedValues static const int kMaxTraceDepth; protected: - // Per-frame GPU uniform data. + // Layout of per-frame parameters. + // Must match the GPU version Frame.slang. struct FrameData { + // The view-projection matrix. mat4 cameraViewProj; + + // The inverse view matrix, also transposed. The *rows* must have the desired vectors: + // right, up, front, and eye position. HLSL array access with [] returns rows, not columns, + // hence the need for the matrix to be supplied transposed. mat4 cameraInvView; + + // The dimensions of the view (in world units) at a distance of 1.0 from the camera, which + // is useful to build ray directions. vec2 viewSize; + + // Whether the camera is using an orthographic projection. Otherwise a perspective + // projection is assumed. int isOrthoProjection; + + // The distance from the camera for sharpest focus, for depth of field. float focalDistance; + + // The diameter of the lens for depth of field. If this is zero, there is no depth of field, + // i.e. pinhole camera. float lensRadius; + + // The size of the scene, specifically the maximum distance between any two points in the + // scene. float sceneSize; - vec2 _padding1; - vec3 lightDir; - float _padding2; - vec4 lightColorAndIntensity; - float lightCosRadius; + + // Whether shadow evaluation should treat all objects as opaque, as a performance + // optimization. int isOpaqueShadowsEnabled; + + // Whether to write the NDC depth result to an output texture. int isDepthNDCEnabled; + + // Whether to render the diffuse material component only. int isDiffuseOnlyEnabled; + + // Whether to display shading errors as bright colored samples. int isDisplayErrorsEnabled; + + // Whether denoising is enabled, which affects how path tracing is performed. int isDenoisingEnabled; + + // Whether to write the AOV data required for denoising. int isDenoisingAOVsEnabled; + + // The maximum recursion level (or path length) when tracing rays. int traceDepth; + + // The maximum luminance for path tracing samples, for simple firefly clamping. float maxLuminance; + + // Pad to 16 byte boundary. + vec2 _padding1; + + // Current light data for scene (duplicated each frame in flight.) + SceneBase::LightData lights; }; // Accumulation settings GPU data. diff --git a/Libraries/Aurora/Source/ResourceStub.cpp b/Libraries/Aurora/Source/ResourceStub.cpp index fcc3618..55915cd 100644 --- a/Libraries/Aurora/Source/ResourceStub.cpp +++ b/Libraries/Aurora/Source/ResourceStub.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -42,6 +42,8 @@ void ResourceStub::clearReferences() void ResourceStub::setProperties(const Properties& props) { + // Count of the number of properties actually applied. + size_t numApplied = 0; // Iterate through all the supplied properties. for (auto iter = props.begin(); iter != props.end(); iter++) @@ -49,48 +51,57 @@ void ResourceStub::setProperties(const Properties& props) // Get name of property. const Path& propName = iter->first; - // Is this a string property (as in, it does *not* have a string applicator function - // assoicated with it)? - bool isStringProperty = - _stringPropertyApplicators.find(propName) != _stringPropertyApplicators.end(); - - // We assume any string properies that do not have an explicit string applicator function - // are actually paths not strings. - if (iter->second.type == PropertyValue::Type::String && !isStringProperty) + // Only apply the property if different from existing value. + if (_properties[propName] != iter->second) { - // Setup a reference for this path property. - const Path& path = iter->second.asString(); - setReference(propName, path); - } + // Increment applied count. + numApplied++; - // We assume any string array properies are actually paths not strings. - else if (iter->second.type == PropertyValue::Type::Strings) - { - Strings paths = iter->second.asStrings(); + // Is this a string property (as in, it does *not* have a string applicator function + // associated with it)? + bool isStringProperty = + _stringPropertyApplicators.find(propName) != _stringPropertyApplicators.end(); - // Set references to all the paths in form "propName[n]" - for (int i = 0; i < static_cast(paths.size()); i++) + // We assume any string properties that do not have an explicit string applicator + // function are actually paths not strings. + if (iter->second.type == PropertyValue::Type::String && !isStringProperty) { - string refName = getIndexedPropertyName(propName, i); - setReference(refName, paths[i]); + // Setup a reference for this path property. + const Path& path = iter->second.asString(); + setReference(propName, path); } - // Clear any previous references that exist past end of array. - for (int i = static_cast(paths.size());; i++) + // We assume any string array properties are actually paths not strings. + else if (iter->second.type == PropertyValue::Type::Strings) { - string refName = getIndexedPropertyName(propName, i); - if (_references.find(refName) == _references.end()) - break; - else - setReference(refName, ""); + Strings paths = iter->second.asStrings(); + + // Set references to all the paths in form "propName[n]" + for (int i = 0; i < static_cast(paths.size()); i++) + { + string refName = getIndexedPropertyName(propName, i); + setReference(refName, paths[i]); + } + + // Clear any previous references that exist past end of array. + for (int i = static_cast(paths.size());; i++) + { + string refName = getIndexedPropertyName(propName, i); + if (_references.find(refName) == _references.end()) + break; + else + setReference(refName, ""); + } } - } - // Apply the property (will apply to the resource itself if we are active.) - applyProperty(iter->first, iter->second); + // Apply the property (will apply to the resource itself if we are active.) + applyProperty(iter->first, iter->second); + } } - if (isActive() && _tracker.resourceModified) + // Call the resource modified callback if the resource is active and some propreties were + // applied. + if (isActive() && numApplied && _tracker.resourceModified) _tracker.resourceModified(*this, props); } diff --git a/Libraries/Aurora/Source/ResourceStub.h b/Libraries/Aurora/Source/ResourceStub.h index 384213e..7796659 100644 --- a/Libraries/Aurora/Source/ResourceStub.h +++ b/Libraries/Aurora/Source/ResourceStub.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/ResourceTracker.h b/Libraries/Aurora/Source/ResourceTracker.h index 307b9e2..5faa6fb 100644 --- a/Libraries/Aurora/Source/ResourceTracker.h +++ b/Libraries/Aurora/Source/ResourceTracker.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ class ResourceNotifier bool empty() const { return _resourceData.empty(); } // Have changes been made to any resources this frame? - bool modified() const { return _modified; } + bool changedThisFrame() const { return _changedThisFrame; } // Get the index for the provided resource implentation within active list. // Will return -1 if resource not currently active. @@ -81,17 +81,17 @@ class ResourceNotifier // Get number of currently active resources. size_t count() const { return _resourceData.size(); } - void clearModifiedFlag() { _modified = false; } + void clearChangedThisFrameFlag() { _changedThisFrame = false; } void clear() { _resourceData.clear(); _indexLookup.clear(); - _modified = true; + _changedThisFrame = true; } void add(ImplementationClass* pDataPtr) { - _modified = true; + _changedThisFrame = true; // Add resource implementation to data list, and add index to lookup. _indexLookup[pDataPtr] = _resourceData.size(); _resourceData.push_back(PointerWrapper(pDataPtr)); @@ -100,7 +100,7 @@ class ResourceNotifier private: vector> _resourceData; map _indexLookup; - bool _modified = false; + bool _changedThisFrame = false; }; /// Typed tracker that will maintain list of active resource stubs of a given type. @@ -147,13 +147,17 @@ class TypedResourceTracker // resource list) if no changes recorded in the tracker for this frame. bool update() { + // Clear the modified notifier (whether anything changed or not.) _modifiedNotifier.clear(); + // Clear the active notifier changed flag (whether anything changed or not.) + _activeNotifier.clearChangedThisFrameFlag(); + // If tracker not changed, do nothing. // This keeps active resource list from previous frame but clears the modified flag. - if (!changed()) + if (_activatedResources.empty() && _deactivatedResources.empty() && + _modifiedResources.empty()) { - _activeNotifier.clearModifiedFlag(); return false; } @@ -169,20 +173,24 @@ class TypedResourceTracker } } - // Set the modified flag and clear lists of resources. - _activeNotifier.clear(); - - // Iterate through all the active resources in tracker. - for (auto iter = _currentlyActiveResources.begin(); iter != _currentlyActiveResources.end(); - iter++) + // If any resources have been activated or deactivated, update the active notifier. + if (!_activatedResources.empty() || !_deactivatedResources.empty()) { - // Get the GPU implementation for the resource stub (sometimes will be null if error in - // activation) - const ResourceClass* pRes = iter->second; - ImplementationClass* pDataPtr = pRes->resource().get(); - if (pDataPtr) + // Set the modified flag and clear lists of resources. + _activeNotifier.clear(); + + // Iterate through all the active resources in tracker. + for (auto iter = _currentlyActiveResources.begin(); + iter != _currentlyActiveResources.end(); iter++) { - _activeNotifier.add(pDataPtr); + // Get the GPU implementation for the resource stub (sometimes will be null if error + // in activation) + const ResourceClass* pRes = iter->second; + ImplementationClass* pDataPtr = pRes->resource().get(); + if (pDataPtr) + { + _activeNotifier.add(pDataPtr); + } } } @@ -209,7 +217,12 @@ class TypedResourceTracker } // Have any changes (modifications, activations or deactivations) been made this frame? - bool changed() const { return _modifiedNotifier.modified() || _activeNotifier.modified(); } + bool changedThisFrame() const + { + // Is the modified list empty or has the active list changed (due to activation or + // deactivation) ? + return !_modifiedNotifier.empty() || _activeNotifier.changedThisFrame(); + } // Get the currently active set of resource implementations, for this frame. ResourceNotifier& active() { return _activeNotifier; } @@ -219,6 +232,7 @@ class TypedResourceTracker ResourceNotifier& modified() { return _modifiedNotifier; } const ResourceNotifier& modified() const { return _modifiedNotifier; } + // How many resources are active? size_t activeCount() { return _currentlyActiveResources.size(); } private: diff --git a/Libraries/Aurora/Source/Resources.cpp b/Libraries/Aurora/Source/Resources.cpp index c50bcf2..d4e8c83 100644 --- a/Libraries/Aurora/Source/Resources.cpp +++ b/Libraries/Aurora/Source/Resources.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -130,6 +130,13 @@ MaterialResource::MaterialResource(const Aurora::Path& path, const ResourceMap& _resource->values().setFloat(propName, value); } } }); + // Setup a default applicator function so all vec2 properties are applied to the underlying + // material resource. + initializeVec2Applicators( + { { ResourceStub::kDefaultPropName, [this](string propName, vec2 value) { + _resource->values().setFloat2(propName, (float*)&value); + } } }); + // Setup a default applicator function so all vec3 properties are applied to the underlying // material resource. initializeVec3Applicators( diff --git a/Libraries/Aurora/Source/Resources.h b/Libraries/Aurora/Source/Resources.h index dc5cc8c..927a860 100644 --- a/Libraries/Aurora/Source/Resources.h +++ b/Libraries/Aurora/Source/Resources.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/SceneBase.cpp b/Libraries/Aurora/Source/SceneBase.cpp index e1391b8..e71f8bd 100644 --- a/Libraries/Aurora/Source/SceneBase.cpp +++ b/Libraries/Aurora/Source/SceneBase.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -122,24 +122,6 @@ void SceneBase::setBounds(const float* min, const float* max) assert(_bounds.isValid()); } -void SceneBase::setLight( - float intensity, const rgb& color, const vec3& direction, float angularDiameter) -{ - _lightIntensity = intensity; - _lightColor = color; - _lightDirection = normalize(direction); - _lightAngularDiameter = angularDiameter; -} - -void SceneBase::setLight( - float intensity, const float* color, const float* direction, float angularDiameter) -{ - _lightIntensity = intensity; - _lightColor = make_vec3(color); - _lightDirection = normalize(make_vec3(direction)); - _lightAngularDiameter = angularDiameter; -} - void SceneBase::addPermanent(const Path& path) { _resources[path]->incrementPermanentRefCount(); @@ -407,8 +389,9 @@ void SceneBase::setInstanceProperties(const Paths& paths, const Properties& inst } } -void SceneBase::update() +void SceneBase::preUpdate() { + // Create a default instance so the scene is not completely empty. if (_instances.activeCount() == 0) { if (!_pDefaultInstanceResource->isActive()) @@ -419,7 +402,10 @@ void SceneBase::update() if (_pDefaultInstanceResource->isActive()) _pDefaultInstanceResource->decrementPermanentRefCount(); } +} +void SceneBase::update() +{ // Update all the active resources, this will build the ResourceNotifier that stores the set of // active resources for this frame. _instances.update(); diff --git a/Libraries/Aurora/Source/SceneBase.h b/Libraries/Aurora/Source/SceneBase.h index 76402b0..da14967 100644 --- a/Libraries/Aurora/Source/SceneBase.h +++ b/Libraries/Aurora/Source/SceneBase.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,6 +26,38 @@ struct ImageAsset; class SceneBase : public IScene { public: + // Limits for light counts of different light light types. + enum LightLimits + { + kMaxDistantLights = 4, // Maximum distant lights (must match GPU version in Frame.slang). + }; + + // Structure representing a single distant light. + // Must match GPU struct in Frame.slang + struct DistantLight + { + // Light color (in RGB) and intensity (in alpha channel.) + vec4 colorAndIntensity; + // Direction of light (inverted as expected by shaders.) + vec3 direction = vec3(0, 0, 1); + // The light size is converted from a diameter in radians to the cosine of the radius. + float cosRadius = 0.0f; + }; + + // Structure representing the lights in the scene. + // Must match GPU struct in Frame.slang + struct LightData + { + // Array of distant lights, only first distantLightCount are used. + DistantLight distantLights[LightLimits::kMaxDistantLights]; + + // Number of active distant lights. + int distantLightCount = 0; + + // Explicitly pad struct to 16-byte boundary. + int pad[3]; + }; + SceneBase(IRenderer* pRenderer) : _pRenderer(pRenderer) {} ~SceneBase(); @@ -34,10 +66,6 @@ class SceneBase : public IScene ResourceType getResourceType(const Path& path) override; void setBounds(const vec3& min, const vec3& max) override; void setBounds(const float* min, const float* max) override; - void setLight(float intensity, const rgb& color, const vec3& direction, - float angularDiameter = 0.1f) override; - void setLight(float intensity, const float* color, const float* direction, - float angularDiameter) override; void setImageDescriptor(const Path& atPath, const ImageDescriptor& desc) override; void setImageFromFilePath( const Path& atPath, const string& filePath, bool forceLinear, bool isEnvironment) override; @@ -64,18 +92,18 @@ class SceneBase : public IScene /*** Functions ***/ void update(); + void preUpdate(); + shared_ptr defaultEnvironment(); const Foundation::BoundingBox& bounds() const { return _bounds; } - float lightIntensity() const { return _lightIntensity; } - const vec3& lightColor() const { return _lightColor; } - const vec3& lightDirection() const { return _lightDirection; } - float lightAngularDiameter() const { return _lightAngularDiameter; } shared_ptr defaultMaterialResource() { return _pDefaultMaterialResource; } RendererBase* rendererBase(); + LightData& lights() { return _lights; } + protected: // Is the path a valid resource path. virtual bool isPathValid(const Path& path); @@ -95,10 +123,8 @@ class SceneBase : public IScene IRenderer* _pRenderer = nullptr; Foundation::BoundingBox _bounds; - float _lightIntensity = 1.0f; - rgb _lightColor = rgb(1.0f, 1.0f, 1.0f); - vec3 _lightDirection = normalize(vec3(-1.0f, -0.5f, -1.0f)); - float _lightAngularDiameter = 0.1f; + + LightData _lights; ResourceMap _resources; diff --git a/Libraries/Aurora/Source/Shaders/BSDFCommon.slang b/Libraries/Aurora/Source/Shaders/BSDFCommon.slang index c14b804..7039864 100644 --- a/Libraries/Aurora/Source/Shaders/BSDFCommon.slang +++ b/Libraries/Aurora/Source/Shaders/BSDFCommon.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang b/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang index 675ef02..d504cdb 100644 --- a/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang +++ b/Libraries/Aurora/Source/Shaders/BackgroundMissShader.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Prefix containing the common code used by all material types. -// NOTE: Does not contain a hit shader. Hit shaders for the different material types must be -// appended to this file. #include "PathTracingCommon.slang" -// The background miss shader, which evaluates the environment as a background. This is -// typically done from primary, transmission, and transparency rays. -[shader("miss")] void BackgroundMissShader(inout RayPayload rayPayload) +// The background miss shader, which evaluates the environment as a background. This is typically +// done from primary, transmission, and transparency rays. +[shader("miss")] +void BackgroundMissShader(inout RayPayload rayPayload) { // Initialize the radiance ray payload for a miss. rayPayload.radianceRay.clear(); @@ -29,8 +27,7 @@ float3 color = evaluateEnvironment(environment, WorldRayDirection(), true); // Store the environment color. - // NOTE: The miss result is considered to be part of direct lighting, to allow for simpler logic - // during accumulation. - rayPayload.radianceRay.color = color; - rayPayload.radianceRay.direct = color; -} + // NOTE: The miss result will not be denoised, so it is included in the "extra" shading. + rayPayload.radianceRay.color = color; + rayPayload.radianceRay.extra = color; +} \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang b/Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang deleted file mode 100644 index 8ac50bf..0000000 --- a/Libraries/Aurora/Source/Shaders/ClosestHitEntryPointTemplate.slang +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2022 Autodesk, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Entry point template that defines a hit shader for a material type. -// NOTE: This file is not valid HLSL as is. This template must be configured at runtime by -// replacing the tags surrounded by the @ character: -// -@MATERIAL_TYPE@ with the unique material type name for this shader. - -// Closest hit shader for radiance rays. -#include "PathTracingCommon.slang" -#include "InitializeMaterial.slang" -#include "ShadeFunctions.slang" - -[shader("closesthit")] void @MATERIAL_TYPE@RadianceHitShader(inout RayPayload rayPayload, in BuiltInTriangleIntersectionAttributes hit) -{ - int depth = rayPayload.radianceRay.depth; - int maxDepth = gFrameData.traceDepth; - bool isOpaqueShadowsEnabled = gFrameData.isOpaqueShadowsEnabled; - - // Get the interpolated vertex data for the hit triangle, at the hit barycentric coordinates. - uint triangleIndex = PrimitiveIndex(); - float3 barycentrics = computeBarycentrics(hit); - ShadingData shading = computeShadingData( - gGeometry, triangleIndex, barycentrics, ObjectToWorld3x4()); - - // Initialize the view direction. - float3 V = -WorldRayDirection(); - float3 materialNormal = shading.normal; - bool isGeneratedNormal; - - // Initialize the material values for repeated evaluations at the current shading point. Also - // initialize the environment. - Material material = initializeMaterial( - shading, - ObjectToWorld3x4(), - materialNormal, - isGeneratedNormal); - Environment environment = prepareEnvironmentValues(); - - // If a new normal has been generated, transform it to world space and build a corresponding - // basis from it. - if (isGeneratedNormal) - { - shading.normal = materialNormal; - buildBasis(shading.normal, shading.tangent, shading.bitangent); - } - - // Modify the material properties if only the diffuse component should be renderered. - if (gFrameData.isDiffuseOnlyEnabled) - { - material.base = 1.0f; - material.specular = 0.0f; - material.metalness = 0.0f; - } - - // Shade any material layers (for front facing hits only) if the ENABLE_LAYERS option is - // defined. - // TODO: Should this be done after the transparency evaluation below, i.e. it is possible this - // work will be thrown away for transparent samples. -#if ENABLE_LAYERS - if (dot(shading.normal, V) > 0.0) - { - // Iterate in reverse order, so innermost layer is shaded last. - for (int layer = gMaterialLayerCount - 1; layer >= 0; layer--) - { - int layerMissShaderIdx = getMaterialLayerIndex(layer); - - // Shade the layer, and return if the ray was absorbed. - if (shadeMaterialLayer(gNullScene, layerMissShaderIdx, rayPayload.radianceRay, shading, - hit, depth, maxDepth)) - { - return; - } - } - } -#endif - - // Handle opacity (transparency) by stochastically determining whether the hit should be - // skipped, based on the luminance of the material opacity. If so, use the ray payload from a - // ray traced at the hit point, i.e. passing straight through the geometry, and return. - // NOTE: This could also be done with an any hit shader, with possibly better or worse - // performance. - float3 transparency = 1.0f - material.opacity; - float P = luminance(transparency); - if (random2D(rayPayload.radianceRay.rng).x < P) - { - // Trace a radiance ray with the unmodified ray direction, and the hit position. Use the - // background miss shader so that the background is sampled, since this is for transparency. - // NOTE: The geometric position (geomPosition) is used to avoid self-intersection. - rayPayload.radianceRay = traceRadianceRay(gScene, environment, shading.geomPosition, -V, - M_RAY_TMIN, depth, maxDepth, true, rayPayload.radianceRay.rng); - - // Scale the color components of the ray payload by transparency, and normalized by the - // probability of this segment being considered transparent. - rayPayload.radianceRay.scaleColor(transparency / P); - - // Nothing more to do. - return; - } - - // Compute the NDC depth and view depth of the hit position: - // - NDC depth: Compute the clip space position using the supplied view-projection matrix, then - // divide the Z component by W, and remap from [-1.0, 1.0] to [0.0, 1.0]. - // - View Depth: For a primary ray, this is simply the T value of the ray. We don't use this - // value for non-primary rays; it is recorded here but not used. - float4 positionClip = mul(gFrameData.cameraViewProj, float4(shading.geomPosition, 1.0f)); - float depthNDC = (positionClip.z / positionClip.w + 1.0f) / 2.0f; - float depthView = RayTCurrent(); - - // Clamp roughness for the ray payload to a minimum, because the denoiser handles materials with - // low (near-zero) roughness poorly, leading to a noisy result. This addresses two separate but - // related issues: low-roughness metallic materials reflecting noisy surroundings, and low- - // roughness dielectric materials where the glossy lobe is sparsely sampled. - // NOTE: The roughness in the ray payload is used for denoising only. The material specular - // roughness (used in shading) is not modified here. - static const float kMinRoughness = 0.05f; - float clampedRoughness = max(material.specularRoughness, kMinRoughness); - - // Store initial data in the ray payload. - rayPayload.radianceRay.color = BLACK; - rayPayload.radianceRay.alpha = 1.0f; - rayPayload.radianceRay.direct = BLACK; - rayPayload.radianceRay.depthNDC = depthNDC; - rayPayload.radianceRay.depthView = depthView; - rayPayload.radianceRay.normal = shading.normal; - rayPayload.radianceRay.baseColor = material.baseColor; - rayPayload.radianceRay.roughness = clampedRoughness; - rayPayload.radianceRay.metalness = material.metalness; - rayPayload.radianceRay.indirect.clear(); - - // Shade with the global directional light. Skip this if the light intensity is zero. - if (gFrameData.lightColorAndIntensity.a > 0.0f) - { - rayPayload.radianceRay.direct = shadeDirectionalLight(gFrameData.lightDir, - gFrameData.lightColorAndIntensity, gFrameData.lightCosRadius, gScene, material, shading, - V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); - - // Add the direct lighting to the color result. - // NOTE: For denoising purposes, "direct" lighting only includes the directional light, and - // not the environment light computed below. - rayPayload.radianceRay.color += rayPayload.radianceRay.direct; - } - - // When denoising, primary rays for indirect lighting should not include the base color (also - // known as albedo, as used by diffuse lobes) in order to avoid blurring out color details. So - // the base color is set to white here and the color is added back after denoising. - if (gFrameData.isDenoisingEnabled && rayPayload.radianceRay.depth == 1) - { - material.baseColor = WHITE; - } - - // Shade with the environment light depending on the importance sampling type: - // - BSDF: The environment is treated as indirect light, and is evaluated in the miss shader. - // - Environment: Sample the environment light as direct lighting. - // - MIS: Use multiple importance sampling on both the material and the light. - // NOTE: For denoising purposes, "indirect" lighting includes the environment light. - if (IMPORTANCE_SAMPLING_MODE != IMPORTANCE_SAMPLING_BSDF) - { - float3 environmentRadiance = BLACK; - if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_ENVIRONMENT) - { - // TODO: This does not currently contribute to indirect lighting for denoising purposes. - // That will require having the material evaluation return separate diffuse and glossy - // components. - environmentRadiance = - shadeEnvironmentLightDirect(environment, gScene, material, shading, - V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); - } - else if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_MIS) - { - environmentRadiance = shadeEnvironmentLightMIS(environment, gScene, material, shading, V, isOpaqueShadowsEnabled, depth, - maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.indirect); - } - - // Add the radiance from the environment light to the color result. - rayPayload.radianceRay.color += environmentRadiance; - } - - // Shade with indirect light from the surrounding scene (i.e. path tracing). - rayPayload.radianceRay.color += shadeIndirectLight( - gScene, environment, material, shading, V, depth, maxDepth, rayPayload.radianceRay.rng, - rayPayload.radianceRay.alpha, rayPayload.radianceRay.indirect); - - // Scale the color components of the ray payload by opacity, and normalized by the probability - // of this segment being considered opaque. - // NOTE: The shading functions do not individually consider opacity, so that it can be handled - // in one place here. - rayPayload.radianceRay.scaleColor(material.opacity / (1.0f - P)); -} diff --git a/Libraries/Aurora/Source/Shaders/Colors.slang b/Libraries/Aurora/Source/Shaders/Colors.slang index c100076..5d44572 100644 --- a/Libraries/Aurora/Source/Shaders/Colors.slang +++ b/Libraries/Aurora/Source/Shaders/Colors.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/DefaultMaterialUniformAccessors.slang b/Libraries/Aurora/Source/Shaders/DefaultMaterialUniformAccessors.slang new file mode 100644 index 0000000..1e50936 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/DefaultMaterialUniformAccessors.slang @@ -0,0 +1,287 @@ +// Auto-generated by unit test BasicTest in AuroraExternals test suite. + + // Get property base from byte address buffer +float Material_base(ByteAddressBuffer buf) { + return asfloat(buf.Load(0)); +} + + // Get property base_color from byte address buffer +float3 Material_baseColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(4)); +} + + // Get property diffuse_roughness from byte address buffer +float Material_diffuseRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(16)); +} + + // Get property metalness from byte address buffer +float Material_metalness(ByteAddressBuffer buf) { + return asfloat(buf.Load(20)); +} + + // Get property specular from byte address buffer +float Material_specular(ByteAddressBuffer buf) { + return asfloat(buf.Load(24)); +} + + // Get property specular_color from byte address buffer +float3 Material_specularColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(32)); +} + + // Get property specular_roughness from byte address buffer +float Material_specularRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(44)); +} + + // Get property specular_IOR from byte address buffer +float Material_specularIOR(ByteAddressBuffer buf) { + return asfloat(buf.Load(48)); +} + + // Get property specular_anisotropy from byte address buffer +float Material_specularAnisotropy(ByteAddressBuffer buf) { + return asfloat(buf.Load(52)); +} + + // Get property specular_rotation from byte address buffer +float Material_specularRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(56)); +} + + // Get property transmission from byte address buffer +float Material_transmission(ByteAddressBuffer buf) { + return asfloat(buf.Load(60)); +} + + // Get property transmission_color from byte address buffer +float3 Material_transmissionColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(64)); +} + + // Get property subsurface from byte address buffer +float Material_subsurface(ByteAddressBuffer buf) { + return asfloat(buf.Load(76)); +} + + // Get property subsurface_color from byte address buffer +float3 Material_subsurfaceColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(80)); +} + + // Get property subsurface_radius from byte address buffer +float3 Material_subsurfaceRadius(ByteAddressBuffer buf) { + return asfloat(buf.Load3(96)); +} + + // Get property subsurface_scale from byte address buffer +float Material_subsurfaceScale(ByteAddressBuffer buf) { + return asfloat(buf.Load(108)); +} + + // Get property subsurface_anisotropy from byte address buffer +float Material_subsurfaceAnisotropy(ByteAddressBuffer buf) { + return asfloat(buf.Load(112)); +} + + // Get property sheen from byte address buffer +float Material_sheen(ByteAddressBuffer buf) { + return asfloat(buf.Load(116)); +} + + // Get property sheen_color from byte address buffer +float3 Material_sheenColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(128)); +} + + // Get property sheen_roughness from byte address buffer +float Material_sheenRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(140)); +} + + // Get property coat from byte address buffer +float Material_coat(ByteAddressBuffer buf) { + return asfloat(buf.Load(144)); +} + + // Get property coat_color from byte address buffer +float3 Material_coatColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(148)); +} + + // Get property coat_roughness from byte address buffer +float Material_coatRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(160)); +} + + // Get property coat_anisotropy from byte address buffer +float Material_coatAnisotropy(ByteAddressBuffer buf) { + return asfloat(buf.Load(164)); +} + + // Get property coat_rotation from byte address buffer +float Material_coatRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(168)); +} + + // Get property coat_IOR from byte address buffer +float Material_coatIOR(ByteAddressBuffer buf) { + return asfloat(buf.Load(172)); +} + + // Get property coat_affect_color from byte address buffer +float Material_coatAffectColor(ByteAddressBuffer buf) { + return asfloat(buf.Load(176)); +} + + // Get property coat_affect_roughness from byte address buffer +float Material_coatAffectRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(180)); +} + + // Get property emission from byte address buffer +float Material_emission(ByteAddressBuffer buf) { + return asfloat(buf.Load(184)); +} + + // Get property emission_color from byte address buffer +float3 Material_emissionColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(192)); +} + + // Get property opacity from byte address buffer +float3 Material_opacity(ByteAddressBuffer buf) { + return asfloat(buf.Load3(208)); +} + + // Get property thin_walled from byte address buffer +int Material_thinWalled(ByteAddressBuffer buf) { + return buf.Load(220); +} + + // Get property has_base_color_image from byte address buffer +int Material_hasBaseColorTex(ByteAddressBuffer buf) { + return buf.Load(224); +} + + // Get property base_color_image_offset from byte address buffer +float2 Material_baseColorTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(228)); +} + + // Get property base_color_image_scale from byte address buffer +float2 Material_baseColorTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(240)); +} + + // Get property base_color_image_pivot from byte address buffer +float2 Material_baseColorTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(248)); +} + + // Get property base_color_image_rotation from byte address buffer +float Material_baseColorTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(256)); +} + + // Get property has_specular_roughness_image from byte address buffer +int Material_hasSpecularRoughnessTex(ByteAddressBuffer buf) { + return buf.Load(260); +} + + // Get property specular_roughness_image_offset from byte address buffer +float2 Material_specularRoughnessTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(264)); +} + + // Get property specular_roughness_image_scale from byte address buffer +float2 Material_specularRoughnessTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(272)); +} + + // Get property specular_roughness_image_pivot from byte address buffer +float2 Material_specularRoughnessTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(280)); +} + + // Get property specular_roughness_image_rotation from byte address buffer +float Material_specularRoughnessTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(288)); +} + + // Get property has_emission_color_image from byte address buffer +int Material_hasEmissionColorTex(ByteAddressBuffer buf) { + return buf.Load(292); +} + + // Get property emission_color_image_offset from byte address buffer +float2 Material_emissionColorTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(296)); +} + + // Get property emission_color_image_scale from byte address buffer +float2 Material_emissionColorTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(304)); +} + + // Get property emission_color_image_pivot from byte address buffer +float2 Material_emissionColorTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(312)); +} + + // Get property emission_color_image_rotation from byte address buffer +float Material_emissionColorTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(320)); +} + + // Get property has_opacity_image from byte address buffer +int Material_hasOpacityTex(ByteAddressBuffer buf) { + return buf.Load(324); +} + + // Get property opacity_image_offset from byte address buffer +float2 Material_opacityTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(328)); +} + + // Get property opacity_image_scale from byte address buffer +float2 Material_opacityTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(336)); +} + + // Get property opacity_image_pivot from byte address buffer +float2 Material_opacityTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(344)); +} + + // Get property opacity_image_rotation from byte address buffer +float Material_opacityTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(352)); +} + + // Get property has_normal_image from byte address buffer +int Material_hasNormalTex(ByteAddressBuffer buf) { + return buf.Load(356); +} + + // Get property normal_image_offset from byte address buffer +float2 Material_normalTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(360)); +} + + // Get property normal_image_scale from byte address buffer +float2 Material_normalTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(368)); +} + + // Get property normal_image_pivot from byte address buffer +float2 Material_normalTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(376)); +} + + // Get property normal_image_rotation from byte address buffer +float Material_normalTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(384)); +} + diff --git a/Libraries/Aurora/Source/Shaders/DefaultMaterialUniformBuffer.slang b/Libraries/Aurora/Source/Shaders/DefaultMaterialUniformBuffer.slang new file mode 100644 index 0000000..acda477 --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/DefaultMaterialUniformBuffer.slang @@ -0,0 +1,301 @@ +// Auto-generated by unit test BasicTest in AuroraExternals test suite. + +struct MaterialConstants +{ + float base; // Offset:0 Property:base + float3 baseColor; // Offset:4 Property:base_color + float diffuseRoughness; // Offset:16 Property:diffuse_roughness + float metalness; // Offset:20 Property:metalness + float specular; // Offset:24 Property:specular + int _padding0; + float3 specularColor; // Offset:32 Property:specular_color + float specularRoughness; // Offset:44 Property:specular_roughness + float specularIOR; // Offset:48 Property:specular_IOR + float specularAnisotropy; // Offset:52 Property:specular_anisotropy + float specularRotation; // Offset:56 Property:specular_rotation + float transmission; // Offset:60 Property:transmission + float3 transmissionColor; // Offset:64 Property:transmission_color + float subsurface; // Offset:76 Property:subsurface + float3 subsurfaceColor; // Offset:80 Property:subsurface_color + int _padding1; + float3 subsurfaceRadius; // Offset:96 Property:subsurface_radius + float subsurfaceScale; // Offset:108 Property:subsurface_scale + float subsurfaceAnisotropy; // Offset:112 Property:subsurface_anisotropy + float sheen; // Offset:116 Property:sheen + int _padding2; + int _padding3; + float3 sheenColor; // Offset:128 Property:sheen_color + float sheenRoughness; // Offset:140 Property:sheen_roughness + float coat; // Offset:144 Property:coat + float3 coatColor; // Offset:148 Property:coat_color + float coatRoughness; // Offset:160 Property:coat_roughness + float coatAnisotropy; // Offset:164 Property:coat_anisotropy + float coatRotation; // Offset:168 Property:coat_rotation + float coatIOR; // Offset:172 Property:coat_IOR + float coatAffectColor; // Offset:176 Property:coat_affect_color + float coatAffectRoughness; // Offset:180 Property:coat_affect_roughness + float emission; // Offset:184 Property:emission + int _padding4; + float3 emissionColor; // Offset:192 Property:emission_color + int _padding5; + float3 opacity; // Offset:208 Property:opacity + int thinWalled; // Offset:220 Property:thin_walled + int hasBaseColorTex; // Offset:224 Property:has_base_color_image + float2 baseColorTexOffset; // Offset:228 Property:base_color_image_offset + int _padding6; + float2 baseColorTexScale; // Offset:240 Property:base_color_image_scale + float2 baseColorTexPivot; // Offset:248 Property:base_color_image_pivot + float baseColorTexRotation; // Offset:256 Property:base_color_image_rotation + int hasSpecularRoughnessTex; // Offset:260 Property:has_specular_roughness_image + float2 specularRoughnessTexOffset; // Offset:264 Property:specular_roughness_image_offset + float2 specularRoughnessTexScale; // Offset:272 Property:specular_roughness_image_scale + float2 specularRoughnessTexPivot; // Offset:280 Property:specular_roughness_image_pivot + float specularRoughnessTexRotation; // Offset:288 Property:specular_roughness_image_rotation + int hasEmissionColorTex; // Offset:292 Property:has_emission_color_image + float2 emissionColorTexOffset; // Offset:296 Property:emission_color_image_offset + float2 emissionColorTexScale; // Offset:304 Property:emission_color_image_scale + float2 emissionColorTexPivot; // Offset:312 Property:emission_color_image_pivot + float emissionColorTexRotation; // Offset:320 Property:emission_color_image_rotation + int hasOpacityTex; // Offset:324 Property:has_opacity_image + float2 opacityTexOffset; // Offset:328 Property:opacity_image_offset + float2 opacityTexScale; // Offset:336 Property:opacity_image_scale + float2 opacityTexPivot; // Offset:344 Property:opacity_image_pivot + float opacityTexRotation; // Offset:352 Property:opacity_image_rotation + int hasNormalTex; // Offset:356 Property:has_normal_image + float2 normalTexOffset; // Offset:360 Property:normal_image_offset + float2 normalTexScale; // Offset:368 Property:normal_image_scale + float2 normalTexPivot; // Offset:376 Property:normal_image_pivot + float normalTexRotation; // Offset:384 Property:normal_image_rotation + int _padding7; + int _padding8; + int _padding9; +} +; + +float Material_base(MaterialConstants mtl) { + return mtl.base; +} + +float3 Material_baseColor(MaterialConstants mtl) { + return mtl.baseColor; +} + +float Material_diffuseRoughness(MaterialConstants mtl) { + return mtl.diffuseRoughness; +} + +float Material_metalness(MaterialConstants mtl) { + return mtl.metalness; +} + +float Material_specular(MaterialConstants mtl) { + return mtl.specular; +} + +float3 Material_specularColor(MaterialConstants mtl) { + return mtl.specularColor; +} + +float Material_specularRoughness(MaterialConstants mtl) { + return mtl.specularRoughness; +} + +float Material_specularIOR(MaterialConstants mtl) { + return mtl.specularIOR; +} + +float Material_specularAnisotropy(MaterialConstants mtl) { + return mtl.specularAnisotropy; +} + +float Material_specularRotation(MaterialConstants mtl) { + return mtl.specularRotation; +} + +float Material_transmission(MaterialConstants mtl) { + return mtl.transmission; +} + +float3 Material_transmissionColor(MaterialConstants mtl) { + return mtl.transmissionColor; +} + +float Material_subsurface(MaterialConstants mtl) { + return mtl.subsurface; +} + +float3 Material_subsurfaceColor(MaterialConstants mtl) { + return mtl.subsurfaceColor; +} + +float3 Material_subsurfaceRadius(MaterialConstants mtl) { + return mtl.subsurfaceRadius; +} + +float Material_subsurfaceScale(MaterialConstants mtl) { + return mtl.subsurfaceScale; +} + +float Material_subsurfaceAnisotropy(MaterialConstants mtl) { + return mtl.subsurfaceAnisotropy; +} + +float Material_sheen(MaterialConstants mtl) { + return mtl.sheen; +} + +float3 Material_sheenColor(MaterialConstants mtl) { + return mtl.sheenColor; +} + +float Material_sheenRoughness(MaterialConstants mtl) { + return mtl.sheenRoughness; +} + +float Material_coat(MaterialConstants mtl) { + return mtl.coat; +} + +float3 Material_coatColor(MaterialConstants mtl) { + return mtl.coatColor; +} + +float Material_coatRoughness(MaterialConstants mtl) { + return mtl.coatRoughness; +} + +float Material_coatAnisotropy(MaterialConstants mtl) { + return mtl.coatAnisotropy; +} + +float Material_coatRotation(MaterialConstants mtl) { + return mtl.coatRotation; +} + +float Material_coatIOR(MaterialConstants mtl) { + return mtl.coatIOR; +} + +float Material_coatAffectColor(MaterialConstants mtl) { + return mtl.coatAffectColor; +} + +float Material_coatAffectRoughness(MaterialConstants mtl) { + return mtl.coatAffectRoughness; +} + +float Material_emission(MaterialConstants mtl) { + return mtl.emission; +} + +float3 Material_emissionColor(MaterialConstants mtl) { + return mtl.emissionColor; +} + +float3 Material_opacity(MaterialConstants mtl) { + return mtl.opacity; +} + +int Material_thinWalled(MaterialConstants mtl) { + return mtl.thinWalled; +} + +int Material_hasBaseColorTex(MaterialConstants mtl) { + return mtl.hasBaseColorTex; +} + +float2 Material_baseColorTexOffset(MaterialConstants mtl) { + return mtl.baseColorTexOffset; +} + +float2 Material_baseColorTexScale(MaterialConstants mtl) { + return mtl.baseColorTexScale; +} + +float2 Material_baseColorTexPivot(MaterialConstants mtl) { + return mtl.baseColorTexPivot; +} + +float Material_baseColorTexRotation(MaterialConstants mtl) { + return mtl.baseColorTexRotation; +} + +int Material_hasSpecularRoughnessTex(MaterialConstants mtl) { + return mtl.hasSpecularRoughnessTex; +} + +float2 Material_specularRoughnessTexOffset(MaterialConstants mtl) { + return mtl.specularRoughnessTexOffset; +} + +float2 Material_specularRoughnessTexScale(MaterialConstants mtl) { + return mtl.specularRoughnessTexScale; +} + +float2 Material_specularRoughnessTexPivot(MaterialConstants mtl) { + return mtl.specularRoughnessTexPivot; +} + +float Material_specularRoughnessTexRotation(MaterialConstants mtl) { + return mtl.specularRoughnessTexRotation; +} + +int Material_hasEmissionColorTex(MaterialConstants mtl) { + return mtl.hasEmissionColorTex; +} + +float2 Material_emissionColorTexOffset(MaterialConstants mtl) { + return mtl.emissionColorTexOffset; +} + +float2 Material_emissionColorTexScale(MaterialConstants mtl) { + return mtl.emissionColorTexScale; +} + +float2 Material_emissionColorTexPivot(MaterialConstants mtl) { + return mtl.emissionColorTexPivot; +} + +float Material_emissionColorTexRotation(MaterialConstants mtl) { + return mtl.emissionColorTexRotation; +} + +int Material_hasOpacityTex(MaterialConstants mtl) { + return mtl.hasOpacityTex; +} + +float2 Material_opacityTexOffset(MaterialConstants mtl) { + return mtl.opacityTexOffset; +} + +float2 Material_opacityTexScale(MaterialConstants mtl) { + return mtl.opacityTexScale; +} + +float2 Material_opacityTexPivot(MaterialConstants mtl) { + return mtl.opacityTexPivot; +} + +float Material_opacityTexRotation(MaterialConstants mtl) { + return mtl.opacityTexRotation; +} + +int Material_hasNormalTex(MaterialConstants mtl) { + return mtl.hasNormalTex; +} + +float2 Material_normalTexOffset(MaterialConstants mtl) { + return mtl.normalTexOffset; +} + +float2 Material_normalTexScale(MaterialConstants mtl) { + return mtl.normalTexScale; +} + +float2 Material_normalTexPivot(MaterialConstants mtl) { + return mtl.normalTexPivot; +} + +float Material_normalTexRotation(MaterialConstants mtl) { + return mtl.normalTexRotation; +} diff --git a/Libraries/Aurora/Source/Shaders/Environment.slang b/Libraries/Aurora/Source/Shaders/Environment.slang index ce517e3..c4ed646 100644 --- a/Libraries/Aurora/Source/Shaders/Environment.slang +++ b/Libraries/Aurora/Source/Shaders/Environment.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/Frame.slang b/Libraries/Aurora/Source/Shaders/Frame.slang index 11c476c..619adc1 100644 --- a/Libraries/Aurora/Source/Shaders/Frame.slang +++ b/Libraries/Aurora/Source/Shaders/Frame.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,7 +24,40 @@ struct SampleData uint seedOffset; }; +// Maximum number of distant lights. +// Must match CPU limit in SceneBase::LightLimits::kMaxDistantLights +#define kMaxDistantLights 4 + +// Structure representing a single distant light. +// Must match CPU struct in SceneBase::DistantLight +struct DistantLight +{ + // Light color (in RGB) and intensity (in alpha channel.) + float4 colorAndIntensity; + + // Direction of light (inverted as expected by shaders.) + float3 direction; + + // The light size is converted from a diameter in radians to the cosine of the radius. + float cosRadius; +}; + +// Structure representing the lights in the scene. +// Must match GPU struct in SceneBase::LightData +struct LightData +{ + // Array of distant lights, only first distantLightCount are used. + DistantLight distantLights[kMaxDistantLights]; + + // Number of active distant lights. + int distantLightCount = 0; + + // Explicitly pad struct to 16-byte boundary. + int pad[3]; +}; + // Layout of per-frame parameters. +// Must match CPU struct in RendererBase.h. struct FrameData { // The view-projection matrix. @@ -53,20 +86,6 @@ struct FrameData // The size of the scene, specifically the maximum distance between any two points in the scene. float sceneSize; - float2 _padding1; - - // The direction of the global light. - // NOTE: This is *toward* the light. - float3 lightDir; - - float _padding2; - - // The color of the light (RGB) and its intensity (A). - float4 lightColorAndIntensity; - - // The cosine of the light radius (as a disc). - float lightCosRadius; - // Whether shadow evaluation should treat all objects as opaque, as a performance optimization. bool isOpaqueShadowsEnabled; @@ -90,6 +109,12 @@ struct FrameData // The maximum luminance for path tracing samples, for simple firefly clamping. float maxLuminance; + + // Explicitly pad to 16-byte boundary. + float2 _padding1; + + // Current light data for scene (duplicated each frame in flight.) + LightData lights; }; #endif // __FRAME_H__ diff --git a/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang b/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang index 5f6d175..75a7455 100644 --- a/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang +++ b/Libraries/Aurora/Source/Shaders/GLSLToHLSL.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/Geometry.slang b/Libraries/Aurora/Source/Shaders/Geometry.slang index b0091c6..a5a9403 100644 --- a/Libraries/Aurora/Source/Shaders/Geometry.slang +++ b/Libraries/Aurora/Source/Shaders/Geometry.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,21 +19,27 @@ interface IGeometry uint3 getIndices(int bufferLocation); float3 getPosition(int bufferLocation); float3 getNormal(int bufferLocation); + float3 getTangent(int bufferLocation); float2 getTexCoord(int bufferLocation); bool hasTexCoords(); bool hasNormals(); + bool hasTangents(); }; // Shading data for an intersection point, with interpolated values from adjacent vertices. struct ShadingData { - float3 position; // the shading position, which may be slightly above the surface - float3 geomPosition; // the geometric position, interpolated from adjancent vertices - float3 normal; // the shading normal, interpolated from adjancent vertices - float2 texCoord; // the shading texture coordinates, interpolated from adjancent vertices - float3 tangent; // the shading tangent (X), interpolated from adjacent vertices - float3 bitangent; // the shading bitangent (Y), interpolated from adjacent vertices + float3 position; // the shading position, which may be slightly above the surface + float3 geomPosition; // the geometric position, interpolated from adjancent vertices + float3 normal; // the shading normal, interpolated from adjancent vertices + float2 texCoord; // the shading texture coordinates, interpolated from adjancent vertices + float3 tangent; // the shading tangent (X), interpolated from adjacent vertices + float3 bitangent; // the shading bitangent (Y), interpolated from adjacent vertices + float3 objectNormal; // the shading position in object space (not world space). Used only in + // materialX generated code. + float3 objectPosition; // the shading normal in object space (not world space). Used only in + // materialX generated code. }; // Computes the barycentric coordinates from the specified triangle intersection. @@ -85,7 +91,7 @@ ShadingData computeShadingData( shading.geomPosition = float3(0.0f, 0.0f, 0.0f); shading.normal = float3(0.0f, 0.0f, 0.0f); shading.texCoord = float2(0.0f, 0.0f); - shading.tangent = float3(1.0f, 0.0f, 0.0f); + shading.tangent = float3(0.0f, 0.0f, 0.0f); // Load the indices for the vertices of the triangle with the specified index. uint3 indices = geometry.getIndices(triangleIndex); @@ -95,6 +101,7 @@ ShadingData computeShadingData( // as 32-bit unsigned ints, which are then read as floats. float3 positions[3]; float3 normals[3]; + float3 tangents[3]; for (uint i = 0; i < 3; i++) { positions[i] = (geometry.getPosition(indices[i])); @@ -104,7 +111,7 @@ ShadingData computeShadingData( // WorldRayOrigin() + WorldRayDirection() * RayTCurrent(). However this will be less // accurate due to floating-point precision; see "Ray Tracing Gems" chapter 6. shading.geomPosition += positions[i] * barycentrics[i]; - + // Accumulate the optional normals, weighted by the barycentric coordinates. if (geometry.hasNormals()) { @@ -112,6 +119,13 @@ ShadingData computeShadingData( shading.normal += normals[i] * barycentrics[i]; } + // Accumulate the optional normals, weighted by the barycentric coordinates. + if (geometry.hasTangents()) + { + tangents[i] = (geometry.getTangent(indices[i])); + shading.tangent += tangents[i] * barycentrics[i]; + } + // Accumulate the optional texture coordinates, weighted by the barycentric coordinates. if (geometry.hasTexCoords()) { @@ -124,25 +138,32 @@ ShadingData computeShadingData( // NOTE: If a provided normal has zero length, then the normal will have undefined components // (likely NaN), leading to rendering artifacts (likely black pixels) along any path that // accesses the normal. - shading.normal = normalize(geometry.hasNormals() + shading.objectNormal = normalize(geometry.hasNormals() ? shading.normal : cross(positions[1] - positions[0], positions[2] - positions[0])); // Compute the shading position from the geometry position and vertex data. If no normals are // provided, use the geometric position. - shading.position = geometry.hasNormals() ? computeShadingPosition(shading.geomPosition, + shading.objectPosition = geometry.hasNormals() ? computeShadingPosition(shading.geomPosition, shading.normal, positions, normals, barycentrics) : shading.geomPosition; // Transform the position, geometric position, and normal to world space. // NOTE: Renormalize the normal as the object-to-world matrix can have a scale. - shading.position = mul(objToWorld, float4(shading.position, 1.0f)); + shading.position = mul(objToWorld, float4(shading.objectPosition, 1.0f)); shading.geomPosition = mul(objToWorld, float4(shading.geomPosition, 1.0f)); - shading.normal = normalize(mul((float3x3)objToWorld, shading.normal)); - - // Generate automatic tangent / bitangent vector from the shading normal. - // TODO: Use tangent vectors from the host instead of this. - buildBasis(shading.normal, shading.tangent, shading.bitangent); + shading.normal = normalize(mul((float3x3)objToWorld, shading.objectNormal)); + + // Arbritary up vector used to calculate tangents, if none provided. + float3 up = + abs(shading.normal.y) < 0.999f ? float3(0.0f, 1.0f, 0.0f) : float3(1.0f, 0.0f, 0.0f); + + // Compute arbritary shading tangent from up vector, or use provided one transformed in world space. + shading.tangent = normalize(geometry.hasTangents() ? normalize(mul((float3x3)objToWorld, shading.tangent)) + : cross(shading.normal, up)); + + // Generate bitangent from normal and tangent. + shading.bitangent = computeBitangent(shading.normal, shading.tangent); return shading; } diff --git a/Libraries/Aurora/Source/Shaders/Globals.slang b/Libraries/Aurora/Source/Shaders/Globals.slang index c52f22d..39fc419 100644 --- a/Libraries/Aurora/Source/Shaders/Globals.slang +++ b/Libraries/Aurora/Source/Shaders/Globals.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -74,4 +74,18 @@ void buildBasis(float3 normal, out float3 tangent, out float3 bitangent) bitangent = cross(normal, tangent); } +// Rebuild tangent and bitangent basis from new normal. +void rebuildBasis(float3 normal, in out float3 tangent, in out float3 bitangent) +{ + bitangent = abs(dot(bitangent, normal)) < 0.999f ? bitangent : tangent; + tangent = normalize(cross(bitangent, normal)); + bitangent = cross(normal, tangent); +} + +// Compute bitangent from a normal and tangent vector. +float3 computeBitangent(float3 normal, float3 tangent) +{ + return cross(normal, tangent); +} + #endif // __GLOBALS_H__ \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/GroundPlane.slang b/Libraries/Aurora/Source/Shaders/GroundPlane.slang index 937cf24..9762114 100644 --- a/Libraries/Aurora/Source/Shaders/GroundPlane.slang +++ b/Libraries/Aurora/Source/Shaders/GroundPlane.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang b/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang index e86d86e..3cbf43e 100644 --- a/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang +++ b/Libraries/Aurora/Source/Shaders/InitializeDefaultMaterialType.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang b/Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang deleted file mode 100644 index a6a7367..0000000 --- a/Libraries/Aurora/Source/Shaders/LayerShaderEntryPointTemplate.slang +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2022 Autodesk, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#include "PathTracingCommon.slang" -#include "InitializeMaterial.slang" -#include "ShadeFunctions.slang" - -[shader("miss")] void @MATERIAL_TYPE@LayerMissShader(inout RayPayload rayPayload) -{ - int depth = rayPayload.radianceRay.depth; - int maxDepth = gFrameData.traceDepth; - bool isOpaqueShadowsEnabled = gFrameData.isOpaqueShadowsEnabled; - - // Get the interpolated vertex data for the hit triangle, at the hit barycentric - // coordinates. - uint triangleIndex = rayPayload.primitiveIndex; - float3 barycentrics = computeBarycentrics(rayPayload.hit); - ShadingData shading = computeShadingData( - gGeometry, triangleIndex, barycentrics, rayPayload.objToWorld); - - // Initialize the view direction. - float3 V = rayPayload.viewDirection; - float3 materialNormal = shading.normal; - bool isGeneratedNormal; - - // Initialize the material values for repeated evaluations at the current shading point. - Material material = initializeMaterial(shading, rayPayload.objToWorld, materialNormal, isGeneratedNormal); - Environment environment = prepareEnvironmentValues(); - - // If a new normal has been generated, transform it to world space and build a corresponding - // basis from it. - if (isGeneratedNormal) - { - shading.normal = materialNormal; - buildBasis(shading.normal, shading.tangent, shading.bitangent); - } - float3 transparency = 1.0f - material.opacity; - float P = luminance(transparency); - - if (random2D(rayPayload.radianceRay.rng).x < P) - { - // Nothing more to do. - return; - } - - // Compute the NDC depth and view depth of the hit position: - // - NDC depth: Compute the clip space position using the supplied view-projection matrix, then - // divide the Z component by W, and remap from [-1.0, 1.0] to [0.0, 1.0]. - // - View Depth: For a primary ray, this is simply the T value of the ray. We don't use this - // value for non-primary rays; it is recorded here but not used. - float4 positionClip = mul(gFrameData.cameraViewProj, float4(shading.geomPosition, 1.0f)); - float depthNDC = (positionClip.z / positionClip.w + 1.0f) / 2.0f; - float depthView = RayTCurrent(); - - // Clamp roughness for the ray payload to a minimum, because the denoiser handles materials with - // low (near-zero) roughness poorly, leading to a noisy result. This addresses two separate but - // related issues: low-roughness metallic materials reflecting noisy surroundings, and low- - // roughness dielectric materials where the glossy lobe is sparsely sampled. - // NOTE: The roughness in the ray payload is used for denoising only. The material specular - // roughness (used in shading) is not modified here. - static const float kMinRoughness = 0.05f; - float clampedRoughness = max(material.specularRoughness, kMinRoughness); - - // Store initial data in the ray payload. - rayPayload.radianceRay.color = BLACK; - rayPayload.radianceRay.alpha = 1.0f; - rayPayload.radianceRay.direct = BLACK; - rayPayload.radianceRay.depthNDC = depthNDC; - rayPayload.radianceRay.depthView = depthView; - rayPayload.radianceRay.normal = shading.normal; - rayPayload.radianceRay.baseColor = material.baseColor; - rayPayload.radianceRay.roughness = clampedRoughness; - rayPayload.radianceRay.metalness = material.metalness; - rayPayload.radianceRay.indirect.clear(); - - // Shade with the global directional light. Skip this if the light intensity is zero. - if (gFrameData.lightColorAndIntensity.a > 0.0f) - { - rayPayload.radianceRay.direct = shadeDirectionalLight(gFrameData.lightDir, - gFrameData.lightColorAndIntensity, gFrameData.lightCosRadius, gScene, material, shading, - V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); - - // Add the direct lighting to the color result. - // NOTE: For denoising purposes, "direct" lighting only includes the directional light, and - // not the environment light computed below. - rayPayload.radianceRay.color += rayPayload.radianceRay.direct; - } - - // When denoising, primary rays for indirect lighting should not include the base color (also - // known as albedo, as used by diffuse lobes) in order to avoid blurring out color details. So - // the base color is set to white here and the color is added back after denoising. - if (gFrameData.isDenoisingEnabled && rayPayload.radianceRay.depth == 1) - { - material.baseColor = WHITE; - } - - // Shade with the environment light depending on the importance sampling type: - // - BSDF: The environment is treated as indirect light, and is evaluated in the miss shader. - // - Environment: Sample the environment light as direct lighting. - // - MIS: Use multiple importance sampling on both the material and the light. - // NOTE: For denoising purposes, "indirect" lighting includes the environment light. - if (IMPORTANCE_SAMPLING_MODE != IMPORTANCE_SAMPLING_BSDF) - { - float3 environmentRadiance = BLACK; - Environment environment = prepareEnvironmentValues(); - if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_ENVIRONMENT) - { - // TODO: This does not currently contribute to indirect lighting for denoising purposes. - // That will require having the material evaluation return separate diffuse and glossy - // components. - environmentRadiance = - shadeEnvironmentLightDirect(environment, gScene, material, shading, - V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); - } - else if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_MIS) - { - environmentRadiance = shadeEnvironmentLightMIS(environment, gScene, material, shading, V, isOpaqueShadowsEnabled, depth, - maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.indirect); - } - - // Add the radiance from the environment light to the color result. - rayPayload.radianceRay.color += environmentRadiance; - } - - // Shade with indirect light from the surrounding scene (i.e. path tracing). - rayPayload.radianceRay.color += shadeIndirectLight(gScene, environment, material, - shading, V, depth, maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.alpha, - rayPayload.radianceRay.indirect); - - // Scale the color components of the ray payload by opacity, and normalized by the probability - // of this segment being considered opaque. - // NOTE: The shading functions do not individually consider opacity, so that it can be handled - // in one place here. - rayPayload.radianceRay.scaleColor(material.opacity / (1.0f - P)); - - rayPayload.absorbedByLayer = true; -} diff --git a/Libraries/Aurora/Source/Shaders/MainEntryPoints.slang b/Libraries/Aurora/Source/Shaders/MainEntryPoints.slang new file mode 100644 index 0000000..31b90be --- /dev/null +++ b/Libraries/Aurora/Source/Shaders/MainEntryPoints.slang @@ -0,0 +1,438 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Entry point template that defines all the entry points for a material shader. +// NOTE: This file is not valid HLSL as is. This template must be configured at runtime by +// replacing the tags surrounded by the ___ characters: +// -___Material___ with the unique material shader name for this shader. + +#include "Definitions.slang" +#include "Options.slang" + +#include "InitializeMaterial.slang" +#include "PathTracingCommon.slang" +#include "ShadeFunctions.slang" + +// RADIANCE_HIT is defined to 1 for shaders with the radiance hit entry point. +#if RADIANCE_HIT +// Closest hit shader for radiance rays. +[shader("closesthit")] void ___Material___RadianceHitShader( + inout RayPayload rayPayload, in BuiltInTriangleIntersectionAttributes hit) { + int depth = rayPayload.radianceRay.depth; + int maxDepth = gFrameData.traceDepth; + bool isOpaqueShadowsEnabled = gFrameData.isOpaqueShadowsEnabled; + + // Get the interpolated vertex data for the hit triangle, at the hit barycentric coordinates. + uint triangleIndex = PrimitiveIndex(); + float3 barycentrics = computeBarycentrics(hit); + ShadingData shading = + computeShadingData(gGeometry, triangleIndex, barycentrics, ObjectToWorld3x4()); + + // Initialize the view direction. + float3 V = -WorldRayDirection(); + float3 materialNormal = shading.normal; + bool isGeneratedNormal; + + // Initialize the material values for repeated evaluations at the current shading point. Also + // initialize the environment. + Material material = + initializeMaterial(shading, ObjectToWorld3x4(), materialNormal, isGeneratedNormal); + Environment environment = prepareEnvironmentValues(); + + // If a new normal has been generated, transform it to world space and build a corresponding + // basis from it. + if (isGeneratedNormal) + { + shading.normal = materialNormal; + buildBasis(shading.normal, shading.tangent, shading.bitangent); + } + + // Modify the material properties if only the diffuse component should be renderered. + if (gFrameData.isDiffuseOnlyEnabled) + { + material.base = 1.0f; + material.specular = 0.0f; + material.metalness = 0.0f; + } + + // Shade any material layers (for front facing hits only) if the ENABLE_LAYERS option is + // defined. + // TODO: Should this be done after the transparency evaluation below, i.e. it is possible this + // work will be thrown away for transparent samples. +#if ENABLE_LAYERS + if (dot(shading.normal, V) > 0.0) + { + // Iterate in reverse order, so innermost layer is shaded last. + for (int layer = gMaterialLayerCount - 1; layer >= 0; layer--) + { + int layerMissShaderIdx = getMaterialLayerIndex(layer); + + // Shade the layer, and return if the ray was absorbed. + if (shadeMaterialLayer(gNullScene, layerMissShaderIdx, rayPayload.radianceRay, shading, + hit, depth, maxDepth)) + { + return; + } + } + } +#endif + + // Handle opacity (transparency) by stochastically determining whether the hit should be + // skipped, based on the luminance of the material opacity. If so, use the ray payload from a + // ray traced at the hit point, i.e. passing straight through the geometry, and return. + // NOTE: This could also be done with an any hit shader, with possibly better or worse + // performance. + float3 transparency = 1.0f - material.opacity; + float P = luminance(transparency); + if (random2D(rayPayload.radianceRay.rng).x < P) + { + // Trace a radiance ray with the unmodified ray direction, and the hit position. Use the + // background miss shader so that the background is sampled, since this is for transparency. + // NOTE: The geometric position (geomPosition) is used to avoid self-intersection. + rayPayload.radianceRay = traceRadianceRay(gScene, environment, shading.geomPosition, -V, + M_RAY_TMIN, depth, maxDepth, true, rayPayload.radianceRay.rng); + + // Scale the color components of the ray payload by transparency, and normalized by the + // probability of this segment being considered transparent. + rayPayload.radianceRay.scaleColor(transparency / P); + + // Nothing more to do. + return; + } + + // Compute the NDC depth and view depth of the hit position: + // - NDC depth: Compute the clip space position using the supplied view-projection matrix, then + // divide the Z component by W, and remap from [-1.0, 1.0] to [0.0, 1.0]. + // - View Depth: For a primary ray, this is simply the T value of the ray. We don't use this + // value for non-primary rays; it is recorded here but not used. + float4 positionClip = mul(gFrameData.cameraViewProj, float4(shading.geomPosition, 1.0f)); + float depthNDC = (positionClip.z / positionClip.w + 1.0f) / 2.0f; + float depthView = RayTCurrent(); + + // Clamp roughness for the ray payload to a minimum, because the denoiser handles materials with + // low (near-zero) roughness poorly, leading to a noisy result. This addresses two separate but + // related issues: low-roughness metallic materials reflecting noisy surroundings, and low- + // roughness dielectric materials where the glossy lobe is sparsely sampled. + // NOTE: The roughness in the ray payload is used for denoising only. The material specular + // roughness (used in shading) is not modified here. + static const float kMinRoughness = 0.05f; + float clampedRoughness = max(material.specularRoughness, kMinRoughness); + + // Store initial data in the ray payload. + rayPayload.radianceRay.color = BLACK; + rayPayload.radianceRay.alpha = 1.0f; + rayPayload.radianceRay.extra = BLACK; + rayPayload.radianceRay.depthNDC = depthNDC; + rayPayload.radianceRay.depthView = depthView; + rayPayload.radianceRay.normal = shading.normal; + rayPayload.radianceRay.baseColor = material.baseColor; + rayPayload.radianceRay.roughness = clampedRoughness; + rayPayload.radianceRay.metalness = material.metalness; + rayPayload.radianceRay.indirect.clear(); + + // Shade with material emission. + rayPayload.radianceRay.extra += shadeEmission(material); + + // Shade with a randomly selected global distant light. Skip this if there are no distant + // lights. + if (gFrameData.lights.distantLightCount > 0) + { + // Choose a random distant light for this sample, by computing an index in range the 0 to + // (distantLightCount-1). + int lightIdx = int( + random2D(rayPayload.radianceRay.rng).x * float(gFrameData.lights.distantLightCount)); + + // Skip if the light has zero intensity. + if (gFrameData.lights.distantLights[lightIdx].colorAndIntensity.a > 0.0f) + { + // Shade the light. + rayPayload.radianceRay.extra += + shadeDirectionalLight(gFrameData.lights.distantLights[lightIdx].direction, + gFrameData.lights.distantLights[lightIdx].colorAndIntensity, + gFrameData.lights.distantLights[lightIdx].cosRadius, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + } + } + + // Add the extra shading (computed above) to the color result. + // NOTE: Extra shading is stored separately in order to not be denoised, but rather to be + // combined with denoised shading later. It does not include shading from the environment light + // (below), as that may be denoised. + rayPayload.radianceRay.color += rayPayload.radianceRay.extra; + + // When denoising, primary rays for indirect lighting should not include the base color (also + // known as albedo, as used by diffuse lobes) in order to avoid blurring out color details. So + // the base color is set to white here and the color is added back after denoising. + if (gFrameData.isDenoisingEnabled && rayPayload.radianceRay.depth == 1) + { + material.baseColor = WHITE; + } + + // Shade with the environment light depending on the importance sampling type: + // - BSDF: The environment is treated as indirect light, and is evaluated in the miss shader. + // - Environment: Sample the environment light as direct lighting. + // - MIS: Use multiple importance sampling on both the material and the light. + // NOTE: For denoising purposes, "indirect" lighting includes the environment light. + if (IMPORTANCE_SAMPLING_MODE != IMPORTANCE_SAMPLING_BSDF) + { + float3 environmentRadiance = BLACK; + if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_ENVIRONMENT) + { + // TODO: This does not currently contribute to indirect lighting for denoising purposes. + // That will require having the material evaluation return separate diffuse and glossy + // components. + environmentRadiance = shadeEnvironmentLightDirect(environment, gScene, material, + shading, V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + } + else if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_MIS) + { + environmentRadiance = shadeEnvironmentLightMIS(environment, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng, + rayPayload.radianceRay.indirect); + } + + // Add the radiance from the environment light to the color result. + rayPayload.radianceRay.color += environmentRadiance; + } + + // Shade with indirect light from the surrounding scene (i.e. path tracing). + rayPayload.radianceRay.color += shadeIndirectLight(gScene, environment, material, shading, V, + depth, maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.alpha, + rayPayload.radianceRay.indirect); + + // Scale the color components of the ray payload by opacity, and normalized by the probability + // of this segment being considered opaque. + // NOTE: The shading functions do not individually consider opacity, so that it can be handled + // in one place here. + rayPayload.radianceRay.scaleColor(material.opacity / (1.0f - P)); +} +#endif + +// SHADOW_ANYHIT is defined to 1 for shaders with the shadow anyhit entry point. +#if SHADOW_ANYHIT + [shader("anyhit")] void ___Material___ShadowAnyHitShader( + inout ShadowRayPayload rayPayload, in BuiltInTriangleIntersectionAttributes hit) +{ +// If the materials for this shader are always opaque, keep this shader to a minimum to save +// compile and runtime performance. +#if SHADOW_ANYHIT_ALWAYS_OPAQUE + rayPayload.visibility = BLACK; + AcceptHitAndEndSearch(); +#else + + // If the material is opaque, set the visibility to zero, accept the hit, and stop searching for + // hits, as the shadow ray is completely blocked. Doing this here is a performance optimization, + // as it avoids calling the full material initialization below, which can be expensive. + if (gIsOpaque) + { + rayPayload.visibility = BLACK; + AcceptHitAndEndSearch(); + } + + // Get the interpolated vertex data for the hit triangle, at the hit barycentric coordinates. + uint triangleIndex = PrimitiveIndex(); + float3 barycentrics = computeBarycentrics(hit); + ShadingData shading = + computeShadingData(gGeometry, triangleIndex, barycentrics, ObjectToWorld3x4()); + + // Initialize the material values. + // NOTE: This evaluates all material properties, when only visibility (or opacity) is needed. It + // may be more efficient to generate a dedicated function for getting the visibility alone. + float3 materialNormal = shading.normal; + bool isGeneratedNormal = false; + Material material = + initializeMaterial(shading, ObjectToWorld3x4(), materialNormal, isGeneratedNormal); + + // Compute the opacity at the current hit from the opacity, transmission, and transmission color + // properties. Then accumulate that (inverted) with the current visibility on the ray payload. + // NOTE: Visibility accumulation like this is order-independent, i.e. it does not matter what + // order intersections are processed. This is important because any hit shader intersections are + // processed in an arbitrary order. Also, while this does consider *transmission* to determine + // visibility, this technique does not support refraction (caustics), just simple straight + // shadow rays. + float3 opacity = + material.opacity * (WHITE - material.transmission * material.transmissionColor); + rayPayload.visibility *= 1.0f - opacity; + + // If the visibility is zero (opaque) at this point, accept this hit and stop searching for + // hits, as the shadow ray is now completely blocked. Otherwise, ignore the hit so that + // visibility can continue to be accumulated from other any hit intersections. + if (isBlack(rayPayload.visibility)) + { + AcceptHitAndEndSearch(); + } + else + { + IgnoreHit(); + } + + // NOTE: The default behavior of accepting the hit while continuing to search for hits is not + // useful for shadow rays. It does not happen here, because of the intrinsic function calls + // above. + +#endif +} +#endif + +// LAYER_MISS is defined to 1 for shaders with the layer miss entry point. +#if LAYER_MISS +[shader("miss")] void ___Material___LayerMissShader(inout RayPayload rayPayload) { + int depth = rayPayload.radianceRay.depth; + int maxDepth = gFrameData.traceDepth; + bool isOpaqueShadowsEnabled = gFrameData.isOpaqueShadowsEnabled; + + // Get the interpolated vertex data for the hit triangle, at the hit barycentric + // coordinates. + uint triangleIndex = rayPayload.primitiveIndex; + float3 barycentrics = computeBarycentrics(rayPayload.hit); + ShadingData shading = + computeShadingData(gGeometry, triangleIndex, barycentrics, rayPayload.objToWorld); + + // Initialize the view direction. + float3 V = rayPayload.viewDirection; + float3 materialNormal = shading.normal; + bool isGeneratedNormal; + + // Initialize the material values for repeated evaluations at the current shading point. + Material material = + initializeMaterial(shading, rayPayload.objToWorld, materialNormal, isGeneratedNormal); + Environment environment = prepareEnvironmentValues(); + + // If a new normal has been generated, transform it to world space and build a corresponding + // basis from it. + if (isGeneratedNormal) + { + shading.normal = materialNormal; + buildBasis(shading.normal, shading.tangent, shading.bitangent); + } + float3 transparency = 1.0f - material.opacity; + float P = luminance(transparency); + + if (random2D(rayPayload.radianceRay.rng).x < P) + { + // Nothing more to do. + return; + } + + // Compute the NDC depth and view depth of the hit position: + // - NDC depth: Compute the clip space position using the supplied view-projection matrix, then + // divide the Z component by W, and remap from [-1.0, 1.0] to [0.0, 1.0]. + // - View Depth: For a primary ray, this is simply the T value of the ray. We don't use this + // value for non-primary rays; it is recorded here but not used. + float4 positionClip = mul(gFrameData.cameraViewProj, float4(shading.geomPosition, 1.0f)); + float depthNDC = (positionClip.z / positionClip.w + 1.0f) / 2.0f; + float depthView = RayTCurrent(); + + // Clamp roughness for the ray payload to a minimum, because the denoiser handles materials with + // low (near-zero) roughness poorly, leading to a noisy result. This addresses two separate but + // related issues: low-roughness metallic materials reflecting noisy surroundings, and low- + // roughness dielectric materials where the glossy lobe is sparsely sampled. + // NOTE: The roughness in the ray payload is used for denoising only. The material specular + // roughness (used in shading) is not modified here. + static const float kMinRoughness = 0.05f; + float clampedRoughness = max(material.specularRoughness, kMinRoughness); + + // Store initial data in the ray payload. + rayPayload.radianceRay.color = BLACK; + rayPayload.radianceRay.alpha = 1.0f; + rayPayload.radianceRay.extra = BLACK; + rayPayload.radianceRay.depthNDC = depthNDC; + rayPayload.radianceRay.depthView = depthView; + rayPayload.radianceRay.normal = shading.normal; + rayPayload.radianceRay.baseColor = material.baseColor; + rayPayload.radianceRay.roughness = clampedRoughness; + rayPayload.radianceRay.metalness = material.metalness; + rayPayload.radianceRay.indirect.clear(); + + // Shade with material emission. + rayPayload.radianceRay.extra += shadeEmission(material); + + // Shade with a randomly selected global distant light. Skip this if there are no distant + // lights. + if (gFrameData.lights.distantLightCount > 0) + { + // Choose a random distant light for this sample, by computing an index in range the 0 to + // (distantLightCount-1). + int lightIdx = int( + random2D(rayPayload.radianceRay.rng).x * float(gFrameData.lights.distantLightCount)); + + // Skip if the light has zero intensity. + if (gFrameData.lights.distantLights[lightIdx].colorAndIntensity.a > 0.0f) + { + // Shade the light. + rayPayload.radianceRay.extra += + shadeDirectionalLight(gFrameData.lights.distantLights[lightIdx].direction, + gFrameData.lights.distantLights[lightIdx].colorAndIntensity, + gFrameData.lights.distantLights[lightIdx].cosRadius, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + } + } + + // Add the extra shading (computed above) to the color result. + // NOTE: Extra shading is stored separately in order to not be denoised, but rather to be + // combined with denoised shading later. It does not include shading from the environment light + // (below), as that may be denoised. + rayPayload.radianceRay.color += rayPayload.radianceRay.extra; + + // When denoising, primary rays for indirect lighting should not include the base color (also + // known as albedo, as used by diffuse lobes) in order to avoid blurring out color details. So + // the base color is set to white here and the color is added back after denoising. + if (gFrameData.isDenoisingEnabled && rayPayload.radianceRay.depth == 1) + { + material.baseColor = WHITE; + } + + // Shade with the environment light depending on the importance sampling type: + // - BSDF: The environment is treated as indirect light, and is evaluated in the miss shader. + // - Environment: Sample the environment light as direct lighting. + // - MIS: Use multiple importance sampling on both the material and the light. + // NOTE: For denoising purposes, "indirect" lighting includes the environment light. + if (IMPORTANCE_SAMPLING_MODE != IMPORTANCE_SAMPLING_BSDF) + { + float3 environmentRadiance = BLACK; + Environment environment = prepareEnvironmentValues(); + if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_ENVIRONMENT) + { + // TODO: This does not currently contribute to indirect lighting for denoising purposes. + // That will require having the material evaluation return separate diffuse and glossy + // components. + environmentRadiance = shadeEnvironmentLightDirect(environment, gScene, material, + shading, V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng); + } + else if (IMPORTANCE_SAMPLING_MODE == IMPORTANCE_SAMPLING_MIS) + { + environmentRadiance = shadeEnvironmentLightMIS(environment, gScene, material, shading, + V, isOpaqueShadowsEnabled, depth, maxDepth, rayPayload.radianceRay.rng, + rayPayload.radianceRay.indirect); + } + + // Add the radiance from the environment light to the color result. + rayPayload.radianceRay.color += environmentRadiance; + } + + // Shade with indirect light from the surrounding scene (i.e. path tracing). + rayPayload.radianceRay.color += shadeIndirectLight(gScene, environment, material, shading, V, + depth, maxDepth, rayPayload.radianceRay.rng, rayPayload.radianceRay.alpha, + rayPayload.radianceRay.indirect); + + // Scale the color components of the ray payload by opacity, and normalized by the probability + // of this segment being considered opaque. + // NOTE: The shading functions do not individually consider opacity, so that it can be handled + // in one place here. + rayPayload.radianceRay.scaleColor(material.opacity / (1.0f - P)); + + rayPayload.absorbedByLayer = true; +} + +#endif diff --git a/Libraries/Aurora/Source/Shaders/Material.slang b/Libraries/Aurora/Source/Shaders/Material.slang index beaf204..a3c78fa 100644 --- a/Libraries/Aurora/Source/Shaders/Material.slang +++ b/Libraries/Aurora/Source/Shaders/Material.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,77 +16,33 @@ #include "Geometry.slang" -// Texture UV transform represent as scale, offset and rotation. -struct TextureTransform -{ - float2 pivot; - float2 scale; - float2 offset; - float rotation; -}; - -// Layout of material constants. -// NOTE: This must match the host (CPU) data structure, MaterialData, including the explicit padding -// variables (need due to Vulkan GLSL padding rules) -struct MaterialConstants -{ - float base; - float3 baseColor; - float diffuseRoughness; - float metalness; - float specular; - float _padding1; - float3 specularColor; - float specularRoughness; - float specularIOR; - float specularAnisotropy; - float specularRotation; - float transmission; - float3 transmissionColor; - float subsurface; - float _padding2; - float3 subsurfaceColor; - float3 subsurfaceRadius; - float subsurfaceScale; - float subsurfaceAnisotropy; - float sheen; - float2 _padding3; - float3 sheenColor; - float sheenRoughness; - float coat; - float3 coatColor; - float coatRoughness; - float coatAnisotropy; - float coatRotation; - float coatIOR; - float coatAffectColor; - float coatAffectRoughness; - float2 _padding4; - float3 opacity; - bool thinWalled; - bool hasBaseColorTex; - float3 _padding5; - TextureTransform baseColorTexTransform; - bool hasSpecularRoughnessTex; - TextureTransform specularRoughnessTexTransform; - bool hasOpacityTex; - TextureTransform opacityTexTransform; - bool hasNormalTex; - TextureTransform normalTexTransform; - bool isOpaque; -}; +#if DIRECTX +// DirectX backend uses the autogenerated byte-array accessors. +#include "DefaultMaterialUniformAccessors.slang" +#else +// HGI backend uses the autogenerated uniform buffers. +#include "DefaultMaterialUniformBuffer.slang" +#endif // The global sampler state, used by default for texture sampling. [[vk::binding(6)]] SamplerState gDefaultSampler : register(s0); #if DIRECTX -// Material global variables, for the current acceleration structure instance only. -// NOTE: These must align with the variables declared in the main source file. -ConstantBuffer gMaterialConstants : register(b1, space1); -Texture2D gBaseColorTexture : register(t4, space1); -Texture2D gSpecularRoughnessTexture : register(t5, space1); -Texture2D gNormalTexture : register(t6, space1); -Texture2D gOpacityTexture : register(t7, space1); + +// Material properties are stored in this ByteAddressBuffer, and accessed with Material_XXX() +// functions, specific to the material type. They are stored in the register space after the vertex +// and texture buffers. +// NOTE: An untyped ByteAddressBuffer is used instead of a typed constant buffer (e.g. +// ConstantBuffer) because it is not possible in DirectX to have constant buffers with +// different types assigned to the same register. +ByteAddressBuffer gMaterialConstants : register(t10, space1); + +// Textures, with hardcoded names. They are stored in the register space after the vertex buffers. +Texture2D gBaseColorTexture : register(t5, space1); +Texture2D gSpecularRoughnessTexture : register(t6, space1); +Texture2D gNormalTexture : register(t7, space1); +Texture2D gEmissionColorTexture : register(t8, space1); +Texture2D gOpacityTexture : register(t9, space1); // Samplers for base color and opacity. // TODO: Add for other textures. @@ -103,6 +59,11 @@ float4 sampleSpecularRoughnessTexture(float2 uv, float level) return gSpecularRoughnessTexture.SampleLevel( gDefaultSampler, uv, level); // Use the default sampler. } +float4 sampleEmissionColorTexture(float2 uv, float level) +{ + return gEmissionColorTexture.SampleLevel( + gDefaultSampler, uv, level); // Use the default sampler. +} float4 sampleOpacityTexture(float2 uv, float level) { return gOpacityTexture.SampleLevel( @@ -118,6 +79,7 @@ float4 sampleNormalTexture(float2 uv, float level) MaterialConstants getMaterial(); float4 sampleBaseColorTexture(float2 uv, float level); float4 sampleSpecularRoughnessTexture(float2 uv, float level); +float4 sampleEmissionColorTexture(float2 uv, float level); float4 sampleOpacityTexture(float2 uv, float level); float4 sampleNormalTexture(float2 uv, float level); #endif @@ -155,6 +117,8 @@ struct Material float coatIOR; float coatAffectColor; float coatAffectRoughness; + float emission; + float3 emissionColor; float3 opacity; bool thinWalled; bool isOpaque; @@ -212,96 +176,104 @@ float3 calculateNormalFromMap(float3 texelValue, int space, float scale, float3 Material initializeDefaultMaterial( ShadingData shading, float3x4 objToWorld, out float3 materialNormal, out bool isGeneratedNormal) { -#if DIRECTX - MaterialConstants materialConstants = gMaterialConstants; -#else - MaterialConstants materialConstants = getMaterial(); +#if !DIRECTX + MaterialConstants gMaterialConstants = getMaterial(); #endif // Copy the constant values to the material from the constant buffer. Material material; - material.base = materialConstants.base; - material.baseColor = materialConstants.baseColor; - material.diffuseRoughness = materialConstants.diffuseRoughness; - material.metalness = materialConstants.metalness; - material.specular = materialConstants.specular; - material.specularColor = materialConstants.specularColor; - material.specularRoughness = materialConstants.specularRoughness; - material.specularIOR = materialConstants.specularIOR; - material.specularAnisotropy = materialConstants.specularAnisotropy; - material.specularRotation = materialConstants.specularRotation; - material.transmission = materialConstants.transmission; - material.transmissionColor = materialConstants.transmissionColor; - material.subsurface = materialConstants.subsurface; - material.subsurfaceColor = materialConstants.subsurfaceColor; - material.subsurfaceRadius = materialConstants.subsurfaceRadius; - material.subsurfaceScale = materialConstants.subsurfaceScale; - material.subsurfaceAnisotropy = materialConstants.subsurfaceAnisotropy; - material.sheen = materialConstants.sheen; - material.sheenColor = materialConstants.sheenColor; - material.sheenRoughness = materialConstants.sheenRoughness; - material.coat = materialConstants.coat; - material.coatColor = materialConstants.coatColor; - material.coatRoughness = materialConstants.coatRoughness; - material.coatAnisotropy = materialConstants.coatAnisotropy; - material.coatRotation = materialConstants.coatRotation; - material.coatIOR = materialConstants.coatIOR; - material.coatAffectColor = materialConstants.coatAffectColor; - material.coatAffectRoughness = materialConstants.coatAffectRoughness; - material.opacity = materialConstants.opacity; - material.thinWalled = materialConstants.thinWalled; - material.isOpaque = materialConstants.isOpaque; + material.base = Material_base(gMaterialConstants); + material.baseColor = Material_baseColor(gMaterialConstants); + material.diffuseRoughness = Material_diffuseRoughness(gMaterialConstants); + material.metalness = Material_metalness(gMaterialConstants); + material.specular = Material_specular(gMaterialConstants); + material.specularColor = Material_specularColor(gMaterialConstants); + material.specularRoughness = Material_specularRoughness(gMaterialConstants); + material.specularIOR = Material_specularIOR(gMaterialConstants); + material.specularAnisotropy = Material_specularAnisotropy(gMaterialConstants); + material.specularRotation = Material_specularRotation(gMaterialConstants); + material.transmission = Material_transmission(gMaterialConstants); + material.transmissionColor = Material_transmissionColor(gMaterialConstants); + material.subsurface = Material_subsurface(gMaterialConstants); + material.subsurfaceColor = Material_subsurfaceColor(gMaterialConstants); + material.subsurfaceRadius = Material_subsurfaceRadius(gMaterialConstants); + material.subsurfaceScale = Material_subsurfaceScale(gMaterialConstants); + material.subsurfaceAnisotropy = Material_subsurfaceAnisotropy(gMaterialConstants); + material.sheen = Material_sheen(gMaterialConstants); + material.sheenColor = Material_sheenColor(gMaterialConstants); + material.sheenRoughness = Material_sheenRoughness(gMaterialConstants); + material.coat = Material_coat(gMaterialConstants); + material.coatColor = Material_coatColor(gMaterialConstants); + material.coatRoughness = Material_coatRoughness(gMaterialConstants); + material.coatAnisotropy = Material_coatAnisotropy(gMaterialConstants); + material.coatRotation = Material_coatRotation(gMaterialConstants); + material.coatIOR = Material_coatIOR(gMaterialConstants); + material.coatAffectColor = Material_coatAffectColor(gMaterialConstants); + material.coatAffectRoughness = Material_coatAffectRoughness(gMaterialConstants); + material.emission = Material_emission(gMaterialConstants); + material.emissionColor = Material_emissionColor(gMaterialConstants); + material.opacity = Material_opacity(gMaterialConstants); + material.thinWalled = Material_thinWalled(gMaterialConstants); - // Sample base color from a texture if necessary. + // Sample base color from a texture if necessary. float4 texCoord = float4(shading.texCoord, 0.0f, 1.0f); - if (materialConstants.hasBaseColorTex) + if (Material_hasBaseColorTex(gMaterialConstants)) { float2 uv = - applyUVTransform(shading.texCoord, materialConstants.baseColorTexTransform.pivot, - materialConstants.baseColorTexTransform.scale, - materialConstants.baseColorTexTransform.rotation, - materialConstants.baseColorTexTransform.offset); + applyUVTransform(shading.texCoord, Material_baseColorTexPivot(gMaterialConstants), + Material_baseColorTexScale(gMaterialConstants), + Material_baseColorTexRotation(gMaterialConstants), + Material_baseColorTexOffset(gMaterialConstants)); material.baseColor = sampleBaseColorTexture(uv, 0.0f).rgb; } // Sample specular roughness from a texture if necessary. - if (materialConstants.hasSpecularRoughnessTex) + if (Material_hasSpecularRoughnessTex(gMaterialConstants)) { - float2 uv = applyUVTransform(shading.texCoord, - materialConstants.specularRoughnessTexTransform.pivot, - materialConstants.specularRoughnessTexTransform.scale, - materialConstants.specularRoughnessTexTransform.rotation, - materialConstants.specularRoughnessTexTransform.offset); + float2 uv = applyUVTransform(shading.texCoord, + Material_specularRoughnessTexPivot(gMaterialConstants), + Material_specularRoughnessTexScale(gMaterialConstants), + Material_specularRoughnessTexRotation(gMaterialConstants), + Material_specularRoughnessTexOffset(gMaterialConstants)); + material.specularRoughness = sampleSpecularRoughnessTexture(uv, 0.0f).r; } + // Sample emission color from a texture if necessary. + if (Material_hasEmissionColorTex(gMaterialConstants)) + { + float2 uv = applyUVTransform(shading.texCoord, + Material_emissionColorTexPivot(gMaterialConstants), + Material_emissionColorTexScale(gMaterialConstants), + Material_emissionColorTexRotation(gMaterialConstants), + Material_emissionColorTexOffset(gMaterialConstants)); + material.emissionColor = sampleEmissionColorTexture(uv, 0.0f).rgb; + } + // Sample opacity from a texture if necessary. - if (materialConstants.hasOpacityTex) + if (Material_hasOpacityTex(gMaterialConstants)) { - float2 uv = applyUVTransform(shading.texCoord, materialConstants.opacityTexTransform.pivot, - materialConstants.opacityTexTransform.scale, - materialConstants.opacityTexTransform.rotation, - materialConstants.opacityTexTransform.offset); + float2 uv = applyUVTransform(shading.texCoord, Material_opacityTexPivot(gMaterialConstants), + Material_opacityTexScale(gMaterialConstants), + Material_opacityTexRotation(gMaterialConstants), + Material_opacityTexOffset(gMaterialConstants)); material.opacity = sampleOpacityTexture(uv, 0.0f).rgb; } - // Set generated normal flag to false, as normal not modified by material. - isGeneratedNormal = false; - // Sample a normal from the normal texture, convert it to an object-space normal, transform to // world space, and store it in the output value. - if (materialConstants.hasNormalTex) + isGeneratedNormal = false; + if (Material_hasNormalTex(gMaterialConstants)) { - float2 uv = applyUVTransform(shading.texCoord, materialConstants.normalTexTransform.pivot, - materialConstants.normalTexTransform.scale, - materialConstants.normalTexTransform.rotation, - materialConstants.normalTexTransform.offset); + float2 uv = applyUVTransform(shading.texCoord, Material_normalTexPivot(gMaterialConstants), + Material_normalTexScale(gMaterialConstants), + Material_normalTexRotation(gMaterialConstants), + Material_normalTexOffset(gMaterialConstants)); float3 normalTexel = sampleNormalTexture(uv, 0.0f).rgb; float3 objectSpaceNormal = calculateNormalFromMap( normalTexel, TANGENT_SPACE, 1.0, shading.normal, shading.tangent); materialNormal = normalize(mul((float3x3)objToWorld, objectSpaceNormal)); - // Set generated normal flag to true, as normal modified by normal map. isGeneratedNormal = true; } @@ -311,4 +283,45 @@ Material initializeDefaultMaterial( return material; } +Material defaultMaterial() +{ + // Copy the constant values to the material from the constant buffer. + Material material; + material.base = 0.8; + material.baseColor = WHITE; + material.diffuseRoughness = 0.0; + material.metalness = 0.0; + material.metalColor = WHITE; + material.specular = 1.0; + material.specularColor = WHITE; + material.specularRoughness = 0.2; + material.specularIOR = 1.5; + material.specularAnisotropy = 0.0; + material.specularRotation = 0.0; + material.transmission = 0.0; + material.transmissionColor = WHITE; + material.subsurface = 0.0; + material.subsurfaceColor = WHITE; + material.subsurfaceRadius = WHITE; + material.subsurfaceScale = 1.0; + material.subsurfaceAnisotropy = 0.0; + material.sheen = 0.0; + material.sheenColor = WHITE; + material.sheenRoughness = 0.3; + material.coat = 0.0; + material.coatColor = WHITE; + material.coatRoughness = 0.1; + material.coatAnisotropy = 0.0; + material.coatRotation = 0.0; + material.coatIOR = 1.5; + material.coatAffectColor = 0.0; + material.coatAffectRoughness = 0.0; + material.emission = 0.0; + material.emissionColor = WHITE; + material.opacity = 1.0; + material.thinWalled = 0; + + return material; +} + #endif // __MATERIAL_H__ diff --git a/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang b/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang index 0820569..6789548 100644 --- a/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang +++ b/Libraries/Aurora/Source/Shaders/MaterialXCommon.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang b/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang index 3b61630..c6f59b5 100644 --- a/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang +++ b/Libraries/Aurora/Source/Shaders/PathTracingCommon.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ // limitations under the License. // Prefix containing the common code used by all material types. -// NOTE: Does not contain a hit shader. Hit shaders for the different material types must be -// appended to this file. +#ifndef PATHTRACINGCOMMON_H +#define PATHTRACINGCOMMON_H // Define this symbol for NRD. #define COMPILER_DXC @@ -54,8 +54,8 @@ RaytracingAccelerationStructure gNullScene : register(t4); // Ray Gen Shader Variables // ================================================================================================= -// The output texture. -[[vk::binding(1)]] RWTexture2D gDirect : register(u0); +// The output textures (AOVs) as UAVs. +[[vk::binding(1)]] RWTexture2D gResult : register(u0); RWTexture2D gDepthNDC : register(u1); RWTexture2D gDepthView : register(u2); RWTexture2D gNormalRoughness : register(u3); @@ -83,21 +83,24 @@ struct MaterialLayerShaderIDs ByteAddressBuffer gIndices : register(t0, space1); ByteAddressBuffer gPositions : register(t1, space1); ByteAddressBuffer gNormals : register(t2, space1); -ByteAddressBuffer gTexCoords : register(t3, space1); +ByteAddressBuffer gTangents : register(t3, space1); +ByteAddressBuffer gTexCoords : register(t4, space1); // NOTE: Material variables are inserted at register(b1, space1) between gGeometryMetadata and // gMaterialLayerIDs by Material.slang. cbuffer gGeometryMetadata : register(b0, space1) { bool gHasNormals; // Are there normals? + bool gHasTangents; // Are there tangents? bool gHasTexCoords; // Are there texture coordinates? int gMaterialLayerCount; // Number of material layer miss shaders. + bool gIsOpaque; // Is the geometry opaque? } // To hide DX-Vulkan differences, expose geometry access using functions. uint3 getIndicesForTriangle(int triangleIndex) { return gIndices.Load3((triangleIndex * 3) * 4); -} +} float3 getPositionForVertex(int vertexIndex) { @@ -109,6 +112,11 @@ float3 getNormalForVertex(int vertexIndex) return asfloat(gNormals.Load3(vertexIndex * 3 * 4)); } +float3 getTangentForVertex(int vertexIndex) +{ + return asfloat(gTangents.Load3(vertexIndex * 3 * 4)); +} + float2 getTexCoordForVertex(int vertexIndex) { return asfloat(gTexCoords.Load2(vertexIndex * 2 * 4)); @@ -118,6 +126,10 @@ bool instanceHasNormals() { return gHasNormals; } +bool instanceHasTangents() +{ + return gHasTangents; +} bool instanceHasTexCoords() { return gHasTexCoords; @@ -129,8 +141,10 @@ bool instanceHasTexCoords() uint3 getIndicesForTriangle(int bufferLocation); float3 getPositionForVertex(int bufferLocation); float3 getNormalForVertex(int bufferLocation); +float3 getTangentForVertex(int bufferLocation); float2 getTexCoordForVertex(int bufferLocation); bool instanceHasNormals(); +bool instanceHasTangents(); bool instanceHasTexCoords(); #endif @@ -140,10 +154,12 @@ struct Geometry : IGeometry { uint3 getIndices(int triangleIndex) { return getIndicesForTriangle(triangleIndex); } float3 getPosition(int vertexIndex) { return getPositionForVertex(vertexIndex); } - float3 getNormal(int vertexIndex) { return normalize(getNormalForVertex(vertexIndex)); } + float3 getNormal(int vertexIndex) { return getNormalForVertex(vertexIndex); } + float3 getTangent(int vertexIndex) { return getTangentForVertex(vertexIndex); } float2 getTexCoord(int vertexIndex) { return getTexCoordForVertex(vertexIndex); } bool hasTexCoords() { return instanceHasTexCoords(); } bool hasNormals() { return instanceHasNormals(); } + bool hasTangents() { return instanceHasTangents(); } } gGeometry; // Constant buffer for layer material shader IDs. @@ -152,7 +168,7 @@ ConstantBuffer gMaterialLayerIDs : register(b2, space1); // Get the layer material index for given layer. int getMaterialLayerIndex(int layer) { - // TODO: Optimize the floattor->scalar look-up. + // TODO: Optimize the float->scalar look-up. return gMaterialLayerIDs.shaderIDs[layer / 4][layer % 4]; } @@ -198,4 +214,6 @@ void adjustRadiance(float maxLuminance, bool displayErrors, inout float3 radianc { radiance = displayErrors ? INF_COLOR : BLACK; } -} \ No newline at end of file +} + +#endif // PATHTRACINGCOMMON_H diff --git a/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang b/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang index 34ea136..fc34650 100644 --- a/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang +++ b/Libraries/Aurora/Source/Shaders/RadianceMissShader.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,12 +11,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// The shadow miss shader. This simply sets full visibility on the ray payload, indicating that -// nothing was hit. -// The radiance miss shader, which evaluates the environment as an environment light. + #include "PathTracingCommon.slang" -[shader("miss")] void RadianceMissShader(inout RayPayload rayPayload) { +// The radiance miss shader, which evaluates the environment as an environment light. +[shader("miss")] +void RadianceMissShader(inout RayPayload rayPayload) +{ // Initialize the radiance ray payload for a miss. rayPayload.radianceRay.clear(); @@ -32,8 +33,7 @@ } // Store the environment color. - // NOTE: The miss result is considered to be part of direct lighting, to allow for simpler logic - // during accumulation. - rayPayload.radianceRay.color = color; - rayPayload.radianceRay.direct = color; + // NOTE: The miss result will not be denoised, so it is included in the "extra" shading. + rayPayload.radianceRay.color = color; + rayPayload.radianceRay.extra = color; } \ No newline at end of file diff --git a/Libraries/Aurora/Source/Shaders/Random.slang b/Libraries/Aurora/Source/Shaders/Random.slang index db47af3..f77533a 100644 --- a/Libraries/Aurora/Source/Shaders/Random.slang +++ b/Libraries/Aurora/Source/Shaders/Random.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/RayGenShader.slang b/Libraries/Aurora/Source/Shaders/RayGenShader.slang index 4cd7c71..bfe270a 100644 --- a/Libraries/Aurora/Source/Shaders/RayGenShader.slang +++ b/Libraries/Aurora/Source/Shaders/RayGenShader.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,8 +13,6 @@ // limitations under the License. // Prefix containing the common code used by all material types. -// NOTE: Does not contain a hit shader. Hit shaders for the different material types must be -// appended to this file. #include"PathTracingCommon.slang" // The ray generation shader. @@ -53,21 +51,23 @@ float4 groundPlaneResult = shadeGroundPlane(gGroundPlane, gScene, environment, origin, dir, rayPayload.depthView, gFrameData.isOpaqueShadowsEnabled, maxDepth, rng); - // Blend the ground plane result with the radiance and direct lighting. - // TODO: The latter ensures the ground plane result appears when denoising, but the result - // itself still needs to be denoised. - result.rgb = lerp(result.rgb, groundPlaneResult.rgb, groundPlaneResult.a); - rayPayload.direct = lerp(rayPayload.direct, groundPlaneResult.rgb, groundPlaneResult.a); + // Blend the ground plane result with the radiance and "extra" shading. The extra shading is + // not itself denoised but is added to denoised output. + // TODO: The ground plane is currently treated as extra shading and is not denoised; it + // needs to contribute to the denoising input in order to be denoised. + result.rgb = lerp(result.rgb, groundPlaneResult.rgb, groundPlaneResult.a); + rayPayload.extra = lerp(rayPayload.extra, groundPlaneResult.rgb, groundPlaneResult.a); } #endif // Adjust the radiance of the sample, e.g. to perform corrections. adjustRadiance(gFrameData.maxLuminance, gFrameData.isDisplayErrorsEnabled, result.rgb); - // Store the result in the output texture. The output texture is called "direct" as it contains - // direct lighting when denoising is enabled, or complete lighting otherwise. - result.rgb = gFrameData.isDenoisingEnabled ? rayPayload.direct : result.rgb; - gDirect[screenCoords] = result; + // Store the result in the "result" output texture. If denoising is enabled, only the "extra" + // (non-denoised) shading is included in the result texture, as the rest of shading is stored in + // the denoising AOVs below. + result.rgb = gFrameData.isDenoisingEnabled ? rayPayload.extra : result.rgb; + gResult[screenCoords] = result; #if DIRECTX // Store the NDC depth value, if enabled. The value is only stored if the first sample was diff --git a/Libraries/Aurora/Source/Shaders/RayTrace.slang b/Libraries/Aurora/Source/Shaders/RayTrace.slang index c884970..0490593 100644 --- a/Libraries/Aurora/Source/Shaders/RayTrace.slang +++ b/Libraries/Aurora/Source/Shaders/RayTrace.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ #include "Random.slang" #include "Sampling.slang" -// A structure for the results of indirect lighting. +// A structure for the results of indirect lighting and environment light sampling, which is used +// for denoising. struct IndirectOutput { float3 diffuse; @@ -42,27 +43,57 @@ struct IndirectOutput }; // The radiance ray payload structure. -// - depthView: The view depth is the distance from the eye to the hit position. This value is in -// world units. -// - depthNDC: The NDC depth is the Z component of NDC space, i.e. after dividing the clip space -// position by W. This is the value normally stored in the depth buffer for rasterization. The -// projection matrix must generate Z components in [-1.0, 1.0], which is remapped to [0.0, 1.0] -// as the NDC depth. -// // NOTE: This is currently 28 floats in size. struct RadianceRayPayload { + // The color value for outgoing radiance from the path. float3 color; + + // The opacity accumulated along the path. This is stochastic, i.e. a zero or one value for the + // entire path, where zero means transparent or transmissive for the entire path and one means + // at least one opaque segment on the path. float alpha; - float3 direct; + + // The color value for outgoing radiance that is not intended to be denoised. This includes + // shading from material emission, discrete lights, and the background. Only data from the first + // hit (primary rays) is stored here. + float3 extra; + + // The NDC depth is the Z component of NDC space, i.e. after dividing the clip space position by + // W. This is the value normally stored in the depth buffer for rasterization. The projection + // matrix must generate Z components in [-1.0, 1.0], which is remapped to [0.0, 1.0] as the NDC + // depth. float depthNDC; + + // The view depth is the distance from the eye to the hit position. This value is in world + // units. float depthView; + + // The normal in world space of the first hit surface. This is based on the material, and may + // include geometric normals perturbed by a bump map. float3 normal; + + // The albedo of the first hit surface, as defined by Standard Surface. float3 baseColor; + + // The roughness of the first hit surface, as defined by Standard Surface. float roughness; + + // The metalness of the first hit surface, as defined by Standard Surface. float metalness; + + // Outgoing radiance and hit distances for diffuse and glossy lobes. This is based on shading + // from indirect light or sampled environment light, and only data from the first hit (primary + // rays) is stored here. This is used for denoising. IndirectOutput indirect; + + // The ray depth or number of segments in the path at this point in path evaluation. This is + // use to prevent a path from exceeding a certain length. At the first intersection, the depth + // value is 1, indicating a first hit (primary ray). int depth; + + // Random number generation state, which is updated during path evaluation so that different + // random numbers are generated along the path. Random rng; // Clears the radiance ray payload to values expected for a miss. @@ -77,7 +108,7 @@ struct RadianceRayPayload { color = BLACK; alpha = 0.0; - direct = BLACK; + extra = BLACK; depthNDC = 1.0f; depthView = INFINITY; normal = -1.0f; @@ -94,7 +125,7 @@ struct RadianceRayPayload void scaleColor(float3 scale) { color *= scale; - direct *= scale; + extra *= scale; baseColor *= scale; indirect.diffuse *= scale; indirect.glossy *= scale; @@ -203,7 +234,7 @@ RadianceRayPayload traceRadianceRay(RaytracingAccelerationStructure scene, Envir return rayPayload; } - + // Set the force opaque ray flag to treat all objects as opaque, so that the any hit shader is // not called. Also set the radiance or background miss shader. uint rayFlags = RAY_FLAG_FORCE_OPAQUE; diff --git a/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang b/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang index f485826..19f6554 100644 --- a/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang +++ b/Libraries/Aurora/Source/Shaders/ReferenceBSDF.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/Sampling.slang b/Libraries/Aurora/Source/Shaders/Sampling.slang index c1accb4..10d3f58 100644 --- a/Libraries/Aurora/Source/Shaders/Sampling.slang +++ b/Libraries/Aurora/Source/Shaders/Sampling.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang b/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang index 9695b7d..85e6914 100644 --- a/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang +++ b/Libraries/Aurora/Source/Shaders/ShadeFunctions.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,6 +13,17 @@ // limitations under the License. #include "BSDF.slang" +// Compute shading from material emission. +float3 shadeEmission(Material material) +{ + // Compute emission from the material properties. The emission is affected by the coat, i.e. the + // coat is on top of a glowing layer. + float3 emissionCoatBlend = lerp(WHITE, material.coatColor, material.coat); + float3 emission = material.emission * material.emissionColor * emissionCoatBlend; + + return emission; +} + // Compute shading with a directional light. float3 shadeDirectionalLight(float3 dir, float4 colorAndIntensity, float cosRadius, RaytracingAccelerationStructure scene, Material material, ShadingData shading, float3 V, diff --git a/Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang b/Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang deleted file mode 100644 index c42d970..0000000 --- a/Libraries/Aurora/Source/Shaders/ShadowHitEntryPointTemplate.slang +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2022 Autodesk, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Entry point template that defines a hit shader for a material type. -// NOTE: This file is not valid HLSL as is. This template must be configured at runtime by -// replacing the tags surrounded by the @ character: -// -@MATERIAL_TYPE@ with the unique material type name for this shader. - -// Any hit shader for shadow rays. -// NOTE: It is possible for this to be called multiple times for the same intersection, which would -// lead to incorrect visibility determination. However, this has not been a problem in practice. -// If this does become an issue, the D3D12_RAYTRACING_FLAG_NO_DUPLICATE_ANYHIT_INVOCATION flag can -// be used, with a performance cost. -#include "PathTracingCommon.slang" -#include "InitializeMaterial.slang" -#include "ShadeFunctions.slang" - -[shader("anyhit")] void @MATERIAL_TYPE@ShadowAnyHitShader( - inout ShadowRayPayload rayPayload, in BuiltInTriangleIntersectionAttributes hit) -{ - // If the material is opaque, set the visibility to zero, accept the hit, and stop searching for - // hits, as the shadow ray is completely blocked. Doing this here is a performance optimization, - // as it avoids calling the full material initialization below, which can be expensive. - if (gMaterialConstants.isOpaque) - { - rayPayload.visibility = BLACK; - AcceptHitAndEndSearch(); - } - - // Get the interpolated vertex data for the hit triangle, at the hit barycentric coordinates. - uint triangleIndex = PrimitiveIndex(); - float3 barycentrics = computeBarycentrics(hit); - ShadingData shading = computeShadingData( - gGeometry, triangleIndex, barycentrics, ObjectToWorld3x4()); - - // Initialize the material values. - // NOTE: This evaluates all material properties, when only visibility (or opacity) is needed. It - // may be more efficient to generate a dedicated function for getting the visibility alone. - float3 materialNormal = shading.normal; - bool isGeneratedNormal = false; - Material material = initializeMaterial(shading, ObjectToWorld3x4(), materialNormal, isGeneratedNormal); - - // Compute the opacity at the current hit from the opacity, transmission, and transmission color - // properties. Then accumulate that (inverted) with the current visibility on the ray payload. - // NOTE: Visibility accumulation like this is order-independent, i.e. it does not matter what - // order intersections are processed. This is important because any hit shader intersections are - // processed in an arbitrary order. Also, while this does consider *transmission* to determine - // visibility, this technique does not support refraction (caustics), just simple straight - // shadow rays. - float3 opacity = - material.opacity * (WHITE - material.transmission * material.transmissionColor); - rayPayload.visibility *= 1.0f - opacity; - - // If the visibility is zero (opaque) at this point, accept this hit and stop searching for - // hits, as the shadow ray is now completely blocked. Otherwise, ignore the hit so that - // visibility can continue to be accumulated from other any hit intersections. - if (isBlack(rayPayload.visibility)) - { - AcceptHitAndEndSearch(); - } - else - { - IgnoreHit(); - } - - // NOTE: The default behavior of accepting the hit while continuing to search for hits is not - // useful for shadow rays. It does not happen here, because of the intrinsic function calls - // above. -} diff --git a/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang b/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang index e939992..03f391f 100644 --- a/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang +++ b/Libraries/Aurora/Source/Shaders/ShadowMissShader.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang b/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang index a1e9bec..2468d04 100644 --- a/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang +++ b/Libraries/Aurora/Source/Shaders/StandardSurfaceBSDF.slang @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Transpiler.cpp b/Libraries/Aurora/Source/Transpiler.cpp index 6343493..c856a3f 100644 --- a/Libraries/Aurora/Source/Transpiler.cpp +++ b/Libraries/Aurora/Source/Transpiler.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/Transpiler.h b/Libraries/Aurora/Source/Transpiler.h index d1db936..b05cbf0 100644 --- a/Libraries/Aurora/Source/Transpiler.h +++ b/Libraries/Aurora/Source/Transpiler.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/UniformBuffer.cpp b/Libraries/Aurora/Source/UniformBuffer.cpp new file mode 100644 index 0000000..f141335 --- /dev/null +++ b/Libraries/Aurora/Source/UniformBuffer.cpp @@ -0,0 +1,439 @@ +// +// Copyright 2023 by Autodesk, Inc. All rights reserved. +// +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. +// +#include "pch.h" + +#include "UniformBuffer.h" + +BEGIN_AURORA + +const UniformBufferPropertyDefinition* UniformBuffer::getPropertyDef( + const string& propertyName) const +{ + // Look up the the first in the field map. return null if not found. + auto iter = _fieldMap.find(propertyName); + if (iter == _fieldMap.end()) + return nullptr; + + // Get the property for the field. + size_t fieldIndex = iter->second; + int propertyIndex = _fields[fieldIndex].index; + return &_definition[propertyIndex]; +} + +const UniformBuffer::Field* UniformBuffer::getField(const string& propertyName) const +{ + // Get pointer to field or null if not found. + auto iter = _fieldMap.find(propertyName); + if (iter == _fieldMap.end()) + return nullptr; + size_t fieldIndex = iter->second; + return &_fields[fieldIndex]; +} + +size_t UniformBuffer::getOffset(const string& propertyName) const +{ + // Get field + auto pField = getField(propertyName); + + // Return offset (multiplied by word size) or -1 if not found. + return pField ? pField->bufferIndex * sizeof(_data[0]) : -1; +} + +size_t UniformBuffer::getIndex(const string& propertyName) const +{ + // Get field + auto pField = getField(propertyName); + + // Return offset (multiplied by word size) or -1 if not found. + return pField ? pField->index : -1; +} + +size_t UniformBuffer::getOffsetForVariable(const string& varName) const +{ + // Get offset, looked up by variable name. + auto iter = _fieldVariableMap.find(varName); + if (iter == _fieldVariableMap.end()) + return (size_t)-1; + size_t fieldIndex = iter->second; + return _fields[fieldIndex].bufferIndex * sizeof(_data[0]); +} + +string UniformBuffer::generateHLSLStruct() const +{ + // Create struct using stringstream + stringstream ss; + + // String begins at first curly brace (no struct or name) + ss << "{" << endl; + + // Iterate through fields, keeping track of padding. + int paddingIndex = 0; + for (size_t i = 0; i < _fields.size(); i++) + { + + // If field is padding add padding int (index is -1). + if (_fields[i].index == -1) + { + ss << "\tint _padding" << paddingIndex++ << ";" << endl; + } + else + { + // If get definition for field. + auto def = _definition[_fields[i].index]; + + // Sdd field type and variable name. + ss << "\t" << getHLSLStringFromType(def.type) << " " << def.variableName << ";"; + + // Add comment. + ss << " // Offset:" << (_fields[i].bufferIndex * sizeof(_data[0])) + << " Property:" << def.name << endl; + } + } + + // Closing brace. + ss << "}" << endl; + + // Return string. + return ss.str(); +} + +string UniformBuffer::generateHLSLStructAndAccessors( + const string& structName, const string& prefix) const +{ + // Create struct using stringstream + stringstream ss; + + // String begins at first curly brace (no struct or name) + ss << "struct " << structName << endl; + ss << generateHLSLStruct(); + ss << ";" << endl; + + for (size_t i = 0; i < _fields.size(); i++) + { + if (_fields[i].index != -1) + { + // If get definition for field. + auto def = _definition[_fields[i].index]; + ss << endl + << getHLSLStringFromType(def.type) << " " << prefix << def.variableName << "(" + << structName << " mtl) {" << endl + << "\treturn mtl." << def.variableName << ";" << endl; + ss << "}" << endl; + } + } + + // Return string. + return ss.str(); +} + +string UniformBuffer::generateByteAddressBufferAccessors(const string& prefix) const +{ + stringstream ss; + for (size_t i = 0; i < _fields.size(); i++) + { + + if (_fields[i].index == -1) + continue; + auto def = _definition[_fields[i].index]; + + ss << " // Get property " << def.name << " from byte address buffer" << endl; + ss << getHLSLStringFromType(def.type) << " " << prefix << def.variableName + << "(ByteAddressBuffer buf) {" << endl; + size_t offset = _fields[i].bufferIndex * sizeof(_data[0]); + switch (def.type) + { + case PropertyValue::Type::Bool: + case PropertyValue::Type::Int: + ss << "\treturn buf.Load(" << offset << ");" << endl; + break; + case PropertyValue::Type::Float: + ss << "\treturn asfloat(buf.Load(" << offset << "));" << endl; + break; + case PropertyValue::Type::Float2: + ss << "\treturn asfloat(buf.Load2(" << offset << "));" << endl; + break; + case PropertyValue::Type::Float3: + ss << "\treturn asfloat(buf.Load3(" << offset << "));" << endl; + break; + case PropertyValue::Type::Float4: + ss << "\treturn asfloat(buf.Load4(" << offset << "));" << endl; + case PropertyValue::Type::Matrix4: + for (int j = 0; j < 16; j++) + { + ss << "\tfloat4 m" << j << " = asfloat(buf.Load(" << offset + (j * 4) << "));" + << endl; + ; + } + ss << "float4x4 mtx = {"; + + for (int j = 0; j < 16; j++) + { + ss << "m" << j; + if (j < 15) + ss << ", "; + } + ss << "};" << endl; + ss << "\treturn mtx;" << endl; + break; + default: + break; + } + + ss << "}" << endl << endl; + } + + return ss.str(); +} + +void UniformBuffer::reset(const string& name) +{ + size_t index = getIndex(name); + if (index == size_t(-1)) + { + AU_ERROR("Unknown property %s", name.c_str()); + return; + } + + set(name, _defaults[index]); +} + +PropertyValue::Type UniformBuffer::getType(const string& propertyName) const +{ + auto pDef = getPropertyDef(propertyName); + if (!pDef) + return PropertyValue::Type::Undefined; + return pDef->type; +} + +const string& UniformBuffer::getVariableName(const string& propertyName) const +{ + static string invalidName = ""; + auto pDef = getPropertyDef(propertyName); + if (!pDef) + return invalidName; + return pDef->variableName; +} + +UniformBuffer::UniformBuffer( + const UniformBufferDefinition& definition, const vector& defaults) : + _definition(definition), _defaults(defaults) +{ + AU_ASSERT(_definition.size() == _defaults.size(), + "Mismatch between defaults size and definitions size"); + + size_t bufferIndex = 0; + for (size_t i = 0; i < definition.size(); i++) + { + // Get size and alignment of current property, in bytes and words. + PropertyValue::Type type = definition[i].type; + size_t valAlignment = getAlignment(type); + size_t valSize = getSizeOfType(type); + size_t wordSize = sizeof(_data[0]); + size_t numAlignmentWords = valAlignment / wordSize; + size_t valSizeWords = valSize / wordSize; + AU_ASSERT(valSize % wordSize == 0, "Type too small for uniform buffer"); + + // HLSL packing ensures properties (that are quadword size or less) cannot cross quad-word + // boundary. + size_t endIndex = bufferIndex + valSizeWords - 1; + bool crossesQuadWordBoundary = (valSize <= 16) && (endIndex / 4 != bufferIndex / 4); + + // Add padding while alignment incorrect and field is crossing quad-word boundary. + while (bufferIndex % numAlignmentWords || crossesQuadWordBoundary) + { + // Add padding field. + _fields.push_back({ bufferIndex, PropertyValue::Type::Int, -1 }); + bufferIndex++; + + // See if we still cross quad word boundary. + endIndex = bufferIndex + valSizeWords - 1; + crossesQuadWordBoundary = (valSize <= 16) && (endIndex / 4 != bufferIndex / 4); + } + + // Create field. + int fieldIndex = (int)_fields.size(); + _fieldMap[definition[i].name] = fieldIndex; + _fieldVariableMap[definition[i].variableName] = fieldIndex; + _fields.push_back({ bufferIndex, definition[i].type, (int)i }); + + // Set default values. + AU_ASSERT( + _definition[i].type == _defaults[i].type, "Default type does not match definition"); + bufferIndex = copyToBuffer(defaults[i], bufferIndex); + } + + // Align buffer to 16 bytes. + while (bufferIndex % 4) + { + _fields.push_back({ bufferIndex, PropertyValue::Type::Int, -1 }); + bufferIndex++; + } + + // Ensure the final padding fields are included in buffer size. + _data.resize(bufferIndex); +} + +const UniformBuffer::Field* UniformBuffer::findField(const string& name) +{ + // Find the index of field, return null if nor found. + auto iter = _fieldMap.find(name); + if (iter == _fieldMap.end()) + return nullptr; + + // Return the indexed field. + return &_fields[iter->second]; +} + +void UniformBuffer::set(const string& name, const PropertyValue& val) +{ + // Find the field for given name, print error and return if not found. + const UniformBuffer::Field* pField = findField(name); + if (!pField) + { + AU_ERROR("Uniform block does not contain property %s", name.c_str()); + return; + } + + // If the value's type does not match field's print error and return. + if (pField->type != val.type) + { + AU_ERROR("Type mismatch in UniformBlock for property %s, %x!=%x", name.c_str(), + pField->type, val.type); + return; + } + + // Copy the vale to the buffer at the index given by bufferIndex. + copyToBuffer(val, pField->bufferIndex); +} + +size_t UniformBuffer::copyToBuffer(const PropertyValue& val, size_t bufferIndex) +{ + switch (val.type) + { + case PropertyValue::Type::Bool: + return copyToBuffer(val.asBool(), bufferIndex); + case PropertyValue::Type::Int: + return copyToBuffer(val.asInt(), bufferIndex); + case PropertyValue::Type::Float: + return copyToBuffer(val.asFloat(), bufferIndex); + case PropertyValue::Type::Float2: + return copyToBuffer(val.asFloat2(), bufferIndex); + case PropertyValue::Type::Float3: + return copyToBuffer(val.asFloat3(), bufferIndex); + case PropertyValue::Type::Float4: + return copyToBuffer(val.asFloat4(), bufferIndex); + case PropertyValue::Type::Matrix4: + return copyToBuffer(val.asMatrix4(), bufferIndex); + default: + AU_FAIL("Unsupported type for uniform block:%x", val.type); + return 0; + } +} + +// Following HLSL alignment rules. +size_t UniformBuffer::getAlignment(PropertyValue::Type type) +{ + // Scalars and vectors of 3 or less fields have single word alignment, everything else is + // quadword. + switch (type) + { + case PropertyValue::Type::Bool: + return sizeof(int); + case PropertyValue::Type::Int: + return sizeof(int); + case PropertyValue::Type::Float: + return sizeof(float); + case PropertyValue::Type::Float2: + return sizeof(float); + case PropertyValue::Type::Float3: + return sizeof(float); + case PropertyValue::Type::Float4: + return sizeof(vec4); + case PropertyValue::Type::Matrix4: + return sizeof(vec4); + default: + AU_FAIL("Unsupported type for uniform block:%x", type); + return 0; + } +} + +string UniformBuffer::getGLSLStringFromType(PropertyValue::Type type) +{ + switch (type) + { + case PropertyValue::Type::Bool: + return "int"; + case PropertyValue::Type::Int: + return "int"; + case PropertyValue::Type::Float: + return "float"; + case PropertyValue::Type::Float2: + return "vec2"; + case PropertyValue::Type::Float3: + return "vec3"; + case PropertyValue::Type::Float4: + return "vec4"; + case PropertyValue::Type::Matrix4: + return "mat4"; + default: + AU_FAIL("Unsupported type for uniform block:%x", type); + return 0; + } +} + +string UniformBuffer::getHLSLStringFromType(PropertyValue::Type type) +{ + switch (type) + { + case PropertyValue::Type::Bool: + return "int"; + case PropertyValue::Type::Int: + return "int"; + case PropertyValue::Type::Float: + return "float"; + case PropertyValue::Type::Float2: + return "float2"; + case PropertyValue::Type::Float3: + return "float3"; + case PropertyValue::Type::Float4: + return "float4"; + case PropertyValue::Type::Matrix4: + return "float4x4"; + default: + AU_FAIL("Unsupported type for uniform block:%x", type); + return 0; + } +} + +size_t UniformBuffer::getSizeOfType(PropertyValue::Type type) +{ + switch (type) + { + case PropertyValue::Type::Bool: + return sizeof(int); + case PropertyValue::Type::Int: + return sizeof(int); + case PropertyValue::Type::Float: + return sizeof(float); + case PropertyValue::Type::Float2: + return sizeof(vec2); + case PropertyValue::Type::Float3: + return sizeof(vec3); + case PropertyValue::Type::Float4: + return sizeof(vec4); + case PropertyValue::Type::Matrix4: + return sizeof(mat4); + default: + AU_FAIL("Unsupported type for uniform block:%x", type); + return 0; + } +} + +END_AURORA diff --git a/Libraries/Aurora/Source/UniformBuffer.h b/Libraries/Aurora/Source/UniformBuffer.h new file mode 100644 index 0000000..35622ca --- /dev/null +++ b/Libraries/Aurora/Source/UniformBuffer.h @@ -0,0 +1,174 @@ +// +// Copyright 2023 by Autodesk, Inc. All rights reserved. +// +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. +// +#pragma once + +#include "Properties.h" + +BEGIN_AURORA + +// Definition of a uniform buffer property. +struct UniformBufferPropertyDefinition +{ + + UniformBufferPropertyDefinition( + const string& nm, const string& varName, PropertyValue::Type type) : + name(nm), variableName(varName), type(type) + { + } + + // Human readable name of property. + string name; + // C-like variable name for property. + string variableName; + // The type of the property. + PropertyValue::Type type = PropertyValue::Type::Undefined; +}; + +// A uniform buffer definition is just an array of property definitions objects. +// TODO: Add a UniformBufferDefinition class that can contain a lot of the stuff (e.g. the fields) +// currently in UniformBuffer. +using UniformBufferDefinition = vector; + +// Definition of a texture. +// TODO: Should be part of equivalent TextureArray class, similar to UniformBuffer. +struct TextureDefinition +{ + // Texture name. + string name; + // Is true, the texture is sRGB that should be converted to linear color space on the GPU. + bool linearize = true; + // Default filename for texture. + string defaultFilename = ""; + // Default U address mode for texture (can have values: periodic, clamp, or mirror) + string addressModeU = ""; + // Default V address mode for texture (can have values: periodic, clamp, or mirror) + string addressModeV = ""; +}; + +// Class representing generic uniform buffer, and a collection of named scalar and vector properties +// packed into a contiguous chunk of memory. Used to represent a material's properties in a +// data-driven manner. +class UniformBuffer +{ +public: + // Constructor. + UniformBuffer(const UniformBufferDefinition& definition, const vector& defaults); + + // Sets a named property in the buffer. + template + void set(const string& name, const ValType& val) + { + PropertyValue propVal = val; + set(name, propVal); + } + + // Gets a named property from the buffer. + template + ValType get(const string& name) const + { + auto pField = getField(name); + ValType res = ValType(); + if (!pField) + { + AU_ERROR("No property named %s in uniform buffer.", name.c_str()); + return res; + } + PropertyValue::Type type = _definition[pField->index].type; + AU_ASSERT(getSizeOfType(type) == sizeof(ValType), "Type mismatch."); + res = *(ValType*)&_data[pField->bufferIndex]; + + return res; + } + + // Resets a property to its default value. + void reset(const string& name); + + // Gets the size of the buffer in bytes. + size_t size() const { return _data.size() * sizeof(_data[0]); } + + // Gets the contents of the buffer. + void* data() { return _data.data(); } + const void* data() const { return _data.data(); } + + // Generates a HLSL struct from this buffer. + string generateHLSLStruct() const; + + // Generates a HLSL struct and accessors from this buffer. + string generateHLSLStructAndAccessors(const string& structName, const string& prefix) const; + + // Generates the HLSL accessor functions for this buffer, with a ByteAddressBuffer being used + // for storage. Each function name is prefixed with the specified string, e.g. using a + // "Material42_" prefix might produce the following string: + // + // "float Material42_specularRoughness(ByteAddressBuffer buf) { return asfloat(buf.Load(44)); }" + string generateByteAddressBufferAccessors(const string& prefix) const; + + // Gets the offset (in bytes) of a property in the buffer + size_t getOffset(const string& propertyName) const; + + // Gets the index of this property in buffer (or -1 if not found.) + size_t getIndex(const string& propertyName) const; + + // Gets the offset (in bytes) of a variable name in the buffer + size_t getOffsetForVariable(const string& varName) const; + + // Gets the type of a property. + PropertyValue::Type getType(const string& propertyName) const; + + // Gets the variable name of a property. + const string& getVariableName(const string& propertyName) const; + + // Returns whether the uniform buffer contains the named property. + bool contains(const string& propertyName) const { return getField(propertyName) != nullptr; } + +private: + // A field within the buffer, can be property or empty padding field (in which case index is -1) + struct Field + { + // Index within data buffer. + size_t bufferIndex = 0; + // Type of property. + PropertyValue::Type type = PropertyValue::Type::Undefined; + // Index to definition in _definition (-1 if padding) + int index = 0; + }; + const UniformBufferPropertyDefinition* getPropertyDef(const string& propertyName) const; + const Field* getField(const string& propertyName) const; + + void set(const string& name, const PropertyValue& val); + + template + size_t copyToBuffer(const ValType& val, size_t bufferIndex) + { + size_t numWords = sizeof(ValType) / sizeof(_data[0]); + if (_data.size() < bufferIndex + numWords) + { + _data.resize(bufferIndex + numWords); + } + ValType* pDstData = (ValType*)&_data[bufferIndex]; + *pDstData = val; + return bufferIndex + numWords; + } + + static size_t getSizeOfType(PropertyValue::Type type); + static string getGLSLStringFromType(PropertyValue::Type type); + static string getHLSLStringFromType(PropertyValue::Type type); + static size_t getAlignment(PropertyValue::Type type); + size_t copyToBuffer(const PropertyValue& val, size_t bufferIndex); + const Field* findField(const string& name); + vector _fields; + vector _data; + map _fieldMap; + map _fieldVariableMap; + const UniformBufferDefinition& _definition; + const vector& _defaults; +}; + +END_AURORA diff --git a/Libraries/Aurora/Source/WindowsHeaders.h b/Libraries/Aurora/Source/WindowsHeaders.h index 78175e5..b703205 100644 --- a/Libraries/Aurora/Source/WindowsHeaders.h +++ b/Libraries/Aurora/Source/WindowsHeaders.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Aurora/Source/pch.h b/Libraries/Aurora/Source/pch.h index 65e80a8..d3fb09e 100644 --- a/Libraries/Aurora/Source/pch.h +++ b/Libraries/Aurora/Source/pch.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ using UINT = unsigned int; { \ if ((p) != nullptr) \ { \ - delete[](p); \ + delete[] (p); \ (p) = nullptr; \ } \ } diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 8c75a4e..06e8057 100644 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(Foundation) add_subdirectory(Aurora) add_subdirectory(HdAurora) +add_subdirectory(ImageProcessingResolver) diff --git a/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h b/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h index 07cf827..17cd366 100644 --- a/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h +++ b/Libraries/Foundation/API/Aurora/Foundation/BoundingBox.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Foundation/API/Aurora/Foundation/Frustum.h b/Libraries/Foundation/API/Aurora/Foundation/Frustum.h index 3bb5be6..2dfab4e 100644 --- a/Libraries/Foundation/API/Aurora/Foundation/Frustum.h +++ b/Libraries/Foundation/API/Aurora/Foundation/Frustum.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ #include #include -//#define USE_SPHERICAL_BOUNDS 1 +// #define USE_SPHERICAL_BOUNDS 1 namespace Aurora { diff --git a/Libraries/Foundation/API/Aurora/Foundation/Geometry.h b/Libraries/Foundation/API/Aurora/Foundation/Geometry.h new file mode 100644 index 0000000..0b212b1 --- /dev/null +++ b/Libraries/Foundation/API/Aurora/Foundation/Geometry.h @@ -0,0 +1,24 @@ +// +// Copyright 2023 by Autodesk, Inc. All rights reserved. +// +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. +// +#pragma once + +namespace Aurora +{ +namespace Foundation +{ + +void calculateNormals(size_t vertexCount, const float* vertex, size_t triangleCount, + const unsigned int* indices, float* normalOut); + +void calculateTangents(size_t vertexCount, const float* vertex, const float* normal, + const float* texcoord, size_t triangleCount, const unsigned int* indices, float* tangentOut); + +} // namespace Foundation +} // namespace Aurora \ No newline at end of file diff --git a/Libraries/Foundation/API/Aurora/Foundation/Log.h b/Libraries/Foundation/API/Aurora/Foundation/Log.h index ba80784..a11e36e 100644 --- a/Libraries/Foundation/API/Aurora/Foundation/Log.h +++ b/Libraries/Foundation/API/Aurora/Foundation/Log.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Foundation/API/Aurora/Foundation/Plane.h b/Libraries/Foundation/API/Aurora/Foundation/Plane.h index 02f1fd7..dcac9bf 100644 --- a/Libraries/Foundation/API/Aurora/Foundation/Plane.h +++ b/Libraries/Foundation/API/Aurora/Foundation/Plane.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Foundation/API/Aurora/Foundation/Timer.h b/Libraries/Foundation/API/Aurora/Foundation/Timer.h index 64849d5..a910514 100644 --- a/Libraries/Foundation/API/Aurora/Foundation/Timer.h +++ b/Libraries/Foundation/API/Aurora/Foundation/Timer.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Foundation/API/Aurora/Foundation/Utilities.h b/Libraries/Foundation/API/Aurora/Foundation/Utilities.h index aa9e902..d446f6a 100644 --- a/Libraries/Foundation/API/Aurora/Foundation/Utilities.h +++ b/Libraries/Foundation/API/Aurora/Foundation/Utilities.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,6 +30,13 @@ namespace Aurora namespace Foundation { +/// Remove illegal chars from a filename. +void sanitizeFileName(std::string& fileName); + +/// Write string to file, returns false if write fails. +bool writeStringToFile( + const std::string& str, const std::string& filename, const std::string& folder = ""); + /// Combine a hash with a seed value. void hashCombine(size_t& seed, size_t otherHash); diff --git a/Libraries/Foundation/CMakeLists.txt b/Libraries/Foundation/CMakeLists.txt index 4e84a6b..d58b41a 100644 --- a/Libraries/Foundation/CMakeLists.txt +++ b/Libraries/Foundation/CMakeLists.txt @@ -1,5 +1,7 @@ project(Foundation) +find_package(glm REQUIRED) # Find the GLM vector maths package. + add_library(${PROJECT_NAME} STATIC "API/Aurora/Foundation/BoundingBox.h" "API/Aurora/Foundation/Frustum.h" @@ -7,11 +9,18 @@ add_library(${PROJECT_NAME} STATIC "API/Aurora/Foundation/Plane.h" "API/Aurora/Foundation/Timer.h" "API/Aurora/Foundation/Utilities.h" + "API/Aurora/Foundation/Geometry.h" + "Source/Geometry.cpp" "Source/Utilities.cpp" "Source/Log.cpp" ) -# Set custom ouput properties. +target_link_libraries(${PROJECT_NAME} +PRIVATE + glm::glm +) + +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Libraries" # TODO: set a custom output name for this target: OUTPUT_NAME "aurora_foundation" diff --git a/Libraries/Foundation/Source/Geometry.cpp b/Libraries/Foundation/Source/Geometry.cpp new file mode 100644 index 0000000..242cbfa --- /dev/null +++ b/Libraries/Foundation/Source/Geometry.cpp @@ -0,0 +1,176 @@ +// +// Copyright 2023 by Autodesk, Inc. All rights reserved. +// +// This computer source code and related instructions and comments +// are the unpublished confidential and proprietary information of +// Autodesk, Inc. and are protected under applicable copyright and +// trade secret law. They may not be disclosed to, copied or used +// by any third party without the prior written consent of Autodesk, Inc. +// + +#include +#include + +#include + +#include + +using namespace glm; + +namespace Aurora +{ +namespace Foundation +{ + +void calculateNormals(size_t vertexCount, const float* vertex, size_t triangleCount, + const unsigned int* indices, float* normalOut) +{ + const bool angleWeighted = true; + float angle1 = 1.0f; + float angle2 = 1.0f; + float angle3 = 1.0f; + + for (size_t face = 0; face < triangleCount; ++face) + { + const unsigned int& ip1 = indices[face * 3]; + const unsigned int& ip2 = indices[(face * 3) + 1]; + const unsigned int& ip3 = indices[(face * 3) + 2]; + + vec3 p1(*(vertex + (ip1 * 3)), *(vertex + (ip1 * 3) + 1), *(vertex + (ip1 * 3) + 2)); + vec3 p2(*(vertex + (ip2 * 3)), *(vertex + (ip2 * 3) + 1), *(vertex + (ip2 * 3) + 2)); + vec3 p3(*(vertex + (ip3 * 3)), *(vertex + (ip3 * 3) + 1), *(vertex + (ip3 * 3) + 2)); + const vec3 vA = p3 - p1; + const vec3 vB = p2 - p1; + + // compute the face normal + const vec3 nrm = normalize(cross(vB, vA)); + vec3* normal = nullptr; + + if (angleWeighted) + { + // compute the face angle at a vertex and weight the normals based on angle. + const vec3 v31 = normalize(vA); + const vec3 v21 = normalize(vB); + const vec3 v32 = normalize(p3 - p2); + vec3 _v31; + _v31.x = -v31.x; + _v31.y = -v31.y; + _v31.z = -v31.z; + vec3 _v21; + _v21.x = -v21.x; + _v21.y = -v21.y; + _v21.z = -v21.z; + vec3 _v32; + _v32.x = -v32.x; + _v32.y = -v32.y; + _v32.z = -v32.z; + + const float d1 = dot(v31, v21); + const float d2 = dot(v32, _v21); + const float d3 = dot(_v31, _v32); + + angle1 = std::abs(std::acos(d1)); + angle2 = std::abs(std::acos(-d2)); + angle3 = std::abs(std::acos(d3)); + } + + // for each vertex, the normals are summed; this code assumes + // that the mesh is supposed to be smooth - there is no + // separation of vertices due to crease angle. + normal = (vec3*)(normalOut + (ip1 * 3)); + *(normal) += (nrm * angle1); + + normal = (vec3*)(normalOut + (ip2 * 3)); + *(normal) += nrm * angle2; + + normal = (vec3*)(normalOut + (ip3 * 3)); + *(normal) += nrm * angle3; + } + + // normalize all the summed up normals + for (size_t i = 0; i < vertexCount; i++) + { + vec3* curPos = (vec3*)(normalOut + (i * 3)); + vec3 n = normalize(*curPos); +#ifdef _WIN32 + ::memcpy_s(normalOut + i * 3, vertexCount * 3 * sizeof(float), &n, sizeof(vec3)); +#else + std::memcpy(normalOut + i * 3 * sizeof(float), &n, sizeof(vec3)); +#endif + } +} + +void calculateTangents(size_t vertexCount, const float* vertex, const float* normal, + const float* texcoord, size_t triangleCount, const unsigned int* indices, float* tangentOut) +{ + // Iterate through faces. + for (size_t faceIndex = 0; faceIndex < triangleCount; faceIndex++) + { + unsigned int i0 = indices[faceIndex * 3]; + unsigned int i1 = indices[faceIndex * 3 + 1]; + unsigned int i2 = indices[faceIndex * 3 + 2]; + + // Assume flattened vertices if no indices. + if (indices) + { + i0 = indices[faceIndex * 3]; + i1 = indices[faceIndex * 3 + 1]; + i2 = indices[faceIndex * 3 + 2]; + } + else + { + i0 = static_cast(faceIndex * 3); + i1 = static_cast(faceIndex * 3 + 1); + i2 = static_cast(faceIndex * 3 + 2); + } + + const vec3& p0 = *reinterpret_cast(&vertex[i0 * 3]); + const vec3& p1 = *reinterpret_cast(&vertex[i1 * 3]); + const vec3& p2 = *reinterpret_cast(&vertex[i2 * 3]); + + const vec2& w0 = *reinterpret_cast(&texcoord[i0 * 2]); + const vec2& w1 = *reinterpret_cast(&texcoord[i1 * 2]); + const vec2& w2 = *reinterpret_cast(&texcoord[i2 * 2]); + + vec3& t0 = *reinterpret_cast(&tangentOut[i0 * 3]); + vec3& t1 = *reinterpret_cast(&tangentOut[i1 * 3]); + vec3& t2 = *reinterpret_cast(&tangentOut[i2 * 3]); + + // Based on Eric Lengyel at http://www.terathon.com/code/tangent.html + vec3 e1 = p1 - p0; + vec3 e2 = p2 - p0; + float x1 = w1[0] - w0[0]; + float x2 = w2[0] - w0[0]; + float y1 = w1[1] - w0[1]; + float y2 = w2[1] - w0[1]; + float denom = x1 * y2 - x2 * y1; + float r = (fabsf(denom) > 1.0e-12) ? (1.0f / denom) : 0.0f; + vec3 t = (e1 * y2 - e2 * y1) * r; + t0 += t; + t1 += t; + t2 += t; + } + // Iterate through vertices. + for (size_t v = 0; v < vertexCount; v++) + { + const vec3& n = *reinterpret_cast(&normal[v * 3]); + vec3& t = *reinterpret_cast(&tangentOut[v * 3]); + if (t != vec3(0.0f)) + { + // Gram-Schmidt orthogonalize. + t = normalize(t - n * dot(n, t)); + } + else + { + // Generate an arbitrary tangent. + // https://graphics.pixar.com/library/OrthonormalB/paper.pdf + float sign = (n[2] < 0.0f) ? -1.0f : 1.0f; + float a = -1.0f / (sign + n[2]); + float b = n[0] * n[1] * a; + t = normalize(vec3(1.0f + sign * n[0] * n[0] * a, sign * b, -sign * n[0])); + } + } +} + +} // namespace Foundation +} // namespace Aurora \ No newline at end of file diff --git a/Libraries/Foundation/Source/Log.cpp b/Libraries/Foundation/Source/Log.cpp index 0dc81c0..39c1077 100644 --- a/Libraries/Foundation/Source/Log.cpp +++ b/Libraries/Foundation/Source/Log.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/Foundation/Source/Utilities.cpp b/Libraries/Foundation/Source/Utilities.cpp index a897eb5..510712e 100644 --- a/Libraries/Foundation/Source/Utilities.cpp +++ b/Libraries/Foundation/Source/Utilities.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,11 +24,43 @@ #include #endif +#include + namespace Aurora { namespace Foundation { +void sanitizeFileName(std::string& fileName) +{ + // Replace non-file chars with underscore + // TODO: More exhaustive set of chars + std::replace(fileName.begin(), fileName.end(), '/', '_'); + std::replace(fileName.begin(), fileName.end(), '\\', '_'); + std::replace(fileName.begin(), fileName.end(), '?', '_'); + std::replace(fileName.begin(), fileName.end(), '*', '_'); +} + +bool writeStringToFile( + const std::string& str, const std::string& filename, const std::string& folder) +{ + // If the folder is empty, use the module path. + std::string pathFolder = folder.empty() ? getModulePath() : folder; + + // Create path from folder+filename (assume slash at end of folder.) + std::string path = pathFolder + filename; + + // Write string to output stream. + std::ofstream outputFile; + outputFile.open(path); + outputFile << str; + if (!outputFile.good()) + return false; + outputFile.close(); + + return true; +} + void hashCombine(size_t& seed, size_t otherHash) { seed ^= otherHash + 0x9e3779b9 + (seed << 6) + (seed >> 2); diff --git a/Libraries/HdAurora/CMakeLists.txt b/Libraries/HdAurora/CMakeLists.txt index 3b99e5b..aed7b30 100644 --- a/Libraries/HdAurora/CMakeLists.txt +++ b/Libraries/HdAurora/CMakeLists.txt @@ -3,18 +3,25 @@ project(hdAurora) find_package(glm REQUIRED) # Find the GLM vector maths package. find_package(OpenGL REQUIRED) -find_package(Vulkan REQUIRED) find_package(GLEW REQUIRED) - +find_package(MaterialX REQUIRED) # MaterialX SDK find_package(pxr REQUIRED) # Find the Universal Scene Description (USD) library +# Alias the namespace to meet the cmake convention on imported targets +add_library(MaterialX::GenGlsl ALIAS MaterialXGenGlsl) + +# On windows, the version resource and header files that add the version information to the DLL. +if(WIN32) +set(VERSION_FILES + ${VERSION_FOLDER}/AuroraVersion.h + HdAurora.rc +) +endif() # Add shard library with all source files. add_library(${PROJECT_NAME} SHARED "DLL.cpp" "HdAuroraImageCache.cpp" "HdAuroraImageCache.h" - "HdAuroraCamera.cpp" - "HdAuroraCamera.h" "HdAuroraInstancer.cpp" "HdAuroraInstancer.h" "HdAuroraLight.cpp" @@ -33,15 +40,19 @@ add_library(${PROJECT_NAME} SHARED "HdAuroraRenderPass.h" "HdAuroraTokens.h" "pch.h" + ${VERSION_FILES} ) -# Set custom ouput properties. +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Libraries" RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + + # Follow the USD convention of lower-case initial on libraries. + OUTPUT_NAME "hdAurora" ) # Add dependencies. @@ -50,6 +61,7 @@ PRIVATE glm::glm OpenGL::GL GLEW::glew + MaterialX::GenGlsl pxr::usd pxr::hf pxr::hd @@ -95,6 +107,12 @@ else() # Linux #TODO anything we need to set to support the Aurora execution? endif() +# Add the version folder required by HdAurora.rc +target_include_directories(${PROJECT_NAME} +PRIVATE + ${VERSION_FOLDER} +) + # Install the binaries. install(TARGETS ${PROJECT_NAME} DESTINATION ${INSTALL_BIN}/usd) install(FILES "resources/plugInfo.json" DESTINATION ${INSTALL_BIN}/usd/hdAurora/resources) \ No newline at end of file diff --git a/Libraries/HdAurora/DLL.cpp b/Libraries/HdAurora/DLL.cpp index da3a01b..6379e8b 100644 --- a/Libraries/HdAurora/DLL.cpp +++ b/Libraries/HdAurora/DLL.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/HdAurora/HdAurora.rc b/Libraries/HdAurora/HdAurora.rc new file mode 100644 index 0000000..4c02f79 --- /dev/null +++ b/Libraries/HdAurora/HdAurora.rc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b9aa2802206d7088df4d013ed93f059a2eef02c6903fd17ce63011a4d12bdfd +size 363 diff --git a/Libraries/HdAurora/HdAuroraCamera.cpp b/Libraries/HdAurora/HdAuroraCamera.cpp deleted file mode 100644 index 3cfe72b..0000000 --- a/Libraries/HdAurora/HdAuroraCamera.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "pch.h" - -#include "HdAuroraCamera.h" -#include "HdAuroraRenderDelegate.h" -#include - -#include - -#pragma warning(disable : 4506) // inline function warning (from USD but appears in this file) - -HdAuroraCamera::HdAuroraCamera(SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : - HdCamera(rprimId), _owner(renderDelegate) -{ -} - -HdAuroraCamera::~HdAuroraCamera() -{ - // Ensure progressive rendering is reset. - _owner->SetSampleRestartNeeded(true); -} - -HdDirtyBits HdAuroraCamera::GetInitialDirtyBitsMask() const -{ - return HdCamera::DirtyParams; -} - -void HdAuroraCamera::Sync( - HdSceneDelegate* delegate, HdRenderParam* /* renderParam */, HdDirtyBits* dirtyBits) -{ - - const auto& id = GetId(); - // Laking of viewmatrix and projection matrix in HDCameraTokens - // Camera' viewMatrix transmit directly by transform cache - GfMatrix4f viewMat = GfMatrix4f(delegate->GetTransform(id).GetInverse()); - GfMatrix4f projMat = GfMatrix4f(0.0f); - - // Set Camera properties to aurora by GfCamera physical propeties - float focalLength = - (delegate->GetCameraParamValue(id, HdCameraTokens->focalLength)).Get() / - float(GfCamera::FOCAL_LENGTH_UNIT); - float horizontalAperture = - (delegate->GetCameraParamValue(id, HdCameraTokens->horizontalAperture)).Get() / - float(GfCamera::APERTURE_UNIT); - float verticalAperture = - (delegate->GetCameraParamValue(id, HdCameraTokens->verticalAperture)).Get() / - float(GfCamera::APERTURE_UNIT); - float horizontalApertureOffset = - (delegate->GetCameraParamValue(id, HdCameraTokens->horizontalApertureOffset)).Get() / - float(GfCamera::APERTURE_UNIT); - float verticalApertureOffset = - (delegate->GetCameraParamValue(id, HdCameraTokens->verticalApertureOffset)).Get() / - float(GfCamera::APERTURE_UNIT); - GfRange1f clippingRange = - (delegate->GetCameraParamValue(id, HdCameraTokens->clippingRange)).Get(); - HdCamera::Projection projectionType = - (delegate->GetCameraParamValue(id, HdCameraTokens->projection)).Get(); - - // Set projection matrix based on projection type. - if (HdCamera::Projection::Perspective == projectionType) - { - projMat[0][0] = 2 * focalLength / horizontalAperture; - projMat[1][1] = 2 * focalLength / verticalAperture; - projMat[2][0] = 2 * horizontalApertureOffset / horizontalAperture; - projMat[2][1] = 2 * verticalApertureOffset / verticalAperture; - projMat[3][2] = 2 * clippingRange.GetMin() * clippingRange.GetMax() / - (clippingRange.GetMin() - clippingRange.GetMax()); - projMat[2][2] = (clippingRange.GetMin() + clippingRange.GetMax()) / - (clippingRange.GetMin() - clippingRange.GetMax()); - } - else if (HdCamera::Projection::Orthographic == projectionType) - { - projMat.SetIdentity(); - projMat[0][0] = (2.0f / float(GfCamera::APERTURE_UNIT)) / horizontalAperture; - projMat[1][1] = (2.0f / float(GfCamera::APERTURE_UNIT)) / verticalAperture; - projMat[3][0] = - static_cast(horizontalApertureOffset / (-0.5 * (horizontalAperture))); - projMat[3][1] = static_cast(verticalApertureOffset / (-0.5 * (verticalAperture))); - projMat[2][2] = static_cast(2 / (clippingRange.GetMin() + clippingRange.GetMax())); - projMat[3][2] = (clippingRange.GetMin() + clippingRange.GetMax()) / - (clippingRange.GetMin() - clippingRange.GetMax()); - } - // Check to see if we need to flip the output image - static TfToken tokenFlipYOutput("HdAuroraFlipYOutput"); - bool flipY = true; - auto flipYValue = this->_owner->GetRenderSetting(tokenFlipYOutput); - if (!flipYValue.IsEmpty() && flipYValue.IsHolding()) - flipY = flipYValue.Get(); - if (flipY) - { - // Invert the second row of the projection matrix to flip the rendered image, - // because OpenGL expects the first pixel to be at the *bottom* corner. - projMat[1][0] = -projMat[1][0]; - projMat[1][1] = -projMat[1][1]; - projMat[1][2] = -projMat[1][2]; - projMat[1][3] = -projMat[1][3]; - } - // check for camera movements - static GfMatrix4f viewMatOld; - static GfMatrix4f projMatOld; - if (viewMatOld != viewMat || projMatOld != projMat) - { - _owner->SetSampleRestartNeeded(true); - viewMatOld = viewMat; - projMatOld = projMat; - } - _owner->GetRenderer()->setCamera((float*)&viewMat, (float*)&projMat); - *dirtyBits = Clean; -} \ No newline at end of file diff --git a/Libraries/HdAurora/HdAuroraCamera.h b/Libraries/HdAurora/HdAuroraCamera.h deleted file mode 100644 index 7af8bed..0000000 --- a/Libraries/HdAurora/HdAuroraCamera.h +++ /dev/null @@ -1,31 +0,0 @@ -//**************************************************************************/ -// Copyright (c) 2022 Autodesk, Inc. -// All rights reserved. -// -// These coded instructions, statements, and computer programs contain -// unpublished proprietary information written by Autodesk, Inc., and are -// protected by Federal copyright law. They may not be disclosed to third -// parties or copied or duplicated in any form, in whole or in part, without -// the prior written consent of Autodesk, Inc. -//**************************************************************************/ -// DESCRIPTION: Camera for HdAurora. -// AUTHOR: Autodesk Inc. -//**************************************************************************/ - -#pragma once - -class HdAuroraRenderDelegate; - -class HdAuroraCamera : public HdCamera -{ -public: - HdAuroraCamera(pxr::SdfPath const& sprimId, HdAuroraRenderDelegate* renderDelegate); - ~HdAuroraCamera() override; - - HdDirtyBits GetInitialDirtyBitsMask() const override; - void Sync( - HdSceneDelegate* delegate, HdRenderParam* renderParam, HdDirtyBits* dirtyBits) override; - -private: - HdAuroraRenderDelegate* _owner; -}; diff --git a/Libraries/HdAurora/HdAuroraImageCache.cpp b/Libraries/HdAurora/HdAuroraImageCache.cpp index 81e2aab..4442d39 100644 --- a/Libraries/HdAurora/HdAuroraImageCache.cpp +++ b/Libraries/HdAurora/HdAuroraImageCache.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +15,12 @@ #include "HdAuroraImageCache.h" +// Flag to set the flip-y flag on loaded images. +void HdAuroraImageCache::setIsYFlipped(bool val) +{ + _isYFlipped = val; +} + Aurora::Path HdAuroraImageCache::acquireImage( const string& sFilePath, bool isEnvironmentImage, bool forceLinear) { @@ -40,7 +46,8 @@ Aurora::Path HdAuroraImageCache::acquireImage( newEntry.imageDesc.isEnvironment = isEnvironmentImage; newEntry.imageDesc.linearize = !forceLinear; - pxr::HioImageSharedPtr const image = pxr::HioImage::OpenForReading(sFilePath); + std::string resolvedPath = ArGetResolver().CreateIdentifier(sFilePath); + pxr::HioImageSharedPtr const image = pxr::HioImage::OpenForReading(resolvedPath); if (image) { pxr::HioImage::StorageSpec imageData; @@ -48,7 +55,7 @@ Aurora::Path HdAuroraImageCache::acquireImage( imageData.width = image->GetWidth(); imageData.height = image->GetHeight(); imageData.depth = 1; - imageData.flipped = true; + imageData.flipped = _isYFlipped; imageData.format = image->GetFormat(); bool paddingRequired = hioFormat == HioFormatUNorm8Vec3srgb || hioFormat == HioFormatUNorm8Vec3; diff --git a/Libraries/HdAurora/HdAuroraImageCache.h b/Libraries/HdAurora/HdAuroraImageCache.h index 05d92e4..2fb6ab9 100644 --- a/Libraries/HdAurora/HdAuroraImageCache.h +++ b/Libraries/HdAurora/HdAuroraImageCache.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,12 +32,16 @@ class HdAuroraImageCache HdAuroraImageCache(Aurora::IScenePtr pAuroraScene) : _pAuroraScene(pAuroraScene) {} ~HdAuroraImageCache() {} + // Set the flag to indicate the Y axis should be flipped on loaded images. + void setIsYFlipped(bool val); + // Acquire an image from the cache, loading if necessary. - // Returns the Aurora path for the image (will be different for environment ismages.) + // Returns the Aurora path for the image (will be different for environment images.) Aurora::Path acquireImage( const string& sFilePath, bool isEnvironmentImage = false, bool linearize = false); private: map _cache; Aurora::IScenePtr _pAuroraScene; + bool _isYFlipped = true; }; diff --git a/Libraries/HdAurora/HdAuroraInstancer.cpp b/Libraries/HdAurora/HdAuroraInstancer.cpp index 0d3b6a1..5a9e56f 100644 --- a/Libraries/HdAurora/HdAuroraInstancer.cpp +++ b/Libraries/HdAurora/HdAuroraInstancer.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,10 @@ HdAuroraInstancer::HdAuroraInstancer(HdSceneDelegate* delegate, SdfPath const& i HdAuroraInstancer::~HdAuroraInstancer() { - TF_FOR_ALL(it, _primvarMap) { delete it->second; } + TF_FOR_ALL(it, _primvarMap) + { + delete it->second; + } _primvarMap.clear(); } diff --git a/Libraries/HdAurora/HdAuroraInstancer.h b/Libraries/HdAurora/HdAuroraInstancer.h index d50009b..e539ade 100644 --- a/Libraries/HdAurora/HdAuroraInstancer.h +++ b/Libraries/HdAurora/HdAuroraInstancer.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/HdAurora/HdAuroraLight.cpp b/Libraries/HdAurora/HdAuroraLight.cpp index a8a5be4..4de58a3 100644 --- a/Libraries/HdAurora/HdAuroraLight.cpp +++ b/Libraries/HdAurora/HdAuroraLight.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -84,26 +84,24 @@ HdAuroraDistantLight::HdAuroraDistantLight( SdfPath const& rprimId, HdAuroraRenderDelegate* renderDelegate) : HdLight(rprimId), _owner(renderDelegate) { + // Create a light with default properties. GfVec3f lightDirection = { 0.1f, -1.0f, 0.1f }; GfVec3f lightColor = { 1.0f, 1.0f, 1.0f }; - float lighIntensity = 0.0f; + float lightIntensity = 0.0f; float lightAngularDiameter = 0.1f; - _owner->GetScene()->setLight( - lighIntensity, lightColor.data(), lightDirection.data(), lightAngularDiameter); + _pDistantLight = _owner->GetScene()->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + _pDistantLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, lightIntensity); + _pDistantLight->values().setFloat( + Aurora::Names::LightProperties::kAngularDiameter, lightAngularDiameter); + _pDistantLight->values().setFloat3(Aurora::Names::LightProperties::kColor, lightColor.data()); + _pDistantLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, lightDirection.data()); } HdAuroraDistantLight::~HdAuroraDistantLight() { // Ensure sampler counter is reset. _owner->SetSampleRestartNeeded(true); - - /// Reset the light intensity to zero. - GfVec3f lightDirection = { 0.1f, -1.0f, 0.1f }; - GfVec3f lightColor = { 1.0f, 1.0f, 1.0f }; - float lighIntensity = 0.0f; - float lightAngularDiameter = 0.1f; - _owner->GetScene()->setLight( - lighIntensity, lightColor.data(), lightDirection.data(), lightAngularDiameter); } HdDirtyBits HdAuroraDistantLight::GetInitialDirtyBitsMask() const @@ -147,11 +145,27 @@ void HdAuroraDistantLight::Sync( GfVec3f lightColor = (delegate->GetLightParamValue(id, HdLightTokens->color)).Get(); + // Get the light angle from Hyrdra + float lightAngleDegrees = (delegate->GetLightParamValue(id, HdLightTokens->angle)).Get(); + // Compute light direction from transform matrix. GfMatrix4f transformMatrix(delegate->GetTransform(id)); GfVec3f lightDirection = { -transformMatrix[2][0], -transformMatrix[2][1], -transformMatrix[2][2] }; - _owner->GetScene()->setLight(intensity, lightColor.data(), lightDirection.data()); + + // Set the light intensity in Aurora. + _pDistantLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, intensity); + + // Set the light color in Aurora. + _pDistantLight->values().setFloat3(Aurora::Names::LightProperties::kColor, lightColor.data()); + + // Set the light direction in Aurora. + _pDistantLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, lightDirection.data()); + + // Set the light angle (in radians) in Aurora. + _pDistantLight->values().setFloat( + Aurora::Names::LightProperties::kAngularDiameter, glm::radians(lightAngleDegrees)); *dirtyBits = Clean; } diff --git a/Libraries/HdAurora/HdAuroraLight.h b/Libraries/HdAurora/HdAuroraLight.h index 3efd788..1b7e3e8 100644 --- a/Libraries/HdAurora/HdAuroraLight.h +++ b/Libraries/HdAurora/HdAuroraLight.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -47,4 +47,5 @@ class HdAuroraDistantLight : public HdLight private: HdAuroraRenderDelegate* _owner; -}; \ No newline at end of file + Aurora::ILightPtr _pDistantLight; +}; diff --git a/Libraries/HdAurora/HdAuroraMaterial.cpp b/Libraries/HdAurora/HdAuroraMaterial.cpp index 2d94921..6585c91 100644 --- a/Libraries/HdAurora/HdAuroraMaterial.cpp +++ b/Libraries/HdAurora/HdAuroraMaterial.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,6 +17,9 @@ #include +#include +#include + #include "HdAuroraImageCache.h" #include "HdAuroraMesh.h" #include "HdAuroraRenderDelegate.h" @@ -24,9 +27,22 @@ #include #include +#include #pragma warning(disable : 4506) // inline function warning (from USD but appears in this file) +// Developer flag to disable separating the MaterialX document into a MaterialX document with +// default values and a separate set of name-value pairs. Should always be 1 in production code. +#define HD_AURORA_SEPARATE_MTLX_DOC 1 + +// Computes the perceived luminance of a color. +static float luminance(const pxr::GfVec3f& value) +{ + static constexpr pxr::GfVec3f kLuminanceFactors = pxr::GfVec3f(0.2125f, 0.7154f, 0.0721f); + + return pxr::GfDot(value, kLuminanceFactors); +} + // Processed Hydra material network. // TODO: These should be cached to avoid recalculating each time. struct ProcessedMaterialNetwork @@ -43,7 +59,7 @@ struct ProcessedMaterialNetwork // Properties require a value to be added to a Uniform block. static const string paramBindingTemplate = R"( - )"; + )"; static const string nodeGraphBindingTemplate = R"( )"; @@ -95,7 +111,187 @@ HdDirtyBits HdAuroraMaterial::GetInitialDirtyBitsMask() const { return HdChangeTracker::AllSceneDirtyBits; } +void HdAuroraMaterial::InitToDefaultValue( + MaterialX::NodePtr& pNode, Aurora::Properties& materialProperties, bool& isNodegraph) +{ + if (!pNode || !pNode->getInputCount()) + { + return; + } + std::pair inputValue; + for (MaterialX::InputPtr input : pNode->getInputs()) + { + std::string namePath; + + // Input is part of standard_surface, directly store its name + // Otherwise store the namepath to know the hierarchical nodegraph name + if (isNodegraph) + { + string ngNamepath = input->getNamePath(); + const size_t first_slash_idx = ngNamepath.find('/'); + if (0 != first_slash_idx) + { + // Get rid of the first path of namepath, namely the name of nodegraph + namePath = ngNamepath.substr(first_slash_idx + 1, ngNamepath.length()); + } + } + else + { + namePath = input->getName(); + } + const std::string type = input->getType(); + const MaterialX::ValuePtr value = input->getValue(); + + // Skip invalid input type and value + if (type.empty() || !value) + continue; + + // Reset all non-zero value to zero according to their types + if (type == MaterialX::getTypeString()) + { + if (value->asA() != 0.0f) + { + materialProperties[namePath] = value->asA(); + input->setValue(0.0f); + } + } + else if (type == MaterialX::getTypeString()) + { + if (value->asA() != 0) + { + // TODO: The "which" value of "switch" node is kept for now, as it is not currently + // supported by the data-driven mechanism. + auto pParent = input->getParent(); + if (pParent && pParent->getCategory() == "switch") + { + const size_t last_slash_idx = namePath.find_last_of('/'); + if (string::npos != last_slash_idx) + { + auto parameterName = namePath.substr(last_slash_idx + 1, namePath.length()); + if ("which" == parameterName) + continue; + } + } + materialProperties[namePath] = value->asA(); + input->setValue(0); + } + } + else if (type == MaterialX::getTypeString()) + { + if (value->asA() != MaterialX::Vector2(0.0f, 0.0f)) + { + glm::vec2 val; + val[0] = value->asA()[0]; + val[1] = value->asA()[1]; + materialProperties[namePath] = val; + input->setValue(MaterialX::Vector2(0.0f, 0.0f)); + } + } + else if (type == MaterialX::getTypeString()) + { + if (value->asA() != MaterialX::Vector3(0.0f, 0.0f, 0.0f)) + { + glm::vec3 val; + val[0] = value->asA()[0]; + val[1] = value->asA()[1]; + val[2] = value->asA()[2]; + materialProperties[namePath] = val; + input->setValue(MaterialX::Vector3(0.0f, 0.0f, 0.0f)); + } + } + else if (type == MaterialX::getTypeString()) + { + if (value->asA() != MaterialX::Vector4(0.0f, 0.0f, 0.0f, 0.0f)) + { + glm::vec4 val; + val[0] = value->asA()[0]; + val[1] = value->asA()[1]; + val[2] = value->asA()[2]; + val[3] = value->asA()[3]; + materialProperties[namePath] = val; + input->setValue(MaterialX::Vector4(0.0f, 0.0f, 0.0f, 0.0f)); + } + } + else if (type == MaterialX::getTypeString()) + { + if (value->asA() != MaterialX::Color3(0.0f, 0.0f, 0.0f)) + { + glm::vec3 val; + val[0] = value->asA()[0]; + val[1] = value->asA()[1]; + val[2] = value->asA()[2]; + materialProperties[namePath] = val; + input->setValue(MaterialX::Color3(0.0f, 0.0f, 0.0f)); + } + } + else if (type == MaterialX::getTypeString()) + { + if (value->asA() != false) + { + materialProperties[namePath] = value->asA(); + input->setValue(false); + } + } + } +} +void HdAuroraMaterial::ReplaceMaterialName(string& materialDocument, const string& targetName) +{ + std::string originalMaterialName; + MaterialX::DocumentPtr originalDoc = MaterialX::createDocument(); + MaterialX::readFromXmlString(originalDoc, materialDocument); + for (MaterialX::ElementPtr elem : originalDoc->traverseTree()) + { + if (elem->isA()) + { + MaterialX::NodePtr pNode = elem->asA(); + if (pNode->getType() == MaterialX::SURFACE_SHADER_TYPE_STRING) + { + originalMaterialName = pNode->getName(); + break; + } + } + } + if (!originalMaterialName.empty()) + { + std::regex srcNodegraphName("nodegraph=\"" + originalMaterialName); + std::regex srcNodeName("name=\"" + originalMaterialName); + string processedMtlXString = + regex_replace(materialDocument, srcNodegraphName, "nodegraph=\"" + targetName); + materialDocument = regex_replace(processedMtlXString, srcNodeName, "name=\"" + targetName); + } +} +string HdAuroraMaterial::SeparateHDMaterialXDocument( + string& materialDocument, Aurora::Properties& materialProperties) +{ + // Step1: Unified naming + std::string targetName = "HdAuroraMaterialX"; + ReplaceMaterialName(materialDocument, targetName); + + // Step2: Collect name-value pair + MaterialX::DocumentPtr hdMaterialXDoc = MaterialX::createDocument(); + MaterialX::readFromXmlString(hdMaterialXDoc, materialDocument); + bool isNodeGraph = false; + for (MaterialX::ElementPtr elem : hdMaterialXDoc->traverseTree()) + { + if (elem->isA()) + { + MaterialX::NodePtr pNode = elem->asA(); + InitToDefaultValue(pNode, materialProperties, isNodeGraph); + } + else if (elem->isA()) + { + MaterialX::NodeGraphPtr pNodeGraph = elem->asA(); + isNodeGraph = true; + for (MaterialX::ElementPtr subElem : pNodeGraph->traverseTree()) + { + MaterialX::NodePtr pNode = subElem->asA(); + InitToDefaultValue(pNode, materialProperties, isNodeGraph); + } + } + } + return MaterialX::writeToXmlString(hdMaterialXDoc); +} bool HdAuroraMaterial::SetupAuroraMaterial( const string& materialType, const string& materialDocument) { @@ -136,8 +332,22 @@ void HdAuroraMaterial::ProcessHDMaterial(HdSceneDelegate* delegate) string hdMaterialXDocument; if (GetHDMaterialXDocument(delegate, hdMaterialXType, hdMaterialXDocument)) { - // If we have Hydra materialX document or filename, just use that. + Aurora::Properties materialProperties; + +#if HD_AURORA_SEPARATE_MTLX_DOC + if (!hdMaterialXDocument.empty()) + { + // Separate hdMaterialXDocument into default doc and name-value pair + hdMaterialXDocument = + SeparateHDMaterialXDocument(hdMaterialXDocument, materialProperties); + } +#endif + + // Send the document to Aurora (with the parameters reset.) SetupAuroraMaterial(hdMaterialXType, hdMaterialXDocument); + + // Send the name-value pair by IScene::setMaterialProperties + _owner->GetScene()->setMaterialProperties(_auroraMaterialPath, materialProperties); } else if (!ProcessHDMaterialNetwork(hdMatVal)) { @@ -301,9 +511,10 @@ bool HdAuroraMaterial::BuildMaterialXDocumentFromHDNetwork( if (vtVal.IsHolding()) { // Add the input binding for input to the dynamically built materialX document. - // Note: Values are applied seperately and not built into the document. - parameterBindingStr += - Aurora::Foundation::sFormat(paramBindingTemplate, auroraName.c_str(), "color3"); + // Note: Values are applied separately and not built into the document, so value is all + // zeros here. + parameterBindingStr += Aurora::Foundation::sFormat( + paramBindingTemplate, auroraName.c_str(), "color3", "0.0,0.0,0.0"); } }; @@ -313,9 +524,30 @@ bool HdAuroraMaterial::BuildMaterialXDocumentFromHDNetwork( if (vtVal.IsHolding()) { // Add the input binding for input to the dynamically built materialX document. - // Note: Values are applied seperately and not built into the document. - parameterBindingStr += - Aurora::Foundation::sFormat(paramBindingTemplate, auroraName.c_str(), "float"); + // Note: Values are applied separately and not built into the document, so value is zero + // here. + parameterBindingStr += Aurora::Foundation::sFormat( + paramBindingTemplate, auroraName.c_str(), "float", "0.0"); + } + }; + + // Float setter function to convert Hydra float values to Aurora values + static auto setOpacityValue = [](string& parameterBindingStr, const VtValue& vtVal, + const std::string& auroraName) { + if (vtVal.IsHolding()) + { + // Add the input binding for input to the dynamically built materialX document. + // Note: Values are applied separately and not built into the document, so value is zero + // here. + parameterBindingStr += Aurora::Foundation::sFormat( + paramBindingTemplate, auroraName.c_str(), "float", "0.0"); + } + else if (vtVal.IsHolding()) + { + // The opacity value can also be passed as a GfVec3f, which must be converted to float. + // all zeros here. + parameterBindingStr += Aurora::Foundation::sFormat( + paramBindingTemplate, auroraName.c_str(), "float", "0.0"); } }; @@ -411,6 +643,19 @@ bool HdAuroraMaterial::BuildMaterialXDocumentFromHDNetwork( } }; + // Get setting for mapping opacity to transmission (default to true if not set.) + VtValue mapOpacityToTransmissionVal = + _owner->GetRenderSetting(HdAuroraTokens::kMapMaterialOpacityToTransmission); + bool mapOpacityToTransmission = mapOpacityToTransmissionVal.IsHolding() + ? mapOpacityToTransmissionVal.Get() + : true; + + // Setup the opacity parameter to map UsdPreviewSurface opacity to Standard Surface + // transmission, as required by kMapMaterialOpacityToTransmission setting. + ParameterInfo opacityParameterInfo = { TfToken("opacity"), "transmission", setOpacityValue }; + if (!mapOpacityToTransmission) + opacityParameterInfo = { TfToken("opacity"), "opacity", setF3Value }; + // Specify the named pair of parameters to translate from Hydra to Aurora // and the translation function to use for the parameter. // { Hydra token, Aurora name (standard_surface), translation function } @@ -422,12 +667,12 @@ bool HdAuroraMaterial::BuildMaterialXDocumentFromHDNetwork( { TfToken("roughness"), "specular_roughness", setF1Value }, { TfToken("ior"), "specular_IOR", setF1Value }, { TfToken("emissiveColor"), "emission_color", setF3Value }, - { TfToken("opacity"), "transmission", setF1Value },// Map UsdPreviewSurface opacity to Standard Surface transmission. + opacityParameterInfo, { TfToken("clearcoat"), "coat", setF1Value }, { TfToken("clearcoatColor"), "coat_color", setF3Value }, { TfToken("clearcoatRoughness"), "coat_roughness", setF1Value }, { TfToken("metallic"), "metalness", setF1Value }, - { TfToken("normal"), "normal", nullptr } // No converter funciton, can only be a texture. + { TfToken("normal"), "normal", nullptr } // No converter function, can only be a texture. }; // clang-format on @@ -465,13 +710,20 @@ bool HdAuroraMaterial::ApplyHDNetwork(const ProcessedMaterialNetwork& network) } }; - // Setter to apply UsdPreviewSurface opacity to statndard surface transmission. + // Setter to apply UsdPreviewSurface opacity to standard surface transmission. auto setTransmissionFromOpacity = [](Aurora::Properties& materialProperties, const VtValue& vtVal, const std::string& auroraName) { if (vtVal.IsHolding()) { float val = vtVal.UncheckedGet(); - materialProperties[auroraName] = 1.0f - val; // Transmission is one minux opacity. + materialProperties[auroraName] = 1.0f - val; // Transmission is one minus opacity. + } + else if (vtVal.IsHolding()) + { + // Opacity can be passed as GfVec3f, in which case it should be converted to float. + GfVec3f val = vtVal.UncheckedGet(); + float floatVal = luminance(val); + materialProperties[auroraName] = 1.0f - floatVal; // Transmission is one minus opacity. } }; @@ -561,6 +813,20 @@ bool HdAuroraMaterial::ApplyHDNetwork(const ProcessedMaterialNetwork& network) } }; + // Get setting for mapping opacity to transmission (default to true if not set.) + VtValue mapOpacityToTransmissionVal = + _owner->GetRenderSetting(HdAuroraTokens::kMapMaterialOpacityToTransmission); + bool mapOpacityToTransmission = mapOpacityToTransmissionVal.IsHolding() + ? mapOpacityToTransmissionVal.Get() + : true; + + // Setup the opacity parameter to map UsdPreviewSurface opacity to Standard Surface + // transmission, as required by kMapMaterialOpacityToTransmission setting. + ParameterInfo opacityParameterInfo = { TfToken("opacity"), "transmission", + setTransmissionFromOpacity }; + if (!mapOpacityToTransmission) + opacityParameterInfo = { TfToken("opacity"), "opacity", setF3Value }; + // Specify the named pair of parameters to translate from Hydra to Aurora // and the translation function to use for the parameter. // { Hydra token, Aurora name (standard_surface), translation function } @@ -572,7 +838,7 @@ bool HdAuroraMaterial::ApplyHDNetwork(const ProcessedMaterialNetwork& network) { TfToken("roughness"), "specular_roughness", setF1Value }, { TfToken("ior"), "specular_IOR", setF1Value }, { TfToken("emissiveColor"), "emission_color", setF3Value }, - { TfToken("opacity"), "transmission", setTransmissionFromOpacity },// Map UsdPreviewSurface opacity to Standard Surface transmission. + opacityParameterInfo, { TfToken("clearcoat"), "coat", setF1Value }, { TfToken("clearcoatColor"), "coat_color", setF3Value }, { TfToken("clearcoatRoughness"), "coat_roughness", setF1Value }, @@ -615,7 +881,7 @@ bool HdAuroraMaterial::ProcessHDMaterialNetwork(VtValue& materialValue) auto networkIter = networkMap.map.find(pxr::HdMaterialTerminalTokens->surface); if (networkIter == networkMap.map.end()) { - TF_WARN("No surface network in materal network map for material " + GetId().GetString()); + TF_WARN("No surface network in material network map for material " + GetId().GetString()); return false; } auto network = networkIter->second; diff --git a/Libraries/HdAurora/HdAuroraMaterial.h b/Libraries/HdAurora/HdAuroraMaterial.h index a3a8633..6426ced 100644 --- a/Libraries/HdAurora/HdAuroraMaterial.h +++ b/Libraries/HdAurora/HdAuroraMaterial.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ #pragma once +#include #include class HdAuroraRenderDelegate; @@ -58,6 +59,16 @@ class HdAuroraMaterial : public HdMaterial // Get the MaterialX document or path associated with the material in Hydra. bool GetHDMaterialXDocument( HdSceneDelegate* pDelegate, string& materialTypeOut, string& documentOut); + // Replace diverse named MaterialX to unified target name + // eg: to + void ReplaceMaterialName(string& materialDocument, const string& targetName); + // Set all the input value of node to zero + void InitToDefaultValue( + MaterialX::NodePtr& pNode, Aurora::Properties& materialProperties, bool& isNodegraph); + // Separate the MaterialX document into a MaterialX document with default values and a separate + // set of name-value pairs. + string SeparateHDMaterialXDocument( + string& materialDocument, Aurora::Properties& materialProperties); // Create a new aurora material with this material type and document, if required. // Does nothing if current material type and document match the ones provided. bool SetupAuroraMaterial(const string& materialType, const string& materialDocument); diff --git a/Libraries/HdAurora/HdAuroraMesh.cpp b/Libraries/HdAurora/HdAuroraMesh.cpp index 2510bf0..5df4222 100644 --- a/Libraries/HdAurora/HdAuroraMesh.cpp +++ b/Libraries/HdAurora/HdAuroraMesh.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,6 +13,8 @@ // limitations under the License. #include "pch.h" +#include "Aurora/Foundation/Geometry.h" + #include "HdAuroraInstancer.h" #include "HdAuroraMaterial.h" #include "HdAuroraMesh.h" @@ -23,19 +25,24 @@ // If true extra validation done on the HDMesh geometry upon loading. #define VALIDATE_HDMESH 1 +// The tangents are causing issues at grazing angles in the BSDF, so disable for now. +// TODO: Fix the issue with grazing angles and re-enable. +#define HDAURORA_HAS_TANGENTS 0 + // Vertex data for mesh, passed to Aurora getVertexData callback. Deleted once vertex update is // complete. struct HdAuroraMeshVertexData { VtVec3fArray points; VtVec3fArray normals; + VtVec3fArray tangents; VtVec2fArray uvs; VtVec3iArray triangulatedIndices; VtVec3fArray flattenedPoints; VtVec3fArray flattenedNormals; + VtVec3fArray flattenedTangents; VtVec2fArray flattenedUVs; const VtArray* st; - bool hasNormals; bool hasTexCoords; }; @@ -99,10 +106,16 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) } // Create vertex data object, that will exist until the renderer has read the vertex data. - _pVertexData = make_unique(); - _pVertexData->points = delegate->Get(id, HdTokens->points).Get(); - _pVertexData->normals = delegate->Get(id, HdTokens->normals).Get(); - _pVertexData->uvs = delegate->Get(id, pxr::TfToken("map1")).Get(); + _pVertexData = make_unique(); + _pVertexData->points = delegate->Get(id, HdTokens->points).Get(); + _pVertexData->normals = delegate->Get(id, HdTokens->normals).Get(); + _pVertexData->tangents = delegate->Get(id, pxr::TfToken("tangents")).Get(); + _pVertexData->uvs = delegate->Get(id, pxr::TfToken("map1")).Get(); + + // Attempt to get UVs using "st" token if "map1" fails. Both can be used in different + // circumstances. + if (_pVertexData->uvs.size() == 0) + _pVertexData->uvs = delegate->Get(id, pxr::TfToken("st")).Get(); // Sample code for extracting extra uv set // The name for the base uv set is st, other uv sets (st0, st1, etc.) @@ -143,21 +156,36 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) } // Does the mesh have normals? - _pVertexData->hasNormals = !_pVertexData->normals.empty() && + bool meshHasNormals = !_pVertexData->normals.empty() && // This is what is received from USD for geometry that has no normals !(_pVertexData->normals.size() == 1 && _pVertexData->normals[0][0] == 0.0f && _pVertexData->normals[0][1] == 0.0f && _pVertexData->normals[0][2] == 0.0f); + if (!meshHasNormals) + { + _pVertexData->normals.resize(_pVertexData->points.size()); + Aurora::Foundation::calculateNormals(_pVertexData->points.size(), + reinterpret_cast(_pVertexData->points.data()), + _pVertexData->triangulatedIndices.size(), + reinterpret_cast(_pVertexData->triangulatedIndices.data()), + reinterpret_cast(_pVertexData->normals.data())); + } // Does the mesh have texture coordinates (UVs or STs)? _pVertexData->hasTexCoords = _pVertexData->uvs.size() || _pVertexData->st; int minAttrCount = static_cast(_pVertexData->points.size()); int maxAttrCount = minAttrCount; + // The tangents flag is true if we have a valid tangents array that matches size of normals + // array, and we haven't disabled tangents with the developer flag. + bool hasTangents = HDAURORA_HAS_TANGENTS + ? _pVertexData->tangents.size() == _pVertexData->normals.size() + : false; + // If there are valid normals but normal and position attribute arrays are not the same // clamp indices to smallest value VtValue pvNormals; - if (_pVertexData->hasNormals && _pVertexData->normals.size() != _pVertexData->points.size()) + if (_pVertexData->normals.size() != _pVertexData->points.size()) { HdPrimvarDescriptorVector fpvs = delegate->GetPrimvarDescriptors(id, HdInterpolation::HdInterpolationFaceVarying); @@ -221,7 +249,7 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) { size_t numFlattenedVerts = _pVertexData->triangulatedIndices.size() * 3; _pVertexData->flattenedPoints.resize(numFlattenedVerts); - if (_pVertexData->hasNormals && !hasFaceVaryingNormals) + if (!hasFaceVaryingNormals) _pVertexData->flattenedNormals.resize(numFlattenedVerts); if (_pVertexData->uvs.size() && !hasFaceVaryingSTs) _pVertexData->flattenedUVs.resize(numFlattenedVerts); @@ -248,7 +276,7 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) _pVertexData->flattenedPoints[j * 3 + 0] = _pVertexData->points[t0]; _pVertexData->flattenedPoints[j * 3 + 1] = _pVertexData->points[t1]; _pVertexData->flattenedPoints[j * 3 + 2] = _pVertexData->points[t2]; - if (_pVertexData->hasNormals && !hasFaceVaryingNormals) + if (!hasFaceVaryingNormals) { _pVertexData->flattenedNormals[j * 3 + 0] = _pVertexData->normals[t0]; _pVertexData->flattenedNormals[j * 3 + 1] = _pVertexData->normals[t1]; @@ -260,7 +288,28 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) _pVertexData->flattenedUVs[j * 3 + 1] = _pVertexData->uvs[t1]; _pVertexData->flattenedUVs[j * 3 + 2] = _pVertexData->uvs[t2]; } + // If we have tangents provided by client, use them. + if (_pVertexData->tangents.size() == _pVertexData->normals.size()) + { + _pVertexData->flattenedTangents[j * 3 + 0] = _pVertexData->tangents[t0]; + _pVertexData->flattenedTangents[j * 3 + 1] = _pVertexData->tangents[t1]; + _pVertexData->flattenedTangents[j * 3 + 2] = _pVertexData->tangents[t2]; + } + } +#if HDAURORA_HAS_TANGENTS + if (!hasTangents && _pVertexData->st->size()) + { + // If there are no client provided tangents, create tangent vectors based on the texture + // coordinates, using a utility function. + Aurora::Foundation::calculateTangents(_pVertexData->flattenedPoints.size(), + reinterpret_cast(_pVertexData->flattenedPoints.data()), + reinterpret_cast(_pVertexData->flattenedNormals.data()), + reinterpret_cast(_pVertexData->st->data()), + _pVertexData->triangulatedIndices.size(), nullptr, + reinterpret_cast(_pVertexData->flattenedTangents.data())); + hasTangents = true; } +#endif } else { @@ -280,6 +329,20 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) " for " + GetId().GetString()); return; } +#if HDAURORA_ENABLE_TANGENTS + if (!hasTangents && _pVertexData->uvs.size()) + { + _pVertexData->tangents.resize(_pVertexData->points.size()); + Aurora::Foundation::calculateTangents(_pVertexData->points.size(), + reinterpret_cast(_pVertexData->points.data()), + reinterpret_cast(_pVertexData->normals.data()), + reinterpret_cast(_pVertexData->uvs.data()), + _pVertexData->triangulatedIndices.size(), + reinterpret_cast(_pVertexData->triangulatedIndices.data()), + reinterpret_cast(_pVertexData->tangents.data())); + hasTangents = true; + } +#endif } // Create a geometry descriptor for mesh's geometry. @@ -290,12 +353,14 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) { Aurora::Names::VertexAttributes::kPosition, Aurora::AttributeFormat::Float3 }, }; - // Set normals attibute type, if the mesh has them. - if (_pVertexData->hasNormals) - geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kNormal] = + // Set normals and tangent attribute type. + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kNormal] = + Aurora::AttributeFormat::Float3; + if (hasTangents) + geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTangent] = Aurora::AttributeFormat::Float3; - // Set texcoord attibute type, if the mesh has them. + // Set texcoord attribute type, if the mesh has them. if (_pVertexData->hasTexCoords) geomDesc.vertexDesc.attributes[Aurora::Names::VertexAttributes::kTexCoord0] = Aurora::AttributeFormat::Float2; @@ -309,7 +374,7 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) // Setup vertex attribute callback to read vertex and index data. // This will be called when the geometry is added to scene via instance. - geomDesc.getAttributeData = [this, hasFaceVaryings, hasFaceVaryingSTs]( + geomDesc.getAttributeData = [this, hasFaceVaryings, hasFaceVaryingSTs, hasTangents]( Aurora::AttributeDataMap& dataOut, size_t firstVertex, size_t vertexCount, size_t firstIndex, size_t indexCount) { // Sanity check, ensure we are not doing a partial update (not actually implemented yet) @@ -331,11 +396,14 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) dataOut[Aurora::Names::VertexAttributes::kPosition].address = &_pVertexData->flattenedPoints[0]; dataOut[Aurora::Names::VertexAttributes::kPosition].stride = sizeof(GfVec3f); - if (_pVertexData->hasNormals) + dataOut[Aurora::Names::VertexAttributes::kNormal].address = + &_pVertexData->flattenedNormals[0]; + dataOut[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(GfVec3f); + if (hasTangents) { - dataOut[Aurora::Names::VertexAttributes::kNormal].address = - &_pVertexData->flattenedNormals[0]; - dataOut[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(GfVec3f); + dataOut[Aurora::Names::VertexAttributes::kTangent].address = + &_pVertexData->flattenedTangents[0]; + dataOut[Aurora::Names::VertexAttributes::kTangent].stride = sizeof(GfVec3f); } // Use the STs. @@ -362,11 +430,13 @@ void HdAuroraMesh::RebuildAuroraInstances(HdSceneDelegate* delegate) // Get position, texcoords, and normals directly from Hydra mesh. dataOut[Aurora::Names::VertexAttributes::kPosition].address = &_pVertexData->points[0]; dataOut[Aurora::Names::VertexAttributes::kPosition].stride = sizeof(GfVec3f); - if (_pVertexData->hasNormals) + dataOut[Aurora::Names::VertexAttributes::kNormal].address = &_pVertexData->normals[0]; + dataOut[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(GfVec3f); + if (hasTangents) { - dataOut[Aurora::Names::VertexAttributes::kNormal].address = - &_pVertexData->normals[0]; - dataOut[Aurora::Names::VertexAttributes::kNormal].stride = sizeof(GfVec3f); + dataOut[Aurora::Names::VertexAttributes::kTangent].address = + &_pVertexData->tangents[0]; + dataOut[Aurora::Names::VertexAttributes::kTangent].stride = sizeof(GfVec3f); } if (_pVertexData->uvs.size()) { @@ -678,7 +748,7 @@ void HdAuroraMesh::Sync(HdSceneDelegate* delegate, HdRenderParam* /* renderParam if (displayColorAttr.IsArrayValued()) { VtVec3fArray dispColorArr = displayColorAttr.Get(); - _displayColor = GfVec3ToGLM(&dispColorArr[0]); + _displayColor = GfVec3ToGLM(&dispColorArr[0]); } // Rebuild the Aurora instances and geometry for this mesh. diff --git a/Libraries/HdAurora/HdAuroraMesh.h b/Libraries/HdAurora/HdAuroraMesh.h index bd6d1a4..23f1353 100644 --- a/Libraries/HdAurora/HdAuroraMesh.h +++ b/Libraries/HdAurora/HdAuroraMesh.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/HdAurora/HdAuroraPlugin.cpp b/Libraries/HdAurora/HdAuroraPlugin.cpp index 2993721..9127ee5 100644 --- a/Libraries/HdAurora/HdAuroraPlugin.cpp +++ b/Libraries/HdAurora/HdAuroraPlugin.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/HdAurora/HdAuroraPlugin.h b/Libraries/HdAurora/HdAuroraPlugin.h index 388e328..f78781e 100644 --- a/Libraries/HdAurora/HdAuroraPlugin.h +++ b/Libraries/HdAurora/HdAuroraPlugin.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,6 +28,6 @@ class HdAuroraRendererPlugin : public HdRendererPlugin bool IsSupported() const override; private: - HdAuroraRendererPlugin(const HdAuroraRendererPlugin&) = delete; + HdAuroraRendererPlugin(const HdAuroraRendererPlugin&) = delete; HdAuroraRendererPlugin& operator=(const HdAuroraRendererPlugin&) = delete; }; diff --git a/Libraries/HdAurora/HdAuroraRenderBuffer.cpp b/Libraries/HdAurora/HdAuroraRenderBuffer.cpp index 93d188d..4c10fb8 100644 --- a/Libraries/HdAurora/HdAuroraRenderBuffer.cpp +++ b/Libraries/HdAurora/HdAuroraRenderBuffer.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ class HgiHdAuroraTextureGL final : public HgiTexture uint32_t GetTextureId() const { return _textureId; } private: - HgiHdAuroraTextureGL() = delete; + HgiHdAuroraTextureGL() = delete; HgiHdAuroraTextureGL& operator=(const HgiHdAuroraTextureGL&) = delete; HgiHdAuroraTextureGL(const HgiHdAuroraTextureGL&) = delete; @@ -211,7 +211,7 @@ class HgiHdAuroraTextureDX final : public HgiTexture uint64_t GetRawResource() const override { return reinterpret_cast(_sharedDXHandle); } private: - HgiHdAuroraTextureDX() = delete; + HgiHdAuroraTextureDX() = delete; HgiHdAuroraTextureDX& operator=(const HgiHdAuroraTextureDX&) = delete; HgiHdAuroraTextureDX(const HgiHdAuroraTextureDX&) = delete; diff --git a/Libraries/HdAurora/HdAuroraRenderBuffer.h b/Libraries/HdAurora/HdAuroraRenderBuffer.h index 8e26358..11e5ff9 100644 --- a/Libraries/HdAurora/HdAuroraRenderBuffer.h +++ b/Libraries/HdAurora/HdAuroraRenderBuffer.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/HdAurora/HdAuroraRenderDelegate.cpp b/Libraries/HdAurora/HdAuroraRenderDelegate.cpp index fe97773..bc70aef 100644 --- a/Libraries/HdAurora/HdAuroraRenderDelegate.cpp +++ b/Libraries/HdAurora/HdAuroraRenderDelegate.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ #include #pragma warning(pop) -#include "HdAuroraCamera.h" #include "HdAuroraImageCache.h" #include "HdAuroraInstancer.h" #include "HdAuroraLight.h" @@ -84,146 +83,142 @@ HdAuroraRenderDelegate::HdAuroraRenderDelegate(HdRenderSettingsMap const& settin _sampleCounter(33, 250, 50), _hgi(nullptr) { - if (_auroraRenderer) - { - // TODO: For long-term, we need an API to set material unit information from client - // side. - // Background: "1 ASM unit = 1 cm" in Inventor! Unit section of ASM tutorial mentions - // the words we want confirm: "ASM is unit-less so it's up to the calling application to - // decide what one unit of ASM represents. Inventor has chosen: 1 ASM unit = 1 cm. - // Inventor allows the user to select mm or inches but all Inventor/ASM transaction is - // done in cm." That is, for Inventor Alpha release, it seems easier to use centimeter - // for Aurora for now. - _auroraRenderer->options().setString("units", "centimeter"); - - // Turn gamma correction off. Leave tonemapping and color space correction to Hydra or - // the application. - _auroraRenderer->options().setBoolean("isGammaCorrectionEnabled", false); - - // These render settings are the same as the default values. - // Convenient to have them here to understand the capabilities and options of the - // renderer. - float intensity[3] = { 1.0f, 1.0f, 1.0f }; - _auroraRenderer->options().setFloat3("brightness", intensity); - _auroraRenderer->options().setBoolean("isToneMappingEnabled", false); - _auroraRenderer->options().setFloat("maxLuminance", 1000.0f); - _auroraRenderer->options().setBoolean("isDiffuseOnlyEnabled", false); - _auroraRenderer->options().setInt("traceDepth", 5); - _auroraRenderer->options().setBoolean("isDenoisingEnabled", false); - _auroraRenderer->options().setBoolean("alphaEnabled", false); - _sampleCounter.setMaxSamples(1000); - _sampleCounter.reset(); - - // create a new scene for this renderer. - _auroraScene = _auroraRenderer->createScene(); - _pImageCache = std::make_unique(_auroraScene); - - // Set default directional light off - float color[3] = { 0.0f, 0.0f, 0.0f }; - float dir[3] = { 0.0f, 0.0f, 1.0f }; - float lightIntensity = 0.0f; - _auroraScene->setLight(lightIntensity, color, dir); - - // Create a ground plane object, which is assigned to the scene. - _pGroundPlane = make_unique(_auroraRenderer.get(), _auroraScene.get()); - - // add the scene to the renderer. - _auroraRenderer->setScene(_auroraScene); - - // Create an aurora path based on Hydra id. - _auroraEnvironmentPath = "HdAuroraEnvironment"; - - // Create an environment for path by setting empty properties.s - _auroraScene->setEnvironmentProperties(_auroraEnvironmentPath, {}); - - // Set the scene's environment. - _auroraScene->setEnvironment(_auroraEnvironmentPath); - - // Set up the setting functions, that are called when render settings change. - _settingFunctions[HdAuroraTokens::kTraceDepth] = [this](VtValue const& value) { - _auroraRenderer->options().setInt("traceDepth", value.Get()); - return true; - }; - _settingFunctions[HdAuroraTokens::kMaxSamples] = [this](VtValue const& value) { - int currentSamples = static_cast(_sampleCounter.currentSamples()); - // Newly max sample value is smaller than current sample, stop render and keep the - // current number, otherwise set to the new value. - _sampleCounter.setMaxSamples(std::max(currentSamples, value.Get())); - return false; - }; - _settingFunctions[HdAuroraTokens::kIsDenoisingEnabled] = [this](VtValue const& value) { - _auroraRenderer->options().setBoolean("isDenoisingEnabled", value.Get()); - return true; - }; - _settingFunctions[HdAuroraTokens::kIsAlphaEnabled] = [this](VtValue const& value) { - _auroraRenderer->options().setBoolean("alphaEnabled", value.Get()); - _bEnvironmentIsDirty = true; - return false; - }; - _settingFunctions[HdAuroraTokens::kUseEnvironmentImageAsBackground] = - [this](VtValue const& value) { - _useEnvironmentLightAsBackground = value.Get(); - _bEnvironmentIsDirty = true; - return false; - }; - _settingFunctions[HdAuroraTokens::kBackgroundImage] = [this](VtValue const& value) { - _backgroundImageFilePath = value.Get().GetAssetPath(); - _bEnvironmentIsDirty = true; - return false; - }; - _settingFunctions[HdAuroraTokens::kBackgroundColors] = [this](VtValue const& value) { - const auto& colors = value.Get(); - if (colors.size() == 2 && - (_backgroundTopColor != colors[0] || _backgroundBottomColor != colors[1])) - { - _backgroundTopColor = colors[0]; - _backgroundBottomColor = colors[1]; - _bEnvironmentIsDirty = true; - } - return false; - }; - _settingFunctions[HdAuroraTokens::kGroundPlaneSettings] = [this](VtValue const& value) { - // Update the ground plane object, which return true if the data has changed. - return _pGroundPlane->update(value.Get()); - }; - _settingFunctions[HdAuroraTokens::kIsSharedHandleEnabled] = [this](VtValue const& value) { - // Aurora provides shared DX or GL texture handles - if (value.Get()) - { - _renderBufferSharingType = RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE; - } - else - _renderBufferSharingType = RenderBufferSharingType::NONE; - - // Set render settings that can be queried by the scene delegate - switch (_renderBufferSharingType) - { - case RenderBufferSharingType::NONE: - HdRenderDelegate::SetRenderSetting( - HdAuroraTokens::kIsSharedHandleDirectX, VtValue(false)); - HdRenderDelegate::SetRenderSetting( - HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(false)); - break; - case RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE: - HdRenderDelegate::SetRenderSetting( - HdAuroraTokens::kIsSharedHandleDirectX, VtValue(true)); - HdRenderDelegate::SetRenderSetting( - HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(false)); - break; - case RenderBufferSharingType::OPENGL_TEXTURE_ID: - HdRenderDelegate::SetRenderSetting( - HdAuroraTokens::kIsSharedHandleDirectX, VtValue(false)); - HdRenderDelegate::SetRenderSetting( - HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(true)); - break; - } - return true; - }; - } - else + if (!_auroraRenderer) { TF_FATAL_ERROR("HdAurora fails to create renderer!"); } + // TODO: For long-term, we need an API to set material unit information from client + // side. + // Background: "1 ASM unit = 1 cm" in Inventor! Unit section of ASM tutorial mentions + // the words we want confirm: "ASM is unit-less so it's up to the calling application to + // decide what one unit of ASM represents. Inventor has chosen: 1 ASM unit = 1 cm. + // Inventor allows the user to select mm or inches but all Inventor/ASM transaction is + // done in cm." That is, for Inventor Alpha release, it seems easier to use centimeter + // for Aurora for now. + _auroraRenderer->options().setString("units", "centimeter"); + + // Turn gamma correction off. Leave tonemapping and color space correction to Hydra or + // the application. + _auroraRenderer->options().setBoolean("isGammaCorrectionEnabled", false); + + // These render settings are the same as the default values. + // Convenient to have them here to understand the capabilities and options of the + // renderer. + float intensity[3] = { 1.0f, 1.0f, 1.0f }; + _auroraRenderer->options().setFloat3("brightness", intensity); + _auroraRenderer->options().setBoolean("isToneMappingEnabled", false); + _auroraRenderer->options().setFloat("maxLuminance", 1000.0f); + _auroraRenderer->options().setBoolean("isDiffuseOnlyEnabled", false); + _auroraRenderer->options().setInt("traceDepth", 5); + _auroraRenderer->options().setBoolean("isDenoisingEnabled", false); + _auroraRenderer->options().setBoolean("alphaEnabled", false); + _sampleCounter.setMaxSamples(1000); + _sampleCounter.reset(); + + // create a new scene for this renderer. + _auroraScene = _auroraRenderer->createScene(); + _pImageCache = std::make_unique(_auroraScene); + + // Create a ground plane object, which is assigned to the scene. + _pGroundPlane = make_unique(_auroraRenderer.get(), _auroraScene.get()); + + // add the scene to the renderer. + _auroraRenderer->setScene(_auroraScene); + + // Create an aurora path based on Hydra id. + _auroraEnvironmentPath = "HdAuroraEnvironment"; + + // Create an environment for path by setting empty properties.s + _auroraScene->setEnvironmentProperties(_auroraEnvironmentPath, {}); + + // Set the scene's environment. + _auroraScene->setEnvironment(_auroraEnvironmentPath); + + // Set up the setting functions, that are called when render settings change. + _settingFunctions[HdAuroraTokens::kTraceDepth] = [this](VtValue const& value) { + _auroraRenderer->options().setInt("traceDepth", value.Get()); + return true; + }; + _settingFunctions[HdAuroraTokens::kMaxSamples] = [this](VtValue const& value) { + int currentSamples = static_cast(_sampleCounter.currentSamples()); + // Newly max sample value is smaller than current sample, stop render and keep the + // current number, otherwise set to the new value. + _sampleCounter.setMaxSamples(std::max(currentSamples, value.Get())); + return false; + }; + _settingFunctions[HdAuroraTokens::kIsDenoisingEnabled] = [this](VtValue const& value) { + _auroraRenderer->options().setBoolean("isDenoisingEnabled", value.Get()); + return true; + }; + _settingFunctions[HdAuroraTokens::kIsAlphaEnabled] = [this](VtValue const& value) { + _auroraRenderer->options().setBoolean("alphaEnabled", value.Get()); + _bEnvironmentIsDirty = true; + return false; + }; + _settingFunctions[HdAuroraTokens::kUseEnvironmentImageAsBackground] = + [this](VtValue const& value) { + _useEnvironmentLightAsBackground = value.Get(); + _bEnvironmentIsDirty = true; + return false; + }; + _settingFunctions[HdAuroraTokens::kBackgroundImage] = [this](VtValue const& value) { + _backgroundImageFilePath = value.Get().GetAssetPath(); + _bEnvironmentIsDirty = true; + return false; + }; + _settingFunctions[HdAuroraTokens::kFlipLoadedImageY] = [this](VtValue const& value) { + bool flipY = value.Get(); + _pImageCache->setIsYFlipped(flipY); + return false; + }; + _settingFunctions[HdAuroraTokens::kBackgroundColors] = [this](VtValue const& value) { + const auto& colors = value.Get(); + if (colors.size() == 2 && + (_backgroundTopColor != colors[0] || _backgroundBottomColor != colors[1])) + { + _backgroundTopColor = colors[0]; + _backgroundBottomColor = colors[1]; + _bEnvironmentIsDirty = true; + } + return false; + }; + _settingFunctions[HdAuroraTokens::kGroundPlaneSettings] = [this](VtValue const& value) { + // Update the ground plane object, which return true if the data has changed. + return _pGroundPlane->update(value.Get()); + }; + _settingFunctions[HdAuroraTokens::kIsSharedHandleEnabled] = [this](VtValue const& value) { + // Aurora provides shared DX or GL texture handles + if (value.Get()) + { + _renderBufferSharingType = RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE; + } + else + _renderBufferSharingType = RenderBufferSharingType::NONE; + + // Set render settings that can be queried by the scene delegate + switch (_renderBufferSharingType) + { + case RenderBufferSharingType::NONE: + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleDirectX, VtValue(false)); + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(false)); + break; + case RenderBufferSharingType::DIRECTX_TEXTURE_HANDLE: + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleDirectX, VtValue(true)); + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(false)); + break; + case RenderBufferSharingType::OPENGL_TEXTURE_ID: + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleDirectX, VtValue(false)); + HdRenderDelegate::SetRenderSetting( + HdAuroraTokens::kIsSharedHandleOpenGL, VtValue(true)); + break; + } + return true; + }; } void HdAuroraRenderDelegate::SetDrivers(HdDriverVector const& drivers) @@ -324,7 +319,6 @@ VtValue HdAuroraRenderDelegate::GetRenderSetting(TfToken const& key) const { return VtValue(_alphaEnabled); } - return HdRenderDelegate::GetRenderSetting(key); } @@ -347,7 +341,7 @@ HdSprim* HdAuroraRenderDelegate::CreateSprim(TfToken const& typeId, SdfPath cons { if (typeId == HdPrimTypeTokens->camera) { - return new HdAuroraCamera(sprimId, this); + return new HdCamera(sprimId); } else if (typeId == HdPrimTypeTokens->material) { diff --git a/Libraries/HdAurora/HdAuroraRenderDelegate.h b/Libraries/HdAurora/HdAuroraRenderDelegate.h index 268ed80..305a9eb 100644 --- a/Libraries/HdAurora/HdAuroraRenderDelegate.h +++ b/Libraries/HdAurora/HdAuroraRenderDelegate.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/HdAurora/HdAuroraRenderPass.cpp b/Libraries/HdAurora/HdAuroraRenderPass.cpp index e6ed577..7b986ec 100644 --- a/Libraries/HdAurora/HdAuroraRenderPass.cpp +++ b/Libraries/HdAurora/HdAuroraRenderPass.cpp @@ -3,6 +3,7 @@ #include "HdAuroraRenderBuffer.h" #include "HdAuroraRenderDelegate.h" #include "HdAuroraRenderPass.h" +#include "HdAuroraTokens.h" #include HdAuroraRenderPass::HdAuroraRenderPass(HdRenderIndex* index, HdRprimCollection const& collection, @@ -36,6 +37,15 @@ void HdAuroraRenderPass::_Execute( int viewportHeight = framing.IsValid() ? framing.dataWindow.GetHeight() : static_cast(renderPassState->GetViewport()[3]); + // If the scene bounds are not valid set the bounds to a minimal bounding box, as it is not + // permitted to render an Aurora scene with empty bounds. + if (!_owner->BoundsValid()) + { + float boundsEpsilon = 0.0001f; + _owner->UpdateBounds(pxr::GfVec3f(-boundsEpsilon, -boundsEpsilon, -boundsEpsilon), + pxr::GfVec3f(+boundsEpsilon, +boundsEpsilon, +boundsEpsilon)); + } + HdFormat format = _owner->GetDefaultAovDescriptor(HdAovTokens->color).format; if (_viewportSize.first != viewportWidth || _viewportSize.second != viewportHeight) @@ -136,6 +146,38 @@ void HdAuroraRenderPass::_Execute( _owner->SetSampleRestartNeeded(true); } + // Get the view and projection matrices using the active camera of the render pass. + const auto camera = renderPassState->GetCamera(); + GfMatrix4f viewMat(camera->GetTransform().GetInverse()); + GfMatrix4f projMat(camera->ComputeProjectionMatrix()); + + // Check to see if we need to flip the output image. + // Default to true if not set. + bool flipY = true; + auto flipYValue = this->_owner->GetRenderSetting(HdAuroraTokens::kFlipYOutput); + if (!flipYValue.IsEmpty() && flipYValue.IsHolding()) + flipY = flipYValue.Get(); + if (flipY) + { + // Invert the second row of the projection matrix to flip the rendered image, because OpenGL + // expects the first pixel to be at the *bottom* corner. + projMat[1][0] = -projMat[1][0]; + projMat[1][1] = -projMat[1][1]; + projMat[1][2] = -projMat[1][2]; + projMat[1][3] = -projMat[1][3]; + } + + // Set the view and projection matrices on the renderer. + _owner->GetRenderer()->setCamera((float*)&viewMat, (float*)&projMat); + + // check for camera movements + if (_cameraView != viewMat || _cameraProj != projMat) + { + _cameraView = viewMat; + _cameraProj = projMat; + _owner->SetSampleRestartNeeded(true); + } + // Update the background. _owner->UpdateAuroraEnvironment(); diff --git a/Libraries/HdAurora/HdAuroraRenderPass.h b/Libraries/HdAurora/HdAuroraRenderPass.h index 0783172..340efd6 100644 --- a/Libraries/HdAurora/HdAuroraRenderPass.h +++ b/Libraries/HdAurora/HdAuroraRenderPass.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -42,4 +42,6 @@ class HdAuroraRenderPass final : public HdRenderPass std::shared_ptr _ownedRenderBuffer; std::map _renderBuffers; + + GfMatrix4f _cameraView, _cameraProj; }; \ No newline at end of file diff --git a/Libraries/HdAurora/HdAuroraTokens.h b/Libraries/HdAurora/HdAuroraTokens.h index ba93631..2c028b4 100644 --- a/Libraries/HdAurora/HdAuroraTokens.h +++ b/Libraries/HdAurora/HdAuroraTokens.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -65,6 +65,12 @@ static const TfToken kIsSharedHandleOpenGL("aurora:is_shared_handle_opengl"); /// The settings for the global ground plane, as defined in the Aurora API. static const TfToken kGroundPlaneSettings("aurora:ground_plane_settings"); +/// Whether to flip the output image. +static const TfToken kFlipYOutput("aurora:flip_y_output"); + +/// Whether to flip the output image. +static const TfToken kFlipLoadedImageY("aurora:flip_loaded_image_y"); + //**************************************************************************/ // Material Tokens //**************************************************************************/ @@ -75,6 +81,11 @@ static const TfToken kMaterialXFilePath("aurora:materialx_file_path"); /// The MaterialX document for a material, as XML in memory. static const TfToken kMaterialXDocument("aurora:materialx_document"); +/// If true the opacity UsdPreviewSurface parameter is mapped to transmission in Standard Surface. +/// This matches the definition more closely (defaults to true.) +static const TfToken kMapMaterialOpacityToTransmission( + "aurora:map_material_opacity_to_transmission"); + //**************************************************************************/ // Mesh Tokens //**************************************************************************/ diff --git a/Libraries/HdAurora/pch.h b/Libraries/HdAurora/pch.h index 3cdb959..dd251fd 100644 --- a/Libraries/HdAurora/pch.h +++ b/Libraries/HdAurora/pch.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Libraries/ImageProcessingResolver/CMakeLists.txt b/Libraries/ImageProcessingResolver/CMakeLists.txt new file mode 100644 index 0000000..9a4fb9a --- /dev/null +++ b/Libraries/ImageProcessingResolver/CMakeLists.txt @@ -0,0 +1,81 @@ +# Specify the library (for dependents), project (IDE), and output (binary file) names. +project(ImageProcessingResolver) + +# Add dependent packages. +find_package(glm REQUIRED) # Find the GLM vector maths package. +find_package(stb REQUIRED) # Find the Universal Scene Description (USD) library +find_package(pxr REQUIRED) # Find the Universal Scene Description (USD) library +find_package(OpenImageIO REQUIRED) +find_package(miniz REQUIRED) +find_package(uriparser REQUIRED) + +# On windows, the version resource and header files that add the version information to the DLL. +if(WIN32) +set(VERSION_FILES + ${VERSION_FOLDER}/AuroraVersion.h + ImageProcessingResolver.rc +) +endif() + +# Add shared library with all source files. +add_library(${PROJECT_NAME} SHARED + "DLL.cpp" + "Resolver.cpp" + "Resolver.h" + "Linearize.cpp" + "Linearize.h" + "ConvertEnvMapLayout.cpp" + "ConvertEnvMapLayout.h" + "pch.h" + ${VERSION_FILES} +) + +# Set custom output properties. +set_target_properties(${PROJECT_NAME} PROPERTIES + FOLDER "Libraries" + RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}" + PDB_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" + # Follow the USD convention of lower-case initial on libraries. + OUTPUT_NAME "imageProcessingResolver" +) + +# Add dependencies. +target_link_libraries(${PROJECT_NAME} +PRIVATE + uriparser::uriparser + miniz::miniz + glm::glm + stb::stb + OpenImageIO::OpenImageIO + pxr::usd + pxr::hf + pxr::hd + pxr::hdx + Foundation + Aurora +) + +# Add OpenEXR to the include path. +target_include_directories(${PROJECT_NAME} +PRIVATE + "${tinyexr_ROOT}/include" + ${VERSION_FOLDER} +) + +# Add default compile definitions (set in root CMakefile) +target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFAULT_COMPILE_DEFINITIONS}) + +if(WIN32) + # Create post-build step to copy to USD folder. + set(IMAGEPROCESSINGRESOLVER_PLUGIN_RESOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/resources) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $<$:$> ${RUNTIME_OUTPUT_DIR}/usd + COMMAND ${CMAKE_COMMAND} -E copy_directory ${IMAGEPROCESSINGRESOLVER_PLUGIN_RESOURCES_DIR} ${RUNTIME_OUTPUT_DIR}/usd/imageProcessingResolver/resources + ) +endif() + +# Install the binaries. +install(TARGETS ${PROJECT_NAME} DESTINATION ${INSTALL_BIN}/usd) +install(FILES "resources/plugInfo.json" DESTINATION ${INSTALL_BIN}/usd/imageProcessingResolver/resources) \ No newline at end of file diff --git a/Libraries/ImageProcessingResolver/ConvertEnvMapLayout.cpp b/Libraries/ImageProcessingResolver/ConvertEnvMapLayout.cpp new file mode 100644 index 0000000..1ea5446 --- /dev/null +++ b/Libraries/ImageProcessingResolver/ConvertEnvMapLayout.cpp @@ -0,0 +1,202 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "ConvertEnvMapLayout.h" +#include "Resolver.h" +#include +#include + +int gcd(int a, int b) +{ + return b ? gcd(b, a % b) : a; +} + +std::vector getSixFacesFromSourceImage(pxr::HioImage::StorageSpec& imageData) +{ + int numChannels = pxr::HioGetComponentCount(imageData.format); + pxr::HioType hioType = pxr::HioGetHioType(imageData.format); + int iGcd = gcd(imageData.width, imageData.height); + int iRatio = 0; + iRatio = imageData.width / iGcd; + std::vector vecImageList = {}; + OIIO::TypeDesc type = convertToOIIODataType(hioType); + OIIO::ImageSpec subImageSpec(iGcd, iGcd, numChannels, type); + OIIO::ImageSpec srcImageSpec(imageData.width, imageData.height, numChannels, type); + OIIO::ImageBuf srcBuf(srcImageSpec, imageData.data); + OIIO::ImageBuf des(subImageSpec); + std::stringstream s; + switch (iRatio) + { + // Vertical cubemap, subimages left to right +X,-X,+Y,-Y,+Z,-Z + case 1: + for (int i = 0; i < 6; ++i) + { + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { 0, iGcd, i * iGcd, (i + 1) * iGcd }); + vecImageList.push_back(des); + } + break; + case 3: + OIIO::ImageBufAlgo::cut( + des, srcBuf, OIIO::ROI { 0, iGcd + 1, iGcd, 2 * iGcd }); //-X //roi [begin,end) + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut( + des, srcBuf, OIIO::ROI { 2 * iGcd - 1, 3 * iGcd, iGcd, 2 * iGcd }); //+X + des = OIIO::ImageBufAlgo::flip(des); + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, 2 * iGcd, 3 * iGcd }); //-Y + des = OIIO::ImageBufAlgo::flip(des); + des = OIIO::ImageBufAlgo::flop(des); + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, 0, iGcd }); //+Y + des = OIIO::ImageBufAlgo::flip(des); + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, 3 * iGcd, 4 * iGcd }); //+Z + des = OIIO::ImageBufAlgo::flip(des); + des = OIIO::ImageBufAlgo::flop(des); + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, iGcd, 2 * iGcd }); //-Z + vecImageList.push_back(des); + break; + case 4: + OIIO::ImageBufAlgo::cut( + des, srcBuf, OIIO::ROI { 2 * iGcd - 1, 3 * iGcd, iGcd, 2 * iGcd }); //+X + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { 0, iGcd + 1, iGcd, 2 * iGcd }); //-X + des = OIIO::ImageBufAlgo::flip(des); + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, 2 * iGcd, 3 * iGcd }); //+Y + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, 0, iGcd }); //-Y + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { iGcd, 2 * iGcd, iGcd, 2 * iGcd }); //+Z + vecImageList.push_back(des); + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { 3 * iGcd, 4 * iGcd, iGcd, 2 * iGcd }); //-Z + vecImageList.push_back(des); + break; + case 6: + // Horizital cubemap, subimages from top to bottom like +X,-X,+Y,-Y,+Z,-Z + for (int i = 0; i < 6; ++i) + { + OIIO::ImageBufAlgo::cut(des, srcBuf, OIIO::ROI { i * iGcd, (i + 1) * iGcd, 0, iGcd }); + vecImageList.push_back(des); + } + break; + default: + break; + } + return vecImageList; +} + +bool convertToLatLongLayout( + pxr::HioImage::StorageSpec& imageData, std::vector& imageBuf) +{ + int nChannels = pxr::HioGetComponentCount(imageData.format); + pxr::HioType hioType = pxr::HioGetHioType(imageData.format); + OIIO::TypeDesc type = convertToOIIODataType(hioType); + OIIO::ImageSpec spec(imageData.width, imageData.height, nChannels, type); + + spec.full_x = spec.x; + spec.full_y = spec.y; + spec.full_z = spec.z; + spec.full_width = spec.width; + spec.full_height = spec.height; + spec.full_depth = spec.depth; + int patch = gcd(imageData.width, imageData.height); + size_t outputSize = 4 * patch * 2 * patch * nChannels * sizeof(float); + OIIO::ImageSpec latlongspec(4 * patch, 2 * patch, nChannels, type); + std::vector newPixels(outputSize); + unsigned char* outputImageBuffer = newPixels.data(); + OIIO::ImageBuf latlong(latlongspec, outputImageBuffer); + float pixelGotten[4]; + // Intergrate a latlong layout image from hcross/vcross + // 1.Construct the circumscribed sphere of the cube + // 2.Fill the sphere patch with cube surface( six images) + // 3.Expand the sphere along the 0 degree longitude + std::vector sixSidesList = {}; + sixSidesList = getSixFacesFromSourceImage(imageData); + for (int h = 0; h < 2 * patch; ++h) + { + float const phi = static_cast(M_PI * (h - patch) / (2 * patch)); //-pi/2~pi/2 + for (int w = 0; w < 4 * patch; ++w) + { + int coord_x, coord_y; + // Translate from sphere coordinate to Cartesian coordinate. + float theta = static_cast(M_PI * (w - 2 * patch) / (2 * patch)); // -pi~pi + float x = cos(phi) * sin(theta); + float y = sin(phi); + float z = cos(phi) * cos(theta); + if (abs(x) >= abs(y) && abs(x) >= abs(z)) + { + coord_x = static_cast(patch * (1.0 - z / x) / 2.0); + coord_y = static_cast(patch * (1.0 + y / x) / 2.0); + x < 0.0 ? sixSidesList.at(1).getpixel(coord_x, coord_y, pixelGotten) + : sixSidesList.at(0).getpixel(coord_x, coord_y, pixelGotten); + } + else if (abs(y) > abs(z)) + { + coord_x = static_cast(patch * (1 + x / y) / 2.0); + coord_y = static_cast(patch * (1 - z / y) / 2.0); + y < 0.0 ? sixSidesList.at(3).getpixel(coord_x, coord_y, pixelGotten) + : sixSidesList.at(2).getpixel(coord_x, coord_y, pixelGotten); + } + else + { + coord_x = static_cast(patch * (1 + x / z) / 2.0); + coord_y = static_cast(patch * (1 + z / abs(z) * y / z) / 2.0); + z < 0.0 ? sixSidesList.at(5).getpixel(coord_x, coord_y, pixelGotten) + : sixSidesList.at(4).getpixel(coord_x, coord_y, pixelGotten); + } + latlong.setpixel(w, h, pixelGotten); + } + } + imageBuf = newPixels; + imageData.width = 4 * patch; + imageData.height = 2 * patch; + imageData.data = imageBuf.data(); + return true; +} + +bool convertEnvMapLayout(const AssetCacheEntry& record, pxr::HioImageSharedPtr const /*pImage*/, + pxr::HioImage::StorageSpec& imageData, std::vector& imageBuf) +{ + + std::string targetLayout; + record.getQuery("targetLayout", &targetLayout); + if (targetLayout.compare("latlon") != 0) + { + AU_WARN("Only latlon target layout supported."); + return false; + } + + // If the width is 2x the height then we infer the image is already in lat-long layout. + if (imageData.width == imageData.height * 2) + { + AU_WARN( + "Image dimensions imply image is already in lat-long format, returning unconverted " + "image."); + return true; + } + + std::string sourceLayout; + record.getQuery("sourceLayout", &sourceLayout); + if (sourceLayout.compare("cube") != 0) + { + AU_WARN("Only cube source layout supported."); + return false; + } + + convertToLatLongLayout(imageData, imageBuf); + return true; +} diff --git a/Libraries/ImageProcessingResolver/ConvertEnvMapLayout.h b/Libraries/ImageProcessingResolver/ConvertEnvMapLayout.h new file mode 100644 index 0000000..6491d5a --- /dev/null +++ b/Libraries/ImageProcessingResolver/ConvertEnvMapLayout.h @@ -0,0 +1,6 @@ +#include "Resolver.h" +#include + +extern bool convertEnvMapLayout(const AssetCacheEntry& cacheEntry, + pxr::HioImageSharedPtr const pImage, pxr::HioImage::StorageSpec& imageData, + std::vector& imageBuf); diff --git a/Libraries/ImageProcessingResolver/DLL.cpp b/Libraries/ImageProcessingResolver/DLL.cpp new file mode 100644 index 0000000..8c794ae --- /dev/null +++ b/Libraries/ImageProcessingResolver/DLL.cpp @@ -0,0 +1,39 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#if defined(WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "Resolver.h" +#include + +#if defined _WIN32 +// The module entry point +BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + pxr::Ar_DefineResolver(); + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#endif \ No newline at end of file diff --git a/Libraries/ImageProcessingResolver/ImageProcessingResolver.rc b/Libraries/ImageProcessingResolver/ImageProcessingResolver.rc new file mode 100644 index 0000000..de5a102 --- /dev/null +++ b/Libraries/ImageProcessingResolver/ImageProcessingResolver.rc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:145658c1e1789ca4e84429bdd47a3960533ac4c8beb76ecd47679d709f34bf76 +size 378 diff --git a/Libraries/ImageProcessingResolver/Linearize.cpp b/Libraries/ImageProcessingResolver/Linearize.cpp new file mode 100644 index 0000000..e1c5c2e --- /dev/null +++ b/Libraries/ImageProcessingResolver/Linearize.cpp @@ -0,0 +1,140 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "Linearize.h" + +float reverseSpectrumToneMappingLow(float x) +{ + const float a = 0.244925773f; + const float t1 = -0.631960243f; + const float t2 = -4.251792105f; + const float t3 = 3.841721426f; + const float d0 = -0.005002772f; + const float d1 = -0.525010335f; + const float d2 = -0.885018509f; + const float d3 = 1.153153531f; + + const float x2 = x * x; + const float x3 = x2 * x; + return a * (t1 * x + t2 * x2 + t3 * x3) / (d0 + d1 * x + d2 * x2 + d3 * x3); +} + +float reverseSpectrumToneMappingHigh(float x) +{ + const float a = 0.514397858f; + const float t1 = -0.553266341f; + const float t2 = -2.677674970f; + const float t3 = 2.903543727f; + const float d0 = -0.005057344f; + const float d1 = -0.804910686f; + const float d2 = -1.427186761f; + const float d3 = 2.068910419f; + + const float x2 = x * x; + const float x3 = x2 * x; + return a * (t1 * x + t2 * x2 + t3 * x3) / (d0 + d1 * x + d2 * x2 + d3 * x3); +} + +void canonInverse( + pxr::HioImage::StorageSpec& imageData, std::vector& imageBuf, float exposure) +{ + // Work out channel count from HIO format. + int numChannels = pxr::HioGetComponentCount(imageData.format); + + // Compute 2^exposure. + float exposure2 = exp2(exposure); + + // Image dims and pixel count. + int imagewidth = imageData.width; + int imageheight = imageData.height; + int pixels = imagewidth * imageheight * numChannels; + + // If the input format is LDR we must convert to float first, use temp buffer. + bool isConversionRequired = imageData.format == pxr::HioFormatUNorm8Vec3srgb; + unsigned char* srcBytes = imageBuf.data(); + float* dst = reinterpret_cast(srcBytes); + if (!dst) + { + return; + } + vector newPixels; + if (isConversionRequired) + { + newPixels.resize(pixels * sizeof(float)); + dst = reinterpret_cast(newPixels.data()); + } + + const float spectrumTonemapMin = -2.152529302052785809f; + const float spectrumTonemapMax = 1.163792197947214113f; + + const float lowHighBreak = 0.9932f; + + const float shift = 0.18f; + for (int i = 0; i < pixels; ++i) + { + float val; + if (isConversionRequired) + val = float(srcBytes[i]) / 255.0f; + else + val = dst[i]; + + if (val < lowHighBreak) + { + val = reverseSpectrumToneMappingLow(val); + } + else + { + val = reverseSpectrumToneMappingHigh(val); + } + val = pxr::GfClamp(val, 0.0f, 1.0f); + val = val * (spectrumTonemapMax - spectrumTonemapMin) + spectrumTonemapMin; + val = pxr::GfPow(10.0f, val); + val /= exposure2; + val *= shift; + + dst[i] = val; + } + + // Copy the temp buffer to the output buffer, if used. + if (isConversionRequired) + { + imageBuf = newPixels; + imageData.data = imageBuf.data(); + imageData.format = pxr::HioFormatFloat32Vec3; + } +} + +bool linearize(const AssetCacheEntry& cacheEntry, pxr::HioImageSharedPtr const /*pImage*/, + pxr::HioImage::StorageSpec& imageData, std::vector& imageBuf) +{ + + // If not RGB float32 return false; + if (imageData.format != pxr::HioFormatFloat32Vec3 && + imageData.format != pxr::HioFormatUNorm8Vec3srgb) + { + AU_WARN("Unsupported format for linearization"); + return false; + } + + // Get exposure from URI query field. + float exposure = 0.0f; + cacheEntry.getQuery("exposure", &exposure); + + // Run inverse tone mapping to linearize image. + canonInverse(imageData, imageBuf, exposure); + + // Return success. + return true; +} diff --git a/Libraries/ImageProcessingResolver/Linearize.h b/Libraries/ImageProcessingResolver/Linearize.h new file mode 100644 index 0000000..9e2ee9f --- /dev/null +++ b/Libraries/ImageProcessingResolver/Linearize.h @@ -0,0 +1,5 @@ +#include "Resolver.h" +#include + +extern bool linearize(const AssetCacheEntry& cacheEntry, pxr::HioImageSharedPtr const pImage, + pxr::HioImage::StorageSpec& imageData, std::vector& imageBuf); diff --git a/Libraries/ImageProcessingResolver/Resolver.cpp b/Libraries/ImageProcessingResolver/Resolver.cpp new file mode 100644 index 0000000..f7236fd --- /dev/null +++ b/Libraries/ImageProcessingResolver/Resolver.cpp @@ -0,0 +1,408 @@ +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "pch.h" + +#include "ConvertEnvMapLayout.h" +#include "Linearize.h" +#include "Resolver.h" + +#define URI_STATIC_BUILD 1 +#include "uriparser/Uri.h" + +#define TINYEXR_USE_MINIZ 1 +#define TINYEXR_IMPLEMENTATION +#pragma warning(push) +#pragma warning(disable : 4706) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-but-set-variable" +#include "tinyexr.h" +#pragma clang diagnostic pop +#pragma warning(pop) + +#include +#include +#include +#include +#include +#include + +using namespace pxr; + +HioFormat convertToFloat(HioFormat format) +{ + switch (format) + { + case HioFormatUNorm8: + return HioFormatFloat32; + case HioFormatUNorm8Vec2: + return HioFormatFloat32Vec2; + case HioFormatUNorm8Vec3: + return HioFormatFloat32Vec3; + case HioFormatUNorm8Vec4: + return HioFormatFloat32Vec4; + + case HioFormatUNorm8srgb: + return HioFormatFloat32; + case HioFormatUNorm8Vec2srgb: + return HioFormatFloat32Vec2; + case HioFormatUNorm8Vec3srgb: + return HioFormatFloat32Vec3; + case HioFormatUNorm8Vec4srgb: + return HioFormatFloat32Vec4; + + case HioFormatSNorm8: + return HioFormatFloat32; + case HioFormatSNorm8Vec2: + return HioFormatFloat32Vec2; + case HioFormatSNorm8Vec3: + return HioFormatFloat32Vec3; + case HioFormatSNorm8Vec4: + return HioFormatFloat32Vec4; + + case HioFormatFloat16: + return HioFormatFloat32; + case HioFormatFloat16Vec2: + return HioFormatFloat32Vec2; + case HioFormatFloat16Vec3: + return HioFormatFloat32Vec3; + case HioFormatFloat16Vec4: + return HioFormatFloat32Vec4; + + case HioFormatFloat32: + return HioFormatFloat32; + case HioFormatFloat32Vec2: + return HioFormatFloat32Vec2; + case HioFormatFloat32Vec3: + return HioFormatFloat32Vec3; + case HioFormatFloat32Vec4: + return HioFormatFloat32Vec4; + default: + AU_FAIL("Unsupported format."); + } + return HioFormatCount; +} + +OIIO::TypeDesc::BASETYPE convertToOIIODataType(pxr::HioType type) +{ + OIIO::TypeDesc::BASETYPE oiioType = OIIO::TypeDesc::UNKNOWN; + switch (type) + { + case pxr::HioTypeUnsignedByte: + oiioType = OIIO::TypeDesc::UINT8; + break; + case pxr::HioTypeUnsignedShort: + oiioType = OIIO::TypeDesc::UINT16; + break; + case pxr::HioTypeUnsignedInt: + oiioType = OIIO::TypeDesc::UINT32; + break; + case pxr::HioTypeHalfFloat: + oiioType = OIIO::TypeDesc::HALF; + break; + case pxr::HioTypeFloat: + oiioType = OIIO::TypeDesc::FLOAT; + break; + case pxr::HioTypeDouble: + oiioType = OIIO::TypeDesc::DOUBLE; + break; + default: + oiioType = OIIO::TypeDesc::UNKNOWN; + break; + } + return oiioType; +} + +// Asset path prefix, used once asset URI is processed. +std::string assetPathPrefix = "@@@ImageProcessingAsset_"; + +// Shink an image to the provided dimensions. +// imageData and imageBuf will be modified to point to the new image. +void shrinkImage(pxr::HioImage::StorageSpec& imageData, std::vector& imageBuf, + int newWidth, int newHeight) +{ + // Create OIIO for input and output images. + int nChannels = pxr::HioGetComponentCount(imageData.format); + pxr::HioType hioType = pxr::HioGetHioType(imageData.format); + OIIO::TypeDesc type = convertToOIIODataType(hioType); + OIIO::ImageSpec inSpec(imageData.width, imageData.height, nChannels, type); + OIIO::ImageSpec outSpec(newWidth, newHeight, nChannels, type); + + // Create buffer for shrunk image. + size_t compSize = HioGetDataSizeOfType(hioType); + std::vector newPixels(newWidth * newHeight * compSize * nChannels); + + // Create image buffers for input and output image. + OIIO::ImageBuf inBuf(inSpec, imageData.data); + OIIO::ImageBuf outBuf(outSpec, newPixels.data()); + + // Resize the image to new dimensions. + OIIO::ImageBufAlgo::resize(outBuf, inBuf); + + // Copy the image data and parameters to output structs. + imageBuf = newPixels; + imageData.width = newWidth; + imageData.height = newHeight; + imageData.data = imageBuf.data(); +} + +ImageProcessingResolverPlugin::ImageProcessingResolverPlugin() : ArDefaultResolver() {} + +ImageProcessingResolverPlugin::~ImageProcessingResolverPlugin() {} + +std::shared_ptr ImageProcessingResolverPlugin::_OpenAsset( + const ArResolvedPath& resolvedPath) const +{ + // Get string for path. + std::string pathStr = resolvedPath.GetPathString(); + + // Is the path an encoded URI path? + if (pathStr.find(assetPathPrefix) != std::string::npos) + { + // Get the cacheEntry for this URI. + AssetCacheEntry& cacheEntry = + const_cast(this)->_assetCache[pathStr]; + + // If we do not have an asset cached, create one. + if (!cacheEntry.pAsset) + { + + // Use the Hio image function to read the source image. + pxr::HioImageSharedPtr const image = + pxr::HioImage::OpenForReading(cacheEntry.sourceFilename); + + // If image load failed, return null asset. + if (!image) + { + return nullptr; + } + + // Create a temp buffer for the source image pixels. + size_t sizeInBytes = image->GetWidth() * image->GetHeight() * image->GetBytesPerPixel(); + std::vector tempBuf(sizeInBytes); + // Read the source image into the temp buffer. + pxr::HioImage::StorageSpec imageData; + imageData.width = image->GetWidth(); + imageData.height = image->GetHeight(); + imageData.depth = 1; + imageData.flipped = false; + imageData.format = image->GetFormat(); + imageData.data = tempBuf.data(); + image->Read(imageData); + + // Get the maxDim query parameter, default to 16k if not specified. + int maxDim = 16 * 1024; + cacheEntry.getQuery("maxDim", &maxDim); + + // If the input image's dimensions are larger than maxDim shrink it, keeping the aspect + // ratio the same. + if (imageData.width > imageData.height) + { + if (imageData.width > maxDim) + { + int newWidth = maxDim; + float sizeRatio = float(maxDim) / float(imageData.width); + int newHeight = int(imageData.height * sizeRatio); + shrinkImage(imageData, tempBuf, newWidth, newHeight); + } + } + else + { + if (imageData.height > maxDim) + { + int newHeight = maxDim; + float sizeRatio = float(maxDim) / float(imageData.height); + int newWidth = int(imageData.width * sizeRatio); + shrinkImage(imageData, tempBuf, newWidth, newHeight); + } + } + + // The asset object (that will be cached in this resolver class.) + std::shared_ptr pAsset; + + if (cacheEntry.host.compare("linearize") == 0) + { + // Run the linearize function if host string indicated that + // function. This will overwrite the temp buffer in tempBuf with a + // resized vector if the input is LDR. + if (!linearize(cacheEntry, image, imageData, tempBuf)) + return nullptr; + } + else if (cacheEntry.host.compare("convertEnvMapLayout") == 0) + { + // Run the convert environment map function if host string indicated + // that function. This will overwrite the temp buffer in tempBuf + // with a resized vector. + if (!convertEnvMapLayout(cacheEntry, image, imageData, tempBuf)) + return nullptr; + } + + // Get pointer to pixels to be written from temp buffer. + void* pPixels = tempBuf.data(); + + // If the image is in any format except float, then convert it to float. + pxr::HioType hioType = pxr::HioGetHioType(imageData.format); + int nChannels = pxr::HioGetComponentCount(imageData.format); + std::vector convertedBuf; + if (hioType != pxr::HioTypeFloat) + { + OIIO::TypeDesc type = convertToOIIODataType(hioType); + OIIO::ImageSpec origImageSpec(imageData.width, imageData.height, nChannels, type); + OIIO::ImageBuf origBuf(origImageSpec, imageData.data); + convertedBuf.resize(imageData.width * imageData.height * nChannels * sizeof(float)); + origBuf.get_pixels(OIIO::ROI::All(), OIIO::TypeDesc::FLOAT, convertedBuf.data()); + imageData.data = convertedBuf.data(); + imageData.format = convertToFloat(imageData.format); + + // Get the pixels from the converted buffer. + pPixels = convertedBuf.data(); + } + + // Re-encode the image data as an EXR (as the rest of the USD stack expects an image + // file from the ArResolver.) + // The EXR file that is produced only works if OIIO is included in USD. Without OIIO in + // USD, the calling code will fail to load this. + // This uses TinyEXR, as a later OIIO version (approx v2.4.5.0) is required for ioproxy + // support for EXR or HDR files. This is not ideal as this brings in more dependencies + // and tinyEXR forces compression, which we don't want. + // + unsigned char* pBuffer; + const char* pErr; + int len = SaveEXRToMemory((const float*)pPixels, imageData.width, imageData.height, + nChannels, 0, (const unsigned char**)&pBuffer, &pErr); + + // If successful create an ArAsset from the EXR in memory. + if (len) + { + pAsset = std::make_shared((void*)pBuffer, len); + } + + // Free the buffer (as the ArAsset has taken a copy.) + free(pBuffer); + + // Set the cached asset. + // TODO: Set a memory limit on size of cached assets. + cacheEntry.pAsset = pAsset; + } + + // Return the cached asset. + return cacheEntry.pAsset; + } + + // If not an imageProcesing URI run the default _OpenAsset Function. + std::shared_ptr pRes = ArDefaultResolver::_OpenAsset(resolvedPath); + return pRes; +} + +ArResolvedPath ImageProcessingResolverPlugin::_Resolve(const std::string& assetPath) const +{ + + // If this is a processed URI path, generated by _CreateIdentifier, just return + // it unaltered. + if (assetPath.find(assetPathPrefix) != std::string::npos) + { + return ArResolvedPath(assetPath); + } + + ArResolvedPath res = ArDefaultResolver::_Resolve(assetPath); + return res; +} + +std::string ImageProcessingResolverPlugin::CreateIdentifierFromURI( + const std::string& assetPath, const ArResolvedPath& anchorAssetPath) const +{ + // Parse the asset path as a URI. + UriUriA uri; + const char* pErrStr; + if (uriParseSingleUriA(&uri, assetPath.c_str(), &pErrStr) == URI_SUCCESS) + { + // If URI parsing succeeds get the URI scheme. + std::string scheme( + uri.scheme.first, (size_t)uri.scheme.afterLast - (size_t)uri.scheme.first); + + // If this is not an imageProcessing URI return empty string. + if (scheme.compare("imageProcessing") != 0) + return ""; + + // Create an cache entry for this path. + AssetCacheEntry cacheEntry = AssetCacheEntry(); + + // Set the host which defines which processing function to run. + cacheEntry.host = std::string( + uri.hostText.first, (size_t)uri.hostText.afterLast - (size_t)uri.hostText.first); + + // Build a query dictionary which defines the function parameters. + UriQueryListA* queryList; + int itemCount; + if (uriDissectQueryMallocA(&queryList, &itemCount, uri.query.first, uri.query.afterLast) == + URI_SUCCESS) + { + while (queryList) + { + cacheEntry.queries[queryList->key] = queryList->value; + queryList = queryList->next; + } + + // Set the source file name from the file query parameter (Which is + // common to all functions) + cacheEntry.sourceFilename = ArDefaultResolver::_CreateIdentifier( + cacheEntry.queries["filename"], anchorAssetPath); + } + + // Create a hash from original URI. + std::size_t assetHash = std::hash {}(assetPath.c_str()); + + // All images are assigned .exr extension currently. + string ext = ".exr"; + + // Create generated asset path from hash. + ArResolvedPath generatedAssetPath = + ArResolvedPath(assetPathPrefix + Aurora::Foundation::sHash(assetHash) + ext); + cacheEntry.assetPath = generatedAssetPath; + + // If there is no cacheEntry for this path, or the sourceFilename has changed + // (due to anchor changing) then add to cache. + auto& assetCache = const_cast(this)->_assetCache; + auto recordIter = assetCache.find(generatedAssetPath); + if (recordIter == assetCache.end() || + recordIter->second.sourceFilename.compare(cacheEntry.sourceFilename) != 0) + assetCache[generatedAssetPath] = cacheEntry; + + // Return processed asset path. + return cacheEntry.assetPath.GetPathString(); + } + return ""; +} + +std::string ImageProcessingResolverPlugin::_CreateIdentifier( + const std::string& assetPath, const ArResolvedPath& anchorAssetPath) const +{ + // If an imageProcessing URI was successfully parsed, return the processed asset + // path. + std::string uriId = CreateIdentifierFromURI(assetPath, anchorAssetPath); + if (!uriId.empty()) + return uriId; + + // Otherwise return default result. + return ArDefaultResolver::_CreateIdentifier(assetPath, anchorAssetPath); +} + +std::string ImageProcessingResolverPlugin::_CreateIdentifierForNewAsset( + const std::string& assetPath, const ArResolvedPath& anchorAssetPath) const +{ + // If an imageProcessing URI was successfully parsed, return the processed asset + // path. + std::string uriId = CreateIdentifierFromURI(assetPath, anchorAssetPath); + if (!uriId.empty()) + return uriId; + + // Otherwise return default result. + return ArDefaultResolver::_CreateIdentifier(assetPath, anchorAssetPath); +} diff --git a/Libraries/ImageProcessingResolver/Resolver.h b/Libraries/ImageProcessingResolver/Resolver.h new file mode 100644 index 0000000..de6d20c --- /dev/null +++ b/Libraries/ImageProcessingResolver/Resolver.h @@ -0,0 +1,167 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include +#include + +#if __clang__ +// Disable the various warnings generated by OIIO on Clang. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wignored-attributes" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic ignored "-Wdelete-non-abstract-non-virtual-dtor" +#endif +#pragma warning(push) +#pragma warning(disable : 4459) // disable simd.h warning about 'cout' declaration. +#pragma warning(disable : 4566) // disable core.h warning about "micro" char type. +#pragma warning(disable : 4100) // disable simd.h warning about unreferenced formal parameters. +#pragma warning(disable : 4244) // disable simd.h warning about type conversion. +#pragma warning(disable : 4267) // disable harmhash.h warning conversion from 'size_t' to 'uint32_t' +#pragma warning( \ + disable : 4456) // disable harmhash.h warning declaration hides previous local declaration +#include "OpenImageIO/imagebuf.h" +#include "OpenImageIO/imagebufalgo.h" +#include "OpenImageIO/imageio.h" +#include +#pragma warning(pop) +#if __clang__ +#pragma clang diagnostic pop +#endif + +class ResolverAsset; + +OIIO::TypeDesc::BASETYPE convertToOIIODataType(pxr::HioType type); + +struct AssetCacheEntry +{ + AssetCacheEntry(pxr::ArResolvedPath path = pxr::ArResolvedPath()) : assetPath(path) {} + + // Pointer to asset for this cache entry. + std::shared_ptr pAsset; + // Scheme string from this entry's URI. + std::string scheme; + // Host string from this entry's URI. + std::string host; + // Dictionary of query strings from this entry's URI. + std::map queries; + // Source filename for this entry (after anchor path applied.) + std::string sourceFilename; + // Processed asset path. + pxr::ArResolvedPath assetPath; + + // Get query from dictionary as float. + bool getQuery(const std::string key, float* pValOut) const + { + std::string strVal; + + if (!getQuery(key, &strVal)) + return false; + + *pValOut = (float)std::atof(strVal.c_str()); + return true; + } + + // Get query from dictionary as int. + bool getQuery(const std::string key, int* pValOut) const + { + std::string strVal; + + if (!getQuery(key, &strVal)) + return false; + + *pValOut = (int)std::atoi(strVal.c_str()); + return true; + } + + // Get query from dictionary as string. + bool getQuery(const std::string key, std::string* pValOut) const + { + auto iter = queries.find(key); + if (iter == queries.end()) + return false; + + *pValOut = iter->second; + return true; + } +}; + +// Custom asset class for ImageProcessingResolverPlugin +class ResolverAsset : public pxr::ArAsset +{ +public: + // Ctor will copy contents of pData to newly allocated asset buffer. + ResolverAsset(void* pData, size_t size) : _pData(new char[size], std::default_delete()) + { + memcpy(_pData.get(), pData, size); + _size = size; + } + + // Get size of asset in bytes. + virtual size_t GetSize() const override { return _size; } + + // Get const pointer to asset buffer. + virtual std::shared_ptr GetBuffer() const override { return _pData; } + // Get pointer to asset buffer. + std::shared_ptr GetBuffer() { return _pData; } + + // Read bytes from asset buffer. + virtual size_t Read(void* buffer, size_t count, size_t offset) const override + { + + if (offset + count > _size) + { + if (offset >= _size) + return 0; + count = _size - offset; + } + memcpy(buffer, _pData.get() + offset, count); + + return count; + } + + // Not implemented. + virtual std::pair GetFileUnsafe() const override + { + return std::make_pair(nullptr, 0); + } + +protected: + std::shared_ptr _pData; + size_t _size; +}; + +// ArResolver plugin for image processing. +class ImageProcessingResolverPlugin : public pxr::ArDefaultResolver +{ +public: + ImageProcessingResolverPlugin(); + ~ImageProcessingResolverPlugin() override; + + // Overriden ArResolver::_OpenAsset implementation. + std::shared_ptr _OpenAsset( + const pxr::ArResolvedPath& resolvedPath) const override; + // Overriden ArResolver::_Resolve implementation. + pxr::ArResolvedPath _Resolve(const std::string& assetPath) const override; + +protected: + // Overriden ArResolver::_CreateIdentifier implementation. + std::string _CreateIdentifier( + const std::string& assetPath, const pxr::ArResolvedPath& anchorAssetPath) const override; + // Overriden ArResolver::_CreateIdentifierForNewAsset implementation. + + // Utility function to create identifier from URI, and add to cache. + std::string _CreateIdentifierForNewAsset( + const std::string& assetPath, const pxr::ArResolvedPath& anchorAssetPath) const override; + + std::string CreateIdentifierFromURI( + const std::string& assetPath, const pxr::ArResolvedPath& anchorAssetPath) const; + + std::map _assetCache; +}; diff --git a/Libraries/ImageProcessingResolver/pch.h b/Libraries/ImageProcessingResolver/pch.h new file mode 100644 index 0000000..875a398 --- /dev/null +++ b/Libraries/ImageProcessingResolver/pch.h @@ -0,0 +1,88 @@ +// Copyright 2022 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +using namespace std; + +#pragma warning(push) +#pragma warning(disable : 4244) // disabling type conversion warnings in USD +#pragma warning(disable : 4305) // disabling type truncation warnings in USD +#pragma warning(disable : 4127) // disabling const comparison warnings in USD +#pragma warning(disable : 4201) // disabling nameless struct warnings in USD +#pragma warning(disable : 4100) // disabling unreferenced parameter warnings in USD +#pragma warning(disable : 4275) // disabling non dll-interface class used as base for dll-interface + // class in USD + +// disable clang warnings about deprecated declarations in USD headers +#if defined(__APPLE__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +// warning: order of some includes here is important for successful compilation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) +#pragma clang diagnostic pop +#endif +#pragma warning(pop) + +// GLM - OpenGL Mathematics. +// NOTE: This is a math library, and not specific to OpenGL. +#define GLM_FORCE_CTOR_INIT +#pragma warning(push) +#pragma warning(disable : 4201) // nameless struct/union +#include "glm/glm.hpp" +#include "glm/gtc/matrix_access.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "glm/gtc/type_ptr.hpp" +#pragma warning(pop) + +// Aurora. +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE diff --git a/Libraries/ImageProcessingResolver/resources/plugInfo.json b/Libraries/ImageProcessingResolver/resources/plugInfo.json new file mode 100644 index 0000000..91c8202 --- /dev/null +++ b/Libraries/ImageProcessingResolver/resources/plugInfo.json @@ -0,0 +1,20 @@ +{ + "Plugins": [ + { + "Info": { + "Types": { + "ImageProcessingResolverPlugin": { + "bases": [ + "ArResolver" + ] + } + } + }, + "LibraryPath": "../imageProcessingResolver.dll", + "Name": "ImageProcessingResolver", + "ResourcePath": "resources", + "Root": "..", + "Type": "library" + } + ] +} diff --git a/Scripts/Patches/USD.patch b/Scripts/Patches/USD.patch index a5142d4..e23c2ca 100644 --- a/Scripts/Patches/USD.patch +++ b/Scripts/Patches/USD.patch @@ -1,8 +1,76 @@ +diff --git a/cmake/modules/FindOpenEXR.cmake b/cmake/modules/FindOpenEXR.cmake +index 639a01cf1..d57af1d79 100644 +--- a/cmake/modules/FindOpenEXR.cmake ++++ b/cmake/modules/FindOpenEXR.cmake +@@ -56,6 +56,10 @@ if(OPENEXR_INCLUDE_DIR) + endif() + endif() + ++if(CMAKE_BUILD_TYPE STREQUAL Debug) ++ SET(LIB_POSTFIX ${CMAKE_DEBUG_POSTFIX}) ++endif() ++ + foreach(OPENEXR_LIB + Half + Iex +@@ -70,8 +74,8 @@ foreach(OPENEXR_LIB + # using both versioned and unversioned names. + find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY + NAMES +- ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION} +- ${OPENEXR_LIB} ++ ${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}${LIB_POSTFIX} ++ ${OPENEXR_LIB}${LIB_POSTFIX} + HINTS + "${OPENEXR_LOCATION}" + "$ENV{OPENEXR_LOCATION}" +diff --git a/cmake/modules/FindOpenImageIO.cmake b/cmake/modules/FindOpenImageIO.cmake +index 9df47ff23..7d2811b84 100644 +--- a/cmake/modules/FindOpenImageIO.cmake ++++ b/cmake/modules/FindOpenImageIO.cmake +@@ -22,6 +22,10 @@ + # language governing permissions and limitations under the Apache License. + # + ++if(CMAKE_BUILD_TYPE STREQUAL Debug) ++ SET(LIB_POSTFIX ${CMAKE_DEBUG_POSTFIX}) ++endif() ++ + if(UNIX) + find_path(OIIO_BASE_DIR + include/OpenImageIO/oiioversion.h +@@ -31,7 +35,7 @@ if(UNIX) + "/opt/oiio" + ) + find_path(OIIO_LIBRARY_DIR +- libOpenImageIO.so ++ libOpenImageIO${LIB_POSTFIX}.so + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" +@@ -49,7 +53,7 @@ elseif(WIN32) + "$ENV{OIIO_LOCATION}" + ) + find_path(OIIO_LIBRARY_DIR +- OpenImageIO.lib ++ OpenImageIO${LIB_POSTFIX}.lib + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" +@@ -81,7 +85,7 @@ foreach(OIIO_LIB + ) + + find_library(OIIO_${OIIO_LIB}_LIBRARY +- ${OIIO_LIB} ++ ${OIIO_LIB}${LIB_POSTFIX} + HINTS + "${OIIO_LOCATION}" + "$ENV{OIIO_LOCATION}" diff --git a/cmake/modules/FindOpenSubdiv.cmake b/cmake/modules/FindOpenSubdiv.cmake -index 686af9b16..26b71e6fa 100644 +index 686af9b16..7cbdb0f47 100644 --- a/cmake/modules/FindOpenSubdiv.cmake +++ b/cmake/modules/FindOpenSubdiv.cmake -@@ -27,16 +27,18 @@ IF(NOT OPENSUBDIV_ROOT_DIR AND NOT $ENV{OPENSUBDIV_ROOT_DIR} STREQUAL "") +@@ -27,15 +27,14 @@ IF(NOT OPENSUBDIV_ROOT_DIR AND NOT $ENV{OPENSUBDIV_ROOT_DIR} STREQUAL "") SET(OPENSUBDIV_ROOT_DIR $ENV{OPENSUBDIV_ROOT_DIR}) ENDIF() @@ -14,18 +82,13 @@ index 686af9b16..26b71e6fa 100644 - _opensubdiv_FIND_COMPONENTS - osdGPU) +if(CMAKE_BUILD_TYPE STREQUAL Debug) -+ SET(_opensubdiv_FIND_COMPONENTS osdCPUd) -+ if(OPENSUBDIV_USE_GPU) -+ list(APPEND _opensubdiv_FIND_COMPONENTS osdGPUd) -+ endif() -+else() -+ SET(_opensubdiv_FIND_COMPONENTS osdCPU) -+ if(OPENSUBDIV_USE_GPU) -+ list(APPEND _opensubdiv_FIND_COMPONENTS osdGPU) -+ endif() ++ SET(LIB_POSTFIX ${CMAKE_DEBUG_POSTFIX}) endif() -- ++SET(_opensubdiv_FIND_COMPONENTS osdCPU${LIB_POSTFIX}) ++if(OPENSUBDIV_USE_GPU) ++ list(APPEND _opensubdiv_FIND_COMPONENTS osdGPU${LIB_POSTFIX}) ++endif() + SET(_opensubdiv_SEARCH_DIRS ${OPENSUBDIV_ROOT_DIR} - /usr/local diff --git a/Scripts/cmake/modules/Findpxr.cmake b/Scripts/cmake/modules/Findpxr.cmake index 1c5a19e..037c241 100644 --- a/Scripts/cmake/modules/Findpxr.cmake +++ b/Scripts/cmake/modules/Findpxr.cmake @@ -34,6 +34,9 @@ else() set(USD_COMPOMPONENTS_USDVIEW "False") endif() +if(NOT Boost_FOUND) + find_package(Boost REQUIRED) +endif() if(NOT TBB_FOUND) find_package(TBB REQUIRED) endif() @@ -43,7 +46,7 @@ if(NOT OPENSUBDIV_FOUND) endif() if(NOT Vulkan_shaderc_combined_FOUND) # Vulkan is required by hgiVulkan - find_package(Vulkan COMPONENTS shaderc_combined REQUIRED) # requires cmake 3.24 + find_package(Vulkan COMPONENTS shaderc_combined) # requires cmake 3.24 endif() if(NOT OpenGL_FOUND) find_package(OpenGL REQUIRED) @@ -59,7 +62,11 @@ set(PXR_LIBRARY_DIR "${PXR_INSTALL_PREFIX}/lib") set(PXR_LIBRARY_DIRS "${PXR_LIBRARY_DIR}") # Configure all USD targets -set(USD_COMPOMPONENTS arch tf gf js trace work plug vt ar kind sdf ndr sdr pcp usd usdGeom usdVol usdMedia usdShade usdLux usdProc usdRender usdHydra usdRi usdSkel usdUI usdUtils usdPhysics garch hf hio cameraUtil pxOsd glf hgi hgiGL hgiVulkan hgiInterop hd hdGp hdsi hdSt hdx usdImaging usdImagingGL usdProcImaging usdRiImaging usdSkelImaging usdVolImaging usdAppUtils) +set(USD_COMPOMPONENTS arch tf gf js trace work plug vt ar kind sdf ndr sdr pcp usd usdGeom usdVol usdMedia usdShade usdLux usdProc usdRender usdHydra usdRi usdSkel usdUI usdUtils usdPhysics garch hf hio cameraUtil pxOsd glf hgi hgiGL hgiInterop hd hdGp hdsi hdSt hdx usdImaging usdImagingGL usdProcImaging usdRiImaging usdSkelImaging usdVolImaging usdAppUtils) + +if(Vulkan_shaderc_combined_FOUND) + list(APPEND USD_COMPOMPONENTS "hgiVulkan") +endif() if(USD_COMPOMPONENTS_USDVIEW) list(APPEND USD_COMPOMPONENTS "usdviewq") diff --git a/Scripts/installExternals.py b/Scripts/installExternals.py index 0e7ab84..54ed279 100644 --- a/Scripts/installExternals.py +++ b/Scripts/installExternals.py @@ -1,6 +1,6 @@ # # Copyright 2017 Pixar -# Copyright 2022 Autodesk +# Copyright 2023 Autodesk # # Licensed under the Apache License, Version 2.0 (the "Apache License") # with the following modification; you may not use this file except in @@ -46,617 +46,14 @@ from shutil import which from urllib.request import urlopen +from installExternalsFunctions import * + if sys.version_info.major < 3: raise Exception("Python 3 or a more recent version is required.") -# Helpers for printing output -verbosity = 1 - -# Script version, incrementing this will force re-installation of all packages. -scriptVersion = "00001" - -def Print(msg): - if verbosity > 0: - print(msg) - -def PrintWarning(warning): - if verbosity > 0: - print("WARNING:", warning) - -def PrintStatus(status): - if verbosity >= 1: - print("STATUS:", status) - -def PrintInfo(info): - if verbosity >= 2: - print("INFO:", info) - -def PrintCommandOutput(output): - if verbosity >= 3: - sys.stdout.write(output) - -def PrintError(error): - if verbosity >= 3 and sys.exc_info()[1] is not None: - import traceback - traceback.print_exc() - print ("ERROR:", error) - -# Helpers for determining platform -def Windows(): - return platform.system() == "Windows" -def Linux(): - return platform.system() == "Linux" - -def GetLocale(): - return sys.stdout.encoding or locale.getdefaultlocale()[1] or "UTF-8" - -def GetCommandOutput(command): - """Executes the specified command and returns output or None.""" - try: - return subprocess.check_output(shlex.split(command), - stderr=subprocess.STDOUT).decode(GetLocale(), 'replace').strip() - except subprocess.CalledProcessError: - pass - return None - -def GetVisualStudioCompilerAndVersion(): - """ - Returns a tuple containing the path to the Visual Studio compiler - and a tuple for its version, e.g. (14, 0). If the compiler is not found - or version number cannot be determined, returns None. - """ - if not Windows(): - return None - - msvcCompiler = which('cl') - if msvcCompiler: - # VisualStudioVersion environment variable should be set by the - # Visual Studio Command Prompt. - match = re.search(r"(\d+)\.(\d+)", - os.environ.get("VisualStudioVersion", "")) - if match: - return (msvcCompiler, tuple(int(v) for v in match.groups())) - return None - -def IsVisualStudioVersionOrGreater(desiredVersion): - if not Windows(): - return False - - msvcCompilerAndVersion = GetVisualStudioCompilerAndVersion() - if msvcCompilerAndVersion: - _, version = msvcCompilerAndVersion - return version >= desiredVersion - return False - -def IsVisualStudio2019OrGreater(): - VISUAL_STUDIO_2019_VERSION = (16, 0) - return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2019_VERSION) - -def GetPythonInfo(context): - """Returns a tuple containing the path to the Python executable, shared - library, and include directory corresponding to the version of Python - currently running. Returns None if any path could not be determined. - - This function is used to extract build information from the Python - interpreter used to launch this script. This information is used - in the Boost and USD builds. By taking this approach we can support - having USD builds for different Python versions built on the same - machine. This is very useful, especially when developers have multiple - versions installed on their machine, which is quite common now with - Python2 and Python3 co-existing. - """ - - # If we were given build python info then just use it. - if context.build_python_info: - return (context.build_python_info['PYTHON_EXECUTABLE'], - context.build_python_info['PYTHON_LIBRARY'], - context.build_python_info['PYTHON_INCLUDE_DIR'], - context.build_python_info['PYTHON_VERSION']) - - # First we extract the information that can be uniformly dealt with across - # the platforms: - pythonExecPath = sys.executable - pythonVersion = sysconfig.get_config_var("py_version_short") # "2.7" - - # Lib path is unfortunately special for each platform and there is no - # config_var for it. But we can deduce it for each platform, and this - # logic works for any Python version. - def _GetPythonLibraryFilename(context): - if Windows(): - return "python{version}.lib".format( - version=sysconfig.get_config_var("py_version_nodot")) - elif Linux(): - return sysconfig.get_config_var("LDLIBRARY") - else: - raise RuntimeError("Platform not supported") - - pythonIncludeDir = sysconfig.get_path("include") - if not pythonIncludeDir or not os.path.isdir(pythonIncludeDir): - # as a backup, and for legacy reasons - not preferred because - # it may be baked at build time - pythonIncludeDir = sysconfig.get_config_var("INCLUDEPY") - - # if in a venv, installed_base will be the "original" python, - # which is where the libs are ("base" will be the venv dir) - pythonBaseDir = sysconfig.get_config_var("installed_base") - if not pythonBaseDir or not os.path.isdir(pythonBaseDir): - # for python-2.7 - pythonBaseDir = sysconfig.get_config_var("base") - - if Windows(): - pythonLibPath = os.path.join(pythonBaseDir, "libs", - _GetPythonLibraryFilename(context)) - elif Linux(): - pythonMultiarchSubdir = sysconfig.get_config_var("multiarchsubdir") - # Try multiple ways to get the python lib dir - for pythonLibDir in (sysconfig.get_config_var("LIBDIR"), - os.path.join(pythonBaseDir, "lib")): - if pythonMultiarchSubdir: - pythonLibPath = \ - os.path.join(pythonLibDir + pythonMultiarchSubdir, - _GetPythonLibraryFilename(context)) - if os.path.isfile(pythonLibPath): - break - pythonLibPath = os.path.join(pythonLibDir, - _GetPythonLibraryFilename(context)) - if os.path.isfile(pythonLibPath): - break - else: - raise RuntimeError("Platform not supported") - - return (pythonExecPath, pythonLibPath, pythonIncludeDir, pythonVersion) - -# Check the installed version of a package, using generated .version.txt file. -def CheckVersion(context, packageName, versionString): - fullVersionString = scriptVersion+":"+versionString - versionTextFilename = os.path.join(context.externalsInstDir, packageName+".version.txt") - if(not os.path.exists(versionTextFilename)): - return False - - versionTxt = pathlib.Path(versionTextFilename).read_text() - return versionTxt==fullVersionString - -# Update generated .version.txt file for a package. -def UpdateVersion(context, packageName, versionString): - if(CheckVersion(context, packageName, versionString)): - return - fullVersionString = scriptVersion+":"+versionString - versionTextFilename = os.path.join(context.externalsInstDir, packageName+".version.txt") - versionFile= open(versionTextFilename, "wt") - versionFile.write(fullVersionString) - versionFile.close() - -def GetCPUCount(): - try: - return multiprocessing.cpu_count() - except NotImplementedError: - return 1 - -def Run(cmd, logCommandOutput = True): - """ - Run the specified command in a subprocess. - """ - PrintInfo('Running "{cmd}"'.format(cmd=cmd)) - - with open("log.txt", mode="a", encoding="utf-8") as logfile: - logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M")) - logfile.write("\n") - logfile.write(cmd) - logfile.write("\n") - - # Let exceptions escape from subprocess calls -- higher level - # code will handle them. - if logCommandOutput: - p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - while True: - l = p.stdout.readline().decode(GetLocale(), 'replace') - if l: - logfile.write(l) - PrintCommandOutput(l) - elif p.poll() is not None: - break - else: - p = subprocess.Popen(shlex.split(cmd)) - p.wait() - - if p.returncode != 0: - # If verbosity >= 3, we'll have already been printing out command output - # so no reason to print the log file again. - if verbosity < 3: - with open("log.txt", "r") as logfile: - Print(logfile.read()) - raise RuntimeError("Failed to run '{cmd}'\nSee {log} for more details." - .format(cmd=cmd, log=os.path.abspath("log.txt"))) - -@contextlib.contextmanager -def CurrentWorkingDirectory(dir): - """ - Context manager that sets the current working directory to the given - directory and resets it to the original directory when closed. - """ - curdir = os.getcwd() - os.chdir(dir) - try: yield - finally: os.chdir(curdir) - -def MakeSymLink(context, src): - """ - Create a symbolic file of dest to src - """ - patternToLinkTo = src - filesToLinkTo = glob.glob(patternToLinkTo) - for linkTo in filesToLinkTo: - symlink = pathlib.Path(linkTo).with_suffix('') - if symlink.exists(): - os.remove(symlink) - PrintCommandOutput(f"Create symlink {symlink} to {linkTo}\n") - os.symlink(linkTo, symlink) - -def CopyFiles(context, src, dest, destPrefix = ''): - """ - Copy files like shutil.copy, but src may be a glob pattern. - """ - filesToCopy = glob.glob(src) - if not filesToCopy: - raise RuntimeError("File(s) to copy {src} not found".format(src=src)) - - instDestDir = os.path.join(context.externalsInstDir, destPrefix, dest) - if not os.path.isdir(instDestDir): - os.makedirs(instDestDir) - - for f in filesToCopy: - PrintCommandOutput("Copying {file} to {destDir}\n" - .format(file=f, destDir=instDestDir)) - shutil.copy(f, instDestDir) - -def CopyDirectory(context, srcDir, destDir, destPrefix = ''): - """ - Copy directory like shutil.copytree. - """ - instDestDir = os.path.join(context.externalsInstDir, destPrefix, destDir) - if os.path.isdir(instDestDir): - shutil.rmtree(instDestDir) - - PrintCommandOutput("Copying {srcDir} to {destDir}\n" - .format(srcDir=srcDir, destDir=instDestDir)) - shutil.copytree(srcDir, instDestDir) - -def FormatMultiProcs(numJobs, generator): - tag = "-j" - if generator: - if "Visual Studio" in generator: - tag = "/M:" # This will build multiple projects at once. - - return "{tag}{procs}".format(tag=tag, procs=numJobs) - -def BuildConfigs(context): - configs = [] - if context.buildDebug: - configs.append("Debug") - if context.buildRelease: - configs.append("Release") - if context.buildRelWithDebInfo : - configs.append("RelWithDebInfo") - return configs - -cmakePrefixPaths = set() - -def RunCMake(context, force, instFolder= None, extraArgs = None, configExtraArgs = None, install = True): - """ - Invoke CMake to configure, build, and install a library whose - source code is located in the current working directory. - """ - # Create a directory for out-of-source builds in the build directory - # using the name of the current working directory. - srcDir = os.getcwd() - generator = context.cmakeGenerator - - if generator is not None: - generator = '-G "{gen}"'.format(gen=generator) - elif IsVisualStudio2019OrGreater(): - generator = '-G "Visual Studio 16 2019" -A x64' - - toolset = context.cmakeToolset - if toolset is not None: - toolset = '-T "{toolset}"'.format(toolset=toolset) - - for config in BuildConfigs(context): - buildDir = os.path.join(context.buildDir, os.path.split(srcDir)[1], config) - if force and os.path.isdir(buildDir): - shutil.rmtree(buildDir) - if not os.path.isdir(buildDir): - os.makedirs(buildDir) - - subFolder = instFolder if instFolder else os.path.basename(srcDir) - instDir = os.path.join(context.externalsInstDir, subFolder) - - cmakePrefixPaths.add(instDir) - - with CurrentWorkingDirectory(buildDir): - # We use -DCMAKE_BUILD_TYPE for single-configuration generators - # (Ninja, make), and --config for multi-configuration generators - # (Visual Studio); technically we don't need BOTH at the same - # time, but specifying both is simpler than branching - Run('cmake ' - '-DCMAKE_INSTALL_PREFIX="{instDir}" ' - '-DCMAKE_PREFIX_PATH="{prefixPaths}" ' - '-DCMAKE_BUILD_TYPE={config} ' - '-DCMAKE_DEBUG_POSTFIX="d" ' - '{generator} ' - '{toolset} ' - '{extraArgs} ' - '{configExtraArgs} ' - '"{srcDir}"' - .format(instDir=instDir, - prefixPaths=';'.join(cmakePrefixPaths), - config=config, - srcDir=srcDir, - generator=(generator or ""), - toolset=(toolset or ""), - extraArgs=(" ".join(extraArgs) if extraArgs else ""), - configExtraArgs=(configExtraArgs[config] if configExtraArgs else ""))) - - Run("cmake --build . --config {config} {install} -- {multiproc}" - .format(config=config, - install=("--target install" if install else ""), - multiproc=FormatMultiProcs(context.numJobs, generator))) - -def GetCMakeVersion(): - """ - Returns the CMake version as tuple of integers (major, minor) or - (major, minor, patch) or None if an error occurred while launching cmake and - parsing its output. - """ - output_string = GetCommandOutput("cmake --version") - if not output_string: - PrintWarning("Could not determine cmake version -- please install it " - "and adjust your PATH") - return None - - # cmake reports, e.g., "... version 3.14.3" - match = re.search(r"version (\d+)\.(\d+)(\.(\d+))?", output_string) - if not match: - PrintWarning("Could not determine cmake version") - return None - - major, minor, patch_group, patch = match.groups() - if patch_group is None: - return (int(major), int(minor)) - else: - return (int(major), int(minor), int(patch)) - -def PatchFile(filename, patches, multiLineMatches=False): - """ - Applies patches to the specified file. patches is a list of tuples - (old string, new string). - """ - if multiLineMatches: - oldLines = [open(filename, 'r').read()] - else: - oldLines = open(filename, 'r').readlines() - newLines = oldLines - for (oldString, newString) in patches: - newLines = [s.replace(oldString, newString) for s in newLines] - if newLines != oldLines: - PrintInfo("Patching file {filename} (original in {oldFilename})..." - .format(filename=filename, oldFilename=filename + ".old")) - shutil.copy(filename, filename + ".old") - open(filename, 'w').writelines(newLines) - -def ApplyGitPatch(patchfile): - try: - patch = os.path.normpath(os.path.join(context.auroraSrcDir, "Scripts", "Patches", patchfile)) - PrintStatus(f"Applying {patchfile} ...") - Run(f'git apply "{patch}"') - except Exception as e: - PrintWarning(f"Failed to apply {patchfile}. Skipped\n") - - - -def DownloadFileWithUrllib(url, outputFilename): - r = urlopen(url) - with open(outputFilename, "wb") as outfile: - outfile.write(r.read()) - -def DownloadURL(url, context, force, extractDir = None, dontExtract = None, destDir = None): - """ - Download and extract the archive file at given URL to the - source directory specified in the context. - - dontExtract may be a sequence of path prefixes that will - be excluded when extracting the archive. - - Returns the absolute path to the directory where files have - been extracted. - """ - with CurrentWorkingDirectory(context.externalsSrcDir): - # Extract filename from URL and see if file already exists. - filename = url.split("/")[-1] - if force and os.path.exists(filename): - os.remove(filename) - - if os.path.exists(filename): - PrintInfo("{0} already exists, skipping download" - .format(os.path.abspath(filename))) - else: - PrintInfo("Downloading {0} to {1}" - .format(url, os.path.abspath(filename))) - - # To work around occasional hiccups with downloading from websites - # (SSL validation errors, etc.), retry a few times if we don't - # succeed in downloading the file. - maxRetries = 5 - lastError = None - - # Download to a temporary file and rename it to the expected - # filename when complete. This ensures that incomplete downloads - # will be retried if the script is run again. - tmpFilename = filename + ".tmp" - if os.path.exists(tmpFilename): - os.remove(tmpFilename) - - for i in range(maxRetries): - try: - context.downloader(url, tmpFilename) - break - except Exception as e: - PrintCommandOutput("Retrying download due to error: {err}\n" - .format(err=e)) - lastError = e - else: - errorMsg = str(lastError) - raise RuntimeError("Failed to download {url}: {err}" - .format(url=url, err=errorMsg)) - - shutil.move(tmpFilename, filename) - - # Open the archive and retrieve the name of the top-most directory. - # This assumes the archive contains a single directory with all - # of the contents beneath it, unless a specific extractDir is specified, - # which is to be used. - archive = None - rootDir = None - members = None - try: - if tarfile.is_tarfile(filename): - archive = tarfile.open(filename) - if extractDir: - rootDir = extractDir - else: - rootDir = archive.getnames()[0].split('/')[0] - if dontExtract != None: - members = (m for m in archive.getmembers() - if not any((fnmatch.fnmatch(m.name, p) - for p in dontExtract))) - elif zipfile.is_zipfile(filename): - archive = zipfile.ZipFile(filename) - if extractDir: - rootDir = extractDir - else: - rootDir = archive.namelist()[0].split('/')[0] - if dontExtract != None: - members = (m for m in archive.getnames() - if not any((fnmatch.fnmatch(m, p) - for p in dontExtract))) - else: - raise RuntimeError("unrecognized archive file type") - - with archive: - extractedPath = os.path.abspath(destDir if destDir else rootDir) - - if force and os.path.isdir(extractedPath): - shutil.rmtree(extractedPath) - - if os.path.isdir(extractedPath): - PrintInfo("Directory {0} already exists, skipping extract" - .format(extractedPath)) - else: - PrintInfo("Extracting archive to {0}".format(extractedPath)) - - # Extract to a temporary directory then move the contents - # to the expected location when complete. This ensures that - # incomplete extracts will be retried if the script is run - # again. - tmpExtractedPath = os.path.abspath("extract_dir") - if os.path.isdir(tmpExtractedPath): - shutil.rmtree(tmpExtractedPath) - - if destDir: - archive.extractall(os.path.join(tmpExtractedPath, destDir), members=members) - shutil.move(os.path.join(tmpExtractedPath, destDir), extractedPath) - else: - archive.extractall(tmpExtractedPath, members=members) - shutil.move(os.path.join(tmpExtractedPath, rootDir), extractedPath) - - if os.path.isdir(tmpExtractedPath): - shutil.rmtree(tmpExtractedPath) - - return extractedPath - except Exception as e: - # If extraction failed for whatever reason, assume the - # archive file was bad and move it aside so that re-running - # the script will try downloading and extracting again. - shutil.move(filename, filename + ".bad") - raise RuntimeError("Failed to extract archive {filename}: {err}" - .format(filename=filename, err=e)) - -def IsGitFolder(path = '.'): - return subprocess.call(['git', '-C', path, 'status'], - stderr=subprocess.STDOUT, - stdout = open(os.devnull, 'w')) == 0 - -def GitClone(url, tag, cloneDir, context): - try: - with CurrentWorkingDirectory(context.externalsSrcDir): - # TODO check if cloneDir is a cloned folder of url - if not os.path.exists(cloneDir): - Run("git clone --recurse-submodules -b {tag} {url} {folder}".format( - tag=tag, url=url, folder=cloneDir)) - elif not IsGitFolder(cloneDir): - raise RuntimeError("Failed to clone repo {url} ({tag}): non-git folder {folder} exists".format( - url=url, tag=tag, folder=cloneDir)) - return os.path.abspath(cloneDir) - except Exception as e: - raise RuntimeError("Failed to clone repo {url} ({tag}): {err}".format( - url=url, tag=tag, err=e)) - -def GitCloneSHA(url, sha, cloneDir, context): - try: - with CurrentWorkingDirectory(context.externalsSrcDir): - # TODO check if cloneDir is a cloned folder of url - if not os.path.exists(cloneDir): - Run("git clone --recurse-submodules {url} {folder}".format(url=url, folder=cloneDir)) - with CurrentWorkingDirectory(os.path.join(context.externalsSrcDir, cloneDir)): - Run("git checkout {sha}".format(sha=sha)) - elif not IsGitFolder(cloneDir): - raise RuntimeError("Failed to clone repo {url} ({tag}): non-git folder {folder} exists".format( - url=url, tag=tag, folder=cloneDir)) - return os.path.abspath(cloneDir) - except Exception as e: - raise RuntimeError("Failed to clone repo {url} ({tag}): {err}".format( - url=url, tag=tag, err=e)) - -def WriteExternalsConfig(context, externals): - win32Header = """ -# Build configurations: {buildConfiguration} -if(WIN32) - message(STATUS "Supported build configurations: {buildConfiguration}") -endif() -""".format(buildConfiguration=";".join(context.buildConfigs)) - - header = """ -# Auto-generated by installExternals.py. Any modification will be overridden -# by the next run of installExternals.py. -{win32Header} - -if(NOT DEFINED EXTERNALS_ROOT) - set(EXTERNALS_ROOT "{externalsRoot}") -endif() - -set(AURORA_DEPENDENCIES "") -""".format(externalsRoot=pathlib.Path(context.externalsInstDir).as_posix(), - win32Header=win32Header if Windows() else "") - - package = """ -if(NOT DEFINED {packageName}_ROOT) - set({packageName}_ROOT "${{EXTERNALS_ROOT}}/{installFolder}") -endif() -list(APPEND AURORA_DEPENDENCIES "${{{packageName}_ROOT}}") -# find_package_verbose({packageName}) -""" - packages = "" - for ext in externals: - packages += package.format(packageName=ext.packageName, installFolder=ext.installFolder) - - externalConfig = os.path.join(context.auroraSrcDir, "Scripts", "cmake", "externalsConfig.cmake") - with open(externalConfig, "w") as f: - f.write(header) - f.write(packages) - - ############################################################ -# External dependencies required by Aurora +# Class representing an external dependency. +# Each instance of the class represents a dependency, and is added to the global dependency list in the constructor, AllDependencies = list() AllDependenciesByName = dict() @@ -669,7 +66,6 @@ def __init__(self, name, packageName, installer, versionString, *files): self.installFolder = name self.versionString = versionString self.filesToCheck = files - AllDependencies.append(self) AllDependenciesByName.setdefault(name.lower(), self) @@ -685,6 +81,10 @@ def IsUpToDate(self, context): return False return CheckVersion(context, self.packageName, self.versionString) + +############################################################ +# External dependencies required by Aurora + ############################################################ # zlib @@ -694,7 +94,7 @@ def IsUpToDate(self, context): def InstallZlib(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(ZLIB_URL, context, force)): - RunCMake(context, force, ZLIB_INSTALL_FOLDER, buildArgs) + RunCMake(context, True, ZLIB_INSTALL_FOLDER, buildArgs) ZLIB = Dependency(ZLIB_INSTALL_FOLDER, ZLIB_PACKAGE_NAME, InstallZlib, ZLIB_URL, "include/zlib.h") @@ -702,6 +102,8 @@ def InstallZlib(context, force, buildArgs): # boost BOOST_URL = "https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.gz" +# Use a sub-version in the version string to force reinstallation, even if 1.78.0 installed. +BOOST_VERSION_STRING = BOOST_URL+".a" if Linux(): BOOST_VERSION_FILE = "include/boost/version.hpp" @@ -730,8 +132,23 @@ def InstallBoost_Helper(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(BOOST_URL, context, force, dontExtract=dontExtract)): + + # Remove the install folder if it exists. + instFolder = os.path.join(context.externalsInstDir, BOOST_INSTALL_FOLDER) + if(os.path.isdir(instFolder)): + PrintInfo("Removing existing install folder:"+instFolder) + shutil.rmtree(instFolder) + + # Set the bootstrap toolset + bsToolset = "" + if Windows(): + if context.cmakeToolset == "v143" or IsVisualStudio2022OrGreater(): + bsToolset = "vc143" + elif context.cmakeToolset == "v142" or IsVisualStudio2019OrGreater(): + bsToolset = "vc142" + bootstrap = "bootstrap.bat" if Windows() else "./bootstrap.sh" - Run(f'{bootstrap}') + Run(f'{bootstrap} {bsToolset}') # b2 supports at most -j64 and will error if given a higher value. numProc = min(64, context.numJobs) @@ -782,10 +199,10 @@ def InstallBoost_Helper(context, force, buildArgs): if Windows(): # toolset parameter for Visual Studio documented here: # https://github.com/boostorg/build/blob/develop/src/tools/msvc.jam - if context.cmakeToolset == "v142" or IsVisualStudio2019OrGreater(): - b2Settings.append("toolset=msvc-14.2") - # elif IsVisualStudio2022OrGreater(): - # b2Settings.append("toolset=msvc-14.x") + if context.cmakeToolset == "v143" or IsVisualStudio2022OrGreater(): + b2Settings.append("toolset=msvc-14.3") + elif context.cmakeToolset == "v142" or IsVisualStudio2019OrGreater(): + b2Settings.append("toolset=msvc-14.2") else: b2Settings.append("toolset=msvc-14.2") @@ -798,16 +215,16 @@ def InstallBoost_Helper(context, force, buildArgs): b2ExtraSettings = [] if context.buildDebug: b2ExtraSettings.append('--prefix="{}" variant=debug --debug-configuration'.format( - os.path.join(context.externalsInstDir, BOOST_INSTALL_FOLDER))) + instFolder)) if context.buildRelease: b2ExtraSettings.append('--prefix="{}" variant=release'.format( - os.path.join(context.externalsInstDir, BOOST_INSTALL_FOLDER))) + instFolder)) if context.buildRelWithDebInfo: b2ExtraSettings.append('--prefix="{}" variant=profile'.format( - os.path.join(context.externalsInstDir, BOOST_INSTALL_FOLDER))) - + instFolder)) for extraSettings in b2ExtraSettings: b2Settings.append(extraSettings) + # Build and install Boost Run('{b2} {options} install'.format(b2=b2, options=" ".join(b2Settings))) b2Settings.pop() @@ -826,7 +243,7 @@ def InstallBoost(context, force, buildArgs): except: pass raise -BOOST = Dependency(BOOST_INSTALL_FOLDER, BOOST_PACKAGE_NAME, InstallBoost, BOOST_URL, BOOST_VERSION_FILE) +BOOST = Dependency(BOOST_INSTALL_FOLDER, BOOST_PACKAGE_NAME, InstallBoost, BOOST_VERSION_STRING, BOOST_VERSION_FILE) ############################################################ # Intel TBB @@ -889,7 +306,7 @@ def InstallTBB_Linux(context, force, buildArgs): def InstallJPEG(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(JPEG_URL, context, force)): - RunCMake(context, force, JPEG_INSTALL_FOLDER, buildArgs) + RunCMake(context, True, JPEG_INSTALL_FOLDER, buildArgs) JPEG = Dependency(JPEG_INSTALL_FOLDER, JPEG_PACKAGE_NAME, InstallJPEG, JPEG_URL, "include/jpeglib.h") @@ -923,7 +340,7 @@ def InstallTIFF(context, force, buildArgs): else: extraArgs = [] extraArgs += buildArgs - RunCMake(context, force, TIFF_INSTALL_FOLDER, extraArgs) + RunCMake(context, True, TIFF_INSTALL_FOLDER, extraArgs) TIFF = Dependency(TIFF_INSTALL_FOLDER, TIFF_PACKAGE_NAME, InstallTIFF, TIFF_URL, "include/tiff.h") @@ -936,7 +353,7 @@ def InstallTIFF(context, force, buildArgs): def InstallPNG(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(PNG_URL, context, force)): - RunCMake(context, force, PNG_INSTALL_FOLDER, buildArgs) + RunCMake(context, True, PNG_INSTALL_FOLDER, buildArgs) PNG = Dependency(PNG_INSTALL_FOLDER, PNG_PACKAGE_NAME, InstallPNG, PNG_URL, "include/png.h") @@ -978,7 +395,7 @@ def InstallSTB(context, force, buildArgs): def InstallTinyGLTF(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(TinyGLTF_URL, context, force)): - RunCMake(context, force, TinyGLTF_INSTALL_FOLDER, buildArgs) + RunCMake(context, True, TinyGLTF_INSTALL_FOLDER, buildArgs) TINYGLTF = Dependency(TinyGLTF_INSTALL_FOLDER, TinyGLTF_PACKAGE_NAME, InstallTinyGLTF, TinyGLTF_URL, "include/tiny_gltf.h") @@ -991,10 +408,65 @@ def InstallTinyGLTF(context, force, buildArgs): def InstallTinyObjLoader(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(TinyObjLoader_URL, context, force)): - RunCMake(context, force, TinyObjLoader_INSTALL_FOLDER, buildArgs) + RunCMake(context, True, TinyObjLoader_INSTALL_FOLDER, buildArgs) TINYOBJLOADER = Dependency(TinyObjLoader_INSTALL_FOLDER, TinyObjLoader_PACKAGE_NAME, InstallTinyObjLoader, TinyObjLoader_URL, "include/tiny_obj_loader.h") +############################################################ +# TinyEXR + +TinyEXR_URL = "https://github.com/syoyo/tinyexr/archive/refs/tags/v1.0.2.zip" +TinyEXR_INSTALL_FOLDER = "tinyexr" +TinyEXR_PACKAGE_NAME = "tinyexr" +TinyEXR_INSTALL_FOLDER = "tinyexr" + +def InstallTinyEXR(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(TinyEXR_URL, context, force)): + CopyFiles(context, "tinyexr.h", "include", TinyEXR_INSTALL_FOLDER) + +TINYEXR = Dependency(TinyEXR_INSTALL_FOLDER, TinyEXR_PACKAGE_NAME, InstallTinyEXR, TinyEXR_URL, "include/tinyexr.h") + +############################################################ +# miniz + +miniz_URL = "https://github.com/richgel999/miniz/archive/refs/tags/3.0.2.zip" + +miniz_INSTALL_FOLDER = "miniz" +miniz_PACKAGE_NAME = "miniz" + +def InstallMiniZ(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(miniz_URL, context, force)): + extraArgs = ['-DCMAKE_POSITION_INDEPENDENT_CODE=ON'] + + # Add on any user-specified extra arguments. + extraArgs += buildArgs + RunCMake(context, True, miniz_INSTALL_FOLDER, extraArgs) + +MINIZ = Dependency(miniz_INSTALL_FOLDER, miniz_PACKAGE_NAME, InstallMiniZ, miniz_URL, "include/miniz/miniz_export.h") + +############################################################ +# uriparser + +URIPARSER_URL = "https://codeload.github.com/uriparser/uriparser/tar.gz/refs/tags/uriparser-0.9.7" + +URIPARSER_INSTALL_FOLDER = "uriparser" +URIPARSER_PACKAGE_NAME = "uriparser" + +def InstallURIParser(context, force, buildArgs): + with CurrentWorkingDirectory(DownloadURL(URIPARSER_URL, context, force)): + + extraArgs = ['-DURIPARSER_BUILD_TESTS=OFF ', + '-DURIPARSER_BUILD_DOCS=OFF ', + '-DBUILD_SHARED_LIBS=OFF ', + '-DCMAKE_POSITION_INDEPENDENT_CODE=ON' + ] + + # Add on any user-specified extra arguments. + extraArgs += buildArgs + RunCMake(context, True, URIPARSER_INSTALL_FOLDER, extraArgs) + +URIPARSER = Dependency(URIPARSER_INSTALL_FOLDER, URIPARSER_PACKAGE_NAME, InstallURIParser, URIPARSER_URL, "bin/uriparse.exe") + ############################################################ # IlmBase/OpenEXR @@ -1020,7 +492,7 @@ def InstallOpenEXR(context, force, buildArgs): # Add on any user-specified extra arguments. extraArgs += buildArgs - RunCMake(context, force, OPENEXR_INSTALL_FOLDER, extraArgs) + RunCMake(context, True, OPENEXR_INSTALL_FOLDER, extraArgs) OPENEXR = Dependency(OPENEXR_INSTALL_FOLDER, OPENEXR_PACKAGE_NAME, InstallOpenEXR, OPENEXR_URL, "include/OpenEXR/ImfVersion.h") @@ -1033,7 +505,7 @@ def InstallOpenEXR(context, force, buildArgs): def InstallOpenImageIO(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(OIIO_URL, context, force)): - ApplyGitPatch("OpenImageIO.patch") + ApplyGitPatch(context, "OpenImageIO.patch") extraArgs = ['-DOIIO_BUILD_TOOLS=OFF', '-DOIIO_BUILD_TESTS=OFF', @@ -1065,7 +537,7 @@ def InstallOpenImageIO(context, force, buildArgs): "RelWithDebInfo": '-DTBB_USE_DEBUG_BUILD=OFF', } - RunCMake(context, force, OIIO_INSTALL_FOLDER, extraArgs, configExtraArgs=tbbConfigs) + RunCMake(context, True, OIIO_INSTALL_FOLDER, extraArgs, configExtraArgs=tbbConfigs) OPENIMAGEIO = Dependency(OIIO_INSTALL_FOLDER, OIIO_PACKAGE_NAME, InstallOpenImageIO, OIIO_URL, "include/OpenImageIO/oiioversion.h") @@ -1114,7 +586,7 @@ def InstallOpenSubdiv(context, force, buildArgs): oldNumJobs = context.numJobs try: - RunCMake(context, force, OPENSUBDIV_INSTALL_FOLDER, extraArgs) + RunCMake(context, True, OPENSUBDIV_INSTALL_FOLDER, extraArgs) finally: context.cmakeGenerator = oldGenerator context.numJobs = oldNumJobs @@ -1134,7 +606,7 @@ def InstallMaterialX(context, force, buildArgs): cmakeOptions = ['-DMATERIALX_BUILD_SHARED_LIBS=ON', '-DMATERIALX_BUILD_TESTS=OFF'] cmakeOptions += buildArgs - RunCMake(context, force, MATERIALX_INSTALL_FOLDER, cmakeOptions) + RunCMake(context, True, MATERIALX_INSTALL_FOLDER, cmakeOptions) MATERIALX = Dependency(MATERIALX_INSTALL_FOLDER, MATERIALX_PACKAGE_NAME, InstallMaterialX, MATERIALX_URL, "include/MaterialXCore/Library.h") @@ -1157,7 +629,7 @@ def InstallUSD(context, force, buildArgs): # with CurrentWorkingDirectory(GitClone(USD_URL, USD_TAG, USD_FOLDER, context)): # We need to apply patch to make USD build with our externals configuration - ApplyGitPatch("USD.patch") + ApplyGitPatch(context, "USD.patch") extraArgs = [] @@ -1181,7 +653,7 @@ def InstallUSD(context, force, buildArgs): extraArgs.append('-DPXR_ENABLE_OPENVDB_SUPPORT=OFF') extraArgs.append('-DPXR_BUILD_EMBREE_PLUGIN=OFF') extraArgs.append('-DPXR_BUILD_PRMAN_PLUGIN=OFF') - extraArgs.append('-DPXR_BUILD_OPENIMAGEIO_PLUGIN=OFF') + extraArgs.append('-DPXR_BUILD_OPENIMAGEIO_PLUGIN=ON') extraArgs.append('-DPXR_BUILD_OPENCOLORIO_PLUGIN=OFF') extraArgs.append('-DPXR_BUILD_USD_IMAGING=ON') @@ -1223,12 +695,14 @@ def InstallUSD(context, force, buildArgs): extraArgs += buildArgs + # extraArgs.append('-DCMAKE_FIND_PACKAGE_PREFER_CONFIG=TRUE') + tbbConfigs = { "Debug": '-DTBB_USE_DEBUG_BUILD=ON', "Release": '-DTBB_USE_DEBUG_BUILD=OFF', "RelWithDebInfo": '-DTBB_USE_DEBUG_BUILD=OFF', } - RunCMake(context, force, USD_INSTALL_FOLDER, extraArgs, configExtraArgs=tbbConfigs) + RunCMake(context, True, USD_INSTALL_FOLDER, extraArgs, configExtraArgs=tbbConfigs) USD = Dependency(USD_INSTALL_FOLDER, USD_PACKAGE_NAME, InstallUSD, USD_URL, "include/pxr/pxr.h") @@ -1259,7 +733,7 @@ def InstallSlang(context, force, buildArgs): def InstallNRD(context, force, buildArgs): NRD_FOLDER = "NRD-"+NRD_TAG with CurrentWorkingDirectory(GitClone(NRD_URL, NRD_TAG, NRD_FOLDER, context)): - RunCMake(context, force, NRD_INSTALL_FOLDER, buildArgs, install=False) + RunCMake(context, True, NRD_INSTALL_FOLDER, buildArgs, install=False) CopyDirectory(context, "Include", "include", NRD_INSTALL_FOLDER) CopyDirectory(context, "Integration", "Integration", NRD_INSTALL_FOLDER) @@ -1332,7 +806,7 @@ def InstallGLFW(context, force, buildArgs): cmakeOptions = ['-DGLFW_BUILD_EXAMPLES=OFF', '-DGLFW_BUILD_TESTS=OFF', '-DGLFW_BUILD_DOCS=OFF'] cmakeOptions += buildArgs - RunCMake(context, force, GLFW_INSTALL_FOLDER, cmakeOptions) + RunCMake(context, True, GLFW_INSTALL_FOLDER, cmakeOptions) GLFW = Dependency(GLFW_INSTALL_FOLDER, GLFW_PACKAGE_NAME, InstallGLFW, GLFW_URL, "include/GLFW/glfw3.h") @@ -1345,7 +819,7 @@ def InstallGLFW(context, force, buildArgs): def InstallCXXOPTS(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(CXXOPTS_URL, context, force)): - RunCMake(context, force, CXXOPTS_INSTALL_FOLDER, buildArgs) + RunCMake(context, True, CXXOPTS_INSTALL_FOLDER, buildArgs) CXXOPTS = Dependency(CXXOPTS_INSTALL_FOLDER, CXXOPTS_PACKAGE_NAME, InstallCXXOPTS, CXXOPTS_URL, "include/cxxopts.hpp") @@ -1359,7 +833,7 @@ def InstallCXXOPTS(context, force, buildArgs): def InstallGTEST(context, force, buildArgs): with CurrentWorkingDirectory(DownloadURL(GTEST_URL, context, force)): extraArgs = [*buildArgs, '-Dgtest_force_shared_crt=ON'] - RunCMake(context, force, GTEST_INSTALL_FOLDER, extraArgs) + RunCMake(context, True, GTEST_INSTALL_FOLDER, extraArgs) GTEST = Dependency(GTEST_INSTALL_FOLDER, GTEST_PACKAGE_NAME, InstallGTEST, GTEST_URL, "include/gtest/gtest.h") @@ -1537,6 +1011,8 @@ def __init__(self, args): self.forceBuildAll = args.force_all self.forceBuild = [dep.lower() for dep in args.force_build] + self.cmakePrefixPaths = set() + def GetBuildArguments(self, dep): return self.buildArgs.get(dep.name.lower(), []) @@ -1549,7 +1025,7 @@ def ForceBuildDependency(self, dep): PrintError(str(e)) sys.exit(1) -verbosity = args.verbosity +SetVerbosity(args.verbosity) # Determine list of external libraries required (directly or indirectly) # by Aurora @@ -1559,8 +1035,11 @@ def ForceBuildDependency(self, dep): PNG, GLM, STB, + MINIZ, + URIPARSER, TINYGLTF, TINYOBJLOADER, + TINYEXR, BOOST, TBB, OPENEXR, @@ -1599,9 +1078,8 @@ def ForceBuildDependency(self, dep): excludes = [ZLIB, JPEG, TIFF, PNG, GLM, GLEW, GLFW, GTEST] for lib in excludes: requiredDependencies.remove(lib) - print(requiredDependencies) -cmakePrefixPaths = set(map(lambda lib: os.path.join(context.externalsInstDir, lib.installFolder), requiredDependencies)) +context.cmakePrefixPaths = set(map(lambda lib: os.path.join(context.externalsInstDir, lib.installFolder), requiredDependencies)) dependenciesToBuild = [] for dep in requiredDependencies: @@ -1657,7 +1135,8 @@ def _JoinVersion(v): summaryMsg += """ Variant {buildVariant} - Dependencies {dependencies} + All dependencies {allDependencies} + Dependencies to install {dependencies} """ if context.buildArgs: @@ -1684,6 +1163,8 @@ def FormatBuildArguments(buildArgs): else context.cmakeGenerator), cmakeToolset=("Default" if not context.cmakeToolset else context.cmakeToolset), + allDependencies=("None" if not AllDependencies else + ", ".join([d.name for d in AllDependencies])), dependencies=("None" if not dependenciesToBuild else ", ".join([d.name for d in dependenciesToBuild])), buildArgs=FormatBuildArguments(context.buildArgs), @@ -1732,7 +1213,7 @@ def FormatBuildArguments(buildArgs): buildStepsMsg = """ Success! -To use the external libraries, please configure Aurora build in "x64 Native Tools Command Prompt for VS 2019" with: +To use the external libraries, please configure Aurora build in "x64 Native Tools Command Prompt" with: cmake -S . -B Build [-D EXTERNALS_ROOT={externalsDir}] cmake --build Build --config {buildConfigs} """.format(externalsDir=context.externalsInstDir, diff --git a/Scripts/installExternalsFunctions.py b/Scripts/installExternalsFunctions.py new file mode 100644 index 0000000..4a4003d --- /dev/null +++ b/Scripts/installExternalsFunctions.py @@ -0,0 +1,663 @@ +# +# Copyright 2017 Pixar +# Copyright 2023 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +from __future__ import print_function + +import argparse +import contextlib +import datetime +import fnmatch +import glob +import locale +import multiprocessing +import os +import platform +import re +import shlex +import shutil +import subprocess +import sys +import sysconfig +import tarfile +import zipfile +import pathlib +from shutil import which +from urllib.request import urlopen + +# Helpers for printing output +verbosity = 1 + +# Script version, incrementing this will force re-installation of all packages. +scriptVersion = "00001" + +# Set the global verbosity variable (integer in range 1-3, with 3 being most verbose) +def SetVerbosity(val): + global verbosity + verbosity = val + +def Print(msg): + if verbosity > 0: + print(msg) + +def PrintWarning(warning): + if verbosity > 0: + print("WARNING:", warning) + +def PrintStatus(status): + if verbosity >= 1: + print("STATUS:", status) + +def PrintInfo(info): + if verbosity >= 2: + print("INFO:", info) + +def PrintCommandOutput(output): + if verbosity >= 3: + sys.stdout.write(output) + +def PrintError(error): + if verbosity >= 3 and sys.exc_info()[1] is not None: + import traceback + traceback.print_exc() + print ("ERROR:", error) + +# Helpers for determining platform +def Windows(): + return platform.system() == "Windows" +def Linux(): + return platform.system() == "Linux" + +def GetLocale(): + return sys.stdout.encoding or locale.getdefaultlocale()[1] or "UTF-8" + +def GetCommandOutput(command): + """Executes the specified command and returns output or None.""" + try: + return subprocess.check_output(shlex.split(command), + stderr=subprocess.STDOUT).decode(GetLocale(), 'replace').strip() + except subprocess.CalledProcessError: + pass + return None + +def GetVisualStudioCompilerAndVersion(): + """ + Returns a tuple containing the path to the Visual Studio compiler + and a tuple for its version, e.g. (14, 0). If the compiler is not found + or version number cannot be determined, returns None. + """ + if not Windows(): + return None + + msvcCompiler = which('cl') + if msvcCompiler: + # VisualStudioVersion environment variable should be set by the + # Visual Studio Command Prompt. + match = re.search(r"(\d+)\.(\d+)", + os.environ.get("VisualStudioVersion", "")) + if match: + return (msvcCompiler, tuple(int(v) for v in match.groups())) + return None + +def IsVisualStudioVersionOrGreater(desiredVersion): + if not Windows(): + return False + + msvcCompilerAndVersion = GetVisualStudioCompilerAndVersion() + if msvcCompilerAndVersion: + _, version = msvcCompilerAndVersion + return version >= desiredVersion + return False + +def IsVisualStudio2019OrGreater(): + VISUAL_STUDIO_2019_VERSION = (16, 0) + return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2019_VERSION) + +def IsVisualStudio2022OrGreater(): + VISUAL_STUDIO_2022_VERSION = (17, 0) + return IsVisualStudioVersionOrGreater(VISUAL_STUDIO_2022_VERSION) + +def GetPythonInfo(context): + """Returns a tuple containing the path to the Python executable, shared + library, and include directory corresponding to the version of Python + currently running. Returns None if any path could not be determined. + + This function is used to extract build information from the Python + interpreter used to launch this script. This information is used + in the Boost and USD builds. By taking this approach we can support + having USD builds for different Python versions built on the same + machine. This is very useful, especially when developers have multiple + versions installed on their machine, which is quite common now with + Python2 and Python3 co-existing. + """ + + # If we were given build python info then just use it. + if context.build_python_info: + return (context.build_python_info['PYTHON_EXECUTABLE'], + context.build_python_info['PYTHON_LIBRARY'], + context.build_python_info['PYTHON_INCLUDE_DIR'], + context.build_python_info['PYTHON_VERSION']) + + # First we extract the information that can be uniformly dealt with across + # the platforms: + pythonExecPath = sys.executable + pythonVersion = sysconfig.get_config_var("py_version_short") # "2.7" + + # Lib path is unfortunately special for each platform and there is no + # config_var for it. But we can deduce it for each platform, and this + # logic works for any Python version. + def _GetPythonLibraryFilename(context): + if Windows(): + return "python{version}.lib".format( + version=sysconfig.get_config_var("py_version_nodot")) + elif Linux(): + return sysconfig.get_config_var("LDLIBRARY") + else: + raise RuntimeError("Platform not supported") + + pythonIncludeDir = sysconfig.get_path("include") + if not pythonIncludeDir or not os.path.isdir(pythonIncludeDir): + # as a backup, and for legacy reasons - not preferred because + # it may be baked at build time + pythonIncludeDir = sysconfig.get_config_var("INCLUDEPY") + + # if in a venv, installed_base will be the "original" python, + # which is where the libs are ("base" will be the venv dir) + pythonBaseDir = sysconfig.get_config_var("installed_base") + if not pythonBaseDir or not os.path.isdir(pythonBaseDir): + # for python-2.7 + pythonBaseDir = sysconfig.get_config_var("base") + + if Windows(): + pythonLibPath = os.path.join(pythonBaseDir, "libs", + _GetPythonLibraryFilename(context)) + elif Linux(): + pythonMultiarchSubdir = sysconfig.get_config_var("multiarchsubdir") + # Try multiple ways to get the python lib dir + for pythonLibDir in (sysconfig.get_config_var("LIBDIR"), + os.path.join(pythonBaseDir, "lib")): + if pythonMultiarchSubdir: + pythonLibPath = \ + os.path.join(pythonLibDir + pythonMultiarchSubdir, + _GetPythonLibraryFilename(context)) + if os.path.isfile(pythonLibPath): + break + pythonLibPath = os.path.join(pythonLibDir, + _GetPythonLibraryFilename(context)) + if os.path.isfile(pythonLibPath): + break + else: + raise RuntimeError("Platform not supported") + + return (pythonExecPath, pythonLibPath, pythonIncludeDir, pythonVersion) + +# Check the installed version of a package, using generated .version.txt file. +def CheckVersion(context, packageName, versionString): + fullVersionString = scriptVersion+":"+versionString + versionTextFilename = os.path.join(context.externalsInstDir, packageName+".version.txt") + if(not os.path.exists(versionTextFilename)): + return False + + versionTxt = pathlib.Path(versionTextFilename).read_text() + return versionTxt==fullVersionString + +# Update generated .version.txt file for a package. +def UpdateVersion(context, packageName, versionString): + if(CheckVersion(context, packageName, versionString)): + return + fullVersionString = scriptVersion+":"+versionString + versionTextFilename = os.path.join(context.externalsInstDir, packageName+".version.txt") + versionFile= open(versionTextFilename, "wt") + versionFile.write(fullVersionString) + versionFile.close() + +def GetCPUCount(): + try: + return multiprocessing.cpu_count() + except NotImplementedError: + return 1 + +def Run(cmd, logCommandOutput = True): + """ + Run the specified command in a subprocess. + """ + PrintInfo('Running "{cmd}"'.format(cmd=cmd)) + + with open("log.txt", mode="a", encoding="utf-8") as logfile: + logfile.write(datetime.datetime.now().strftime("%Y-%m-%d %H:%M")) + logfile.write("\n") + logfile.write(cmd) + logfile.write("\n") + + # Let exceptions escape from subprocess calls -- higher level + # code will handle them. + if logCommandOutput: + p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + while True: + l = p.stdout.readline().decode(GetLocale(), 'replace') + if l: + logfile.write(l) + PrintCommandOutput(l) + elif p.poll() is not None: + break + else: + p = subprocess.Popen(shlex.split(cmd)) + p.wait() + + if p.returncode != 0: + # If verbosity >= 3, we'll have already been printing out command output + # so no reason to print the log file again. + if verbosity < 3: + with open("log.txt", "r") as logfile: + Print(logfile.read()) + raise RuntimeError("Failed to run '{cmd}'\nSee {log} for more details." + .format(cmd=cmd, log=os.path.abspath("log.txt"))) + +@contextlib.contextmanager +def CurrentWorkingDirectory(dir): + """ + Context manager that sets the current working directory to the given + directory and resets it to the original directory when closed. + """ + curdir = os.getcwd() + os.chdir(dir) + try: yield + finally: os.chdir(curdir) + +def MakeSymLink(context, src): + """ + Create a symbolic file of dest to src + """ + patternToLinkTo = src + filesToLinkTo = glob.glob(patternToLinkTo) + for linkTo in filesToLinkTo: + symlink = pathlib.Path(linkTo).with_suffix('') + if symlink.exists(): + os.remove(symlink) + PrintCommandOutput(f"Create symlink {symlink} to {linkTo}\n") + os.symlink(linkTo, symlink) + +def CopyFiles(context, src, dest, destPrefix = ''): + """ + Copy files like shutil.copy, but src may be a glob pattern. + """ + filesToCopy = glob.glob(src) + if not filesToCopy: + raise RuntimeError("File(s) to copy {src} not found".format(src=src)) + + instDestDir = os.path.join(context.externalsInstDir, destPrefix, dest) + if not os.path.isdir(instDestDir): + os.makedirs(instDestDir) + + for f in filesToCopy: + PrintCommandOutput("Copying {file} to {destDir}\n" + .format(file=f, destDir=instDestDir)) + shutil.copy(f, instDestDir) + +def CopyDirectory(context, srcDir, destDir, destPrefix = ''): + """ + Copy directory like shutil.copytree. + """ + instDestDir = os.path.join(context.externalsInstDir, destPrefix, destDir) + if os.path.isdir(instDestDir): + shutil.rmtree(instDestDir) + + PrintCommandOutput("Copying {srcDir} to {destDir}\n" + .format(srcDir=srcDir, destDir=instDestDir)) + shutil.copytree(srcDir, instDestDir) + +def FormatMultiProcs(numJobs, generator): + tag = "-j" + if generator: + if "Visual Studio" in generator: + tag = "/M:" # This will build multiple projects at once. + + return "{tag}{procs}".format(tag=tag, procs=numJobs) + +def BuildConfigs(context): + configs = [] + if context.buildDebug: + configs.append("Debug") + if context.buildRelease: + configs.append("Release") + if context.buildRelWithDebInfo : + configs.append("RelWithDebInfo") + return configs + +def RunCMake(context, clean, instFolder= None, extraArgs = None, configExtraArgs = None, install = True): + """ + Invoke CMake to configure, build, and install a library whose + source code is located in the current working directory. + """ + # Create a directory for out-of-source builds in the build directory + # using the name of the current working directory. + srcDir = os.getcwd() + generator = context.cmakeGenerator + + if generator is not None: + generator = '-G "{gen}"'.format(gen=generator) + elif IsVisualStudio2022OrGreater(): + generator = '-G "Visual Studio 17 2022" -A x64' + elif IsVisualStudio2019OrGreater(): + generator = '-G "Visual Studio 16 2019" -A x64' + + toolset = context.cmakeToolset + if toolset is not None: + toolset = '-T "{toolset}"'.format(toolset=toolset) + + for config in BuildConfigs(context): + buildDir = os.path.join(context.buildDir, os.path.split(srcDir)[1], config) + if clean and os.path.isdir(buildDir): + shutil.rmtree(buildDir) + if not os.path.isdir(buildDir): + os.makedirs(buildDir) + + subFolder = instFolder if instFolder else os.path.basename(srcDir) + instDir = os.path.join(context.externalsInstDir, subFolder) + + context.cmakePrefixPaths.add(instDir) + + with CurrentWorkingDirectory(buildDir): + # We use -DCMAKE_BUILD_TYPE for single-configuration generators + # (Ninja, make), and --config for multi-configuration generators + # (Visual Studio); technically we don't need BOTH at the same + # time, but specifying both is simpler than branching + Run('cmake ' + '-DCMAKE_INSTALL_PREFIX="{instDir}" ' + '-DCMAKE_PREFIX_PATH="{prefixPaths}" ' + '-DCMAKE_BUILD_TYPE={config} ' + '-DCMAKE_DEBUG_POSTFIX="d" ' + '{generator} ' + '{toolset} ' + '{extraArgs} ' + '{configExtraArgs} ' + '"{srcDir}"' + .format(instDir=instDir, + prefixPaths=';'.join(context.cmakePrefixPaths), + config=config, + srcDir=srcDir, + generator=(generator or ""), + toolset=(toolset or ""), + extraArgs=(" ".join(extraArgs) if extraArgs else ""), + configExtraArgs=(configExtraArgs[config] if configExtraArgs else ""))) + + Run("cmake --build . --config {config} {install} -- {multiproc}" + .format(config=config, + install=("--target install" if install else ""), + multiproc=FormatMultiProcs(context.numJobs, generator))) + +def GetCMakeVersion(): + """ + Returns the CMake version as tuple of integers (major, minor) or + (major, minor, patch) or None if an error occurred while launching cmake and + parsing its output. + """ + output_string = GetCommandOutput("cmake --version") + if not output_string: + PrintWarning("Could not determine cmake version -- please install it " + "and adjust your PATH") + return None + + # cmake reports, e.g., "... version 3.14.3" + match = re.search(r"version (\d+)\.(\d+)(\.(\d+))?", output_string) + if not match: + PrintWarning("Could not determine cmake version") + return None + + major, minor, patch_group, patch = match.groups() + if patch_group is None: + return (int(major), int(minor)) + else: + return (int(major), int(minor), int(patch)) + +def PatchFile(filename, patches, multiLineMatches=False): + """ + Applies patches to the specified file. patches is a list of tuples + (old string, new string). + """ + if multiLineMatches: + oldLines = [open(filename, 'r').read()] + else: + oldLines = open(filename, 'r').readlines() + newLines = oldLines + for (oldString, newString) in patches: + newLines = [s.replace(oldString, newString) for s in newLines] + if newLines != oldLines: + PrintInfo("Patching file {filename} (original in {oldFilename})..." + .format(filename=filename, oldFilename=filename + ".old")) + shutil.copy(filename, filename + ".old") + open(filename, 'w').writelines(newLines) + +def ApplyGitPatch(context, patchfile): + try: + patch = os.path.normpath(os.path.join(context.auroraSrcDir, "Scripts", "Patches", patchfile)) + PrintStatus(f" Applying {patchfile} ...") + Run(f'git apply "{patch}"') + PrintStatus(f" Done") + except Exception as e: + PrintWarning(f"Failed to apply {patchfile}. Skipped\n") + + + +def DownloadFileWithUrllib(url, outputFilename): + r = urlopen(url) + with open(outputFilename, "wb") as outfile: + outfile.write(r.read()) + +def DownloadURL(url, context, force, extractDir = None, dontExtract = None, destDir = None): + """ + Download and extract the archive file at given URL to the + source directory specified in the context. + + dontExtract may be a sequence of path prefixes that will + be excluded when extracting the archive. + + Returns the absolute path to the directory where files have + been extracted. + """ + with CurrentWorkingDirectory(context.externalsSrcDir): + # Extract filename from URL and see if file already exists. + filename = url.split("/")[-1] + if force and os.path.exists(filename): + os.remove(filename) + + if os.path.exists(filename): + PrintInfo("{0} already exists, skipping download" + .format(os.path.abspath(filename))) + else: + PrintInfo("Downloading {0} to {1}" + .format(url, os.path.abspath(filename))) + + # To work around occasional hiccups with downloading from websites + # (SSL validation errors, etc.), retry a few times if we don't + # succeed in downloading the file. + maxRetries = 5 + lastError = None + + # Download to a temporary file and rename it to the expected + # filename when complete. This ensures that incomplete downloads + # will be retried if the script is run again. + tmpFilename = filename + ".tmp" + if os.path.exists(tmpFilename): + os.remove(tmpFilename) + + for i in range(maxRetries): + try: + context.downloader(url, tmpFilename) + break + except Exception as e: + PrintCommandOutput("Retrying download due to error: {err}\n" + .format(err=e)) + lastError = e + else: + errorMsg = str(lastError) + raise RuntimeError("Failed to download {url}: {err}" + .format(url=url, err=errorMsg)) + + shutil.move(tmpFilename, filename) + + # Open the archive and retrieve the name of the top-most directory. + # This assumes the archive contains a single directory with all + # of the contents beneath it, unless a specific extractDir is specified, + # which is to be used. + archive = None + rootDir = None + members = None + try: + if tarfile.is_tarfile(filename): + archive = tarfile.open(filename) + if extractDir: + rootDir = extractDir + else: + rootDir = archive.getnames()[0].split('/')[0] + if dontExtract != None: + members = (m for m in archive.getmembers() + if not any((fnmatch.fnmatch(m.name, p) + for p in dontExtract))) + elif zipfile.is_zipfile(filename): + archive = zipfile.ZipFile(filename) + if extractDir: + rootDir = extractDir + else: + rootDir = archive.namelist()[0].split('/')[0] + if dontExtract != None: + members = (m for m in archive.getnames() + if not any((fnmatch.fnmatch(m, p) + for p in dontExtract))) + else: + raise RuntimeError("unrecognized archive file type") + + with archive: + extractedPath = os.path.abspath(destDir if destDir else rootDir) + + if force and os.path.isdir(extractedPath): + shutil.rmtree(extractedPath) + + if os.path.isdir(extractedPath): + PrintInfo("Directory {0} already exists, skipping extract" + .format(extractedPath)) + else: + PrintInfo("Extracting archive to {0}".format(extractedPath)) + + # Extract to a temporary directory then move the contents + # to the expected location when complete. This ensures that + # incomplete extracts will be retried if the script is run + # again. + tmpExtractedPath = os.path.abspath("extract_dir") + if os.path.isdir(tmpExtractedPath): + shutil.rmtree(tmpExtractedPath) + + if destDir: + archive.extractall(os.path.join(tmpExtractedPath, destDir), members=members) + shutil.move(os.path.join(tmpExtractedPath, destDir), extractedPath) + else: + archive.extractall(tmpExtractedPath, members=members) + shutil.move(os.path.join(tmpExtractedPath, rootDir), extractedPath) + + if os.path.isdir(tmpExtractedPath): + shutil.rmtree(tmpExtractedPath) + + return extractedPath + except Exception as e: + # If extraction failed for whatever reason, assume the + # archive file was bad and move it aside so that re-running + # the script will try downloading and extracting again. + shutil.move(filename, filename + ".bad") + raise RuntimeError("Failed to extract archive {filename}: {err}" + .format(filename=filename, err=e)) + +def IsGitFolder(path = '.'): + return subprocess.call(['git', '-C', path, 'status'], + stderr=subprocess.STDOUT, + stdout = open(os.devnull, 'w')) == 0 + +def GitClone(url, tag, cloneDir, context): + try: + with CurrentWorkingDirectory(context.externalsSrcDir): + # TODO check if cloneDir is a cloned folder of url + if not os.path.exists(cloneDir): + Run("git clone --recurse-submodules -b {tag} {url} {folder}".format( + tag=tag, url=url, folder=cloneDir)) + elif not IsGitFolder(cloneDir): + raise RuntimeError("Failed to clone repo {url} ({tag}): non-git folder {folder} exists".format( + url=url, tag=tag, folder=cloneDir)) + return os.path.abspath(cloneDir) + except Exception as e: + raise RuntimeError("Failed to clone repo {url} ({tag}): {err}".format( + url=url, tag=tag, err=e)) + +def GitCloneSHA(url, sha, cloneDir, context): + try: + with CurrentWorkingDirectory(context.externalsSrcDir): + # TODO check if cloneDir is a cloned folder of url + if not os.path.exists(cloneDir): + Run("git clone --recurse-submodules {url} {folder}".format(url=url, folder=cloneDir)) + with CurrentWorkingDirectory(os.path.join(context.externalsSrcDir, cloneDir)): + Run("git checkout {sha}".format(sha=sha)) + elif not IsGitFolder(cloneDir): + raise RuntimeError("Failed to clone repo {url} ({tag}): non-git folder {folder} exists".format( + url=url, tag=tag, folder=cloneDir)) + return os.path.abspath(cloneDir) + except Exception as e: + raise RuntimeError("Failed to clone repo {url} ({tag}): {err}".format( + url=url, tag=tag, err=e)) + +def WriteExternalsConfig(context, externals): + win32Header = """ +# Build configurations: {buildConfiguration} +if(WIN32) + message(STATUS "Supported build configurations: {buildConfiguration}") +endif() +""".format(buildConfiguration=";".join(context.buildConfigs)) + + header = """ +# Auto-generated by installExternals.py. Any modification will be overridden +# by the next run of installExternals.py. +{win32Header} + +if(NOT DEFINED EXTERNALS_ROOT) + set(EXTERNALS_ROOT "{externalsRoot}") +endif() + +set(AURORA_DEPENDENCIES "") +""".format(externalsRoot=pathlib.Path(context.externalsInstDir).as_posix(), + win32Header=win32Header if Windows() else "") + + package = """ +if(NOT DEFINED {packageName}_ROOT) + set({packageName}_ROOT "${{EXTERNALS_ROOT}}/{installFolder}") +endif() +list(APPEND AURORA_DEPENDENCIES "${{{packageName}_ROOT}}") +# find_package_verbose({packageName}) +""" + packages = "" + for ext in externals: + packages += package.format(packageName=ext.packageName, installFolder=ext.installFolder) + + externalConfig = os.path.join(context.auroraSrcDir, "Scripts", "cmake", "externalsConfig.cmake") + print("Written CMake config file: "+externalConfig) + with open(externalConfig, "w") as f: + f.write(header) + f.write(packages) diff --git a/Tests/Assets/Materials/HdAuroraTest.mtlx b/Tests/Assets/Materials/HdAuroraTest.mtlx index 5c6d0c1..3f43b25 100644 --- a/Tests/Assets/Materials/HdAuroraTest.mtlx +++ b/Tests/Assets/Materials/HdAuroraTest.mtlx @@ -1,22 +1,14 @@ - - - - - - - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/Tests/Assets/Materials/HdAuroraTextureTest.mtlx b/Tests/Assets/Materials/HdAuroraTextureTest.mtlx new file mode 100644 index 0000000..bb97b09 --- /dev/null +++ b/Tests/Assets/Materials/HdAuroraTextureTest.mtlx @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Assets/Materials/NormalMapExample.mtlx b/Tests/Assets/Materials/NormalMapExample.mtlx new file mode 100644 index 0000000..69e3607 --- /dev/null +++ b/Tests/Assets/Materials/NormalMapExample.mtlx @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Assets/Materials/TestThread.mtlx b/Tests/Assets/Materials/TestThread.mtlx new file mode 100644 index 0000000..8c3fe17 --- /dev/null +++ b/Tests/Assets/Materials/TestThread.mtlx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Assets/Materials/Thread.png b/Tests/Assets/Materials/Thread.png new file mode 100644 index 0000000..71c3f6d --- /dev/null +++ b/Tests/Assets/Materials/Thread.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3240d13224a86dc3ea11dd37e1ca55fd7a34f34976c660758010ead3c34f0d75 +size 86 diff --git a/Tests/Assets/Materials/Thread_normal.png b/Tests/Assets/Materials/Thread_normal.png new file mode 100644 index 0000000..a3b831f --- /dev/null +++ b/Tests/Assets/Materials/Thread_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a9a74e1a441e13d69b3f697cfffc7d47eeef595c613e58562dce48f1b5149bb +size 116 diff --git a/Tests/Assets/TextFiles/DefaultMaterialUniformAccessors.slang b/Tests/Assets/TextFiles/DefaultMaterialUniformAccessors.slang new file mode 100644 index 0000000..1e50936 --- /dev/null +++ b/Tests/Assets/TextFiles/DefaultMaterialUniformAccessors.slang @@ -0,0 +1,287 @@ +// Auto-generated by unit test BasicTest in AuroraExternals test suite. + + // Get property base from byte address buffer +float Material_base(ByteAddressBuffer buf) { + return asfloat(buf.Load(0)); +} + + // Get property base_color from byte address buffer +float3 Material_baseColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(4)); +} + + // Get property diffuse_roughness from byte address buffer +float Material_diffuseRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(16)); +} + + // Get property metalness from byte address buffer +float Material_metalness(ByteAddressBuffer buf) { + return asfloat(buf.Load(20)); +} + + // Get property specular from byte address buffer +float Material_specular(ByteAddressBuffer buf) { + return asfloat(buf.Load(24)); +} + + // Get property specular_color from byte address buffer +float3 Material_specularColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(32)); +} + + // Get property specular_roughness from byte address buffer +float Material_specularRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(44)); +} + + // Get property specular_IOR from byte address buffer +float Material_specularIOR(ByteAddressBuffer buf) { + return asfloat(buf.Load(48)); +} + + // Get property specular_anisotropy from byte address buffer +float Material_specularAnisotropy(ByteAddressBuffer buf) { + return asfloat(buf.Load(52)); +} + + // Get property specular_rotation from byte address buffer +float Material_specularRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(56)); +} + + // Get property transmission from byte address buffer +float Material_transmission(ByteAddressBuffer buf) { + return asfloat(buf.Load(60)); +} + + // Get property transmission_color from byte address buffer +float3 Material_transmissionColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(64)); +} + + // Get property subsurface from byte address buffer +float Material_subsurface(ByteAddressBuffer buf) { + return asfloat(buf.Load(76)); +} + + // Get property subsurface_color from byte address buffer +float3 Material_subsurfaceColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(80)); +} + + // Get property subsurface_radius from byte address buffer +float3 Material_subsurfaceRadius(ByteAddressBuffer buf) { + return asfloat(buf.Load3(96)); +} + + // Get property subsurface_scale from byte address buffer +float Material_subsurfaceScale(ByteAddressBuffer buf) { + return asfloat(buf.Load(108)); +} + + // Get property subsurface_anisotropy from byte address buffer +float Material_subsurfaceAnisotropy(ByteAddressBuffer buf) { + return asfloat(buf.Load(112)); +} + + // Get property sheen from byte address buffer +float Material_sheen(ByteAddressBuffer buf) { + return asfloat(buf.Load(116)); +} + + // Get property sheen_color from byte address buffer +float3 Material_sheenColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(128)); +} + + // Get property sheen_roughness from byte address buffer +float Material_sheenRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(140)); +} + + // Get property coat from byte address buffer +float Material_coat(ByteAddressBuffer buf) { + return asfloat(buf.Load(144)); +} + + // Get property coat_color from byte address buffer +float3 Material_coatColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(148)); +} + + // Get property coat_roughness from byte address buffer +float Material_coatRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(160)); +} + + // Get property coat_anisotropy from byte address buffer +float Material_coatAnisotropy(ByteAddressBuffer buf) { + return asfloat(buf.Load(164)); +} + + // Get property coat_rotation from byte address buffer +float Material_coatRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(168)); +} + + // Get property coat_IOR from byte address buffer +float Material_coatIOR(ByteAddressBuffer buf) { + return asfloat(buf.Load(172)); +} + + // Get property coat_affect_color from byte address buffer +float Material_coatAffectColor(ByteAddressBuffer buf) { + return asfloat(buf.Load(176)); +} + + // Get property coat_affect_roughness from byte address buffer +float Material_coatAffectRoughness(ByteAddressBuffer buf) { + return asfloat(buf.Load(180)); +} + + // Get property emission from byte address buffer +float Material_emission(ByteAddressBuffer buf) { + return asfloat(buf.Load(184)); +} + + // Get property emission_color from byte address buffer +float3 Material_emissionColor(ByteAddressBuffer buf) { + return asfloat(buf.Load3(192)); +} + + // Get property opacity from byte address buffer +float3 Material_opacity(ByteAddressBuffer buf) { + return asfloat(buf.Load3(208)); +} + + // Get property thin_walled from byte address buffer +int Material_thinWalled(ByteAddressBuffer buf) { + return buf.Load(220); +} + + // Get property has_base_color_image from byte address buffer +int Material_hasBaseColorTex(ByteAddressBuffer buf) { + return buf.Load(224); +} + + // Get property base_color_image_offset from byte address buffer +float2 Material_baseColorTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(228)); +} + + // Get property base_color_image_scale from byte address buffer +float2 Material_baseColorTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(240)); +} + + // Get property base_color_image_pivot from byte address buffer +float2 Material_baseColorTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(248)); +} + + // Get property base_color_image_rotation from byte address buffer +float Material_baseColorTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(256)); +} + + // Get property has_specular_roughness_image from byte address buffer +int Material_hasSpecularRoughnessTex(ByteAddressBuffer buf) { + return buf.Load(260); +} + + // Get property specular_roughness_image_offset from byte address buffer +float2 Material_specularRoughnessTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(264)); +} + + // Get property specular_roughness_image_scale from byte address buffer +float2 Material_specularRoughnessTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(272)); +} + + // Get property specular_roughness_image_pivot from byte address buffer +float2 Material_specularRoughnessTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(280)); +} + + // Get property specular_roughness_image_rotation from byte address buffer +float Material_specularRoughnessTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(288)); +} + + // Get property has_emission_color_image from byte address buffer +int Material_hasEmissionColorTex(ByteAddressBuffer buf) { + return buf.Load(292); +} + + // Get property emission_color_image_offset from byte address buffer +float2 Material_emissionColorTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(296)); +} + + // Get property emission_color_image_scale from byte address buffer +float2 Material_emissionColorTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(304)); +} + + // Get property emission_color_image_pivot from byte address buffer +float2 Material_emissionColorTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(312)); +} + + // Get property emission_color_image_rotation from byte address buffer +float Material_emissionColorTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(320)); +} + + // Get property has_opacity_image from byte address buffer +int Material_hasOpacityTex(ByteAddressBuffer buf) { + return buf.Load(324); +} + + // Get property opacity_image_offset from byte address buffer +float2 Material_opacityTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(328)); +} + + // Get property opacity_image_scale from byte address buffer +float2 Material_opacityTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(336)); +} + + // Get property opacity_image_pivot from byte address buffer +float2 Material_opacityTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(344)); +} + + // Get property opacity_image_rotation from byte address buffer +float Material_opacityTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(352)); +} + + // Get property has_normal_image from byte address buffer +int Material_hasNormalTex(ByteAddressBuffer buf) { + return buf.Load(356); +} + + // Get property normal_image_offset from byte address buffer +float2 Material_normalTexOffset(ByteAddressBuffer buf) { + return asfloat(buf.Load2(360)); +} + + // Get property normal_image_scale from byte address buffer +float2 Material_normalTexScale(ByteAddressBuffer buf) { + return asfloat(buf.Load2(368)); +} + + // Get property normal_image_pivot from byte address buffer +float2 Material_normalTexPivot(ByteAddressBuffer buf) { + return asfloat(buf.Load2(376)); +} + + // Get property normal_image_rotation from byte address buffer +float Material_normalTexRotation(ByteAddressBuffer buf) { + return asfloat(buf.Load(384)); +} + diff --git a/Tests/Assets/TextFiles/DefaultMaterialUniformBuffer.slang b/Tests/Assets/TextFiles/DefaultMaterialUniformBuffer.slang new file mode 100644 index 0000000..acda477 --- /dev/null +++ b/Tests/Assets/TextFiles/DefaultMaterialUniformBuffer.slang @@ -0,0 +1,301 @@ +// Auto-generated by unit test BasicTest in AuroraExternals test suite. + +struct MaterialConstants +{ + float base; // Offset:0 Property:base + float3 baseColor; // Offset:4 Property:base_color + float diffuseRoughness; // Offset:16 Property:diffuse_roughness + float metalness; // Offset:20 Property:metalness + float specular; // Offset:24 Property:specular + int _padding0; + float3 specularColor; // Offset:32 Property:specular_color + float specularRoughness; // Offset:44 Property:specular_roughness + float specularIOR; // Offset:48 Property:specular_IOR + float specularAnisotropy; // Offset:52 Property:specular_anisotropy + float specularRotation; // Offset:56 Property:specular_rotation + float transmission; // Offset:60 Property:transmission + float3 transmissionColor; // Offset:64 Property:transmission_color + float subsurface; // Offset:76 Property:subsurface + float3 subsurfaceColor; // Offset:80 Property:subsurface_color + int _padding1; + float3 subsurfaceRadius; // Offset:96 Property:subsurface_radius + float subsurfaceScale; // Offset:108 Property:subsurface_scale + float subsurfaceAnisotropy; // Offset:112 Property:subsurface_anisotropy + float sheen; // Offset:116 Property:sheen + int _padding2; + int _padding3; + float3 sheenColor; // Offset:128 Property:sheen_color + float sheenRoughness; // Offset:140 Property:sheen_roughness + float coat; // Offset:144 Property:coat + float3 coatColor; // Offset:148 Property:coat_color + float coatRoughness; // Offset:160 Property:coat_roughness + float coatAnisotropy; // Offset:164 Property:coat_anisotropy + float coatRotation; // Offset:168 Property:coat_rotation + float coatIOR; // Offset:172 Property:coat_IOR + float coatAffectColor; // Offset:176 Property:coat_affect_color + float coatAffectRoughness; // Offset:180 Property:coat_affect_roughness + float emission; // Offset:184 Property:emission + int _padding4; + float3 emissionColor; // Offset:192 Property:emission_color + int _padding5; + float3 opacity; // Offset:208 Property:opacity + int thinWalled; // Offset:220 Property:thin_walled + int hasBaseColorTex; // Offset:224 Property:has_base_color_image + float2 baseColorTexOffset; // Offset:228 Property:base_color_image_offset + int _padding6; + float2 baseColorTexScale; // Offset:240 Property:base_color_image_scale + float2 baseColorTexPivot; // Offset:248 Property:base_color_image_pivot + float baseColorTexRotation; // Offset:256 Property:base_color_image_rotation + int hasSpecularRoughnessTex; // Offset:260 Property:has_specular_roughness_image + float2 specularRoughnessTexOffset; // Offset:264 Property:specular_roughness_image_offset + float2 specularRoughnessTexScale; // Offset:272 Property:specular_roughness_image_scale + float2 specularRoughnessTexPivot; // Offset:280 Property:specular_roughness_image_pivot + float specularRoughnessTexRotation; // Offset:288 Property:specular_roughness_image_rotation + int hasEmissionColorTex; // Offset:292 Property:has_emission_color_image + float2 emissionColorTexOffset; // Offset:296 Property:emission_color_image_offset + float2 emissionColorTexScale; // Offset:304 Property:emission_color_image_scale + float2 emissionColorTexPivot; // Offset:312 Property:emission_color_image_pivot + float emissionColorTexRotation; // Offset:320 Property:emission_color_image_rotation + int hasOpacityTex; // Offset:324 Property:has_opacity_image + float2 opacityTexOffset; // Offset:328 Property:opacity_image_offset + float2 opacityTexScale; // Offset:336 Property:opacity_image_scale + float2 opacityTexPivot; // Offset:344 Property:opacity_image_pivot + float opacityTexRotation; // Offset:352 Property:opacity_image_rotation + int hasNormalTex; // Offset:356 Property:has_normal_image + float2 normalTexOffset; // Offset:360 Property:normal_image_offset + float2 normalTexScale; // Offset:368 Property:normal_image_scale + float2 normalTexPivot; // Offset:376 Property:normal_image_pivot + float normalTexRotation; // Offset:384 Property:normal_image_rotation + int _padding7; + int _padding8; + int _padding9; +} +; + +float Material_base(MaterialConstants mtl) { + return mtl.base; +} + +float3 Material_baseColor(MaterialConstants mtl) { + return mtl.baseColor; +} + +float Material_diffuseRoughness(MaterialConstants mtl) { + return mtl.diffuseRoughness; +} + +float Material_metalness(MaterialConstants mtl) { + return mtl.metalness; +} + +float Material_specular(MaterialConstants mtl) { + return mtl.specular; +} + +float3 Material_specularColor(MaterialConstants mtl) { + return mtl.specularColor; +} + +float Material_specularRoughness(MaterialConstants mtl) { + return mtl.specularRoughness; +} + +float Material_specularIOR(MaterialConstants mtl) { + return mtl.specularIOR; +} + +float Material_specularAnisotropy(MaterialConstants mtl) { + return mtl.specularAnisotropy; +} + +float Material_specularRotation(MaterialConstants mtl) { + return mtl.specularRotation; +} + +float Material_transmission(MaterialConstants mtl) { + return mtl.transmission; +} + +float3 Material_transmissionColor(MaterialConstants mtl) { + return mtl.transmissionColor; +} + +float Material_subsurface(MaterialConstants mtl) { + return mtl.subsurface; +} + +float3 Material_subsurfaceColor(MaterialConstants mtl) { + return mtl.subsurfaceColor; +} + +float3 Material_subsurfaceRadius(MaterialConstants mtl) { + return mtl.subsurfaceRadius; +} + +float Material_subsurfaceScale(MaterialConstants mtl) { + return mtl.subsurfaceScale; +} + +float Material_subsurfaceAnisotropy(MaterialConstants mtl) { + return mtl.subsurfaceAnisotropy; +} + +float Material_sheen(MaterialConstants mtl) { + return mtl.sheen; +} + +float3 Material_sheenColor(MaterialConstants mtl) { + return mtl.sheenColor; +} + +float Material_sheenRoughness(MaterialConstants mtl) { + return mtl.sheenRoughness; +} + +float Material_coat(MaterialConstants mtl) { + return mtl.coat; +} + +float3 Material_coatColor(MaterialConstants mtl) { + return mtl.coatColor; +} + +float Material_coatRoughness(MaterialConstants mtl) { + return mtl.coatRoughness; +} + +float Material_coatAnisotropy(MaterialConstants mtl) { + return mtl.coatAnisotropy; +} + +float Material_coatRotation(MaterialConstants mtl) { + return mtl.coatRotation; +} + +float Material_coatIOR(MaterialConstants mtl) { + return mtl.coatIOR; +} + +float Material_coatAffectColor(MaterialConstants mtl) { + return mtl.coatAffectColor; +} + +float Material_coatAffectRoughness(MaterialConstants mtl) { + return mtl.coatAffectRoughness; +} + +float Material_emission(MaterialConstants mtl) { + return mtl.emission; +} + +float3 Material_emissionColor(MaterialConstants mtl) { + return mtl.emissionColor; +} + +float3 Material_opacity(MaterialConstants mtl) { + return mtl.opacity; +} + +int Material_thinWalled(MaterialConstants mtl) { + return mtl.thinWalled; +} + +int Material_hasBaseColorTex(MaterialConstants mtl) { + return mtl.hasBaseColorTex; +} + +float2 Material_baseColorTexOffset(MaterialConstants mtl) { + return mtl.baseColorTexOffset; +} + +float2 Material_baseColorTexScale(MaterialConstants mtl) { + return mtl.baseColorTexScale; +} + +float2 Material_baseColorTexPivot(MaterialConstants mtl) { + return mtl.baseColorTexPivot; +} + +float Material_baseColorTexRotation(MaterialConstants mtl) { + return mtl.baseColorTexRotation; +} + +int Material_hasSpecularRoughnessTex(MaterialConstants mtl) { + return mtl.hasSpecularRoughnessTex; +} + +float2 Material_specularRoughnessTexOffset(MaterialConstants mtl) { + return mtl.specularRoughnessTexOffset; +} + +float2 Material_specularRoughnessTexScale(MaterialConstants mtl) { + return mtl.specularRoughnessTexScale; +} + +float2 Material_specularRoughnessTexPivot(MaterialConstants mtl) { + return mtl.specularRoughnessTexPivot; +} + +float Material_specularRoughnessTexRotation(MaterialConstants mtl) { + return mtl.specularRoughnessTexRotation; +} + +int Material_hasEmissionColorTex(MaterialConstants mtl) { + return mtl.hasEmissionColorTex; +} + +float2 Material_emissionColorTexOffset(MaterialConstants mtl) { + return mtl.emissionColorTexOffset; +} + +float2 Material_emissionColorTexScale(MaterialConstants mtl) { + return mtl.emissionColorTexScale; +} + +float2 Material_emissionColorTexPivot(MaterialConstants mtl) { + return mtl.emissionColorTexPivot; +} + +float Material_emissionColorTexRotation(MaterialConstants mtl) { + return mtl.emissionColorTexRotation; +} + +int Material_hasOpacityTex(MaterialConstants mtl) { + return mtl.hasOpacityTex; +} + +float2 Material_opacityTexOffset(MaterialConstants mtl) { + return mtl.opacityTexOffset; +} + +float2 Material_opacityTexScale(MaterialConstants mtl) { + return mtl.opacityTexScale; +} + +float2 Material_opacityTexPivot(MaterialConstants mtl) { + return mtl.opacityTexPivot; +} + +float Material_opacityTexRotation(MaterialConstants mtl) { + return mtl.opacityTexRotation; +} + +int Material_hasNormalTex(MaterialConstants mtl) { + return mtl.hasNormalTex; +} + +float2 Material_normalTexOffset(MaterialConstants mtl) { + return mtl.normalTexOffset; +} + +float2 Material_normalTexScale(MaterialConstants mtl) { + return mtl.normalTexScale; +} + +float2 Material_normalTexPivot(MaterialConstants mtl) { + return mtl.normalTexPivot; +} + +float Material_normalTexRotation(MaterialConstants mtl) { + return mtl.normalTexRotation; +} diff --git a/Tests/Assets/Textures/Verde_Guatemala_Slatted_Marble_normal.png b/Tests/Assets/Textures/Verde_Guatemala_Slatted_Marble_normal.png new file mode 100644 index 0000000..7849a34 --- /dev/null +++ b/Tests/Assets/Textures/Verde_Guatemala_Slatted_Marble_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa8f5083fd712594cdc1dd1df60d478d974bb39b8f8c8507c6d802bd5bf70fdb +size 2905319 diff --git a/Tests/Aurora/AuroraMain.cpp b/Tests/Aurora/AuroraMain.cpp index ef1d037..2a616e8 100644 --- a/Tests/Aurora/AuroraMain.cpp +++ b/Tests/Aurora/AuroraMain.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Aurora/BaselineImages/Images/TestNormalMapImage_0.DirectX.png b/Tests/Aurora/BaselineImages/Images/TestNormalMapImage_0.DirectX.png index 7e32522..7cc9ca7 100644 --- a/Tests/Aurora/BaselineImages/Images/TestNormalMapImage_0.DirectX.png +++ b/Tests/Aurora/BaselineImages/Images/TestNormalMapImage_0.DirectX.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1296c5324a0f7214a90e9134dc7e975b0d70808491c013bc4d1c7ac8dbaf9418 -size 13400 +oid sha256:a3deb261f1e578c4740868503b2ee5d5a79c7250ad27f39ef2304987d76afe2e +size 16014 diff --git a/Tests/Aurora/BaselineImages/Light/TestChangeLightEnvTexture_0.DirectX0.png b/Tests/Aurora/BaselineImages/Light/TestChangeLightEnvTexture_0.DirectX0.png new file mode 100644 index 0000000..7a8f75d --- /dev/null +++ b/Tests/Aurora/BaselineImages/Light/TestChangeLightEnvTexture_0.DirectX0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a02e94355e24f0719cf01b6578c0f7b0a5b80f43decce319e9ebd5bb32481dc4 +size 13965 diff --git a/Tests/Aurora/BaselineImages/Light/TestChangeLightEnvTexture_0.DirectX1.png b/Tests/Aurora/BaselineImages/Light/TestChangeLightEnvTexture_0.DirectX1.png new file mode 100644 index 0000000..c5e9ecc --- /dev/null +++ b/Tests/Aurora/BaselineImages/Light/TestChangeLightEnvTexture_0.DirectX1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d39b75532be82a930dc42d4ad5e8d3a24e2d0e9fb5ad1b27cc98ecb3a933540 +size 13479 diff --git a/Tests/Aurora/BaselineImages/Light/TestMultipleLights_0.DirectX.png b/Tests/Aurora/BaselineImages/Light/TestMultipleLights_0.DirectX.png new file mode 100644 index 0000000..ca4e786 --- /dev/null +++ b/Tests/Aurora/BaselineImages/Light/TestMultipleLights_0.DirectX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a578f20b55185104bc73283e2820e904df9dc7d72c47e872317b7b318ebc6a8 +size 19503 diff --git a/Tests/Aurora/BaselineImages/Materials/TestHdAuroraMaterialX_0.DirectX_HdAuroraMtlX.png b/Tests/Aurora/BaselineImages/Materials/TestHdAuroraMaterialX_0.DirectX_HdAuroraMtlX.png index 4ff6bed..e8d6236 100644 --- a/Tests/Aurora/BaselineImages/Materials/TestHdAuroraMaterialX_0.DirectX_HdAuroraMtlX.png +++ b/Tests/Aurora/BaselineImages/Materials/TestHdAuroraMaterialX_0.DirectX_HdAuroraMtlX.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e5679119a00088cecfbab5fd9b7c06f7d1588741de7dc4206d229d076242ede -size 10248 +oid sha256:dfc06ee95d7fd7c925efe756c43f50846956d25d5475f8c2f92158dfe5226f09 +size 8299 diff --git a/Tests/Aurora/BaselineImages/Materials/TestHdAuroraTextureMaterialX_0.DirectX_HdAuroraMtlX.png b/Tests/Aurora/BaselineImages/Materials/TestHdAuroraTextureMaterialX_0.DirectX_HdAuroraMtlX.png new file mode 100644 index 0000000..786925e --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestHdAuroraTextureMaterialX_0.DirectX_HdAuroraMtlX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfd3e1dd678ccdc7674f1194cacf1bc624e2c42336cd28f365e4141527623aa2 +size 8578 diff --git a/Tests/Aurora/BaselineImages/Materials/TestLotsOfMaterialX_0.DirectX.png b/Tests/Aurora/BaselineImages/Materials/TestLotsOfMaterialX_0.DirectX.png new file mode 100644 index 0000000..36a9b5a --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestLotsOfMaterialX_0.DirectX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bab7416232c4206a7cde208fc31699c5882a9ddd4a8d8ab20365bbc5337d22c +size 11945 diff --git a/Tests/Aurora/BaselineImages/Materials/TestMaterialEmission_0.DirectXEmission.png b/Tests/Aurora/BaselineImages/Materials/TestMaterialEmission_0.DirectXEmission.png new file mode 100644 index 0000000..d9e57e9 --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestMaterialEmission_0.DirectXEmission.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d2a77f1087505e94c4cc2c33e4bf7618a4b10e547d09b7452207d6d43aa8bd7 +size 6161 diff --git a/Tests/Aurora/BaselineImages/Materials/TestMaterialEmission_0.DirectXEmissionImage.png b/Tests/Aurora/BaselineImages/Materials/TestMaterialEmission_0.DirectXEmissionImage.png new file mode 100644 index 0000000..6b171d2 --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestMaterialEmission_0.DirectXEmissionImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fd98966e3acbf6e57882237aa89273ae952025e42070631e027d8b88986e8b6 +size 8893 diff --git a/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXOpacity.png b/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXOpacity.png new file mode 100644 index 0000000..c19123f --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXOpacity.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e09293a70efa82b30c1cc90c7f7d54b4feaec2adf2906c40721c8c1a304e011 +size 28613 diff --git a/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXOpacityImage.png b/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXOpacityImage.png new file mode 100644 index 0000000..5494752 --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXOpacityImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:451873a0c4f1bb0d11169e22d7e11ae6d636596d2e85871b871906b02d31380d +size 26024 diff --git a/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXTransmission.png b/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXTransmission.png new file mode 100644 index 0000000..f3ac292 --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestMaterialShadowTransparency_0.DirectXTransmission.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a819a69f96c9ee6745ec77c39d823a0e2d27497c091946ce2c716b5f730f812 +size 25110 diff --git a/Tests/Aurora/BaselineImages/Materials/TestMaterialX_0.DirectX_ParameterOverride.png b/Tests/Aurora/BaselineImages/Materials/TestMaterialX_0.DirectX_ParameterOverride.png index 824d847..75be305 100644 --- a/Tests/Aurora/BaselineImages/Materials/TestMaterialX_0.DirectX_ParameterOverride.png +++ b/Tests/Aurora/BaselineImages/Materials/TestMaterialX_0.DirectX_ParameterOverride.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a61e42070b2e6e69a3a6e3da53c803a74ca79a65cda3b425f3914d32822911c5 -size 7399 +oid sha256:dd3ded8c64e43eb2e2fd59e5ccc85a4331a416cc2ee394b188d32ddc24c17de1 +size 8146 diff --git a/Tests/Aurora/BaselineImages/Materials/TestNormalMapMaterialX_0.DirectX.png b/Tests/Aurora/BaselineImages/Materials/TestNormalMapMaterialX_0.DirectX.png new file mode 100644 index 0000000..ce50e6c --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestNormalMapMaterialX_0.DirectX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b39e94a23fdd9606cc26a187c76a28d553c2a76ec2d39609e1120f0ab21c095 +size 25891 diff --git a/Tests/Aurora/BaselineImages/Materials/TestObjectSpaceMaterialX_0.DirectX_ThreadMtlX.png b/Tests/Aurora/BaselineImages/Materials/TestObjectSpaceMaterialX_0.DirectX_ThreadMtlX.png new file mode 100644 index 0000000..a4ebdca --- /dev/null +++ b/Tests/Aurora/BaselineImages/Materials/TestObjectSpaceMaterialX_0.DirectX_ThreadMtlX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:268b6d38df793a146054f1fa497a4ff972b86394ae0ab9d56f5694362462fcc3 +size 6605 diff --git a/Tests/Aurora/BaselineImages/TestDebugNormals_0.DirectX.png b/Tests/Aurora/BaselineImages/TestDebugNormals_0.DirectX.png new file mode 100644 index 0000000..075657e --- /dev/null +++ b/Tests/Aurora/BaselineImages/TestDebugNormals_0.DirectX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d67cd680f41adc7e24613a7b747b24cb2b118d9122fb19bb976ba5202633302 +size 28728 diff --git a/Tests/Aurora/BaselineImages/TestRendererGroundPlane_0.DirectX.png b/Tests/Aurora/BaselineImages/TestRendererGroundPlane_0.DirectX.png new file mode 100644 index 0000000..d8848e2 --- /dev/null +++ b/Tests/Aurora/BaselineImages/TestRendererGroundPlane_0.DirectX.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2022924057623f5ba86b2dac9df94ecf4503837d1c7aef63d12177e539779f02 +size 24103 diff --git a/Tests/Aurora/CMakeLists.txt b/Tests/Aurora/CMakeLists.txt index cee9c93..7ec907d 100644 --- a/Tests/Aurora/CMakeLists.txt +++ b/Tests/Aurora/CMakeLists.txt @@ -34,7 +34,7 @@ add_executable(${PROJECT_NAME} source_group("Helpers" FILES ${HELPER_FILES}) source_group("Tests" FILES ${TEST_FILES}) -# Set custom ouput properties. +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Tests" RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" @@ -83,6 +83,7 @@ set_property(TARGET ${PROJECT_NAME} PROPERTY FOLDER "Tests") target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFAULT_COMPILE_DEFINITIONS}) if(WIN32) + add_dependencies(CopyOpenImageIODLLs MakeRuntimeDir) add_dependencies(${PROJECT_NAME} CopyOpenImageIODLLs) endif() diff --git a/Tests/Aurora/Tests/TestBenchmarks.cpp b/Tests/Aurora/Tests/TestBenchmarks.cpp index fac4e50..fc82bc8 100644 --- a/Tests/Aurora/Tests/TestBenchmarks.cpp +++ b/Tests/Aurora/Tests/TestBenchmarks.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Aurora/Tests/TestImage.cpp b/Tests/Aurora/Tests/TestImage.cpp index dd43558..4cbdeb9 100644 --- a/Tests/Aurora/Tests/TestImage.cpp +++ b/Tests/Aurora/Tests/TestImage.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable unit test as causes failure in debug mode +#if _DEBUG +#define DISABLE_UNIT_TESTS +#endif + #if !defined(DISABLE_UNIT_TESTS) #include @@ -261,9 +266,10 @@ TEST_P(ImageTest, TestNormalMapImage) if (!pRenderer) return; - rgb lightColor(1, 1, 1); - vec3 lightDirection(0.0f, -0.25f, +1.0f); - pScene->setLight(2.0f, lightColor, lightDirection); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0.0f, -0.25f, +1.0f))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1, 1, 1))); // Load pixels for test image file. const std::string txtName = dataPath() + "/Textures/fishscale_normal.png"; diff --git a/Tests/Aurora/Tests/TestLight.cpp b/Tests/Aurora/Tests/TestLight.cpp index 6e70735..3f96fcc 100644 --- a/Tests/Aurora/Tests/TestLight.cpp +++ b/Tests/Aurora/Tests/TestLight.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable unit test as causes failure in debug mode +#if _DEBUG +#define DISABLE_UNIT_TESTS +#endif + #if !defined(DISABLE_UNIT_TESTS) #include @@ -127,9 +132,10 @@ TEST_P(LightTest, TestLightEnvTexture) return; // Disable the directional light. - vec3 lightDirec(0, 0, 1); - rgb lightColor(0, 0, 0); - pScene->setLight(0.0, lightColor, lightDirec); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, 0, 1))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(0, 0, 0))); // Create procedural image data. std::vector buffer; @@ -171,6 +177,85 @@ TEST_P(LightTest, TestLightEnvTexture) ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName(), "Light"); } +TEST_P(LightTest, TestChangeLightEnvTexture) +{ + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + // Disable the directional light. + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, 0, 1))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(0, 0, 0))); + + // Create procedural image data. + std::vector buffer; + array colors = { + glm::vec3(1.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 1.0f, 0.0f), + glm::vec3(0.0f, 0.75f, 1.0f), + glm::vec3(0.75f, 0.0f, 1.0f), + glm::vec3(0.8f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.8f, 0.0f), + }; + const Path kBackgroundEnvironmentImagePath = "BackgroundEnvironmentImage"; + createTestEnv(pScene, kBackgroundEnvironmentImagePath, 512, colors, glm::vec3(), glm::vec3(), 0, + 0, &buffer, false); + + // Create environment and set background and light image. + const Path kBackgroundEnvironmentPath = "BackgroundEnvironment"; + pScene->setEnvironmentProperties(kBackgroundEnvironmentPath, + { + { Names::EnvironmentProperties::kLightImage, kBackgroundEnvironmentImagePath }, + { Names::EnvironmentProperties::kBackgroundImage, kBackgroundEnvironmentImagePath }, + }); + + // Set the environment. + pScene->setEnvironment(kBackgroundEnvironmentPath); + + // Create a material. + const Path kMaterialPath = "DefaultMaterial"; + pScene->setMaterialType(kMaterialPath); + + // Create teapot instance. + Path GeomPath = createTeapotGeometry(*pScene); + + // Create instance with material. + EXPECT_TRUE(pScene->addInstance( + nextPath(), GeomPath, { { Names::InstanceProperties::kMaterial, kMaterialPath } })); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "0", "Light"); + + // Create procedural image data. + std::vector buffer1; + array colors1 = { + glm::vec3(1.0f, 1.0f, 0.0f), + glm::vec3(0.0f, 0.1f, 0.0f), + glm::vec3(1.0f, 0.75f, 1.0f), + glm::vec3(0.75f, 0.0f, 1.0f), + glm::vec3(0.8f, 0.0f, 0.0f), + glm::vec3(1.0f, 0.8f, 0.4f), + }; + const Path kSecondBackgroundEnvironmentImagePath = "SecondBackgroundEnvironmentImage"; + createTestEnv(pScene, kSecondBackgroundEnvironmentImagePath, 512, colors1, glm::vec3(), + glm::vec3(), 0, 0, &buffer1, false); + pScene->setEnvironmentProperties(kBackgroundEnvironmentPath, + { + { Names::EnvironmentProperties::kLightImage, kSecondBackgroundEnvironmentImagePath }, + { Names::EnvironmentProperties::kBackgroundImage, + kSecondBackgroundEnvironmentImagePath }, + }); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "1", "Light"); +} + // Basic environment map image test. TEST_P(LightTest, TestLightEnvTextureMIS) { @@ -185,9 +270,10 @@ TEST_P(LightTest, TestLightEnvTextureMIS) setDefaultRendererPerspective(60.0f); // Disable the directional light. - vec3 lightDirec(0, 0, 1); - rgb lightColor(0, 0, 0); - pScene->setLight(0.0, lightColor, lightDirec); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, 0, 1))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(0, 0, 0))); // Create procedural image data with small bright region. std::vector buffer; @@ -248,6 +334,124 @@ TEST_P(LightTest, TestLightEnvTextureMIS) ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName(), "Light"); } +// Test multiple distant lights. +TEST_P(LightTest, TestMultipleLights) +{ + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + // Set wider FOV so both teapots visible. + setDefaultRendererPerspective(60.0f); + + // Add four directional lights (including default light). + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, 0, 1))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(0.5f, 1, 0.6f))); + defaultDistantLight()->values().setFloat(Aurora::Names::LightProperties::kIntensity, 3.0f); + defaultDistantLight()->values().setFloat( + Aurora::Names::LightProperties::kAngularDiameter, 0.4f); + + ILightPtr pSecondLight = + defaultScene()->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + pSecondLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, -1, 0))); + pSecondLight->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1.0f, 1, 0.2f))); + pSecondLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, 2.0f); + pSecondLight->values().setFloat(Aurora::Names::LightProperties::kAngularDiameter, 0.2f); + + ILightPtr pThirdLight = + defaultScene()->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + pThirdLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(-1, -1, 0))); + pThirdLight->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(0.1f, 0.1, 1.0f))); + pThirdLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, 1.2f); + pThirdLight->values().setFloat(Aurora::Names::LightProperties::kAngularDiameter, 0.1f); + + ILightPtr pFourthLight = + defaultScene()->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + pFourthLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(+1, -1, 0))); + pFourthLight->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1.0f, 0.1, 0.0f))); + pFourthLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, 1.2f); + pFourthLight->values().setFloat(Aurora::Names::LightProperties::kAngularDiameter, 0.1f); + + // Fifth light will be ignored as only 4 supported. + ILightPtr pFifthLight = + defaultScene()->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + pFifthLight->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, -1, 0))); + pFifthLight->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1.0f, 1.0, 1.0f))); + pFifthLight->values().setFloat(Aurora::Names::LightProperties::kIntensity, 100.0f); + pFifthLight->values().setFloat(Aurora::Names::LightProperties::kAngularDiameter, 0.8f); + + // Create black environment map. + std::vector buffer; + array colors = { + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3(0.0f, 0.0f, 0.0f), + }; + const Path kBackgroundEnvironmentImagePath = "BackgroundEnvironmentImage"; + createTestEnv(pScene, kBackgroundEnvironmentImagePath, 1024, colors, glm::vec3(0, 0.2f, 1), + glm::vec3(0.9f, 0.8f, -0.8f), 0.0001f, 0.0f, &buffer, false); + + // Create environment and set background and light image. + const Path kBackgroundEnvironmentPath = "BackgroundEnvironment"; + pScene->setEnvironmentProperties(kBackgroundEnvironmentPath, + { + { Names::EnvironmentProperties::kLightImage, kBackgroundEnvironmentImagePath }, + { Names::EnvironmentProperties::kBackgroundImage, kBackgroundEnvironmentImagePath }, + }); + + // Set the environment. + pScene->setEnvironment(kBackgroundEnvironmentPath); + + // Create geometry. + Path teapotPath = createTeapotGeometry(*pScene); + Path planePath = createPlaneGeometry(*pScene); + + // Create one glossy and one diffuse material + const Path kMaterialPath0 = "Material0"; + pScene->setMaterialProperties(kMaterialPath0, { { "specular_roughness", 0.95f } }); + const Path kMaterialPath1 = "Material1"; + pScene->setMaterialProperties(kMaterialPath1, { { "specular_roughness", 0.05f } }); + + EXPECT_TRUE(pScene->addInstance(nextPath(), teapotPath, + { { Names::InstanceProperties::kTransform, glm::translate(glm::vec3(+3, -1.5, 3)) }, + { Names::InstanceProperties::kMaterial, kMaterialPath0 } })); + EXPECT_TRUE(pScene->addInstance(nextPath(), planePath, + { { Names::InstanceProperties::kTransform, + glm::translate(glm::vec3(+50, -1.5, 0)) * + glm::rotate(static_cast(M_PI * 0.5), glm::vec3(1, 0, 0)) * + glm::scale(glm::vec3(50, 50, 50)) }, + { Names::InstanceProperties::kMaterial, kMaterialPath1 } })); + + EXPECT_TRUE(pScene->addInstance(nextPath(), teapotPath, + { { Names::InstanceProperties::kTransform, glm::translate(glm::vec3(-3, -1.5, 3)) }, + { Names::InstanceProperties::kMaterial, kMaterialPath0 } })); + EXPECT_TRUE(pScene->addInstance(nextPath(), planePath, + { { Names::InstanceProperties::kTransform, + glm::translate(glm::vec3(-50, -1.5, 0)) * + glm::rotate(static_cast(M_PI * 0.5), glm::vec3(1, 0, 0)) * + glm::scale(glm::vec3(50, 50, 50)) }, + { Names::InstanceProperties::kMaterial, kMaterialPath1 } })); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName(), "Light"); +} + INSTANTIATE_TEST_SUITE_P(LightTests, LightTest, TEST_SUITE_RENDERER_TYPES()); } // namespace diff --git a/Tests/Aurora/Tests/TestMaterial.cpp b/Tests/Aurora/Tests/TestMaterial.cpp index 1512a55..cfc353e 100644 --- a/Tests/Aurora/Tests/TestMaterial.cpp +++ b/Tests/Aurora/Tests/TestMaterial.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable unit test as causes failure in debug mode +#if _DEBUG +#define DISABLE_UNIT_TESTS +#endif + #if !defined(DISABLE_UNIT_TESTS) #include @@ -21,6 +26,7 @@ #include #include +#include #include #include #include @@ -73,6 +79,9 @@ class MaterialTest : public TestHelpers::FixtureBase } ~MaterialTest() {} + // Test for the existence of the ADSK materialX libraries (in the working folder for the tests) + bool adskMaterialXSupport() { return std::filesystem::exists("MaterialX/libraries/adsk"); } + // Load a MaterialX document and process file paths to correct locations for unit tests. string loadAndProcessMaterialXFile(const string& filename) { @@ -116,14 +125,10 @@ TEST_P(MaterialTest, TestMaterialDefault) " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloat3Value(*pScene, testMaterial, "base_color", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloatValue(*pScene, testMaterial, "thin_film_IOR", true, - " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloatValue(*pScene, testMaterial, "specular_rotation", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloatValue(*pScene, testMaterial, "coat_IOR", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloatValue(*pScene, testMaterial, "transmission_depth", true, - " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloatValue(*pScene, testMaterial, "transmission", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloatValue(*pScene, testMaterial, "diffuse_roughness", true, @@ -142,11 +147,7 @@ TEST_P(MaterialTest, TestMaterialDefault) " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloatValue(*pScene, testMaterial, "subsurface_anisotropy", true, " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloatValue(*pScene, testMaterial, "transmission_extra_roughness", true, - " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloatValue(*pScene, testMaterial, "transmission_scatter_anisotropy", true, - " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloatValue(*pScene, testMaterial, "transmission_dispersion", true, + testFloat3Value(*pScene, testMaterial, "transmission_color", true, " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloatValue(*pScene, testMaterial, "subsurface_scale", true, " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); @@ -158,12 +159,8 @@ TEST_P(MaterialTest, TestMaterialDefault) " material param test" + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloat3Value(*pScene, testMaterial, "specular_color", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloat3Value(*pScene, testMaterial, "transmission_color", true, - " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloat3Value(*pScene, testMaterial, "subsurface_color", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); - testFloat3Value(*pScene, testMaterial, "transmission_scatter", true, - " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloat3Value(*pScene, testMaterial, "subsurface_radius", true, " material param test " + to_string(__LINE__) + " (" + rendererDescription() + ")"); testFloat3Value(*pScene, testMaterial, "sheen_color", true, @@ -191,11 +188,6 @@ static void clearAllProperties(Properties& props) props["metalness"].clear(); props["transmission"].clear(); props["transmission_color"].clear(); - props["transmission_depth"].clear(); - props["transmission_scatter"].clear(); - props["transmission_scatter_anisotropy"].clear(); - props["transmission_dispersion"].clear(); - props["transmission_extra_roughness"].clear(); props["subsurface"].clear(); props["subsurface_color"].clear(); props["subsurface_radius"].clear(); @@ -213,10 +205,6 @@ static void clearAllProperties(Properties& props) props["coat_IOR"].clear(); props["coat_affect_color"].clear(); props["coat_affect_roughness"].clear(); - props["thin_film_thickness"].clear(); - props["thin_film_IOR"].clear(); - props["emission"].clear(); - props["emission_color"].clear(); props["opacity"].clear(); props["base_color_image"].clear(); @@ -224,7 +212,8 @@ static void clearAllProperties(Properties& props) props["specular_color_image"].clear(); props["coat_color_image"].clear(); props["coat_roughness_image"].clear(); - + props["emission_color_image"].clear(); + props["opacity_image"].clear(); props["normal_image"].clear(); props["displacement_image"].clear(); } @@ -563,6 +552,51 @@ TEST_P(MaterialTest, TestMaterialSetTransmission) pRenderer->waitForTask(); } +// Test emission properties. +TEST_P(MaterialTest, TestMaterialEmission) +{ + // Create the default scene and renderer. + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + + // If the renderer is null, this renderer type is not supported, so skip the rest of the test. + if (!pRenderer) + { + return; + } + + // Create a material with a non-zero emission value and a specific emission color, along with + // minimal diffuse / specular reflectance so that the emission is dominant. + Path material("EmissionMaterial"); + pScene->setMaterialProperties(material, + { { "emission", 1.0f }, { "emission_color", vec3(0.0f, 1.0f, 0.0f) }, { "base", 0.1f }, + { "specular", 0.1f } }); + + // Create a teapot geometry object. + Path geometry = createTeapotGeometry(*pScene); + + // Create an instance of the geometry, using the test material. + Properties instProps; + instProps[Names::InstanceProperties::kMaterial] = material; + EXPECT_TRUE(pScene->addInstance(Path("instance0"), geometry, instProps)); + + // Render and compare against the baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "Emission", "Materials"); + + // Load a test image from disk as an Aurora image. + const std::string imageFilePath = dataPath() + "/Textures/Mr._Smiley_Face.png"; + TestHelpers::ImageData imageData; + loadImage(imageFilePath, &imageData); + const Path kImagePath = "EmissionColorImage"; + pScene->setImageDescriptor(kImagePath, imageData.descriptor); + + // Set the image as the emission color image on the material. + pScene->setMaterialProperties(material, { { "emission_color_image", kImagePath } }); + + // Render and compare against the baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "EmissionImage", "Materials"); +} + // Test more advanced material properties using baseline image testing TEST_P(MaterialTest, TestMaterialAdvancedMaterialProperties) { @@ -794,7 +828,7 @@ TEST_P(MaterialTest, TestMaterialAdvancedMaterialProperties) } } -// Test material creation using MaterialX +// Test material type creation using MaterialX TEST_P(MaterialTest, TestMaterialTypes) { // No MaterialX on HGI yet. @@ -951,10 +985,7 @@ TEST_P(MaterialTest, TestMaterialX) // Render the scene and check baseline image. ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "_FromString", "Materials"); - // On pathtracer we don't actually support setting arbritary materialX parameters, so mix1_mix - // won't exist. - ASSERT_THROW(pScene->setMaterialProperties(testMaterial, { { "mix1_mix", 0.1f } }), - TestHelpers::AuroraLoggerException); + pScene->setMaterialProperties(testMaterial, { { "mix_amount/value", 0.9f } }); pScene->setInstanceProperties(instance, instProps); @@ -997,10 +1028,49 @@ TEST_P(MaterialTest, TestHdAuroraMaterialX) ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "_HdAuroraMtlX", "Materials"); } +// Test material creation using MaterialX file dumped from HdAurora that has a texture. +TEST_P(MaterialTest, TestHdAuroraTextureMaterialX) +{ + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + if (!pRenderer) + return; + + setDefaultRendererPathTracingIterations(256); + + setupAssetPaths(); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + // Create teapot geom. + Path geometry = createTeapotGeometry(*pScene); + + // Try loading a MtlX file dumped from HdAurora. + Path material("HdAuroraMaterial"); + pScene->setMaterialType(material, Names::MaterialTypes::kMaterialXPath, + dataPath() + "/Materials/HdAuroraTextureTest.mtlx"); + + // Add to scene. + Path instance("HdAuroraInstance"); + Properties instProps; + instProps[Names::InstanceProperties::kMaterial] = material; + EXPECT_TRUE(pScene->addInstance(instance, geometry, instProps)); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "_HdAuroraMtlX", "Materials"); +} + // Test different settings for isFlipImageYEnabled option. // Disabled as this testcase fails with error in MaterialGenerator::generate -TEST_P(MaterialTest, DISABLED_TestMaterialXFlipImageY) +TEST_P(MaterialTest, TestMaterialXFlipImageY) { + // This mtlx file requires support ADSK materialX support. + if (!adskMaterialXSupport()) + return; + // No MaterialX on HGI yet. if (!isDirectX()) return; @@ -1048,10 +1118,123 @@ TEST_P(MaterialTest, DISABLED_TestMaterialXFlipImageY) } } +// Test material creation using MaterialX +TEST_P(MaterialTest, TestLotsOfMaterialX) +{ + // No MaterialX on HGI yet. + if (!isDirectX()) + return; + + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + setDefaultRendererPathTracingIterations(256); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + // clang-format off + // Test Material + string testMtl0 = R""""( + + + + + + + + + + + + + + + )""""; + + string testMtl1 = R""""( + + + + + + + + + + + + + + )""""; + + string testMtl2 = R""""( + + + + + + + + + + + + + )""""; + + string testMtl3 = R""""( + + + + + + + + + + + + )""""; + + // clang-format on + + // Create teapot instance. + Path geometry = createTeapotGeometry(*pScene); + Path testMaterial0("TestMaterialX0"); + pScene->setMaterialType(testMaterial0, Names::MaterialTypes::kMaterialX, testMtl0); + Path testMaterial1("TestMaterialX1"); + pScene->setMaterialType(testMaterial1, Names::MaterialTypes::kMaterialX, testMtl1); + Path testMaterial2("TestMaterialX2"); + pScene->setMaterialType(testMaterial2, Names::MaterialTypes::kMaterialX, testMtl2); + Path testMaterial3("TestMaterialX3"); + pScene->setMaterialType(testMaterial3, Names::MaterialTypes::kMaterialX, testMtl3); + + EXPECT_TRUE(pScene->addInstance("MaterialXInstance0", geometry, + { { Names::InstanceProperties::kMaterial, testMaterial0 }, + { Names::InstanceProperties::kTransform, translate(vec3(+1.5, 0.0, 4.0)) } })); + EXPECT_TRUE(pScene->addInstance("MaterialXInstance1", geometry, + { { Names::InstanceProperties::kMaterial, testMaterial1 }, + { Names::InstanceProperties::kTransform, translate(vec3(-1.5, 0.0, 4.0)) } })); + EXPECT_TRUE(pScene->addInstance("MaterialXInstance2", geometry, + { { Names::InstanceProperties::kMaterial, testMaterial2 }, + { Names::InstanceProperties::kTransform, translate(vec3(+1.5, -2.0, 4.0)) } })); + EXPECT_TRUE(pScene->addInstance("MaterialXInstance3", geometry, + { { Names::InstanceProperties::kMaterial, testMaterial3 }, + { Names::InstanceProperties::kTransform, translate(vec3(-1.5, -2.0, 4.0)) } })); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName(), "Materials"); +} + // Test different MtlX file that loads a BMP. // Disabled as this testcase fails with error in MaterialGenerator::generate -TEST_P(MaterialTest, DISABLED_TestMaterialXBMP) +TEST_P(MaterialTest, TestMaterialXBMP) { + // This mtlx file requires support ADSK materialX support. + if (!adskMaterialXSupport()) + return; // Create the default scene (also creates renderer) auto pScene = createDefaultScene(); @@ -1143,9 +1326,85 @@ TEST_P(MaterialTest, TestMaterialTransparency) ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "Opacity", "Materials"); } +TEST_P(MaterialTest, TestMaterialShadowTransparency) +{ + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, -.5f, 1))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1, 1, 1))); + defaultDistantLight()->values().setFloat(Aurora::Names::LightProperties::kIntensity, 2.0f); + defaultDistantLight()->values().setFloat( + Aurora::Names::LightProperties::kAngularDiameter, 0.04f); + + // Load pixels for test image file. + const std::string txtName = dataPath() + "/Textures/Triangle.png"; + + // Load image + TestHelpers::ImageData imageData; + loadImage(txtName, &imageData); + + // Create the image. + const Path kImagePath = "OpacityImage"; + pScene->setImageDescriptor(kImagePath, imageData.descriptor); + + // Constant colors. + vec3 color0(0.5f, 1.0f, 0.3f); + vec3 opacity0(0.5f, 0.5f, 0.3f); + + // Create geometry for teapot and plane geometry. + Path planeGeom = createPlaneGeometry(*pScene); + Path teapotGeom = createTeapotGeometry(*pScene); + + // Create materials. + Path transpMtl("TransparentMaterial"); + pScene->setMaterialProperties(transpMtl, {}); + Path opaqueMtl("OpaqueMaterial"); + pScene->setMaterialProperties(opaqueMtl, { { "base_color", vec3(1, 1, 1) } }); + + // Create opaque plane instance behind a transparent teapot instance. + mat4 xform = translate(vec3(0, 0.5f, +2)) * scale(vec3(5, 5, 5)); + Path planeInst("PlaneInstance"); + Properties planeInstProps; + planeInstProps[Names::InstanceProperties::kMaterial] = opaqueMtl; + planeInstProps[Names::InstanceProperties::kTransform] = xform; + EXPECT_TRUE(pScene->addInstance(planeInst, planeGeom, planeInstProps)); + + Path teapotInst("TeapotInstance"); + Properties teapotInstProps; + teapotInstProps[Names::InstanceProperties::kMaterial] = transpMtl; + EXPECT_TRUE(pScene->addInstance(teapotInst, teapotGeom, teapotInstProps)); + + // Render baseline image with transmission. + pScene->setMaterialProperties( + transpMtl, { { "transmission", 0.5f }, { "thin_walled", false } }); + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "Transmission", "Materials"); + + // Render baseline image with transmission and thin_walled flag set. + pScene->setMaterialProperties(transpMtl, { { "opacity_image", kImagePath } }); + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "OpacityImage", "Materials"); + + // Render baseline image with opacity, no transmission and thin_walled flag not set. + pScene->setMaterialProperties(transpMtl, + { { "transmission", 0.0f }, { "opacity_image", "" }, { "opacity", opacity0 }, + { "thin_walled", false } }); + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "Opacity", "Materials"); +} + // Disabled as this testcase fails with error in MaterialGenerator::generate -TEST_P(MaterialTest, DISABLED_TestMtlXSamplers) +TEST_P(MaterialTest, TestMtlXSamplers) { + // This mtlx file requires support ADSK materialX support. + if (!adskMaterialXSupport()) + return; + // Create the default scene (also creates renderer) auto pScene = createDefaultScene(); auto pRenderer = defaultRenderer(); @@ -1183,8 +1442,12 @@ TEST_P(MaterialTest, DISABLED_TestMtlXSamplers) // MaterialX as layered materials // Disabled as this testcase fails with error in MaterialGenerator::generate -TEST_P(MaterialTest, DISABLED_TestMaterialMaterialXLayers) +TEST_P(MaterialTest, TestMaterialMaterialXLayers) { + // This mtlx file requires support ADSK materialX support. + if (!adskMaterialXSupport()) + return; + // No MaterialX on HGI yet. if (!isDirectX()) return; @@ -1304,6 +1567,92 @@ TEST_P(MaterialTest, DISABLED_TestMaterialMaterialXLayers) ASSERT_BASELINE_IMAGE_PASSES(currentTestName() + "_Removed"); } +// Normal map image test. +TEST_P(MaterialTest, TestNormalMapMaterialX) +{ + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + setupAssetPaths(); + + defaultDistantLight()->values().setFloat(Aurora::Names::LightProperties::kIntensity, 2.0f); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0.0f, -0.25f, +1.0f))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1, 1, 1))); + + // Create geometry. + Path planePath = createPlaneGeometry(*pScene); + Path teapotPath = createTeapotGeometry(*pScene); + + // Create material from mtlx document containing normal map. + string materialXFullPath = dataPath() + "/Materials/NormalMapExample.mtlx"; + string processedMtlXString = loadAndProcessMaterialXFile(materialXFullPath); + EXPECT_FALSE(processedMtlXString.empty()); + const Path kMaterialPath = "NormalMaterial"; + pScene->setMaterialType(kMaterialPath, Names::MaterialTypes::kMaterialX, processedMtlXString); + + mat4 scaleMtx = scale(vec3(2, 2, 2)); + + // Create geometry with the material. + Properties instProps; + instProps[Names::InstanceProperties::kMaterial] = kMaterialPath; + instProps[Names::InstanceProperties::kTransform] = scaleMtx; + EXPECT_TRUE(pScene->addInstance(nextPath(), planePath, instProps)); + + instProps[Names::InstanceProperties::kMaterial] = kMaterialPath; + instProps[Names::InstanceProperties::kTransform] = mat4(); + EXPECT_TRUE(pScene->addInstance(nextPath(), teapotPath, instProps)); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName(), "Materials"); +} + +// Test object space normal in materialX. +TEST_P(MaterialTest, TestObjectSpaceMaterialX) +{ + // This mtlx file requires support ADSK materialX support. + if (!adskMaterialXSupport()) + return; + + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + setDefaultRendererPathTracingIterations(256); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + setupAssetPaths(); + + // Create teapot geom. + Path geometry = createTeapotGeometry(*pScene); + + // Try loading a MtlX file dumped from Oxide. + Path material("ThreadMaterial"); + pScene->setMaterialType( + material, Names::MaterialTypes::kMaterialXPath, dataPath() + "/Materials/TestThread.mtlx"); + + // Add to scene. + Path instance("ThreadInstance"); + Properties instProps; + instProps[Names::InstanceProperties::kMaterial] = material; + EXPECT_TRUE(pScene->addInstance(instance, geometry, instProps)); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES_IN_FOLDER(currentTestName() + "_ThreadMtlX", "Materials"); +} + INSTANTIATE_TEST_SUITE_P(MaterialTests, MaterialTest, TEST_SUITE_RENDERER_TYPES()); } // namespace diff --git a/Tests/Aurora/Tests/TestPaths.cpp b/Tests/Aurora/Tests/TestPaths.cpp index 4d38af6..32f5247 100644 --- a/Tests/Aurora/Tests/TestPaths.cpp +++ b/Tests/Aurora/Tests/TestPaths.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable unit test as causes failure in debug mode +#if _DEBUG +#define DISABLE_UNIT_TESTS +#endif + #if !defined(DISABLE_UNIT_TESTS) #include diff --git a/Tests/Aurora/Tests/TestRenderer.cpp b/Tests/Aurora/Tests/TestRenderer.cpp index d1f74a5..f5cb5ea 100644 --- a/Tests/Aurora/Tests/TestRenderer.cpp +++ b/Tests/Aurora/Tests/TestRenderer.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable unit test as causes failure in debug mode +#if _DEBUG +#define DISABLE_UNIT_TESTS +#endif + #if !defined(DISABLE_UNIT_TESTS) #include @@ -437,6 +442,43 @@ TEST_P(RendererTest, TestRendererEmptySceneBounds) ASSERT_NO_FATAL_FAILURE(pRenderer->render()); } +// Test ground plane. +TEST_P(RendererTest, TestRendererGroundPlane) +{ + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + Aurora::IGroundPlanePtr pGroundPlane = defaultRenderer()->createGroundPlanePointer(); + pGroundPlane->values().setBoolean("enabled", true); + pGroundPlane->values().setFloat3("position", value_ptr(vec3(0, 0, 0))); + pGroundPlane->values().setFloat3("normal", value_ptr(vec3(0, 1, 0))); + pGroundPlane->values().setFloat("shadow_opacity", 1.0f); + pGroundPlane->values().setFloat("reflection_opacity", 1.0f); + pScene->setGroundPlanePointer(pGroundPlane); + + setDefaultRendererCamera(vec3(0, 1, -5), vec3(0, 0, 0)); + + // Set arbritary bounds. + vec3 boundsMin(-1, -1, -1); + vec3 boundsMax(+1, +1, +1); + pScene->setBounds(boundsMin, boundsMax); + + Path kMaterialPath = "MaterialPath"; + pScene->setMaterialProperties(kMaterialPath, { { "base_color", vec3(1, 0, 0) } }); + + // Create a teapot instance with a default material. + Path geometry = createTeapotGeometry(*pScene); + EXPECT_TRUE(pScene->addInstance( + "TeapotInstance", geometry, { { Names::InstanceProperties::kMaterial, kMaterialPath } })); + + // Ensure result is correct by comparing to the baseline image. + ASSERT_BASELINE_IMAGE_PASSES(currentTestName()); +} + // Test null environment. TEST_P(RendererTest, TestRendererSetNullEnvironment) { @@ -1092,8 +1134,7 @@ TEST_P(RendererTest, TestLocales) pRenderer->render(); // Ensure no warnings or errors. - // TODO: Fix the space input which generates these two warnings in path tracing (See OGSMOD-1255) - ASSERT_EQ(errorAndWarningCount(), (i+1)); + ASSERT_EQ(errorAndWarningCount(), 0); } } @@ -1180,9 +1221,12 @@ TEST_P(RendererTest, TestRendererBackFaceLighting) return; // Rotate directional light facing to back-face of quad. - glm::vec3 lightDirec(0, 0, -1); - glm::vec3 lightColor(1, 1, 1); - pScene->setLight(2.0, &lightColor.x, &lightDirec.x); + defaultDistantLight()->values().setFloat( + Aurora::Names::LightProperties::kIntensity, 2.0f); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0, 0, -1))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1, 1, 1))); // Create a material (setting material type to default triggers creation.) const Path kMaterialPath = "DefaultMaterial"; @@ -1201,6 +1245,74 @@ TEST_P(RendererTest, TestRendererBackFaceLighting) } +// Normal map image test. +TEST_P(RendererTest, TestDebugNormals) +{ + // Create the default scene (also creates renderer) + auto pScene = createDefaultScene(); + auto pRenderer = defaultRenderer(); + + if(!pRenderer) + return; + + Aurora::IValues& options = pRenderer->options(); + + setDefaultRendererSampleCount(1); + + //Set debug mode to normals (kDebugModeNormal==3) + options.setInt("debugMode", 3); + + // If pRenderer is null this renderer type not supported, skip rest of the test. + if (!pRenderer) + return; + + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kDirection, value_ptr(glm::vec3(0.0f, -0.25f, +1.0f))); + defaultDistantLight()->values().setFloat3( + Aurora::Names::LightProperties::kColor, value_ptr(glm::vec3(1, 1, 1))); + + // Load pixels for test image file. + const std::string txtName = dataPath() + "/Textures/Verde_Guatemala_Slatted_Marble_normal.png"; + // Load image + TestHelpers::ImageData imageData; + loadImage(txtName, &imageData); + + // Create the image. + const Path kImagePath = "NormalImage"; + imageData.descriptor.linearize = false; + pScene->setImageDescriptor(kImagePath, imageData.descriptor); + + // Create geometry. + Path planePath = createPlaneGeometry(*pScene, vec2(0.5f,0.5f)); + Path teapotPath = createTeapotGeometry(*pScene); + + // Create matrix and material for first instance (which is linearized). + const Path kMaterialPath = "NormalMaterial"; + pScene->setMaterialProperties(kMaterialPath, { { "normal_image", kImagePath },{ "normal_image_scale", vec2(5.f, 5.f)} }); + const Path kBasicMaterialPath = "BasicMaterial"; + pScene->setMaterialProperties(kBasicMaterialPath, { { "base_color", vec3(0,1,0) } }); + + mat4 scaleMtx = scale(vec3(2, 2, 2)); + + // Create geometry with the material. + Properties instProps; + instProps[Names::InstanceProperties::kMaterial] = kMaterialPath; + instProps[Names::InstanceProperties::kTransform] = scaleMtx; + EXPECT_TRUE(pScene->addInstance(nextPath(), planePath, instProps)); + + instProps[Names::InstanceProperties::kMaterial] = kMaterialPath; + instProps[Names::InstanceProperties::kTransform] = mat4(); + EXPECT_TRUE(pScene->addInstance(nextPath(), teapotPath, instProps)); + + instProps[Names::InstanceProperties::kMaterial] = kBasicMaterialPath; + instProps[Names::InstanceProperties::kTransform] = translate(vec3(0,-1.5,0)); + EXPECT_TRUE(pScene->addInstance(nextPath(), teapotPath, instProps)); + + // Render the scene and check baseline image. + ASSERT_BASELINE_IMAGE_PASSES(currentTestName()); +} + + INSTANTIATE_TEST_SUITE_P(RendererTests, RendererTest, TEST_SUITE_RENDERER_TYPES()); } // namespace diff --git a/Tests/AuroraInternals/AuroraInternalsMain.cpp b/Tests/AuroraInternals/AuroraInternalsMain.cpp index ef1d037..2a616e8 100644 --- a/Tests/AuroraInternals/AuroraInternalsMain.cpp +++ b/Tests/AuroraInternals/AuroraInternalsMain.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/AuroraInternals/CMakeLists.txt b/Tests/AuroraInternals/CMakeLists.txt index e4c1dad..dc01663 100644 --- a/Tests/AuroraInternals/CMakeLists.txt +++ b/Tests/AuroraInternals/CMakeLists.txt @@ -1,5 +1,12 @@ project(AuroraInternalsTests) +# Add the preprocessor definition for MaterialX flag. +add_compile_definitions(ENABLE_MATERIALX=1) + +find_package(MaterialX REQUIRED) # MaterialX SDK +# Alias the namespace to meet the cmake convention on imported targets +add_library(MaterialX::GenGlsl ALIAS MaterialXGenGlsl) + # List of common helper files shared by all tests. TEST_HELPERS_FOLDER variable is set in parent cmake file. set(HELPER_FILES "${TEST_HELPERS_FOLDER}/TestHelpers.cpp" @@ -11,6 +18,8 @@ set(TEST_FILES "Common/TestAssetManager.cpp" "Common/TestProperties.cpp" "Common/TestResources.cpp" + "Common/TestMaterialGenerator.cpp" + "Common/TestUniformBuffer.cpp" ) set(AURORA_DIR "${CMAKE_SOURCE_DIR}/Libraries/Aurora") @@ -23,6 +32,10 @@ set(AURORA_FILES "${AURORA_DIR}/Source/EnvironmentBase.h" "${AURORA_DIR}/Source/MaterialBase.cpp" "${AURORA_DIR}/Source/MaterialBase.h" + "${AURORA_DIR}/Source/MaterialDefinition.cpp" + "${AURORA_DIR}/Source/MaterialDefinition.h" + "${AURORA_DIR}/Source/MaterialShader.cpp" + "${AURORA_DIR}/Source/MaterialShader.h" "${AURORA_DIR}/Source/pch.h" "${AURORA_DIR}/Source/Properties.h" "${AURORA_DIR}/Source/RendererBase.cpp" @@ -33,9 +46,15 @@ set(AURORA_FILES "${AURORA_DIR}/Source/ResourceStub.h" "${AURORA_DIR}/Source/SceneBase.cpp" "${AURORA_DIR}/Source/SceneBase.h" + "${AURORA_DIR}/Source/UniformBuffer.cpp" + "${AURORA_DIR}/Source/UniformBuffer.h" "${AURORA_DIR}/Source/Aurora.cpp" "${AURORA_DIR}/Source/AuroraNames.cpp" "${AURORA_DIR}/Source/WindowsHeaders.h" + "${AURORA_DIR}/Source/MaterialX/MaterialGenerator.h" + "${AURORA_DIR}/Source/MaterialX/MaterialGenerator.cpp" + "${AURORA_DIR}/Source/MaterialX/BSDFCodeGenerator.h" + "${AURORA_DIR}/Source/MaterialX/BSDFCodeGenerator.cpp" ) @@ -52,7 +71,7 @@ source_group("Helpers" FILES ${HELPER_FILES}) source_group("Tests" FILES ${TEST_FILES}) source_group("Aurora" FILES ${AURORA_FILES}) -# Set custom ouput properties. +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Tests" RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" @@ -71,6 +90,7 @@ PRIVATE glm::glm stb::stb Foundation + MaterialXGenGlsl ) # Add helpers include folder. diff --git a/Tests/AuroraInternals/Common/TestAssetManager.cpp b/Tests/AuroraInternals/Common/TestAssetManager.cpp index f0e60b4..a4e573d 100644 --- a/Tests/AuroraInternals/Common/TestAssetManager.cpp +++ b/Tests/AuroraInternals/Common/TestAssetManager.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/AuroraInternals/Common/TestMaterialGenerator.cpp b/Tests/AuroraInternals/Common/TestMaterialGenerator.cpp new file mode 100644 index 0000000..1f4a472 --- /dev/null +++ b/Tests/AuroraInternals/Common/TestMaterialGenerator.cpp @@ -0,0 +1,234 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if !defined(DISABLE_UNIT_TESTS) + +#include "TestHelpers.h" +#include + +// Include the Aurora PCH (this is an internal test so needs all the internal Aurora includes) +#include "pch.h" + +#include "MaterialShader.h" +#include "MaterialX/MaterialGenerator.h" + +namespace +{ + +// Test fixture accessing test asset data path. +class MaterialGeneratorTest : public ::testing::Test +{ +public: + MaterialGeneratorTest() : _dataPath(TestHelpers::kSourceRoot + "/Tests/Assets") {} + ~MaterialGeneratorTest() {} + const std::string& dataPath() { return _dataPath; } + +protected: + std::string _dataPath; +}; +const char* materialXString0 = R""""( + + + + + + + + + + + + + + + )""""; + +const char* materialXString1 = R""""( + + + + + + + + + + + + + + + )""""; + +const char* materialXString2 = R""""( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +)""""; + +// Basic material generator test. +TEST_F(MaterialGeneratorTest, BasicTest) +{ + string mtlxFolder = Foundation::getModulePath() + "MaterialX"; + Aurora::MaterialXCodeGen::MaterialGenerator matGen(mtlxFolder); + + Aurora::MaterialDefinitionPtr pMtlDef0 = matGen.generate(materialXString0); + ASSERT_NE(pMtlDef0, nullptr); + ASSERT_STREQ(pMtlDef0->source().uniqueId.c_str(), "MaterialX_d183faa1b8cb18d7"); + ASSERT_EQ(pMtlDef0->defaults().properties.size(), 7); + ASSERT_EQ(pMtlDef0->defaults().propertyDefinitions.size(), 7); + ASSERT_NEAR(pMtlDef0->defaults().properties[2].asFloat(), 0.8f, 0.01f); + ASSERT_EQ(pMtlDef0->defaults().textureNames.size(), 0); + ASSERT_EQ(pMtlDef0->defaults().textures.size(), 0); + + Aurora::MaterialDefinitionPtr pMtlDef1 = matGen.generate(materialXString1); + ASSERT_NE(pMtlDef1, nullptr); + ASSERT_STREQ(pMtlDef1->source().uniqueId.c_str(), "MaterialX_d183faa1b8cb18d7"); + ASSERT_EQ(pMtlDef1->defaults().properties.size(), 7); + ASSERT_EQ(pMtlDef1->defaults().propertyDefinitions.size(), 7); + ASSERT_NEAR(pMtlDef1->defaults().properties[2].asFloat(), 0.1f, 0.01f); + ASSERT_EQ(pMtlDef1->defaults().textureNames.size(), 0); + ASSERT_EQ(pMtlDef1->defaults().textures.size(), 0); + + Aurora::MaterialShaderDefinition shaderDef0; + pMtlDef0->getShaderDefinition(shaderDef0); + + Aurora::MaterialShaderDefinition shaderDef1; + pMtlDef1->getShaderDefinition(shaderDef1); + ASSERT_TRUE(shaderDef0.compare(shaderDef1)); + + Aurora::MaterialDefinitionPtr pMtlDef2 = matGen.generate(materialXString2); + ASSERT_NE(pMtlDef2, nullptr); + ASSERT_STREQ(pMtlDef2->source().uniqueId.c_str(), "MaterialX_58010dd45510ab94"); + ASSERT_EQ(pMtlDef2->defaults().properties.size(), 6); + ASSERT_EQ(pMtlDef2->defaults().propertyDefinitions.size(), 6); + ASSERT_EQ(pMtlDef2->defaults().textureNames.size(), 2); + ASSERT_EQ(pMtlDef2->defaults().textures.size(), 2); + ASSERT_STREQ(pMtlDef2->defaults().textureNames[1].c_str(), "opacity_image"); + ASSERT_STREQ(pMtlDef2->defaults().textures[1].defaultFilename.c_str(), + "../Textures/fishscale_roughness.png"); + + Aurora::MaterialShaderDefinition shaderDef2; + pMtlDef2->getShaderDefinition(shaderDef2); + ASSERT_FALSE(shaderDef0.compare(shaderDef2)); + + Aurora::MaterialDefinitionPtr pMtlDef2Dupe = matGen.generate(materialXString2); + Aurora::MaterialDefinition* pMtlDef2Raw = pMtlDef2.get(); + ASSERT_EQ(pMtlDef2Dupe.get(), pMtlDef2Raw); + ASSERT_EQ(pMtlDef2.use_count(), 2); + pMtlDef2Dupe.reset(); + ASSERT_EQ(pMtlDef2.use_count(), 1); + pMtlDef2.reset(); +} + +TEST_F(MaterialGeneratorTest, MaterialShaderLibraryTest) +{ + string mtlxFolder = Foundation::getModulePath() + "MaterialX"; + Aurora::MaterialXCodeGen::MaterialGenerator matGen(mtlxFolder); + + Aurora::MaterialShaderLibrary shaderLib({ "foo", "bar" }); + + Aurora::MaterialDefinitionPtr pMtlDef0 = matGen.generate(materialXString0); + Aurora::MaterialShaderDefinition shaderDef0; + pMtlDef0->getShaderDefinition(shaderDef0); + + Aurora::MaterialShaderPtr pShader0 = shaderLib.acquire(shaderDef0); + ASSERT_NE(pShader0, nullptr); + ASSERT_EQ(pShader0->libraryIndex(), 0); + ASSERT_EQ(pShader0->entryPoints().size(), 2); + + Aurora::MaterialDefinitionPtr pMtlDef1 = matGen.generate(materialXString1); + Aurora::MaterialShaderDefinition shaderDef1; + pMtlDef1->getShaderDefinition(shaderDef1); + + Aurora::MaterialShaderPtr pShader1 = shaderLib.acquire(shaderDef1); + ASSERT_NE(pShader1, nullptr); + ASSERT_EQ(pShader1.get(), pShader0.get()); + + Aurora::MaterialDefinitionPtr pMtlDef2 = matGen.generate(materialXString2); + Aurora::MaterialShaderDefinition shaderDef2; + pMtlDef2->getShaderDefinition(shaderDef2); + + Aurora::MaterialShaderPtr pShader2 = shaderLib.acquire(shaderDef2); + ASSERT_NE(pShader2, nullptr); + ASSERT_EQ(pShader2->libraryIndex(), 1); + ASSERT_NE(pShader2.get(), pShader0.get()); + + int numCompiled = 0; + int numDestroyed = 0; + auto compileFunc = [&numCompiled](const Aurora::MaterialShader&) { + numCompiled++; + return true; + }; + auto destroyFunc = [&numDestroyed](int) { numDestroyed++; }; + + shaderLib.update(compileFunc, destroyFunc); + ASSERT_EQ(numCompiled, 2); + ASSERT_EQ(numDestroyed, 0); + + pShader2.reset(); + shaderLib.update(compileFunc, destroyFunc); + ASSERT_EQ(numCompiled, 2); + ASSERT_EQ(numDestroyed, 1); + + pShader0->incrementRefCount("foo"); + shaderLib.update(compileFunc, destroyFunc); + ASSERT_EQ(numCompiled, 3); + ASSERT_EQ(numDestroyed, 1); + + Aurora::MaterialShaderPtr pShader3 = shaderLib.acquire(shaderDef2); + ASSERT_NE(pShader3, nullptr); + ASSERT_EQ(pShader3->libraryIndex(), 1); + + shaderLib.update(compileFunc, destroyFunc); + ASSERT_EQ(numCompiled, 4); + ASSERT_EQ(numDestroyed, 1); +} + +} // namespace + +#endif diff --git a/Tests/AuroraInternals/Common/TestProperties.cpp b/Tests/AuroraInternals/Common/TestProperties.cpp index de716a0..96188f0 100644 --- a/Tests/AuroraInternals/Common/TestProperties.cpp +++ b/Tests/AuroraInternals/Common/TestProperties.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/AuroraInternals/Common/TestResources.cpp b/Tests/AuroraInternals/Common/TestResources.cpp index 0139752..1b0f3ed 100644 --- a/Tests/AuroraInternals/Common/TestResources.cpp +++ b/Tests/AuroraInternals/Common/TestResources.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -268,57 +268,58 @@ TEST_F(ResourcesTest, TrackerTest) Aurora::ResourceMap resources; shared_ptr pFlub0 = make_shared("flub0", resources, tracker); shared_ptr pFlub1 = make_shared("flub1", resources, tracker); - ASSERT_FALSE(tracker.changed()); + ASSERT_FALSE(tracker.changedThisFrame()); tracker.update(); - ResourceNotifier& notifier = tracker.active(); - ASSERT_EQ(notifier.count(), 0); - ASSERT_TRUE(notifier.modified()); + + ResourceNotifier& activeNotifier = tracker.active(); + ASSERT_EQ(activeNotifier.count(), 0); + ASSERT_FALSE(activeNotifier.changedThisFrame()); pFlub0->setProperties({ { "uv", vec2(0.5, 0.5) } }); - ASSERT_TRUE(tracker.changed()); + ASSERT_FALSE(tracker.changedThisFrame()); // Activate resource now stuff has changed. pFlub0->incrementPermanentRefCount(); - ASSERT_TRUE(tracker.changed()); + ASSERT_FALSE(tracker.changedThisFrame()); tracker.update(); - ASSERT_EQ(notifier.count(), 1); - ASSERT_TRUE(notifier.modified()); - auto& activeRes = notifier.resources(); + ASSERT_EQ(activeNotifier.count(), 1); + ASSERT_TRUE(activeNotifier.changedThisFrame()); + auto& activeRes = activeNotifier.resources(); ASSERT_EQ(activeRes[0].ptr(), pFlub0->resource().get()); - ASSERT_TRUE(tracker.changed()); + ASSERT_TRUE(tracker.changedThisFrame()); // Update again, active count remains at one, but no changes. tracker.update(); - ASSERT_EQ(notifier.count(), 1); - ASSERT_TRUE(notifier.modified()); - ASSERT_TRUE(tracker.changed()); + ASSERT_EQ(activeNotifier.count(), 1); + ASSERT_FALSE(activeNotifier.changedThisFrame()); + ASSERT_FALSE(tracker.changedThisFrame()); - // Modify active resource, changes reported. pFlub0->setProperties({ { "uv", vec2(0.75, 0.5) } }); - ASSERT_TRUE(tracker.changed()); + ASSERT_FALSE(tracker.changedThisFrame()); tracker.update(); - ASSERT_EQ(notifier.count(), 1); - ASSERT_TRUE(notifier.modified()); - ASSERT_TRUE(tracker.changed()); + ASSERT_EQ(activeNotifier.count(), 1); + ASSERT_FALSE(activeNotifier.changedThisFrame()); + ASSERT_TRUE(tracker.changedThisFrame()); + tracker.update(); pFlub1->incrementPermanentRefCount(); - ASSERT_TRUE(tracker.changed()); + ASSERT_FALSE(tracker.changedThisFrame()); tracker.update(); - ASSERT_EQ(notifier.count(), 2); - ASSERT_EQ(notifier.resources()[0].ptr(), pFlub0->resource().get()); - ASSERT_EQ(notifier.resources()[1].ptr(), pFlub1->resource().get()); - ASSERT_TRUE(notifier.modified()); - ASSERT_TRUE(tracker.changed()); + ASSERT_EQ(activeNotifier.count(), 2); + ASSERT_EQ(activeNotifier.resources()[0].ptr(), pFlub0->resource().get()); + ASSERT_EQ(activeNotifier.resources()[1].ptr(), pFlub1->resource().get()); + ASSERT_TRUE(activeNotifier.changedThisFrame()); + ASSERT_TRUE(tracker.changedThisFrame()); pFlub0->decrementPermanentRefCount(); pFlub1->decrementPermanentRefCount(); - ASSERT_TRUE(tracker.changed()); + ASSERT_TRUE(tracker.changedThisFrame()); tracker.update(); - ASSERT_TRUE(tracker.changed()); - ASSERT_EQ(notifier.count(), 0); + ASSERT_TRUE(tracker.changedThisFrame()); + ASSERT_EQ(activeNotifier.count(), 0); } size_t hashFlub(const Flub& flub) @@ -357,6 +358,90 @@ TEST_F(ResourcesTest, HashLookupTest) ASSERT_EQ(lookup.count(), 0); } +TEST_F(ResourcesTest, TrackerUpdateTest) +{ + // Create two active resources. + TypedResourceTracker tracker; + Aurora::ResourceMap resources; + shared_ptr pFlub0 = make_shared("flub0", resources, tracker); + shared_ptr pFlub1 = make_shared("flub1", resources, tracker); + pFlub0->incrementPermanentRefCount(); + pFlub1->incrementPermanentRefCount(); + + // Get the active and modified notifier from the tracker. + ResourceNotifier& activeNotifier = tracker.active(); + ResourceNotifier& modifiedNotifier = tracker.modified(); + + // Update has not been called so nothing is shown as changed yet. + ASSERT_FALSE(activeNotifier.changedThisFrame()); + ASSERT_EQ(modifiedNotifier.count(), 0); + ASSERT_FALSE(tracker.changedThisFrame()); + + // Update the tracker + tracker.update(); + + // Active notifier has now changed, and two modified object this frame. + ASSERT_EQ(modifiedNotifier.count(), 2); + ASSERT_TRUE(activeNotifier.changedThisFrame()); + ASSERT_TRUE(tracker.changedThisFrame()); + + // Update the tracker (with no changes at all) + tracker.update(); + + // Nothing has changed, even after update. + ASSERT_EQ(modifiedNotifier.count(), 0); + ASSERT_FALSE(activeNotifier.changedThisFrame()); + ASSERT_FALSE(tracker.changedThisFrame()); + + // Change a property + pFlub0->setProperties({ { "uv", vec2(0, 1) } }); + + // Update the tracker + tracker.update(); + + // The modified list is non-empty. + ASSERT_EQ(modifiedNotifier.count(), 1); + ASSERT_TRUE(tracker.changedThisFrame()); + // Active notifier not changed this frame as nothing activated or deactivated. + ASSERT_FALSE(activeNotifier.changedThisFrame()); + + // Update with no changes. + tracker.update(); + + // Nothing was changed after update. + ASSERT_EQ(modifiedNotifier.count(), 0); + ASSERT_FALSE(activeNotifier.changedThisFrame()); + ASSERT_FALSE(tracker.changedThisFrame()); + + // Create and activate a new object. + shared_ptr pFlub2 = make_shared("flub2", resources, tracker); + pFlub2->incrementPermanentRefCount(); + + // Update the tracker + tracker.update(); + + // Update has now been called so modified list is non-empty (As an activate counts as + // modification). + ASSERT_EQ(modifiedNotifier.count(), 1); + ASSERT_TRUE(tracker.changedThisFrame()); + // Active notifier has changed and now has three entries. + ASSERT_TRUE(activeNotifier.changedThisFrame()); + ASSERT_EQ(activeNotifier.count(), 3); + + // Delete an object. + pFlub2->decrementPermanentRefCount(); + + // Update the tracker + tracker.update(); + + // Modified list is empty as deactivations don't count as modifications. + ASSERT_EQ(modifiedNotifier.count(), 0); + ASSERT_TRUE(tracker.changedThisFrame()); + // Active notifier has changed and now has twp entries. + ASSERT_TRUE(activeNotifier.changedThisFrame()); + ASSERT_EQ(activeNotifier.count(), 2); +} + } // namespace #endif diff --git a/Tests/AuroraInternals/Common/TestUniformBuffer.cpp b/Tests/AuroraInternals/Common/TestUniformBuffer.cpp new file mode 100644 index 0000000..592fb1e --- /dev/null +++ b/Tests/AuroraInternals/Common/TestUniformBuffer.cpp @@ -0,0 +1,85 @@ +// Copyright 2023 Autodesk, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if !defined(DISABLE_UNIT_TESTS) + +#include "TestHelpers.h" +#include + +// Include the Aurora PCH (this is an internal test so needs all the internal Aurora includes) +#include "pch.h" + +#include "MaterialBase.h" +#include "UniformBuffer.h" + +namespace +{ + +// Test fixture accessing test asset data path. +class UniformBufferTest : public ::testing::Test +{ +public: + UniformBufferTest() : _dataPath(TestHelpers::kSourceRoot + "/Tests/Assets") {} + ~UniformBufferTest() {} + const std::string& dataPath() { return _dataPath; } + +protected: + std::string _dataPath; +}; + +// Basic uniform buffer test, will also auto-generate the default material shaders. +TEST_F(UniformBufferTest, BasicTest) +{ + // Create a default material uniform buffer object. + UniformBuffer defaultUniformBuffer( + MaterialBase::StandardSurfaceUniforms, MaterialBase::StandardSurfaceDefaults.properties); + + // String for comparison error message (empty if no error.) + std::string errMsg; + + // Comment prefix in generated text file. + std::string commentPrefixString = "// Auto-generated by unit test " + + std::string(testing::UnitTest::GetInstance()->current_test_info()->name()) + + " in AuroraExternals test suite.\n\n"; + + // Generate uniform buffer HLSL from uniform buffer object. + std::string hlslString = commentPrefixString + + defaultUniformBuffer.generateHLSLStructAndAccessors("MaterialConstants", "Material_"); + + // Compare HLSL with file on disk. Fail if differences found. + // NOTE: This file is reused as the autogenerated shader file + // Libraries\Aurora\Source\Shaders\DefaultMaterialUniformBuffer.slang, if changes are made to + // default material properties this test file should be copied to + // Libraries\Aurora\Source\Shaders. + errMsg = + TestHelpers::compareTextFile(dataPath() + "/TextFiles/DefaultMaterialUniformBuffer.slang", + hlslString, "HLSL uniform buffer baseline comparison failed. "); + ASSERT_TRUE(errMsg.empty()) << errMsg; + + // Compare HLSL with file on disk. Fail if differences found. + // NOTE: This file is reused as the autogenerated shader file + // Libraries\Aurora\Source\Shaders\DefaultMaterialUniformAccessors.slang, if changes are made to + // default material properties this test file should be copied to + // Libraries\Aurora\Source\Shaders. + std::string hlslAccessorString = + commentPrefixString + defaultUniformBuffer.generateByteAddressBufferAccessors("Material_"); + errMsg = TestHelpers::compareTextFile( + dataPath() + "/TextFiles/DefaultMaterialUniformAccessors.slang", hlslAccessorString, + "HLSL byte address accessor baseline comparison failed. "); + ASSERT_TRUE(errMsg.empty()) << errMsg; +} + +} // namespace + +#endif diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index f92212a..a5e6ec8 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -6,7 +6,6 @@ find_package(OpenImageIO REQUIRED) find_package(OpenGL REQUIRED) find_package(GTest REQUIRED) # Find the GoogleTest unit test package. -find_package(Vulkan REQUIRED) find_package(pxr REQUIRED) # Build list of backends diff --git a/Tests/Foundation/CMakeLists.txt b/Tests/Foundation/CMakeLists.txt index 99dbbe6..29fa1df 100644 --- a/Tests/Foundation/CMakeLists.txt +++ b/Tests/Foundation/CMakeLists.txt @@ -23,7 +23,7 @@ add_executable(${PROJECT_NAME} source_group("Helpers" FILES ${HELPER_FILES}) source_group("Tests" FILES ${TEST_FILES}) -# Set custom ouput properties. +# Set custom output properties. set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "Tests" RUNTIME_OUTPUT_DIRECTORY "${RUNTIME_OUTPUT_DIR}" diff --git a/Tests/Foundation/FoundationMain.cpp b/Tests/Foundation/FoundationMain.cpp index ef1d037..2a616e8 100644 --- a/Tests/Foundation/FoundationMain.cpp +++ b/Tests/Foundation/FoundationMain.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Foundation/Tests/TestLogger.cpp b/Tests/Foundation/Tests/TestLogger.cpp index 434f8e4..243142b 100644 --- a/Tests/Foundation/Tests/TestLogger.cpp +++ b/Tests/Foundation/Tests/TestLogger.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Foundation/Tests/TestMath.cpp b/Tests/Foundation/Tests/TestMath.cpp index 9262d74..693d0d1 100644 --- a/Tests/Foundation/Tests/TestMath.cpp +++ b/Tests/Foundation/Tests/TestMath.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Foundation/Tests/TestUtilities.cpp b/Tests/Foundation/Tests/TestUtilities.cpp index fcf74d2..08ee1cf 100644 --- a/Tests/Foundation/Tests/TestUtilities.cpp +++ b/Tests/Foundation/Tests/TestUtilities.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Helpers/AuroraTestHelpers.cpp b/Tests/Helpers/AuroraTestHelpers.cpp index 9d885e0..a372733 100644 --- a/Tests/Helpers/AuroraTestHelpers.cpp +++ b/Tests/Helpers/AuroraTestHelpers.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ #include "AuroraTestHelpers.h" +#include #include #include #include @@ -23,7 +24,8 @@ #pragma warning(push) // Disabe type conversion warnings intruduced from stb master. -// refer to the commit in stb https://github.com/nothings/stb/commit/b15b04321dfd8a2307c49ad9c5bf3c0c6bcc04cc +// refer to the commit in stb +// https://github.com/nothings/stb/commit/b15b04321dfd8a2307c49ad9c5bf3c0c6bcc04cc #pragma warning(disable : 4244) #define STB_IMAGE_IMPLEMENTATION #include @@ -66,6 +68,24 @@ static const array g_planeNormalArray = { -1.0f, }; +// Tangent data for single planar quad. +static const array g_planeTangentArray = { + 0.0f, + -1.0f, + 0.0f, + + 0.0f, + -1.0f, + 0.0f, + + 0.0f, + -1.0f, + 0.0f, + + 0.0f, + -1.0f, + 0.0f, +}; // UV data for single planar quad. static const array g_planeUVArray = { 0.0f, @@ -88,6 +108,9 @@ static const array g_planeIndexArray = { 3, }; +// Teapot tangents (generated on demand.) +vector gTeapotTangents; + const float* TeapotModel::vertices() { // Get pointer to array generated by from OBJ file. @@ -106,6 +129,21 @@ const float* TeapotModel::uvs() return &gTeapotUVs[0]; } +const float* TeapotModel::tangents() +{ + // Generate the tangents as needed. + if (gTeapotTangents.empty()) + { + gTeapotTangents.resize(gTeapotNormals.size()); + Aurora::Foundation::calculateTangents(gTeapotNormals.size() / 3, gTeapotVertices.data(), + gTeapotNormals.data(), gTeapotUVs.data(), gTeapotIndices.size() / 3, + gTeapotIndices.data(), gTeapotTangents.data()); + } + + // Get pointer to array generated by from OBJ file. + return &gTeapotTangents[0]; +} + uint32_t TeapotModel::verticesCount() { // Get size of array generated by from OBJ file (divide by three for 3D vertices.) @@ -219,25 +257,20 @@ void FixtureBase::testFloat3Value( // Expect an assert if set with wrong type. matProps[name] = 123.0f; - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException) + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) << " " << __FUNCTION__ << " value:" << name << " " << message; - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: typesMatch")) + ; + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) << " " << __FUNCTION__ << " value:" << name << " " << message; ; } else { - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException) + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) << " " << __FUNCTION__ << " value:" << name << " " << message; ; - - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: find(name) != end")) + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) << " " << __FUNCTION__ << " value:" << name << " " << message; - ; } } @@ -261,21 +294,21 @@ void FixtureBase::testFloatValue( // Expect an assert if set with wrong type. matProps[name] = vec3(1.0f, 2.0f, 3.0f); - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException) + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) << " " << __FUNCTION__ << " value:" << name << " " << message; ; - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: typesMatch")) + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) << " " << __FUNCTION__ << " value:" << name << " " << message; ; } else { - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException); - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: find(name) != end")); + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) + << " " << __FUNCTION__ << " value:" << name << " " << message; + ; + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) + << " " << __FUNCTION__ << " value:" << name << " " << message; + ; } } @@ -300,17 +333,20 @@ void FixtureBase::testMatrixValue( // Expect an assert if set with wrong type. matProps[name] = 42; - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException); - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: typesMatch")); + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) + << " " << __FUNCTION__ << " value:" << name << " " << message; + ; + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) + << " " << __FUNCTION__ << " value:" << name << " " << message; } else { - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException); - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: find(name) != end")); + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) + << " " << __FUNCTION__ << " value:" << name << " " << message; + ; + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) + << " " << __FUNCTION__ << " value:" << name << " " << message; + ; } } @@ -334,11 +370,11 @@ void FixtureBase::testBooleanValue( // Expect an assert if set with wrong type. matProps[name] = vec3(1.0f, 2.0f, 3.0f); - - ASSERT_THROW( - scene.setMaterialProperties(material, matProps), TestHelpers::AuroraLoggerException); - ASSERT_THAT(lastLogMessage(), - ::testing::StartsWith("AU_ASSERT test failed:\nEXPRESSION: typesMatch")); + ASSERT_NO_FATAL_FAILURE(scene.setMaterialProperties(material, matProps)) + << " " << __FUNCTION__ << " value:" << name << " " << message; + ; + ASSERT_THAT(lastLogMessage(), ::testing::StartsWith("Type mismatch in UniformBlock")) + << " " << __FUNCTION__ << " value:" << name << " " << message; } else { @@ -558,6 +594,7 @@ Path FixtureBase::createTeapotGeometry(IScene& scene) geomDesc.type = PrimitiveType::Triangles; geomDesc.vertexDesc.attributes[Names::VertexAttributes::kPosition] = AttributeFormat::Float3; geomDesc.vertexDesc.attributes[Names::VertexAttributes::kNormal] = AttributeFormat::Float3; + geomDesc.vertexDesc.attributes[Names::VertexAttributes::kTangent] = AttributeFormat::Float3; geomDesc.vertexDesc.attributes[Names::VertexAttributes::kTexCoord0] = AttributeFormat::Float2; geomDesc.vertexDesc.count = TeapotModel::verticesCount(); geomDesc.indexCount = TeapotModel::indicesCount(); @@ -572,7 +609,13 @@ Path FixtureBase::createTeapotGeometry(IScene& scene) buffers[Names::VertexAttributes::kNormal].address = TeapotModel::normals(); buffers[Names::VertexAttributes::kNormal].size = TeapotModel::verticesCount() * sizeof(vec3); - buffers[Names::VertexAttributes::kNormal].stride = sizeof(vec3); + buffers[Names::VertexAttributes::kNormal].stride = sizeof(vec3); + + buffers[Names::VertexAttributes::kTangent].address = TeapotModel::tangents(); + buffers[Names::VertexAttributes::kTangent].size = + TeapotModel::verticesCount() * sizeof(vec3); + buffers[Names::VertexAttributes::kTangent].stride = sizeof(vec3); + buffers[Names::VertexAttributes::kTexCoord0].address = TeapotModel::uvs(); buffers[Names::VertexAttributes::kTexCoord0].size = TeapotModel::verticesCount() * sizeof(vec2); @@ -600,6 +643,7 @@ Path FixtureBase::createPlaneGeometry(IScene& scene, vec2 uvScale, vec2 uvOffset geomDesc.type = PrimitiveType::Triangles; geomDesc.vertexDesc.attributes[Names::VertexAttributes::kPosition] = AttributeFormat::Float3; geomDesc.vertexDesc.attributes[Names::VertexAttributes::kNormal] = AttributeFormat::Float3; + geomDesc.vertexDesc.attributes[Names::VertexAttributes::kTangent] = AttributeFormat::Float3; geomDesc.vertexDesc.attributes[Names::VertexAttributes::kTexCoord0] = AttributeFormat::Float2; geomDesc.vertexDesc.count = g_planePositionArray.size() / 3; @@ -627,7 +671,11 @@ Path FixtureBase::createPlaneGeometry(IScene& scene, vec2 uvScale, vec2 uvOffset buffers[Names::VertexAttributes::kPosition].stride = sizeof(vec3); buffers[Names::VertexAttributes::kNormal].address = g_planeNormalArray.data(); buffers[Names::VertexAttributes::kNormal].size = g_planeNormalArray.size() * sizeof(float); - buffers[Names::VertexAttributes::kNormal].stride = sizeof(vec3); + buffers[Names::VertexAttributes::kNormal].stride = sizeof(vec3); + buffers[Names::VertexAttributes::kTangent].address = g_planeTangentArray.data(); + buffers[Names::VertexAttributes::kTangent].size = + g_planeTangentArray.size() * sizeof(float); + buffers[Names::VertexAttributes::kTangent].stride = sizeof(vec3); buffers[Names::VertexAttributes::kTexCoord0].address = &transformedUVs[0]; buffers[Names::VertexAttributes::kTexCoord0].size = g_planeUVArray.size() * sizeof(vec2); @@ -672,6 +720,9 @@ IScenePtr FixtureBase::createDefaultScene() vec3 boundsMax(+1, +1, +1); _pDefaultScene->setBounds(boundsMin, boundsMax); + _pDefaultDistantLight = + _pDefaultScene->addLightPointer(Aurora::Names::LightTypes::kDistantLight); + // Set the default scene in renderer. _pDefaultRenderer->setScene(_pDefaultScene); diff --git a/Tests/Helpers/AuroraTestHelpers.h b/Tests/Helpers/AuroraTestHelpers.h index 480683c..4c5830c 100644 --- a/Tests/Helpers/AuroraTestHelpers.h +++ b/Tests/Helpers/AuroraTestHelpers.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -59,6 +59,9 @@ class TeapotModel // Gets a pointer to normals for teapot test model static const float* uvs(); + // Gets a pointer to tangents for teapot test model + static const float* tangents(); + // Gets the number of floats in teapot vertices static uint32_t verticesCount(); @@ -134,6 +137,8 @@ class FixtureBase : public testing::TestWithParam /// \brief Gets the default scene. (null if createDefaultScene not called) Aurora::IScenePtr defaultScene() { return _pDefaultScene; } + Aurora::ILightPtr defaultDistantLight() { return _pDefaultDistantLight; } + /// \brief Gets the default renderer's render buffer width. size_t defaultRendererWidth() { return _defaultRendererWidth; } @@ -259,6 +264,9 @@ class FixtureBase : public testing::TestWithParam // Default renderer object Aurora::IRendererPtr _pDefaultRenderer; + // Default distant light. + Aurora::ILightPtr _pDefaultDistantLight; + // Default renderer's render buffer object Aurora::IRenderBufferPtr _pDefaultRenderBuffer; diff --git a/Tests/Helpers/BaselineImageHelpers.cpp b/Tests/Helpers/BaselineImageHelpers.cpp index 324aba7..f74bc56 100644 --- a/Tests/Helpers/BaselineImageHelpers.cpp +++ b/Tests/Helpers/BaselineImageHelpers.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ #include #include +#include + using namespace std; #if __clang__ @@ -137,7 +139,7 @@ Result compare(const uint8_t* renderedPixels, size_t width, size_t height, string fileName = name + ".png"; // replace non-file chars with underscore - sanitizeFileName(fileName); + Aurora::Foundation::sanitizeFileName(fileName); // Output and baseline file named identically in baseline and output folder string outputFile = combinePaths(ouputImageFolder, fileName); diff --git a/Tests/Helpers/BaselineImageHelpers.h b/Tests/Helpers/BaselineImageHelpers.h index 913b469..380d788 100644 --- a/Tests/Helpers/BaselineImageHelpers.h +++ b/Tests/Helpers/BaselineImageHelpers.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Helpers/RendererTestHelpers.cpp b/Tests/Helpers/RendererTestHelpers.cpp index 4c86b05..f69d3e2 100644 --- a/Tests/Helpers/RendererTestHelpers.cpp +++ b/Tests/Helpers/RendererTestHelpers.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Helpers/RendererTestHelpers.h b/Tests/Helpers/RendererTestHelpers.h index 8484670..12d1457 100644 --- a/Tests/Helpers/RendererTestHelpers.h +++ b/Tests/Helpers/RendererTestHelpers.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Helpers/TeapotModel.h b/Tests/Helpers/TeapotModel.h index 02346c6..3e7e8c4 100644 --- a/Tests/Helpers/TeapotModel.h +++ b/Tests/Helpers/TeapotModel.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/Tests/Helpers/TestHelpers.cpp b/Tests/Helpers/TestHelpers.cpp index b39d0a4..bf77627 100644 --- a/Tests/Helpers/TestHelpers.cpp +++ b/Tests/Helpers/TestHelpers.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ #endif #include +#include #include // Compatibility macros for stat file access modes @@ -45,6 +46,79 @@ float as_float(const uint32_t x) return *(float*)&x; } +std::string compareTextFile( + const std::string filename, const std::string cmpString, const std::string message) +{ + std::string errMsg = ""; + + std::stringstream cmpsstream(cmpString); + std::fstream infile; + infile.open(filename, std::ios::in); + if (infile.bad()) + { + errMsg = "Failed to open baseline text file:" + filename; + } + if (errMsg.empty()) + { + std::string inlinestr; + std::string cmplinestr; + bool atEof = false; + int lineNo = 0; + do + { + lineNo++; + std::getline(infile, inlinestr); + std::getline(cmpsstream, cmplinestr); + if (infile.eof() && cmpsstream.eof()) + atEof = true; + else if (infile.bad() || cmpsstream.bad()) + errMsg = "Error reading file " + filename; + else if (!infile.good() && cmpsstream.good()) + errMsg = "Reached end of " + filename + " at line " + std::to_string(lineNo) + + " before end of comparison string."; + else if (infile.good() && !cmpsstream.good()) + errMsg = "Reached end of comparison string at line " + std::to_string(lineNo) + + " before end of " + filename; + else + { + if (inlinestr.compare(cmplinestr) != 0) + { + errMsg = "Line comparison failed for line " + std::to_string(lineNo) + " of " + + filename + "\n" + inlinestr + "is not the same as\n" + cmplinestr; + } + } + + } while (errMsg.empty() && !atEof); + + infile.close(); + } + if (!errMsg.empty()) + { + + if (getFlagEnvironmentVariable("UPDATE_BASELINE")) + { + std::cout << "UPDATE_BASELINE env. var. is set. generating new baseline textfile for " + << filename << std::endl; + + std::ofstream newfile; + newfile.open(filename, std::ios::out); + newfile << cmpString; + newfile.close(); + + return ""; + } + else + { + errMsg = message + errMsg; + errMsg += + "\nRun tests with the environment variable UPDATE_BASELINE set to update the " + "baseline."; + } + } + + return errMsg; +} + float halfToFloat(const uint16_t x) { // IEEE-754 16-bit floating-point format (without infinity): 1-5-10, exp-15, +-131008.0, // +-6.1035156E-5, +-5.9604645E-8, 3.311 digits @@ -158,16 +232,6 @@ std::string combinePaths(const std::string& path0, const std::string& path1) return path0 + "/" + path1; } -void sanitizeFileName(std::string& fileName) -{ - // Replace non-file chars with underscore - // TODO: More exhaustive set of chars - std::replace(fileName.begin(), fileName.end(), '/', '_'); - std::replace(fileName.begin(), fileName.end(), '\\', '_'); - std::replace(fileName.begin(), fileName.end(), '?', '_'); - std::replace(fileName.begin(), fileName.end(), '*', '_'); -} - bool createDirectory(const std::string& path) { // If already exists return true diff --git a/Tests/Helpers/TestHelpers.h b/Tests/Helpers/TestHelpers.h index 2f46f95..3ec3cea 100644 --- a/Tests/Helpers/TestHelpers.h +++ b/Tests/Helpers/TestHelpers.h @@ -1,4 +1,4 @@ -// Copyright 2022 Autodesk, Inc. +// Copyright 2023 Autodesk, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -141,6 +141,13 @@ typedef union } parts; } DecompFloat16; +/// Compare string with baseline text file on disk. +/// Returns an error message or empty string if comparison passed. +/// message argument is concatenated with returned error string if test fails. +/// With regenerate text file if UPDATE_BASELINE env. var. is set. +std::string compareTextFile( + const std::string filename, const std::string cmpString, const std::string message = ""); + /// \brief Convert 16-bit half float to 32-bit full float float halfToFloat(const uint16_t x); @@ -153,9 +160,6 @@ float createFloat32(uint32_t mantisa, uint32_t exponent, uint32_t sign); /// \brief Create half from integer mantisa, exponent, and sign components uint16_t createFloat16(uint16_t mantisa, uint16_t exponent, uint16_t sign); -/// \brief Sanitize file name, by replacing any non-file name chars with underscore -void sanitizeFileName(std::string& fileName); - /// \brief Get the named environment variable. // \return The contents of the variable, or empty string if none. std::string getEnvironmentVariable(const std::string& name);