diff --git a/.github/actions/dep-versions/action.yml b/.github/actions/dep-versions/action.yml index dc2b48b59c..bdd2e32a76 100644 --- a/.github/actions/dep-versions/action.yml +++ b/.github/actions/dep-versions/action.yml @@ -3,5 +3,5 @@ runs: using: "composite" steps: - name: Set up dependency versions - shell: bash + shell: bash run: cat ./build/common/versions >> $GITHUB_ENV diff --git a/.github/actions/web-prereq/action.yml b/.github/actions/web-prereq/action.yml new file mode 100644 index 0000000000..d239dea0f1 --- /dev/null +++ b/.github/actions/web-prereq/action.yml @@ -0,0 +1,16 @@ +name: 'Web Preqrequisites' +runs: + using: "composite" + steps: + - uses: ./.github/actions/dep-versions + - name: Cache EMSDK + id: emsdk-cache + uses: actions/cache@v4 # Use a specific version + with: + path: emsdk + key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }} + - name: Install Web Prerequisites + shell: bash + run: | + bash ./build/common/get-emscripten.sh + echo "EMSDK=$PWD/emsdk" >> $GITHUB_ENV diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index ca4b3e2a89..f9b738f154 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -96,6 +96,7 @@ jobs: with: fetch-depth: 0 - uses: ./.github/actions/linux-prereq + - uses: ./.github/actions/web-prereq - name: Run build script run: | cd build/web && printf "y" | ./build.sh presubmit @@ -123,13 +124,15 @@ jobs: - uses: ./.github/actions/mac-prereq - name: Cache Mesa and deps id: mesa-cache - uses: actions/cache@v4 # Use a specific version + uses: actions/cache@v4 with: path: mesa key: ${{ runner.os }}-mesa-deps-2-${{ vars.MESA_VERSION }} - - name: Get Mesa - id: mesa-prereq - run: bash test/utils/get_mesa.sh + - name: Prerequisites + id: prereqs + run: | + bash test/utils/get_mesa.sh + pip install tifffile numpy - name: Run Test run: bash test/renderdiff/test.sh - uses: actions/upload-artifact@v4 @@ -150,7 +153,7 @@ jobs: - name: Run test run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl* - code-correcteness: + code-correctness: name: code-correctness runs-on: 'macos-14-xlarge' steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ed2148880..d1b2fba3bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,6 +118,7 @@ jobs: with: ref: ${{ steps.git_ref.outputs.ref }} - uses: ./.github/actions/linux-prereq + - uses: ./.github/actions/web-prereq - name: Run build script env: TAG: ${{ steps.git_ref.outputs.tag }} diff --git a/.github/workflows/web-continuous.yml b/.github/workflows/web-continuous.yml index 74aa57de5c..180f1a26f5 100644 --- a/.github/workflows/web-continuous.yml +++ b/.github/workflows/web-continuous.yml @@ -17,6 +17,7 @@ jobs: with: fetch-depth: 0 - uses: ./.github/actions/linux-prereq + - uses: ./.github/actions/web-prereq - name: Run build script run: | cd build/web && printf "y" | ./build.sh continuous diff --git a/BUILDING.md b/BUILDING.md index 167e1736ed..24614ccf46 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -363,6 +363,8 @@ python ./emsdk.py activate latest source ./emsdk_env.sh ``` +Alternatively, you can try running the script `build/common/get-emscripten.sh`. + After this you can invoke the [easy build](#easy-build) script as follows: ```shell diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7..c3161a7700 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,3 +7,4 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut + diff --git a/README.md b/README.md index ddd4d0db1c..dd57352cc5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.59.5' + implementation 'com.google.android.filament:filament-android:1.60.0' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.59.5' +pod 'Filament', '~> 1.60.0' ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e8e64b7319..7d4e8a79af 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,13 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.60.0 + +- materials: remove dependence on per-view descset layout from filamat. [⚠️ **New Material Version**] +- matc non-functional change: Update GLSL postprocessor to + isolate calls to SPVRemap from calls to SPIRV-Cross. + + ## v1.59.5 diff --git a/android/gradle.properties b/android/gradle.properties index 24ee5c5917..bd0c8270e0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.59.5 +VERSION_NAME=1.60.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/build/android/build.sh b/build/android/build.sh index da673271e7..2fab3f833f 100755 --- a/build/android/build.sh +++ b/build/android/build.sh @@ -1,28 +1,6 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x @@ -30,11 +8,6 @@ set -x UNAME=`echo $(uname)` LC_UNAME=`echo $UNAME | tr '[:upper:]' '[:lower:]'` -# build-common.sh will generate the following variables: -# $GENERATE_ARCHIVES -# $BUILD_DEBUG -# $BUILD_RELEASE -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh if [[ "$GITHUB_WORKFLOW" ]]; then diff --git a/build/common/build-common.sh b/build/common/build-common.sh index 79c2231115..df4094add9 100755 --- a/build/common/build-common.sh +++ b/build/common/build-common.sh @@ -1,5 +1,20 @@ #!/bin/bash +# build-common.sh will generate the following variables: +# $GENERATE_ARCHIVES +# $BUILD_DEBUG +# $BUILD_RELEASE + +# Typically a build script (build.sh) would source this script. For example, +# source `dirname $0`/../common/build-common.sh + +# Usage: the first argument selects the build type: +# - release, to build release only +# - debug, to build debug only +# - continuous, to build release and debug +# - presubmit, for presubmit builds +# +# The default is release if [[ ! "$TARGET" ]]; then if [[ "$1" ]]; then TARGET=$1 diff --git a/build/common/ci-check.sh b/build/common/ci-check.sh new file mode 100644 index 0000000000..022d1523e0 --- /dev/null +++ b/build/common/ci-check.sh @@ -0,0 +1,19 @@ +echo "This script is intended to run in a CI environment and may modify your current environment." +echo "Please refer to BUILDING.md for more information." + +read -r -p "Do you wish to proceed (y/n)? " choice +case "${choice}" in + y|Y) + echo "Build will proceed..." + ;; + n|N) + exit 0 + ;; + *) + exit 0 + ;; +esac + +if [[ "$GITHUB_WORKFLOW" ]]; then + echo "Running workflow $GITHUB_WORKFLOW (event: $GITHUB_EVENT_NAME, action: $GITHUB_ACTION)" +fi diff --git a/build/common/ci-common.sh b/build/common/ci-common.sh deleted file mode 100755 index 2b66ec0375..0000000000 --- a/build/common/ci-common.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -if [[ "$GITHUB_WORKFLOW" ]]; then - echo "Running workflow $GITHUB_WORKFLOW (event: $GITHUB_EVENT_NAME, action: $GITHUB_ACTION)" -fi diff --git a/build/common/get-emscripten.sh b/build/common/get-emscripten.sh new file mode 100755 index 0000000000..ec16d3c144 --- /dev/null +++ b/build/common/get-emscripten.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if [ -d "./emsdk" ]; then + echo "emsdk folder found. Assume emsdk has been installed." + cd emsdk + ./emsdk activate latest + source ./emsdk_env.sh + export EMSDK="$PWD" + cd .. + exit 0 +fi + +# Install emscripten. +EMSDK_VERSION=${GITHUB_EMSDK_VERSION-3.1.60} +curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/${EMSDK_VERSION}.zip > emsdk.zip +unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk +./emsdk install latest +./emsdk activate latest +source ./emsdk_env.sh + +export EMSDK="$PWD" +cd .. diff --git a/build/common/versions b/build/common/versions index 49749b5231..bc836873e8 100644 --- a/build/common/versions +++ b/build/common/versions @@ -3,4 +3,5 @@ GITHUB_CMAKE_VERSION=3.19.5 GITHUB_NINJA_VERSION=1.10.2 GITHUB_MESA_VERSION=24.2.1 GITHUB_LLVM_VERSION=16 -GITHUB_NDK_VERSION=27.0.11718014 \ No newline at end of file +GITHUB_NDK_VERSION=27.0.11718014 +GITHUB_EMSDK_VERSION=3.1.60 \ No newline at end of file diff --git a/build/ios/build.sh b/build/ios/build.sh index e43ddbb12b..b9940a6ead 100755 --- a/build/ios/build.sh +++ b/build/ios/build.sh @@ -1,35 +1,11 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh - pushd `dirname $0`/../.. > /dev/null # If we're generating an archive for release or continuous builds, then we'll also build for the diff --git a/build/linux/build.sh b/build/linux/build.sh index 021800ad13..06a64afdde 100755 --- a/build/linux/build.sh +++ b/build/linux/build.sh @@ -1,38 +1,11 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -# build-common.sh will generate the following variables: -# $GENERATE_ARCHIVES -# $BUILD_DEBUG -# $BUILD_RELEASE -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh - pushd `dirname $0`/../.. > /dev/null + ./build.sh -c $RUN_TESTS $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE diff --git a/build/mac/build.sh b/build/mac/build.sh index 388316c10d..06a64afdde 100755 --- a/build/mac/build.sh +++ b/build/mac/build.sh @@ -1,34 +1,11 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -source `dirname $0`/../common/ci-common.sh source `dirname $0`/../common/build-common.sh - pushd `dirname $0`/../.. > /dev/null + ./build.sh -c $RUN_TESTS $GENERATE_ARCHIVES $BUILD_DEBUG $BUILD_RELEASE diff --git a/build/web/build.sh b/build/web/build.sh index 624c091d53..dc038b7581 100755 --- a/build/web/build.sh +++ b/build/web/build.sh @@ -1,34 +1,10 @@ #!/bin/bash -# Usage: the first argument selects the build type: -# - release, to build release only -# - debug, to build debug only -# - continuous, to build release and debug -# - presubmit, for presubmit builds -# -# The default is release - -echo "This script is intended to run in a CI environment and may modify your current environment." -echo "Please refer to BUILDING.md for more information." - -read -r -p "Do you wish to proceed (y/n)? " choice -case "${choice}" in - y|Y) - echo "Build will proceed..." - ;; - n|N) - exit 0 - ;; - *) - exit 0 - ;; -esac +source `dirname $0`/../common/ci-check.sh set -e set -x -source `dirname $0`/../common/ci-common.sh -source `dirname $0`/ci-common.sh source `dirname $0`/../common/build-common.sh pushd `dirname $0`/../.. > /dev/null diff --git a/build/web/ci-common.sh b/build/web/ci-common.sh deleted file mode 100755 index 1a5a19a4b1..0000000000 --- a/build/web/ci-common.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# Install emscripten. -curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.60.zip > emsdk.zip -unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk -./emsdk install latest -./emsdk activate latest -source ./emsdk_env.sh - -export EMSDK="$PWD" -cd .. diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 6669898637..9d8dfae47d 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -5,6 +5,18 @@ set(TARGET backend) set(PUBLIC_HDR_DIR include) set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR}) +# ================================================================================================== +# Compilation options +# ================================================================================================== +# +set(BACKEND_SANITIZATION "" CACHE STRING "Sanitization option") +set_property(CACHE BACKEND_SANITIZATION PROPERTY STRINGS ";ASAN") + +set(BACKEND_SANITIZERS) +if (BACKEND_SANITIZATION STREQUAL "ASAN") + set(BACKEND_SANITIZERS -fsanitize=address) +endif() + # ================================================================================================== # Sources and headers # ================================================================================================== @@ -472,6 +484,7 @@ target_compile_options(${TARGET} PRIVATE ${OSMESA_COMPILE_FLAGS} $<$:${OPTIMIZATION_FLAGS}> $<$,$>:${DARWIN_OPTIMIZATION_FLAGS}> + ${BACKEND_SANITIZERS} ) if (FILAMENT_SUPPORTS_METAL) @@ -482,6 +495,8 @@ if (FILAMENT_SUPPORTS_WEBGPU) target_compile_definitions(${TARGET} PRIVATE $<$:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>) endif() +target_link_options(${TARGET} PRIVATE ${BACKEND_SANITIZERS}) + target_link_libraries(${TARGET} PRIVATE ${OSMESA_LINKER_FLAGS} $<$,$>:${LINUX_LINKER_OPTIMIZATION_FLAGS}> @@ -551,6 +566,8 @@ if (APPLE AND NOT IOS) test/test_RenderExternalImage.cpp) add_library(backend_test STATIC ${BACKEND_TEST_SRC}) target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS}) + target_compile_options(backend_test PRIVATE ${BACKEND_SANITIZERS}) + target_link_options(backend_test PRIVATE ${BACKEND_SANITIZERS}) set(BACKEND_TEST_DEPS OSDependent @@ -589,6 +606,7 @@ if (APPLE AND NOT IOS) # linker from removing "unused" symbols. target_link_libraries(backend_test_mac PRIVATE -force_load backend_test) set_target_properties(backend_test_mac PROPERTIES FOLDER Tests) + target_link_options(backend_test_mac PRIVATE ${BACKEND_SANITIZERS}) # This is needed after XCode 15.3 set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) @@ -598,6 +616,8 @@ endif() if (LINUX) add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC}) + target_compile_options(backend_test_linux PRIVATE ${BACKEND_SANITIZERS}) + target_link_options(backend_test_linux PRIVATE ${BACKEND_SANITIZERS}) target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS}) set_target_properties(backend_test_linux PROPERTIES FOLDER Tests) endif() diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index d8b281000c..bfb63363f1 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -1139,6 +1140,7 @@ struct ExternalSamplerDatum { static_assert(sizeof(ExternalSamplerDatum) == 12); struct DescriptorSetLayout { + std::variant label; utils::FixedCapacityVector bindings; // TODO: uncomment when needed diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index f8e9d50eb8..d19d5b1ba8 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -44,8 +44,8 @@ public: struct Descriptor { utils::CString name; - backend::DescriptorType type; - backend::descriptor_binding_t binding; + DescriptorType type; + descriptor_binding_t binding; }; struct SpecializationConstant { diff --git a/filament/backend/src/metal/MetalEnums.h b/filament/backend/src/metal/MetalEnums.h index 5a2038ed10..46e0dac622 100644 --- a/filament/backend/src/metal/MetalEnums.h +++ b/filament/backend/src/metal/MetalEnums.h @@ -185,6 +185,7 @@ inline MTLPixelFormat getMetalFormat(PixelDataFormat format, PixelDataType type) CONVERT(RGBA_INTEGER, UINT, RGBA32Uint); CONVERT(RGBA_INTEGER, INT, RGBA32Sint); CONVERT(RGBA, FLOAT, RGBA32Float); + CONVERT(DEPTH_COMPONENT, FLOAT, Depth32Float); #undef CONVERT return MTLPixelFormatInvalid; diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index d4804bf31b..0ca6d05722 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -142,6 +142,10 @@ public: return mPortabilitySubsetFeatures.imageView2DOn3DImage == VK_TRUE; } + inline bool isUnifiedMemoryArchitecture() const noexcept { + return mIsUnifiedMemoryArchitecture; + } + private: VkPhysicalDeviceMemoryProperties mMemoryProperties = {}; VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = { @@ -164,6 +168,7 @@ private: bool mDebugUtilsSupported = false; bool mLazilyAllocatedMemorySupported = false; bool mProtectedMemorySupported = false; + bool mIsUnifiedMemoryArchitecture = false; fvkutils::VkFormatList mDepthStencilFormats; fvkutils::VkFormatList mBlittableDepthStencilFormats; diff --git a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp index 4323aba877..46763e5c8a 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp @@ -631,6 +631,21 @@ fvkutils::VkFormatList findBlittableDepthStencilFormats(VkPhysicalDevice device) return ret; } +/** + * Check if the GPU has a unified memory architecture. + */ +bool hasUnifiedMemoryArchitecture(VkPhysicalDeviceMemoryProperties memoryProperties) noexcept { + // Try to identify if the platform is running on a Unified Memory Architecture by inspecting the + // memory heap flags, if they are all VK_MEMORY_HEAP_DEVICE_LOCAL_BIT it's UMA, otherwise not + // enough information to make a decision, so default to false. + for (uint32_t i = 0; i < memoryProperties.memoryHeapCount; ++i) { + if ((memoryProperties.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0) { + return false; + } + } + return true; +} + }// anonymous namespace using SwapChainPtr = VulkanPlatform::SwapChainPtr; @@ -864,6 +879,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, } } + context.mIsUnifiedMemoryArchitecture = hasUnifiedMemoryArchitecture(context.mMemoryProperties); + #ifdef NDEBUG // If we are in release build, we should not have turned on debug extensions FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported) diff --git a/filament/backend/src/webgpu/WGPUProgram.cpp b/filament/backend/src/webgpu/WGPUProgram.cpp index 42b9717a02..c81e4e8668 100644 --- a/filament/backend/src/webgpu/WGPUProgram.cpp +++ b/filament/backend/src/webgpu/WGPUProgram.cpp @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -125,21 +126,64 @@ namespace { return module; } +// This is a 1 to 1 mapping of the ReservedSpecializationConstants enum in EngineEnums.h +// The _hack is a workaround until https://issues.chromium.org/issues/42250586 is resolved +// This workaround is the same one being used on the generateSpecializationConstant() function +wgpu::StringView getSpecConstantStringId(uint32_t id) { + switch (id) { + case 0: + return "0";// BACKEND_FEATURE_LEVEL_hack + case 1: + return "1";// CONFIG_MAX_INSTANCES_hack + case 2: + return "2";// ONFIG_STATIC_TEXTURE_TARGET_WORKAROUND_hack + case 3: + return "3";// CONFIG_SRGB_SWAPCHAIN_EMULATION_hack + case 4: + return "4";// CONFIG_FROXEL_BUFFER_HEIGHT_hack + case 5: + return "5";// CONFIG_POWER_VR_SHADER_WORKAROUNDS_hack + case 6: + return "6";// CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP_hack + case 7: + return "7";// CONFIG_DEBUG_FROXEL_VISUALIZATION_hack + case 8: + return "8";// CONFIG_STEREO_EYE_COUNT_hack + case 9: + return "9";// CONFIG_SH_BANDS_COUNT_hack + case 10: + return "10";// CONFIG_SHADOW_SAMPLING_METHOD_hack + default: + PANIC_POSTCONDITION("Unknown/unhandled spec constant key/id: %d", id); + } +} + std::vector convertConstants( utils::FixedCapacityVector const& constantsInfo) { - std::vector constants(constantsInfo.size()); - for (size_t i = 0; i < constantsInfo.size(); i++) { - filament::backend::Program::SpecializationConstant const& specConstant = constantsInfo[i]; - wgpu::ConstantEntry& constantEntry = constants[i]; - constantEntry.key = wgpu::StringView(std::to_string(specConstant.id)); - if (auto* v = std::get_if(&specConstant.value)) { - constantEntry.value = static_cast(*v); - } else if (auto* f = std::get_if(&specConstant.value)) { - constantEntry.value = static_cast(*f); - } else if (auto* b = std::get_if(&specConstant.value)) { - constantEntry.value = *b ? 0.0 : 1.0; + std::vector constants; + constants.reserve(constantsInfo.size()); + for (filament::backend::Program::SpecializationConstant const& constant: constantsInfo) { + // CONFIG_MAX_INSTANCES (1) and CONFIG_FROXEL_BUFFER_HEIGHT (4) will not be present + // as constant overrides in the generated WGSL, because WGSL doesn't support specialization + // constants as an array length + // More information at https://github.com/gpuweb/gpuweb/issues/572#issuecomment-649760005 + // CONFIG_SRGB_SWAPCHAIN_EMULATION (3) is being skipped all together since it's only + // included for the case of mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0, which should + // not be possible for WebGPU + if (constant.id == 1 || constant.id == 3 || constant.id == 4) { + continue; } + double value = 0.0; + if (auto* v = std::get_if(&constant.value)) { + value = static_cast(*v); + } else if (auto* f = std::get_if(&constant.value)) { + value = static_cast(*f); + } else if (auto* b = std::get_if(&constant.value)) { + value = *b ? 0.0 : 1.0; + } + constants.push_back( + wgpu::ConstantEntry{ .key = getSpecConstantStringId(constant.id), .value = value }); } return constants; } diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index a6157947f9..56b4bc114c 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#include #include "webgpu/WebGPUDriver.h" #include "WebGPUPipelineCreation.h" @@ -389,9 +389,7 @@ Handle WebGPUDriver::createTextureS() noexcept { return allocHandle(); } -Handle WebGPUDriver::importTextureS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); -} +Handle WebGPUDriver::importTextureS() noexcept { return allocHandle(); } Handle WebGPUDriver::createProgramS() noexcept { return allocHandle(); @@ -410,7 +408,7 @@ Handle WebGPUDriver::createIndexBufferS() noexcept { } Handle WebGPUDriver::createTextureViewS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createBufferObjectS() noexcept { @@ -438,7 +436,7 @@ Handle WebGPUDriver::createVertexBufferInfoS() noexcept { } Handle WebGPUDriver::createTextureViewSwizzleS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createDefaultRenderTargetS() noexcept { @@ -450,15 +448,15 @@ Handle WebGPUDriver::createDescriptorSetLayoutS() noexcep } Handle WebGPUDriver::createTextureExternalImageS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createTextureExternalImage2S() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } Handle WebGPUDriver::createTextureExternalImagePlaneS() noexcept { - return Handle((Handle::HandleId) mNextFakeHandle++); + return allocHandle(); } void WebGPUDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags) { @@ -470,6 +468,8 @@ void WebGPUDriver::createSwapChainR(Handle sch, void* nativeWindow, mSwapChain = constructHandle(sch, std::move(surface), surfaceSize, mAdapter, mDevice, flags); assert_invariant(mSwapChain); + WebGPUDescriptorSet::initializeDummyResourcesIfNotAlready(mDevice, + mSwapChain->getColorFormat()); FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only " "background components have been instantiated/selected, such as surface/screen, " "graphics device/GPU, etc.), thus nothing is being drawn to the screen." @@ -512,30 +512,46 @@ void WebGPUDriver::createBufferObjectR(Handle boh, uint32_t byte void WebGPUDriver::createTextureR(Handle th, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, - TextureUsage usage) {} + TextureUsage usage) { + constructHandle(th, target, levels, format, samples, w, h, depth, usage, mDevice); +} void WebGPUDriver::createTextureViewR(Handle th, Handle srch, - uint8_t baseLevel, uint8_t levelCount) {} + uint8_t baseLevel, uint8_t levelCount) { + auto source = handleCast(srch); + + constructHandle(th, source, baseLevel, levelCount); +} void WebGPUDriver::createTextureViewSwizzleR(Handle th, Handle srch, backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, - backend::TextureSwizzle a) {} + backend::TextureSwizzle a) { + PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported"); +} void WebGPUDriver::createTextureExternalImage2R(Handle th, backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, - Platform::ExternalImageHandleRef externalImage) {} + Platform::ExternalImageHandleRef externalImage) { + PANIC_POSTCONDITION("External WebGPU Texture is not supported"); +} void WebGPUDriver::createTextureExternalImageR(Handle th, backend::SamplerType target, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, - void* externalImage) {} + void* externalImage) { + PANIC_POSTCONDITION("External WebGPU Texture is not supported"); +} void WebGPUDriver::createTextureExternalImagePlaneR(Handle th, backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, - void* image, uint32_t plane) {} + void* image, uint32_t plane) { + PANIC_POSTCONDITION("External WebGPU Texture is not supported"); +} void WebGPUDriver::importTextureR(Handle th, intptr_t id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, - uint32_t depth, TextureUsage usage) {} + uint32_t depth, TextureUsage usage) { + PANIC_POSTCONDITION("Import WebGPU Texture is not supported"); +} void WebGPUDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, PrimitiveType pt) { @@ -575,7 +591,7 @@ void WebGPUDriver::createDescriptorSetLayoutR(Handle dslh void WebGPUDriver::createDescriptorSetR(Handle dsh, Handle dslh) { auto layout = handleCast(dslh); - constructHandle(dsh, layout->getLayout(), layout->getLayoutSize()); + constructHandle(dsh, layout->getLayout(), layout->getBindGroupEntries()); } Handle WebGPUDriver::createStreamNative(void* nativeStream) { @@ -612,7 +628,7 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle fh) { // We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that // the GPU supports the given texture format with non-zero optimal tiling features. bool WebGPUDriver::isTextureFormatSupported(TextureFormat format) { - return true; + return WGPUTexture::fToWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined; } bool WebGPUDriver::isTextureSwizzleSupported() { @@ -760,8 +776,31 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority, } } -void WebGPUDriver::beginRenderPass(Handle rth, const RenderPassParams& params) { +void WebGPUDriver::beginRenderPass(Handle rth, RenderPassParams const& params) { assert_invariant(mCommandEncoder); + + auto* renderTarget = handleCast(rth); + // if (renderTarget == mDefaultRenderTarget) { + // FWGPU_LOGW << "Default render target" + // << utils::io::endl; + // } else { + // FWGPU_LOGW << "Non Default render target" + // << utils::io::endl; + // } + wgpu::RenderPassDescriptor renderPassDescriptor2; + wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{ + .view = mSwapChain->getDepthTextureView(), + .depthLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH), + .depthStoreOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH), + .depthClearValue = static_cast(params.clearDepth), + .depthReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0, + .stencilLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL), + .stencilStoreOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL), + .stencilClearValue = params.clearStencil, + .stencilReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0 + }; + renderTarget->setUpRenderPassAttachments(renderPassDescriptor2, mTextureView, params); + renderPassDescriptor2.depthStencilAttachment = &depthStencilAttachment; // TODO: Remove this code once WebGPU pipeline is implemented static float red = 1.0f; if (red - 0.01 > 0) { @@ -786,7 +825,7 @@ void WebGPUDriver::beginRenderPass(Handle rth, const RenderPassP .timestampWrites = nullptr, }; - mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor); + mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor2); mRenderPassEncoder.SetViewport(params.viewport.left, params.viewport.bottom, params.viewport.width, params.viewport.height, params.depthRange.near, params.depthRange.far); } @@ -877,6 +916,14 @@ void WebGPUDriver::blit( } void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { + // TODO Investigate implications of this hash more closely. Vulkan has a whole class + // VulkanPipelineCache to handle this, may be missing nuance + static auto pipleineStateHasher = utils::hash::MurmurHashFn(); + auto hash = pipleineStateHasher(pipelineState); + if(mPipelineMap.find(hash) != mPipelineMap.end()){ + mRenderPassEncoder.SetPipeline(mPipelineMap[hash]); + return; + } const auto* program = handleCast(pipelineState.program); assert_invariant(program); assert_invariant(program->computeShaderModule == nullptr && @@ -913,8 +960,8 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { *vertexBufferInfo, layout, pipelineState.rasterState, pipelineState.stencilState, pipelineState.polygonOffset, pipelineState.primitiveType, mSwapChain->getColorFormat(), mSwapChain->getDepthFormat()); - // TODO: uncomment once we have a valid pipeline to set - // mRenderPassEncoder.SetPipeline(pipeline); + mPipelineMap[hash] = pipeline; + mRenderPassEncoder.SetPipeline(pipeline); } void WebGPUDriver::bindRenderPrimitive(Handle rph) { @@ -934,10 +981,18 @@ void WebGPUDriver::bindRenderPrimitive(Handle rph) { } void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { + // Calling DrawIndexed with "firstInstance = 0" results in a NON spinning triangle + // mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 0); + // Calling DrawIndexed with "firstInstance = 1" results in a spinning triangle + mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 1); + // Calling Draw with "firstInstance = 0" results in a NON spinning triangle + // Calling Draw with "firstInstance = 1" results in a spinning triangle + // mRenderPassEncoder.Draw(indexCount, instanceCount, 0, 1); } -void WebGPUDriver::draw(PipelineState pipelineState, Handle rph, - uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { +void WebGPUDriver::draw(PipelineState, Handle, uint32_t indexOffset, + uint32_t indexCount, uint32_t instanceCount) { + draw2(indexOffset, indexCount, instanceCount); } void WebGPUDriver::dispatchCompute(Handle program, math::uint3 workGroupCount) { @@ -973,34 +1028,156 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle dsh, void WebGPUDriver::updateDescriptorSetTexture(Handle dsh, backend::descriptor_binding_t binding, Handle th, SamplerParams params) { - /* auto bindGroup = handleCast(dsh); auto texture = handleCast(th); - // TODO very high odds badd assumptions are in here about handling HwTexture. Revisit with more - // understanding. Right now assuming there is a wgpu::TextureView filled in if (!bindGroup->getIsLocked()) { + // Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache. + // Making a cache might save us minor perf by reducing param translation + auto sampler = makeSampler(params); // TODO making assumptions that size and offset mean the same thing here. wgpu::BindGroupEntry tEntry{ .binding = static_cast(binding * 2), - .textureView = texture->texView }; + .textureView = texture->getTexView() }; bindGroup->addEntry(tEntry.binding, std::move(tEntry)); wgpu::BindGroupEntry sEntry{ .binding = static_cast(binding * 2 + 1), - .sampler = texture->sampler }; + .sampler = sampler }; bindGroup->addEntry(sEntry.binding, std::move(sEntry)); } - //TODO Just the setup, this function stilll needs the rest of logic implemented - */ } -void WebGPUDriver::bindDescriptorSet(Handle dsh, backend::descriptor_set_t set, - backend::DescriptorSetOffsetArray&& offsets) { - auto bindGroup = handleCast(dsh); - // TODO: presume we need this, use it. Probably Encoder::SetBindGroup - auto wbg = bindGroup->lockAndReturn(mDevice); +void WebGPUDriver::bindDescriptorSet(Handle dsh, + backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) { + const auto bindGroup = handleCast(dsh); + const auto wbg = bindGroup->lockAndReturn(mDevice); + assert_invariant(mRenderPassEncoder); + // TODO is this how we should be getting the dynamic offsets? + // should we add offsets for unused entries or is the input already have them? + // this implementation assumes unused entries are not provided, and adds dummy values. + // The count also includes unused entities, as not doing so produces errors + const size_t dynamicOffsetCount = bindGroup->countEntitiesWithDynamicOffsets(); + uint32_t const* const dynamicOffsetsWithUnused = bindGroup->setDynamicOffsets(offsets.data()); + mRenderPassEncoder.SetBindGroup(setIndex, wbg, dynamicOffsetCount, dynamicOffsetsWithUnused); } void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) { } +wgpu::Sampler WebGPUDriver::makeSampler(SamplerParams const& params) { + wgpu::SamplerDescriptor desc; + + desc.label = "TODO"; + desc.addressModeU = fWrapModeToWAddressMode(params.wrapS); + desc.addressModeV = fWrapModeToWAddressMode(params.wrapR); + desc.addressModeW = fWrapModeToWAddressMode(params.wrapT); + switch (params.filterMag) { + case SamplerMagFilter::NEAREST: { + desc.magFilter = wgpu::FilterMode::Nearest; + break; + } + case SamplerMagFilter::LINEAR: { + desc.magFilter = wgpu::FilterMode::Linear; + break; + } + } + switch (params.filterMin) { + case SamplerMinFilter::NEAREST: { + desc.minFilter = wgpu::FilterMode::Nearest; + // Metal Driver uses an explicit not-mipmapped value webgpu lacks. Nearest should + // suffice + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + break; + } + case SamplerMinFilter::LINEAR: { + desc.minFilter = wgpu::FilterMode::Linear; + // Metal Driver uses an explicit not-mipmapped value webgpu lacks. Nearest should + // suffice + + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + break; + } + case SamplerMinFilter::NEAREST_MIPMAP_NEAREST: { + desc.minFilter = wgpu::FilterMode::Nearest; + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + break; + } + case SamplerMinFilter::LINEAR_MIPMAP_NEAREST: { + desc.minFilter = wgpu::FilterMode::Linear; + desc.mipmapFilter = wgpu::MipmapFilterMode::Nearest; + + break; + } + case SamplerMinFilter::NEAREST_MIPMAP_LINEAR: { + desc.minFilter = wgpu::FilterMode::Nearest; + desc.mipmapFilter = wgpu::MipmapFilterMode::Linear; + + break; + } + case SamplerMinFilter::LINEAR_MIPMAP_LINEAR: { + desc.minFilter = wgpu::FilterMode::Linear; + desc.mipmapFilter = wgpu::MipmapFilterMode::Linear; + break; + } + } + switch (params.compareFunc) { + case SamplerCompareFunc::LE: { + desc.compare = wgpu::CompareFunction::LessEqual; + break; + } + case SamplerCompareFunc::GE: { + desc.compare = wgpu::CompareFunction::GreaterEqual; + break; + } + case SamplerCompareFunc::L: { + desc.compare = wgpu::CompareFunction::Less; + break; + } + case SamplerCompareFunc::G: { + desc.compare = wgpu::CompareFunction::Greater; + break; + } + case SamplerCompareFunc::E: { + desc.compare = wgpu::CompareFunction::Equal; + break; + } + case SamplerCompareFunc::NE: { + desc.compare = wgpu::CompareFunction::NotEqual; + break; + } + case SamplerCompareFunc::A: { + desc.compare = wgpu::CompareFunction::Always; + break; + } + case SamplerCompareFunc::N: { + desc.compare = wgpu::CompareFunction::Never; + break; + } + } + + desc.maxAnisotropy = 1u << params.anisotropyLog2; + + + // Unused: Filament's compareMode, WGPU lodMinClamp/lodMaxClamp + + //TODO Once we can properly map to descriptorsetlayout use the sampler. + return mDevice.CreateSampler(/*&desc*/); +} +wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& fWrapMode) { + switch (fWrapMode) { + case SamplerWrapMode::CLAMP_TO_EDGE: { + return wgpu::AddressMode::ClampToEdge; + break; + } + case SamplerWrapMode::REPEAT: { + return wgpu::AddressMode::Repeat; + break; + } + case SamplerWrapMode::MIRRORED_REPEAT: { + return wgpu::AddressMode::MirrorRepeat; + break; + } + } + return wgpu::AddressMode::Undefined; +} + } // namespace filament diff --git a/filament/backend/src/webgpu/WebGPUDriver.h b/filament/backend/src/webgpu/WebGPUDriver.h index df85e64971..c6a2d99d23 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.h +++ b/filament/backend/src/webgpu/WebGPUDriver.h @@ -56,7 +56,8 @@ private: explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept; [[nodiscard]] ShaderModel getShaderModel() const noexcept final; [[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final; - + [[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params); + [[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage); template void updateGPUBuffer(GPUBufferObject* gpuBufferObject, BufferDescriptor&& bufferDescriptor, uint32_t byteOffset) { @@ -92,6 +93,8 @@ private: wgpu::RenderPassEncoder mRenderPassEncoder = nullptr; wgpu::CommandBuffer mCommandBuffer = nullptr; WGPURenderTarget* mDefaultRenderTarget = nullptr; + + tsl::robin_map mPipelineMap; /* * Driver interface */ diff --git a/filament/backend/src/webgpu/WebGPUHandles.cpp b/filament/backend/src/webgpu/WebGPUHandles.cpp index 9e8f951ae7..06c0344334 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.cpp +++ b/filament/backend/src/webgpu/WebGPUHandles.cpp @@ -16,7 +16,16 @@ #include "WebGPUHandles.h" +#include + +#include + +#include + +#include +#include #include +#include namespace { constexpr wgpu::BufferUsage getBufferObjectUsage( @@ -110,6 +119,44 @@ wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool nor } } +wgpu::StringView getUserTextureLabel(filament::backend::SamplerType target) { + // TODO will be helpful to get more useful info than this + using filament::backend::SamplerType; + switch (target) { + case SamplerType::SAMPLER_2D: + return "a_2D_user_texture"; + case SamplerType::SAMPLER_2D_ARRAY: + return "a_2D_array_user_texture"; + case SamplerType::SAMPLER_CUBEMAP: + return "a_cube_map_user_texture"; + case SamplerType::SAMPLER_EXTERNAL: + return "an_external_user_texture"; + case SamplerType::SAMPLER_3D: + return "a_3D_user_texture"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "a_cube_mape_array_user_texture"; + } +} + +wgpu::StringView getUserTextureViewLabel(filament::backend::SamplerType target) { + // TODO will be helpful to get more useful info than this + using filament::backend::SamplerType; + switch (target) { + case SamplerType::SAMPLER_2D: + return "a_2D_user_texture_view"; + case SamplerType::SAMPLER_2D_ARRAY: + return "a_2D_array_user_texture_view"; + case SamplerType::SAMPLER_CUBEMAP: + return "a_cube_map_user_texture_view"; + case SamplerType::SAMPLER_EXTERNAL: + return "an_external_user_texture_view"; + case SamplerType::SAMPLER_3D: + return "a_3D_user_texture_view"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "a_cube_mape_array_user_texture_view"; + } +} + }// namespace namespace filament::backend { @@ -137,7 +184,7 @@ WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attribut mAttributes[attrib.buffer].push_back({ .format = vertexFormat, .offset = attrib.offset, - .shaderLocation = static_cast(mAttributes[attrib.buffer].size()), + .shaderLocation = attribIndex, }); mVertexBufferLayout[attrib.buffer].stepMode = wgpu::VertexStepMode::Vertex; @@ -192,11 +239,20 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& wgpu::Device const& device) { assert_invariant(device); + std::string baseLabel; + if (std::holds_alternative(layout.label)) { + const auto& temp = std::get_if(&layout.label); + baseLabel = temp->c_str(); + } else if (std::holds_alternative(layout.label)) { + const auto& temp = std::get_if(&layout.label); + baseLabel = temp->c_str(); + } + // TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for // debugging. For now, hack an incrementing value. static int layoutNum = 0; - uint samplerCount = + unsigned int samplerCount = std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) { return fEntry.type == DescriptorType::SAMPLER || fEntry.type == DescriptorType::SAMPLER_EXTERNAL; @@ -205,86 +261,204 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& std::vector wEntries; wEntries.reserve(layout.bindings.size() + samplerCount); + mBindGroupEntries.reserve(wEntries.capacity()); for (auto fEntry: layout.bindings) { auto& wEntry = wEntries.emplace_back(); + auto& entryInfo = mBindGroupEntries.emplace_back(); wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags); wEntry.binding = fEntry.binding * 2; + entryInfo.binding = wEntry.binding; switch (fEntry.type) { - // TODO Metal treats these the same. Is this fine? case DescriptorType::SAMPLER_EXTERNAL: case DescriptorType::SAMPLER: { - // Sampler binding is 2n+1 due to split. auto& samplerEntry = wEntries.emplace_back(); + auto& samplerEntryInfo = mBindGroupEntries.emplace_back(); samplerEntry.binding = fEntry.binding * 2 + 1; + samplerEntryInfo.binding = samplerEntry.binding; + samplerEntryInfo.type = WebGPUDescriptorSetLayout::BindGroupEntryType::SAMPLER; samplerEntry.visibility = wEntry.visibility; // We are simply hoping that undefined and defaults suffices here. - samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined; - wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined; + samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering; // Example default + wEntry.texture.sampleType = wgpu::TextureSampleType::Float; // Example default + // TODO: FIX! THIS IS HACK FOR HELLO-TRIANGLE! + if (baseLabel.find("Skybox") != std::string::npos || + (baseLabel == "Filament Default Material_perView" && wEntry.binding == 22)) { + wEntry.texture.viewDimension = wgpu::TextureViewDimension::Cube; + } else { + wEntry.texture.viewDimension = + wgpu::TextureViewDimension::e2D;// Example default + } + entryInfo.type = WebGPUDescriptorSetLayout::BindGroupEntryType::TEXTURE_VIEW; break; } case DescriptorType::UNIFORM_BUFFER: { wEntry.buffer.hasDynamicOffset = any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET); + entryInfo.hasDynamicOffset = wEntry.buffer.hasDynamicOffset; wEntry.buffer.type = wgpu::BufferBindingType::Uniform; // TODO: Ideally we fill minBindingSize break; } case DescriptorType::INPUT_ATTACHMENT: { - // TODO: support INPUT_ATTACHMENT. Metal does not currently. PANIC_POSTCONDITION("Input Attachment is not supported"); break; } - case DescriptorType::SHADER_STORAGE_BUFFER: { - // TODO: Vulkan does not support this, can we? PANIC_POSTCONDITION("Shader storage is not supported"); break; } } - - // Currently flags are only used to specify dynamic offset. - - // UNUSED - // fEntry.count + // fEntry.count is unused currently } - + std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ; wgpu::BindGroupLayoutDescriptor layoutDescriptor{ - // TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for - // debugging. For now, hack an incrementing value. - .label{ "layout_" + std::to_string(++layoutNum) }, + .label{label.c_str()}, // Use .c_str() if label needs to be const char* .entryCount = wEntries.size(), .entries = wEntries.data() }; - // TODO Do we need to defer this until we have more info on textures and samplers?? - mLayoutSize = wEntries.size(); mLayout = device.CreateBindGroupLayout(&layoutDescriptor); } WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {} -WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize) +wgpu::Buffer WebGPUDescriptorSet::sDummyUniformBuffer = nullptr; +wgpu::Texture WebGPUDescriptorSet::sDummyTexture = nullptr; +wgpu::TextureView WebGPUDescriptorSet::sDummyTextureView = nullptr; +wgpu::Sampler WebGPUDescriptorSet::sDummySampler = nullptr; + +void WebGPUDescriptorSet::initializeDummyResourcesIfNotAlready(wgpu::Device const& device, + wgpu::TextureFormat aColorFormat) { + if (!sDummyUniformBuffer) { + wgpu::BufferDescriptor bufferDescriptor{ + .label = "dummy_uniform_not_to_be_used", + .usage = wgpu::BufferUsage::Uniform, + .size = 4 + }; + sDummyUniformBuffer = device.CreateBuffer(&bufferDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) + << "Failed to create dummy uniform buffer?"; + } + if (!sDummyTexture || !sDummyTextureView) { + wgpu::TextureDescriptor textureDescriptor{ + .label = "dummy_texture_not_to_be_used", + .usage = wgpu::TextureUsage::TextureBinding, + .dimension = wgpu::TextureDimension::e2D, + .size = wgpu::Extent3D{ .width = 4, .height = 4, .depthOrArrayLayers = 1 }, + .format = aColorFormat, + }; + if (!sDummyTexture) { + sDummyTexture = device.CreateTexture(&textureDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) << "Failed to create dummy texture?"; + } + if (!sDummyTextureView) { + wgpu::TextureViewDescriptor textureViewDescriptor{ + .label = "dummy_texture_view_not_to_be_used" + }; + sDummyTextureView = sDummyTexture.CreateView(&textureViewDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) + << "Failed to create dummy texture view?"; + } + } + if (!sDummySampler) { + wgpu::SamplerDescriptor samplerDescriptor{ + .label = "dummy_sampler_not_to_be_used" + }; + sDummySampler = device.CreateSampler(&samplerDescriptor); + FILAMENT_CHECK_POSTCONDITION(sDummyUniformBuffer) << "Failed to create dummy sampler?"; + } +} + +std::vector WebGPUDescriptorSet::createDummyEntriesSortedByBinding( + std::vector const& + bindGroupEntries) { + assert_invariant(WebGPUDescriptorSet::sDummyUniformBuffer && + "Dummy uniform buffer must have been created before " + "creating dummy bind group entries."); + assert_invariant( + WebGPUDescriptorSet::sDummyTexture && + "Dummy texture must have been created before creating dummy bind group entries."); + assert_invariant( + WebGPUDescriptorSet::sDummyTextureView && + "Dummy texture view must have been created before creating dummy bind group entries."); + assert_invariant( + WebGPUDescriptorSet::sDummySampler && + "Dummy sampler must have been created before creating dummy bind group entries."); + using filament::backend::WebGPUDescriptorSetLayout; + std::vector entries; + entries.reserve(bindGroupEntries.size()); + for (auto const& entryInfo: bindGroupEntries) { + auto& entry = entries.emplace_back(); + entry.binding = entryInfo.binding; + switch (entryInfo.type) { + case WebGPUDescriptorSetLayout::BindGroupEntryType::UNIFORM_BUFFER: + entry.buffer = WebGPUDescriptorSet::sDummyUniformBuffer; + break; + case WebGPUDescriptorSetLayout::BindGroupEntryType::TEXTURE_VIEW: + entry.textureView = WebGPUDescriptorSet::sDummyTextureView; + break; + case WebGPUDescriptorSetLayout::BindGroupEntryType::SAMPLER: + entry.sampler = WebGPUDescriptorSet::sDummySampler; + break; + } + } + std::sort(entries.begin(), entries.end(), + [](wgpu::BindGroupEntry const& a, wgpu::BindGroupEntry const& b) { + return a.binding < b.binding; + }); + return entries; +} + +WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout, + std::vector const& bindGroupEntries) : mLayout(layout), - entries(layoutSize, wgpu::BindGroupEntry{}) { + mEntriesSortedByBinding(createDummyEntriesSortedByBinding(bindGroupEntries)) { // Establish the size of entries based on the layout. This should be reliable and efficient. + assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size()); + for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) { + mEntryIndexByBinding[i] = INVALID_INDEX; + } + for (size_t index = 0; index < mEntriesSortedByBinding.size(); index++) { + wgpu::BindGroupEntry const& entry = mEntriesSortedByBinding[index]; + assert_invariant(entry.binding < mEntryIndexByBinding.size()); + mEntryIndexByBinding[entry.binding] = static_cast(index); + } + for (auto const& entry : bindGroupEntries) { + if (entry.hasDynamicOffset) { + assert_invariant(entry.binding < mEntriesByBindingWithDynamicOffsets.size()); + mEntriesByBindingWithDynamicOffsets[entry.binding] = true; + } + } + mDynamicOffsets.reserve(mEntriesSortedByBinding.size()); +} + +WebGPUDescriptorSet::~WebGPUDescriptorSet() { + mBindGroup = nullptr; + mLayout = nullptr; } -WebGPUDescriptorSet::~WebGPUDescriptorSet() {} wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(const wgpu::Device& device) { if (mBindGroup) { return mBindGroup; } // TODO label? Should we just copy layout label? - wgpu::BindGroupDescriptor desc{ .layout = mLayout, - .entryCount = entries.size(), - .entries = entries.data() }; + wgpu::BindGroupDescriptor desc{ + .layout = mLayout, + .entryCount = mEntriesSortedByBinding.size(), + .entries = mEntriesSortedByBinding.data() + }; mBindGroup = device.CreateBindGroup(&desc); + FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?"; + // once we have created the bind group itself we should no longer need any other state + mLayout = nullptr; + mEntriesSortedByBinding.clear(); + mEntriesSortedByBinding.shrink_to_fit(); return mBindGroup; } -void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) { +void WebGPUDescriptorSet::addEntry(unsigned int index, wgpu::BindGroupEntry&& entry) { if (mBindGroup) { // We will keep getting hits from future updates, but shouldn't do anything // Filament guarantees this won't change after things have locked. @@ -292,6 +466,490 @@ void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) { } // TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the // layout index for efficiency. Add guards if wrong. - entries[index] = std::move(entry); + FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size()) + << "impossible/invalid index for a descriptor/binding (our of range or >= " + "MAX_DESCRIPTOR_COUNT) " + << index; + uint8_t entryIndex = mEntryIndexByBinding[index]; + FILAMENT_CHECK_POSTCONDITION( + entryIndex != INVALID_INDEX && entryIndex < mEntriesSortedByBinding.size()) + << "Invalid binding " << index; + entry.binding = index; + mEntriesSortedByBinding[entryIndex] = std::move(entry); + mEntriesByBindingAdded[index] = true; } + +uint32_t const* WebGPUDescriptorSet::setDynamicOffsets(uint32_t const* offsets) { + // mDynamicOffsets already reserves enough memory for the number of entries in the set + mDynamicOffsets.clear(); + // this implementation copies the offsets to mDynamicOffsets, but also adds values for + // unused entries TODO: is this necessary? + size_t inputIndex = 0; + size_t outputIndex = 0; + for (auto const& entry : mEntriesSortedByBinding) { + if (mEntriesByBindingWithDynamicOffsets[entry.binding]) { + if (mEntriesByBindingAdded[entry.binding]) { + mDynamicOffsets[outputIndex++] = offsets[inputIndex++]; + } else { + mDynamicOffsets[outputIndex++] = 0; // dummy offset, as it was never added + } + } + } + return mDynamicOffsets.data(); +} + +size_t WebGPUDescriptorSet::countEntitiesWithDynamicOffsets() const { + return mEntriesByBindingWithDynamicOffsets.count(); +} + +// From createTextureR +WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, + uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, + wgpu::Device const& device) noexcept { + assert_invariant( + samples == 1 || + samples == 4 && + "An invalid number of samples were requested, as WGPU requires the sample " + "count to either be 1 (no multisampling) or 4, at least as of April 2025 of " + "the spec. See https://www.w3.org/TR/webgpu/#texture-creation or " + "https://gpuweb.github.io/gpuweb/#multisample-state"); + // First, the texture aspect, starting with the defaults/basic configuration + mUsage = fToWGPUTextureUsage(usage); + mFormat = fToWGPUTextureFormat(format); + wgpu::TextureDescriptor textureDescriptor{ + .label = getUserTextureLabel(target), + .usage = mUsage, + .dimension = target == SamplerType::SAMPLER_3D ? wgpu::TextureDimension::e3D + : wgpu::TextureDimension::e2D, + .size = { .width = width, .height = height, .depthOrArrayLayers = depth }, + .format = mFormat, + .mipLevelCount = levels, + .sampleCount = samples, + // TODO Is this fine? Could do all-the-things, a naive mapping or get something from + // Filament + .viewFormatCount = 0, + .viewFormats = nullptr, + }; + // adjust for specific cases + switch (target) { + case SamplerType::SAMPLER_2D: + mArrayLayerCount = 1; + break; + case SamplerType::SAMPLER_2D_ARRAY: + mArrayLayerCount = textureDescriptor.size.depthOrArrayLayers; + break; + case SamplerType::SAMPLER_CUBEMAP: + textureDescriptor.size.depthOrArrayLayers = 6; + mArrayLayerCount = textureDescriptor.size.depthOrArrayLayers; + break; + case SamplerType::SAMPLER_EXTERNAL: + case SamplerType::SAMPLER_3D: + mArrayLayerCount = 1; + break; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + textureDescriptor.size.depthOrArrayLayers = depth * 6; + mArrayLayerCount = textureDescriptor.size.depthOrArrayLayers; + break; + } + assert_invariant(textureDescriptor.format != wgpu::TextureFormat::Undefined && + "Could not find appropriate WebGPU format"); + mTexture = device.CreateTexture(&textureDescriptor); + FILAMENT_CHECK_POSTCONDITION(mTexture) + << "Failed to create texture for " << textureDescriptor.label; + // Second, the texture view aspect + mTexView = makeTextureView(0, levels, target); +} + +// From createTextureViewR +WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept { + mTexture = src->mTexture; + mTexView = makeTextureView(baseLevel, levelCount, target); +} + +wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(const TextureUsage& fUsage) { + wgpu::TextureUsage retUsage = wgpu::TextureUsage::None; + + // Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini + // TODO Validate assumptions, revisit if issues. + if (any(TextureUsage::BLIT_SRC & fUsage)) { + retUsage |= wgpu::TextureUsage::CopySrc; + } + if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) { + retUsage |= wgpu::TextureUsage::CopyDst; + } + if (any(TextureUsage::SAMPLEABLE & fUsage)) { + retUsage |= wgpu::TextureUsage::TextureBinding; + } + // WGPU Render attachment covers either color or stencil situation dependant + // NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If + // issues, investigate here. + if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT | + TextureUsage::DEPTH_ATTACHMENT) & + fUsage)) { + retUsage |= wgpu::TextureUsage::RenderAttachment; + } + + // This is from Vulkan logic- if there are any issues try disabling this first, allows perf + // benefit though + const bool useTransientAttachment = + // Usage consists of attachment flags only. + none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) && + // Usage contains at least one attachment flag. + any(fUsage & TextureUsage::ALL_ATTACHMENTS) && + // Depth resolve cannot use transient attachment because it uses a custom shader. + // TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this + // restriction. + // Note that the custom shader does not resolve stencil. We do need to move to vk 1.2 + // and above to be able to support stencil resolve (along with depth). + !(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1); + if (useTransientAttachment) { + retUsage |= wgpu::TextureUsage::TransientAttachment; + } + // NOTE: Unused wgpu flags: + // StorageBinding + // StorageAttachment + + // NOTE: Unused Filament flags: + // SUBPASS_INPUT VK goes to input attachment which we don't support right now + // PROTECTED + return retUsage; +} +wgpu::TextureFormat WGPUTexture::fToWGPUTextureFormat(const TextureFormat& fUsage) { + switch (fUsage) { + case filament::backend::TextureFormat::R8: + return wgpu::TextureFormat::R8Unorm; + case filament::backend::TextureFormat::R8_SNORM: + return wgpu::TextureFormat::R8Snorm; + case filament::backend::TextureFormat::R8UI: + return wgpu::TextureFormat::R8Uint; + case filament::backend::TextureFormat::R8I: + return wgpu::TextureFormat::R8Sint; + case filament::backend::TextureFormat::STENCIL8: + return wgpu::TextureFormat::Stencil8; + case filament::backend::TextureFormat::R16F: + return wgpu::TextureFormat::R16Float; + case filament::backend::TextureFormat::R16UI: + return wgpu::TextureFormat::R16Uint; + case filament::backend::TextureFormat::R16I: + return wgpu::TextureFormat::R16Sint; + case filament::backend::TextureFormat::RG8: + return wgpu::TextureFormat::RG8Unorm; + case filament::backend::TextureFormat::RG8_SNORM: + return wgpu::TextureFormat::RG8Snorm; + case filament::backend::TextureFormat::RG8UI: + return wgpu::TextureFormat::RG8Uint; + case filament::backend::TextureFormat::RG8I: + return wgpu::TextureFormat::RG8Sint; + case filament::backend::TextureFormat::R32F: + return wgpu::TextureFormat::R32Float; + case filament::backend::TextureFormat::R32UI: + return wgpu::TextureFormat::R32Uint; + case filament::backend::TextureFormat::R32I: + return wgpu::TextureFormat::R32Sint; + case filament::backend::TextureFormat::RG16F: + return wgpu::TextureFormat::RG16Float; + case filament::backend::TextureFormat::RG16UI: + return wgpu::TextureFormat::RG16Uint; + case filament::backend::TextureFormat::RG16I: + return wgpu::TextureFormat::RG16Sint; + case filament::backend::TextureFormat::RGBA8: + return wgpu::TextureFormat::RGBA8Unorm; + case filament::backend::TextureFormat::SRGB8_A8: + return wgpu::TextureFormat::RGBA8UnormSrgb; + case filament::backend::TextureFormat::RGBA8_SNORM: + return wgpu::TextureFormat::RGBA8Snorm; + case filament::backend::TextureFormat::RGBA8UI: + return wgpu::TextureFormat::RGBA8Uint; + case filament::backend::TextureFormat::RGBA8I: + return wgpu::TextureFormat::RGBA8Sint; + case filament::backend::TextureFormat::DEPTH16: + return wgpu::TextureFormat::Depth16Unorm; + case filament::backend::TextureFormat::DEPTH24: + return wgpu::TextureFormat::Depth24Plus; + case filament::backend::TextureFormat::DEPTH32F: + return wgpu::TextureFormat::Depth32Float; + case filament::backend::TextureFormat::DEPTH24_STENCIL8: + return wgpu::TextureFormat::Depth24PlusStencil8; + case filament::backend::TextureFormat::DEPTH32F_STENCIL8: + return wgpu::TextureFormat::Depth32FloatStencil8; + case filament::backend::TextureFormat::RG32F: + return wgpu::TextureFormat::RG32Float; + case filament::backend::TextureFormat::RG32UI: + return wgpu::TextureFormat::RG32Uint; + case filament::backend::TextureFormat::RG32I: + return wgpu::TextureFormat::RG32Sint; + case filament::backend::TextureFormat::RGBA16F: + return wgpu::TextureFormat::RGBA16Float; + case filament::backend::TextureFormat::RGBA16UI: + return wgpu::TextureFormat::RGBA16Uint; + case filament::backend::TextureFormat::RGBA16I: + return wgpu::TextureFormat::RGBA16Sint; + case filament::backend::TextureFormat::RGBA32F: + return wgpu::TextureFormat::RGBA32Float; + case filament::backend::TextureFormat::RGBA32UI: + return wgpu::TextureFormat::RGBA32Uint; + case filament::backend::TextureFormat::RGBA32I: + return wgpu::TextureFormat::RGBA32Sint; + case filament::backend::TextureFormat::EAC_R11: + return wgpu::TextureFormat::EACR11Unorm; + case filament::backend::TextureFormat::EAC_R11_SIGNED: + return wgpu::TextureFormat::EACR11Snorm; + case filament::backend::TextureFormat::EAC_RG11: + return wgpu::TextureFormat::EACRG11Unorm; + case filament::backend::TextureFormat::EAC_RG11_SIGNED: + return wgpu::TextureFormat::EACRG11Snorm; + case filament::backend::TextureFormat::ETC2_RGB8: + return wgpu::TextureFormat::ETC2RGB8Unorm; + case filament::backend::TextureFormat::ETC2_SRGB8: + return wgpu::TextureFormat::ETC2RGB8UnormSrgb; + case filament::backend::TextureFormat::ETC2_RGB8_A1: + return wgpu::TextureFormat::ETC2RGB8A1Unorm; + case filament::backend::TextureFormat::ETC2_SRGB8_A1: + return wgpu::TextureFormat::ETC2RGB8A1UnormSrgb; + case filament::backend::TextureFormat::ETC2_EAC_RGBA8: + return wgpu::TextureFormat::ETC2RGBA8Unorm; + case filament::backend::TextureFormat::ETC2_EAC_SRGBA8: + return wgpu::TextureFormat::ETC2RGBA8UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_4x4: + return wgpu::TextureFormat::ASTC4x4Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_4x4: + return wgpu::TextureFormat::ASTC4x4UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_5x4: + return wgpu::TextureFormat::ASTC5x4Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_5x4: + return wgpu::TextureFormat::ASTC5x4UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_5x5: + return wgpu::TextureFormat::ASTC5x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_5x5: + return wgpu::TextureFormat::ASTC5x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_6x5: + return wgpu::TextureFormat::ASTC6x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_6x5: + return wgpu::TextureFormat::ASTC6x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_6x6: + return wgpu::TextureFormat::ASTC6x6Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_6x6: + return wgpu::TextureFormat::ASTC6x6UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_8x5: + return wgpu::TextureFormat::ASTC8x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x5: + return wgpu::TextureFormat::ASTC8x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_8x6: + return wgpu::TextureFormat::ASTC8x6Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x6: + return wgpu::TextureFormat::ASTC8x6UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_8x8: + return wgpu::TextureFormat::ASTC8x8Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_8x8: + return wgpu::TextureFormat::ASTC8x8UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x5: + return wgpu::TextureFormat::ASTC10x5Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x5: + return wgpu::TextureFormat::ASTC10x5UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x6: + return wgpu::TextureFormat::ASTC10x6Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x6: + return wgpu::TextureFormat::ASTC10x6UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x8: + return wgpu::TextureFormat::ASTC10x8Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x8: + return wgpu::TextureFormat::ASTC10x8UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_10x10: + return wgpu::TextureFormat::ASTC10x10Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_10x10: + return wgpu::TextureFormat::ASTC10x10UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_12x10: + return wgpu::TextureFormat::ASTC12x10Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_12x10: + return wgpu::TextureFormat::ASTC12x10UnormSrgb; + case filament::backend::TextureFormat::RGBA_ASTC_12x12: + return wgpu::TextureFormat::ASTC12x12Unorm; + case filament::backend::TextureFormat::SRGB8_ALPHA8_ASTC_12x12: + return wgpu::TextureFormat::ASTC12x12UnormSrgb; + case filament::backend::TextureFormat::RED_RGTC1: + return wgpu::TextureFormat::BC4RUnorm; + case filament::backend::TextureFormat::SIGNED_RED_RGTC1: + return wgpu::TextureFormat::BC4RSnorm; + case filament::backend::TextureFormat::RED_GREEN_RGTC2: + return wgpu::TextureFormat::BC5RGUnorm; + case filament::backend::TextureFormat::SIGNED_RED_GREEN_RGTC2: + return wgpu::TextureFormat::BC5RGSnorm; + case filament::backend::TextureFormat::RGB_BPTC_UNSIGNED_FLOAT: + return wgpu::TextureFormat::BC6HRGBUfloat; + case filament::backend::TextureFormat::RGB_BPTC_SIGNED_FLOAT: + return wgpu::TextureFormat::BC6HRGBFloat; + case filament::backend::TextureFormat::RGBA_BPTC_UNORM: + return wgpu::TextureFormat::BC7RGBAUnorm; + case filament::backend::TextureFormat::SRGB_ALPHA_BPTC_UNORM: + return wgpu::TextureFormat::BC7RGBAUnormSrgb; + case filament::backend::TextureFormat::RGB565: + // No direct mapping in wgpu. Could potentially map to RGBA8Unorm + // and discard the alpha and lower precision. + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGB9_E5: + return wgpu::TextureFormat::RGB9E5Ufloat; + case filament::backend::TextureFormat::RGB5_A1: + // No direct mapping in wgpu. Could potentially map to RGBA8Unorm + // and handle the packing/unpacking in shaders. + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGBA4: + // No direct mapping in wgpu. Could potentially map to RGBA8Unorm + // and handle the packing/unpacking in shaders. + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGB8: + // No direct sRGB equivalent in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Unorm; + case filament::backend::TextureFormat::SRGB8: + // No direct sRGB equivalent in wgpu without alpha. + return wgpu::TextureFormat::RGBA8UnormSrgb; + case filament::backend::TextureFormat::RGB8_SNORM: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Snorm; + case filament::backend::TextureFormat::RGB8UI: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Uint; + case filament::backend::TextureFormat::RGB8I: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA8Sint; + case filament::backend::TextureFormat::R11F_G11F_B10F: + return wgpu::TextureFormat::RG11B10Ufloat; + case filament::backend::TextureFormat::UNUSED: + return wgpu::TextureFormat::Undefined; + case filament::backend::TextureFormat::RGB10_A2: + return wgpu::TextureFormat::RGB10A2Unorm; + case filament::backend::TextureFormat::RGB16F: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA16Float; + case filament::backend::TextureFormat::RGB16UI: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA16Uint; + case filament::backend::TextureFormat::RGB16I: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA16Sint; + case filament::backend::TextureFormat::RGB32F: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA32Float; + case filament::backend::TextureFormat::RGB32UI: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA32Uint; + case filament::backend::TextureFormat::RGB32I: + // No direct mapping in wgpu without alpha. + return wgpu::TextureFormat::RGBA32Sint; + case filament::backend::TextureFormat::DXT1_RGB: + return wgpu::TextureFormat::BC1RGBAUnorm; + case filament::backend::TextureFormat::DXT1_RGBA: + return wgpu::TextureFormat::BC1RGBAUnorm; + case filament::backend::TextureFormat::DXT3_RGBA: + return wgpu::TextureFormat::BC2RGBAUnorm; + case filament::backend::TextureFormat::DXT5_RGBA: + return wgpu::TextureFormat::BC3RGBAUnorm; + case filament::backend::TextureFormat::DXT1_SRGB: + return wgpu::TextureFormat::BC1RGBAUnormSrgb; + case filament::backend::TextureFormat::DXT1_SRGBA: + return wgpu::TextureFormat::BC1RGBAUnormSrgb; + case filament::backend::TextureFormat::DXT3_SRGBA: + return wgpu::TextureFormat::BC2RGBAUnormSrgb; + case filament::backend::TextureFormat::DXT5_SRGBA: + return wgpu::TextureFormat::BC3RGBAUnormSrgb; + } +} + +wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount, + SamplerType target) { + // starting with the defaults/basic configuration + wgpu::TextureViewDescriptor textureViewDescriptor{ + .label = getUserTextureViewLabel(target), + .format = mFormat, + // dimension depends on target and is set below + .baseMipLevel = baseLevel, + .mipLevelCount = levelCount, + // baseArrayLayer is required, making a guess + .baseArrayLayer = 0, + .arrayLayerCount = mArrayLayerCount, + // Have not found an analog to aspect in other drivers, but ALL should be unrestrictive. + // TODO Can we make this better? + .aspect = wgpu::TextureAspect::All, + .usage = mUsage + }; + // adjust for specific cases + switch (target) { + case SamplerType::SAMPLER_2D: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D; + break; + case SamplerType::SAMPLER_2D_ARRAY: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2DArray; + break; + case SamplerType::SAMPLER_CUBEMAP: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::Cube; + break; + case SamplerType::SAMPLER_EXTERNAL: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D; + break; + case SamplerType::SAMPLER_3D: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::e3D; + break; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + textureViewDescriptor.dimension = wgpu::TextureViewDimension::CubeArray; + break; + } + wgpu::TextureView textureView = mTexture.CreateView(&textureViewDescriptor); + FILAMENT_CHECK_POSTCONDITION(textureView) + << "Failed to create texture view " << textureViewDescriptor.label; + return textureView; +} + +WGPURenderTarget::Attachment WGPURenderTarget::getDrawColorAttachment(size_t index) { + assert_invariant( index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); + auto result = color[index]; + if (index == 0 && defaultRenderTarget) { + + } + + return result; +} + +wgpu::LoadOp WGPURenderTarget::getLoadOperation(RenderPassParams const& params, + TargetBufferFlags buffer) { + auto clearFlags = params.flags.clear; + auto discardStartFlags = params.flags.discardStart; + if (any(clearFlags & buffer)) { + return wgpu::LoadOp::Clear; + } else if (any(discardStartFlags & buffer)) { + return wgpu::LoadOp::Clear; + } + return wgpu::LoadOp::Load; +} + +wgpu::StoreOp WGPURenderTarget::getStoreOperation(RenderPassParams const& params, + TargetBufferFlags buffer) { + const auto discardEndFlags = params.flags.discardEnd; + if (any(discardEndFlags & buffer)) { + return wgpu::StoreOp::Discard; + } + return wgpu::StoreOp::Store; +} +void WGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& descriptor, + wgpu::TextureView const& textureView, RenderPassParams const& params) { + // auto discardFlags = params.flags.discardEnd; + // (void) discardFlags; + // std::vector colorAttachments; + colorAttachments.clear(); + for (size_t i = 0; i < 1/*MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT*/; i++) { + // auto attachment = getDrawColorAttachment(i); + // if (attachment) { + wgpu::RenderPassColorAttachment colorAttachment; + colorAttachment.view = textureView; + colorAttachment.loadOp = getLoadOperation(params, getTargetBufferFlagsAt(i)); + colorAttachment.storeOp = getStoreOperation(params, getTargetBufferFlagsAt(i)); + colorAttachment.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a }; + colorAttachments.emplace_back(colorAttachment); + // } + } + descriptor.colorAttachments = colorAttachments.data(); + descriptor.colorAttachmentCount = colorAttachments.size(); + descriptor.depthStencilAttachment = nullptr; + descriptor.timestampWrites = nullptr; +} + + }// namespace filament::backend diff --git a/filament/backend/src/webgpu/WebGPUHandles.h b/filament/backend/src/webgpu/WebGPUHandles.h index 3c936e904f..fdc51e92b0 100644 --- a/filament/backend/src/webgpu/WebGPUHandles.h +++ b/filament/backend/src/webgpu/WebGPUHandles.h @@ -27,6 +27,8 @@ #include +#include +#include #include #include @@ -98,80 +100,124 @@ struct WGPUBufferObject : HwBufferObject { wgpu::Buffer buffer = nullptr; const BufferObjectBinding bufferObjectBinding; }; + class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout { public: + + enum class BindGroupEntryType : uint8_t { + UNIFORM_BUFFER, + TEXTURE_VIEW, + SAMPLER + }; + + struct BindGroupEntryInfo final { + uint8_t binding = 0; + BindGroupEntryType type = BindGroupEntryType::UNIFORM_BUFFER; + bool hasDynamicOffset = false; + }; + WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device); ~WebGPUDescriptorSetLayout(); [[nodiscard]] const wgpu::BindGroupLayout& getLayout() const { return mLayout; } - [[nodiscard]] uint getLayoutSize() const { return mLayoutSize; } + [[nodiscard]] std::vector const& getBindGroupEntries() const { + return mBindGroupEntries; + } private: // TODO: If this is useful elsewhere, remove it from this class // Convert Filament Shader Stage Flags bitmask to webgpu equivilant static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags); - uint mLayoutSize; + std::vector mBindGroupEntries; wgpu::BindGroupLayout mLayout; }; class WebGPUDescriptorSet final : public HwDescriptorSet { public: - WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize); + static void initializeDummyResourcesIfNotAlready(wgpu::Device const&, + wgpu::TextureFormat aColorFormat); + + WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout, + std::vector const& bindGroupEntries); ~WebGPUDescriptorSet(); - wgpu::BindGroup lockAndReturn(wgpu::Device const& device); - void addEntry(uint index, wgpu::BindGroupEntry&& entry); + wgpu::BindGroup lockAndReturn(wgpu::Device const&); + void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry); + [[nodiscard]] uint32_t const* setDynamicOffsets(uint32_t const* offsets); [[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; } - + [[nodiscard]] size_t countEntitiesWithDynamicOffsets() const; private: + + static wgpu::Buffer sDummyUniformBuffer; + static wgpu::Texture sDummyTexture; + static wgpu::TextureView sDummyTextureView; + static wgpu::Sampler sDummySampler; + + static std::vector createDummyEntriesSortedByBinding( + std::vector const&); + // TODO: Consider storing what we used to make the layout. However we need to essentially // Recreate some of the info (Sampler in slot X with the actual sampler) so letting Dawn confirm // there isn't a mismatch may be easiest. // Also storing the wgpu ObjectBase takes care of ownership challenges in theory - wgpu::BindGroupLayout mLayout; - std::vector entries; - wgpu::BindGroup mBindGroup; + wgpu::BindGroupLayout mLayout = nullptr; + static constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1; + std::array mEntryIndexByBinding {}; + std::vector mEntriesSortedByBinding; + std::bitset mEntriesByBindingWithDynamicOffsets {}; + std::bitset mEntriesByBindingAdded {}; + std::vector mDynamicOffsets; + wgpu::BindGroup mBindGroup = nullptr; }; -// TODO: Currently WGPUTexture is not used by WebGPU for useful task. -// Update the struct when used by WebGPU driver. -struct WGPUTexture : public HwTexture { +class WGPUTexture : public HwTexture { +public: WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, - uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept; + uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, + wgpu::Device const& device) noexcept; - // constructors for creating texture views - WGPUTexture(WGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept; + WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept; - wgpu::Texture texture = nullptr; - // TODO: Adding this but not yet setting it up. Filament "Textures" are combined image samplers, - // rep both. - wgpu::Sampler sampler = nullptr; - //TODO: Not sure all the ways HwTexture is used. Overloading like this might be entirely wrong. - wgpu::TextureView texView = nullptr; + const wgpu::Texture& getTexture() const { return mTexture; } + const wgpu::TextureView& getTexView() const { return mTexView; } + + // Public to allow checking for support of a texture format + static wgpu::TextureFormat fToWGPUTextureFormat(const filament::backend::TextureFormat& fUsage); + +private: + wgpu::TextureView makeTextureView(const uint8_t& baseLevel, const uint8_t& levelCount, + SamplerType target); + // CreateTextureR has info for a texture and sampler. Texture Views are needed for binding, + // along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a + // ref counted pointer) when making views. View is optional + wgpu::Texture mTexture = nullptr; + wgpu::TextureUsage mUsage = wgpu::TextureUsage::None; + wgpu::TextureFormat mFormat = wgpu::TextureFormat::Undefined; + uint32_t mArrayLayerCount = 1; + wgpu::TextureView mTexView = nullptr; + wgpu::TextureUsage fToWGPUTextureUsage(const filament::backend::TextureUsage& fUsage); }; struct WGPURenderPrimitive : public HwRenderPrimitive { - WGPURenderPrimitive() {} - - void setBuffers(WGPUVertexBufferInfo const* const vbi, - WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer); WGPUVertexBuffer* vertexBuffer = nullptr; WGPUIndexBuffer* indexBuffer = nullptr; }; -// TODO: Currently WGPURenderTarget is not used by WebGPU for useful task. -// Update the struct when used by WebGPU driver. -struct WGPURenderTarget : public HwRenderTarget { +class WGPURenderTarget : public HwRenderTarget { +public: class Attachment { public: - friend struct WGPURenderTarget; + friend class WGPURenderTarget; Attachment() = default; Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0) : level(level), layer(layer), - texture(gpuTexture->texture), + texture(gpuTexture->getTexture()), mWGPUTexture(gpuTexture) {} + operator bool() const { + return mWGPUTexture != nullptr; + } uint8_t level = 0; uint16_t layer = 0; @@ -187,8 +233,8 @@ struct WGPURenderTarget : public HwRenderTarget { : HwRenderTarget(0, 0), defaultRenderTarget(true) {} - void setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor, - const RenderPassParams& params); + void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& descriptor, + wgpu::TextureView const& textureView, RenderPassParams const& params); math::uint2 getAttachmentSize() noexcept; @@ -198,15 +244,15 @@ struct WGPURenderTarget : public HwRenderTarget { Attachment getDrawColorAttachment(size_t index); Attachment getReadColorAttachment(size_t index); + static wgpu::LoadOp getLoadOperation(const RenderPassParams& params, TargetBufferFlags buffer); + static wgpu::StoreOp getStoreOperation(const RenderPassParams& params, TargetBufferFlags buffer); private: - static wgpu::LoadOp getLoadAction(const RenderPassParams& params, TargetBufferFlags buffer); - static wgpu::LoadOp getStoreAction(const RenderPassParams& params, TargetBufferFlags buffer); - bool defaultRenderTarget = false; uint8_t samples = 1; Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; math::uint2 attachmentSize = {}; + std::vector colorAttachments {}; }; }// namespace filament::backend diff --git a/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp b/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp index cb908f97ef..e435ca64bb 100644 --- a/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp +++ b/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp @@ -235,6 +235,7 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device, fragmentState.constantCount = program.constants.size(), fragmentState.constants = program.constants.data(), fragmentState.targetCount = 1; // TODO need to get this from the render target + fragmentState.targets = colorTargets.data(); assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) { auto& colorTarget = colorTargets[targetIndex]; diff --git a/filament/backend/src/webgpu/WebGPUSwapChain.cpp b/filament/backend/src/webgpu/WebGPUSwapChain.cpp index f9809ebe5e..d9a0444afe 100644 --- a/filament/backend/src/webgpu/WebGPUSwapChain.cpp +++ b/filament/backend/src/webgpu/WebGPUSwapChain.cpp @@ -105,7 +105,7 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config, } #endif -constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, +[[nodiscard]] constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) { const std::array expectedColorFormats = useSRGBColorSpace ? @@ -123,7 +123,7 @@ constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, return *firstFoundColorFormat; } -constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled, +[[nodiscard]] constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled, bool needStencil) { if (needStencil) { if (depth32FloatStencil8Enabled) { @@ -137,7 +137,7 @@ constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled } } -constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, +[[nodiscard]] constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, wgpu::PresentMode const* availablePresentModes) { // Verify that our chosen present mode is supported. In practice all devices support the FIFO // mode, but we check for it anyway for completeness. (and to avoid validation warnings) @@ -151,7 +151,7 @@ constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, return desiredPresentMode; } -constexpr wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, +[[nodiscard]] constexpr wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, wgpu::CompositeAlphaMode const* availableAlphaModes) { bool autoAvailable = false; bool inheritAvailable = false; @@ -222,13 +222,58 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device, config.alphaMode = selectAlphaMode(capabilities.alphaModeCount, capabilities.alphaModes); } +[[nodiscard]] wgpu::Texture createDepthTexture(wgpu::Device const& device, + wgpu::Extent2D const& extent, wgpu::TextureFormat depthFormat) { + wgpu::TextureDescriptor descriptor{ .label = "depth_texture", + .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment, + .dimension = wgpu::TextureDimension::e2D, + .size = { .width = extent.width, .height = extent.height, .depthOrArrayLayers = 1 }, + .format = depthFormat, + .mipLevelCount = 1, + .sampleCount = 1, + .viewFormatCount = 1, + .viewFormats = &depthFormat + }; + wgpu::Texture depthTexture = device.CreateTexture(&descriptor); + FILAMENT_CHECK_POSTCONDITION(depthTexture) << "Failed to create depth texture with width " + << extent.width << " and height " << extent.height; + return depthTexture; +} + +[[nodiscard]] wgpu::TextureView createDepthTextureView(wgpu::Texture const& depthTexture, + wgpu::TextureFormat depthFormat, bool needStencil) { + wgpu::TextureViewDescriptor descriptor{ + .label = "depth_texture_view", + .format = depthFormat, + .dimension = wgpu::TextureViewDimension::e2D, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .arrayLayerCount = 1, + .aspect = wgpu::TextureAspect::DepthOnly, + .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment + }; + if (needStencil) { + descriptor.aspect = wgpu::TextureAspect::All; + } + wgpu::TextureView depthTextureView = depthTexture.CreateView(&descriptor); + FILAMENT_CHECK_POSTCONDITION(depthTextureView) << "Failed to create depth texture view"; + return depthTextureView; +} + }// namespace namespace filament::backend { WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize, wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags) - : mSurface(surface) { + : mDevice(device), + mSurface(surface), + mNeedStencil((flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0), + mDepthFormat(selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8), + mNeedStencil)), + mDepthTexture(createDepthTexture(device, surfaceSize, mDepthFormat)), + mDepthTextureView(createDepthTextureView(mDepthTexture, mDepthFormat, mNeedStencil)) { wgpu::SurfaceCapabilities capabilities = {}; if (!mSurface.GetCapabilities(adapter, &capabilities)) { FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl; @@ -238,19 +283,14 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& #endif } const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0; - const bool needStencil = (flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0; initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace); - mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8), - needStencil); #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) printSurfaceConfiguration(mConfig, mDepthFormat); #endif mSurface.Configure(&mConfig); } -WebGPUSwapChain::~WebGPUSwapChain() { - mSurface.Unconfigure(); -} +WebGPUSwapChain::~WebGPUSwapChain() { mSurface.Unconfigure(); } void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) { FILAMENT_CHECK_POSTCONDITION(currentSurfaceSize.width > 0 || currentSurfaceSize.height > 0) @@ -267,6 +307,8 @@ void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) { // TODO we may need to ensure no surface texture is in flight when we do this. some // synchronization may be necessary mSurface.Configure(&mConfig); + mDepthTexture = createDepthTexture(mDevice, currentSurfaceSize, mDepthFormat); + mDepthTextureView = createDepthTextureView(mDepthTexture, mDepthFormat, mNeedStencil); } } diff --git a/filament/backend/src/webgpu/WebGPUSwapChain.h b/filament/backend/src/webgpu/WebGPUSwapChain.h index 47ed7313ed..9b3b796f3b 100644 --- a/filament/backend/src/webgpu/WebGPUSwapChain.h +++ b/filament/backend/src/webgpu/WebGPUSwapChain.h @@ -38,14 +38,20 @@ public: [[nodiscard]] wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&); + [[nodiscard]] wgpu::TextureView getDepthTextureView() const { return mDepthTextureView; } + void present(); private: void setExtent(wgpu::Extent2D const&); + wgpu::Device mDevice = nullptr; wgpu::Surface mSurface = {}; wgpu::SurfaceConfiguration mConfig = {}; + bool mNeedStencil = false; wgpu::TextureFormat mDepthFormat = wgpu::TextureFormat::Undefined; + wgpu::Texture mDepthTexture = nullptr; + wgpu::TextureView mDepthTextureView = nullptr; }; } // namespace filament::backend diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp index 6cc264d055..f41d71a30b 100644 --- a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp +++ b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp @@ -99,22 +99,45 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) { wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) { // TODO consider passing limits - constexpr std::array desiredFeatures = { - wgpu::FeatureName::DepthClipControl, - wgpu::FeatureName::Depth32FloatStencil8, - wgpu::FeatureName::CoreFeaturesAndLimits }; - std::vector requiredFeatures; - requiredFeatures.reserve(desiredFeatures.size()); + constexpr std::array optionalFeatures = { wgpu::FeatureName::DepthClipControl, + wgpu::FeatureName::Depth32FloatStencil8, wgpu::FeatureName::CoreFeaturesAndLimits }; + + constexpr std::array requiredFeatures = { wgpu::FeatureName::TransientAttachments }; + wgpu::SupportedFeatures supportedFeatures; adapter.GetFeatures(&supportedFeatures); + + std::vector enabledFeatures; + enabledFeatures.reserve(requiredFeatures.size() + optionalFeatures.size()); + std::set_intersection(supportedFeatures.features, - supportedFeatures.features + supportedFeatures.featureCount, desiredFeatures.begin(), - desiredFeatures.end(), std::back_inserter(requiredFeatures)); + supportedFeatures.features + supportedFeatures.featureCount, requiredFeatures.begin(), + requiredFeatures.end(), std::back_inserter(enabledFeatures)); + + if (enabledFeatures.size() != requiredFeatures.size()) { + std::vector missingFeatures; + std::set_difference(requiredFeatures.begin(), requiredFeatures.end(), + supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, + std::back_inserter(missingFeatures)); + + std::stringstream missingFeaturesStream{}; + for (const auto& entry: missingFeatures) { + missingFeaturesStream << std::to_string(static_cast(entry)) << " "; + } + PANIC_POSTCONDITION("Some required features are not available %s/n", + missingFeaturesStream.str().c_str()); + } + + std::set_intersection(supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, optionalFeatures.begin(), + optionalFeatures.end(), std::back_inserter(enabledFeatures)); + wgpu::DeviceDescriptor deviceDescriptor{}; deviceDescriptor.label = "graphics_device"; deviceDescriptor.defaultQueue.label = "default_queue"; - deviceDescriptor.requiredFeatureCount = requiredFeatures.size(); - deviceDescriptor.requiredFeatures = requiredFeatures.data(); + deviceDescriptor.requiredFeatureCount = enabledFeatures.size(); + deviceDescriptor.requiredFeatures = enabledFeatures.data(); deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, [](wgpu::Device const&, wgpu::DeviceLostReason const& reason, wgpu::StringView message) { diff --git a/filament/backend/test/BackendTest.cpp b/filament/backend/test/BackendTest.cpp index 31c81040f2..2788bad598 100644 --- a/filament/backend/test/BackendTest.cpp +++ b/filament/backend/test/BackendTest.cpp @@ -45,6 +45,7 @@ namespace test { Backend BackendTest::sBackend = Backend::NOOP; OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER; bool BackendTest::sIsMobilePlatform = false; +std::vector BackendTest::sFailedImages; void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) { sBackend = backend; @@ -63,11 +64,12 @@ BackendTest::~BackendTest() { flushAndWait(); mImageExpectations->evaluate(); // Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen. - if (sBackend == Backend::OPENGL) { - return; + if (sBackend != Backend::OPENGL) { + driver->terminate(); + delete driver; } - driver->terminate(); - delete driver; + + recordFailedImages(); } void BackendTest::initializeDriver() { @@ -167,8 +169,24 @@ bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) { return sOperatingSystem == operatingSystem; } -bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) { - return matchesEnvironment(operatingSystem) && matchesEnvironment(backend); +void BackendTest::markImageAsFailure(std::string failedImageName) { + sFailedImages.emplace_back(std::move(failedImageName)); +} + +void BackendTest::recordFailedImages() { + if (!sFailedImages.empty()) { + std::string failedImages; + for (auto& failedTestImageName: sFailedImages) { + if (failedImages.empty()) { + failedImages = failedTestImageName; + } else { + failedImages.append(","); + failedImages.append(failedTestImageName); + } + } + RecordProperty("FailedImages", failedImages); + } + sFailedImages.clear(); } class Environment : public ::testing::Environment { diff --git a/filament/backend/test/BackendTest.h b/filament/backend/test/BackendTest.h index c2d05cb37d..a5c16fd203 100644 --- a/filament/backend/test/BackendTest.h +++ b/filament/backend/test/BackendTest.h @@ -38,6 +38,9 @@ public: static OperatingSystem sOperatingSystem; static bool sIsMobilePlatform; + // Takes the name of the image that wasn't correct, without the .png suffix + static void markImageAsFailure(std::string failedImageName); + protected: BackendTest(); @@ -73,8 +76,13 @@ protected: static bool matchesEnvironment(Backend backend); static bool matchesEnvironment(OperatingSystem operatingSystem); - static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend); private: + // Adds all the images that failed an ImageExpectation to the XML metadata for the current tests + // case. Add --gtest_output=xml as a command line argument to generate a test_detail.xml file in + // the directory where the tests are run. + static void recordFailedImages(); + + static std::vector sFailedImages; filament::backend::Driver* driver = nullptr; filament::backend::CommandBufferQueue commandBufferQueue; diff --git a/filament/backend/test/ImageExpectations.cpp b/filament/backend/test/ImageExpectations.cpp index 0ad4ce2543..054b118792 100644 --- a/filament/backend/test/ImageExpectations.cpp +++ b/filament/backend/test/ImageExpectations.cpp @@ -21,6 +21,7 @@ #include "utils/Hash.h" #include +#include "BackendTest.h" #include "backend/PixelBufferDescriptor.h" #include "private/backend/DriverApi.h" @@ -32,6 +33,8 @@ #endif +namespace test { + ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName, uint32_t expectedHash, bool isSrgb) : mWidth(width), @@ -80,6 +83,10 @@ std::string ScreenshotParams::expectedFilePath() const { return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName()); } +const std::string ScreenshotParams::filePrefix() const { + return mFileName; +} + ImageExpectation::ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api, ScreenshotParams params, filament::backend::RenderTargetHandle renderTarget) @@ -113,7 +120,11 @@ void ImageExpectation::compareImage() const { #ifndef FILAMENT_IOS LoadedPng loadedImage(mParams.expectedFilePath()); uint32_t loadedImageHash = loadedImage.hash(); - EXPECT_THAT(actualHash, testing::Eq(loadedImageHash)) << mParams.expectedFileName(); + auto compareToImageMatcher = testing::Eq(loadedImageHash); + if (!testing::Matches(compareToImageMatcher)(actualHash)) { + BackendTest::markImageAsFailure(mParams.filePrefix()); + } + EXPECT_THAT(actualHash, compareToImageMatcher) << mParams.expectedFileName(); #endif // For builds that can't load PNGs (currently iOS only) use the expected hash. EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())) << mParams.expectedFileName(); @@ -237,3 +248,5 @@ uint32_t LoadedPng::hash() const { const std::vector& LoadedPng::bytes() const { return mBytes; } + +} // namespace test diff --git a/filament/backend/test/ImageExpectations.h b/filament/backend/test/ImageExpectations.h index 0432e00b7f..c3a293184f 100644 --- a/filament/backend/test/ImageExpectations.h +++ b/filament/backend/test/ImageExpectations.h @@ -35,6 +35,8 @@ do { \ screenshotParams); \ } while (0) +namespace test { + /** * Stores user-provided configuration values for an image expectation */ @@ -54,6 +56,7 @@ public: static std::string expectedDirectoryPath(); std::string expectedFileName() const; std::string expectedFilePath() const; + const std::string filePrefix() const; private: int mWidth; @@ -153,4 +156,6 @@ private: std::vector> mExpectations; }; +} // namespace test + #endif //TNT_IMAGE_EXPECTATIONS_H diff --git a/filament/backend/test/Shader.cpp b/filament/backend/test/Shader.cpp index 30b6e50a0e..bed1b0ee31 100644 --- a/filament/backend/test/Shader.cpp +++ b/filament/backend/test/Shader.cpp @@ -57,7 +57,7 @@ Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup if (!kLayouts.empty()) { mDescriptorSetLayout = - cleanup.add(api.createDescriptorSetLayout(DescriptorSetLayout{ kLayouts })); + cleanup.add(api.createDescriptorSetLayout(DescriptorSetLayout{ .bindings = kLayouts })); } } diff --git a/filament/backend/test/ShaderGenerator.cpp b/filament/backend/test/ShaderGenerator.cpp index e493c444af..39bc3f9036 100644 --- a/filament/backend/test/ShaderGenerator.cpp +++ b/filament/backend/test/ShaderGenerator.cpp @@ -17,7 +17,6 @@ #include "ShaderGenerator.h" #include -#include #include #include diff --git a/filament/backend/test/move_actual_images_to_expected.py b/filament/backend/test/move_actual_images_to_expected.py index 612882a370..a76a30350b 100644 --- a/filament/backend/test/move_actual_images_to_expected.py +++ b/filament/backend/test/move_actual_images_to_expected.py @@ -1,62 +1,143 @@ -import os, shutil, argparse, typing - -def match_sufffix(file_name: str, suffix: str, accepted_prefixes: typing.List[str]) -> str: - """ - Check if the file name is one of the searched for ones with the given suffix and if so return it. - :param accepted_prefixes: If None accepts any prefix - :return: file_name with the suffix removed or "" if it doesn't match. This does mean a string that - is just the suffix is considered to not match as it will return the empty string. - """ - if file_name.endswith(suffix): - prefix = file_name.removesuffix(suffix) - if accepted_prefixes is None or prefix in accepted_prefixes: - return prefix - return "" +import os, shutil, argparse, typing, xml.etree.ElementTree, subprocess, platform -def replace_file_names(path: str, removed: str, replacement: str = "", output_path: str = "", - prefixes: typing.List[str] = None): - if not output_path: - output_path = path - for file_name in os.listdir(path=path): - prefix = match_sufffix(file_name, removed, prefixes) - if prefix: - # Remove the prefix from the list so that prefixes is the list of intended but not yet found - # files. - if prefixes is not None: - prefixes.remove(prefix) - new_file_name = prefix + replacement - new_file_path = os.path.join(output_path, new_file_name) - old_file_path = os.path.join(path, file_name) - print(f'{old_file_path} to {new_file_path}') - shutil.move(old_file_path, new_file_path) - if prefixes is not None: - for unfound_prefix in prefixes: - print(f'Failed to find {unfound_prefix}_actual.png') +class TestResults(object): + ACTUAL_SUFFIX = '_actual.png' + EXPECTED_SUFFIX = '.png' + + def __init__(self, results_directory: str, source_expected_directory: str): + self.results_directory = results_directory + self.actual_directory = os.path.join(self.results_directory, 'images', 'actual_images') + self.expected_directory = os.path.join(self.results_directory, 'images', 'expected_images') + self.source_expected_directory = source_expected_directory + + def get_latest_failed_images(self) -> typing.List[str]: + failed_images = [] + xml_tree = xml.etree.ElementTree.parse( + os.path.join(self.results_directory, 'test_detail.xml')) + testsuites = xml_tree.getroot() + for testsuite in testsuites.findall('testsuite'): + for testcase in testsuite.findall('testcase'): + for properties in testcase.findall('properties'): + for property in properties.findall('property'): + if property.get('name') == 'FailedImages': + failed_images.extend(property.get('value').split(',')) + + return failed_images + + def handle_failed_image(self, failed_image): + self.show_images(failed_image) + print(f'Update {failed_image}\'s expected image? y/n') + while True: + user_input = input() + if user_input == 'y': + self.move_actual_to_source([failed_image]) + break + elif user_input == 'n': + break + + def handle_all_failed_images(self): + for failed_image in self.get_latest_failed_images(): + self.handle_failed_image(failed_image) + + def show_images(self, failed_image): + # TODO: Test more on non-mac systems + open_command: str + os_name = platform.system().lower() + if 'windows' in os_name: + open_command = 'start' + elif 'osx' in os_name or 'darwin' in os_name: + open_command = 'open' + else: + open_command = 'xdg-open' + + subprocess.run( + [open_command, + os.path.join(self.actual_directory, failed_image + TestResults.ACTUAL_SUFFIX)]) + subprocess.run( + [open_command, + os.path.join(self.expected_directory, failed_image + TestResults.EXPECTED_SUFFIX)]) + + def move_actual_to_source(self, file_prefixes: typing.List[str]): + replace_file_names(path=self.actual_directory, removed=TestResults.ACTUAL_SUFFIX, + replacement=TestResults.EXPECTED_SUFFIX, + output_path=self.source_expected_directory, prefixes=file_prefixes) + + def batch_move(self, prefixes: typing.Optional[typing.List[str]] = None): + replace_file_names(path=self.actual_directory, removed=TestResults.ACTUAL_SUFFIX, + replacement=TestResults.EXPECTED_SUFFIX, + output_path=self.source_expected_directory, prefixes=prefixes) + + +def match_suffix(file_name: str, suffix: str, accepted_prefixes: typing.List[str]) -> str: + """ + Check if the file name is one of the searched for ones with the given suffix and if so return + it. + :param accepted_prefixes: If None accepts any prefix + :return: file_name with the suffix removed or "" if it doesn't match. This does mean a string + that is just the suffix is considered to not match as it will return the empty string. + """ + if file_name.endswith(suffix): + prefix = file_name.removesuffix(suffix) + if accepted_prefixes is None or prefix in accepted_prefixes: + return prefix + return '' + + +def replace_file_names(path: str, removed: str, replacement: str = '', output_path: str = '', + prefixes: typing.Optional[typing.List[str]] = None): + if not output_path: + output_path = path + for file_name in os.listdir(path=path): + prefix = match_suffix(file_name, removed, prefixes) + if prefix: + # Remove the prefix from the list so that prefixes is the list of intended but not yet + # found files. + if prefixes is not None: + prefixes.remove(prefix) + new_file_name = prefix + replacement + new_file_path = os.path.join(output_path, new_file_name) + old_file_path = os.path.join(path, file_name) + print(f'{old_file_path} to {new_file_path}') + shutil.copyfile(old_file_path, new_file_path) + if prefixes is not None: + for unfound_prefix in prefixes: + print(f'Failed to find {unfound_prefix}_actual.png') + if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='Backend Test File Renamer', - description='Moves actual generated test images to the expected ' - 'images directory, to update the test requirements. ' - 'test_cases accepts multiple arguments that should ' - 'be the name of the expected image file without the ' - '.png suffix. Also --all can be passed to copy all ' - 'images.\n' - 'Remember to sync CMake after running this to move ' - 'the new expected images to the binary directory.') - parser.add_argument('-i', '--input_path') - parser.add_argument('-o', '--output_path', default="./expected_images") - parser.add_argument('-t', '--test_cases', action='extend', nargs='*') - parser.add_argument('-a', '--all', action='store_true') + parser = argparse.ArgumentParser(prog='Backend Test File Renamer', + description='Moves actual generated test images to the ' + 'expected images directory, to update the test ' + 'requirements. test_cases accepts multiple ' + 'arguments that should be the name of the ' + 'expected image file without the .png suffix. ' + 'Also --all can be passed to copy all images.\n' + 'Remember to sync CMake after running this to ' + 'move the new expected images to the binary ' + 'directory.') + parser.add_argument('-r', '--results_path') + parser.add_argument('-s', '--source_expected_path', default="./expected_images") + # The mutually exclusive options for how to process the actual images + parser.add_argument('-b', '--batch', action='extend', nargs='*') + parser.add_argument('-a', '--all', action='store_true') + parser.add_argument('-t', '--tests', action='store_true') + parser.add_argument('-c', '--compare', action='extend', nargs='*') - args = parser.parse_args() - input_path = "." - if args.input_path: - input_path = args.input_path + args = parser.parse_args() + if not args.results_path: + raise AssertionError("No result path provided") + results_path = args.results_path - prefixes = args.test_cases - if args.all: - prefixes = None + results = TestResults(results_directory=results_path, + source_expected_directory=args.source_expected_path) - replace_file_names(path=input_path, output_path=args.output_path, removed="_actual.png", - replacement=".png", prefixes=prefixes) + if args.all: + results.batch_move() + elif args.tests: + results.handle_all_failed_images() + elif args.compare: + for file_prefix in args.compare: + results.show_images(file_prefix) + else: + results.batch_move(args.batch) diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index c217f6c911..e3800804c7 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -648,44 +648,32 @@ bool ChunkAttributeInfo::unflatten(Unflattener& unflattener, bool ChunkDescriptorBindingsInfo::unflatten(Unflattener& unflattener, MaterialParser::DescriptorBindingsContainer* container) { - uint8_t setCount; - if (!unflattener.read(&setCount)) { + static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t)); + + uint8_t descriptorCount; + if (!unflattener.read(&descriptorCount)) { return false; } - for (size_t j = 0; j < setCount; j++) { - static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t)); - - DescriptorSetBindingPoints set; - if (!unflattener.read(reinterpret_cast(&set))) { + auto& descriptors = (*container)[+DescriptorSetBindingPoints::PER_MATERIAL]; + descriptors.reserve(descriptorCount); + for (size_t i = 0; i < descriptorCount; i++) { + CString name; + if (!unflattener.read(&name)) { return false; } - - uint8_t descriptorCount; - if (!unflattener.read(&descriptorCount)) { + uint8_t type; + if (!unflattener.read(&type)) { return false; } - - auto& descriptors = (*container)[+set]; - descriptors.reserve(descriptorCount); - for (size_t i = 0; i < descriptorCount; i++) { - CString name; - if (!unflattener.read(&name)) { - return false; - } - uint8_t type; - if (!unflattener.read(&type)) { - return false; - } - uint8_t binding; - if (!unflattener.read(&binding)) { - return false; - } - descriptors.push_back({ - std::move(name), - DescriptorType(type), - descriptor_binding_t(binding)}); + uint8_t binding; + if (!unflattener.read(&binding)) { + return false; } + descriptors.push_back({ + std::move(name), + DescriptorType(type), + descriptor_binding_t(binding)}); } return true; @@ -693,42 +681,40 @@ bool ChunkDescriptorBindingsInfo::unflatten(Unflattener& unflattener, bool ChunkDescriptorSetLayoutInfo::unflatten(Unflattener& unflattener, MaterialParser::DescriptorSetLayoutContainer* container) { - for (size_t j = 0; j < 2; j++) { - uint8_t descriptorCount; - if (!unflattener.read(&descriptorCount)) { + uint8_t descriptorCount; + if (!unflattener.read(&descriptorCount)) { + return false; + } + auto& descriptors = container->bindings; + descriptors.reserve(descriptorCount); + for (size_t i = 0; i < descriptorCount; i++) { + uint8_t type; + if (!unflattener.read(&type)) { return false; } - auto& descriptors = (*container)[j].bindings; - descriptors.reserve(descriptorCount); - for (size_t i = 0; i < descriptorCount; i++) { - uint8_t type; - if (!unflattener.read(&type)) { - return false; - } - uint8_t stageFlags; - if (!unflattener.read(&stageFlags)) { - return false; - } - uint8_t binding; - if (!unflattener.read(&binding)) { - return false; - } - uint8_t flags; - if (!unflattener.read(&flags)) { - return false; - } - uint16_t count; - if (!unflattener.read(&count)) { - return false; - } - descriptors.push_back({ - DescriptorType(type), - ShaderStageFlags(stageFlags), - descriptor_binding_t(binding), - DescriptorFlags(flags), - count, - }); + uint8_t stageFlags; + if (!unflattener.read(&stageFlags)) { + return false; } + uint8_t binding; + if (!unflattener.read(&binding)) { + return false; + } + uint8_t flags; + if (!unflattener.read(&flags)) { + return false; + } + uint16_t count; + if (!unflattener.read(&count)) { + return false; + } + descriptors.push_back({ + DescriptorType(type), + ShaderStageFlags(stageFlags), + descriptor_binding_t(binding), + DescriptorFlags(flags), + count, + }); } return true; } diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index 761698d526..9787d64ab5 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -93,7 +93,7 @@ public: using DescriptorBindingsContainer = backend::Program::DescriptorSetInfo; bool getDescriptorBindings(DescriptorBindingsContainer* container) const noexcept; - using DescriptorSetLayoutContainer = std::array; + using DescriptorSetLayoutContainer = backend::DescriptorSetLayout; bool getDescriptorSetLayout(DescriptorSetLayoutContainer* container) const noexcept; bool getDepthWriteSet(bool* value) const noexcept; diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index 421ab9f637..91447aed0d 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -609,9 +609,12 @@ Program FMaterial::getProgramWithVariants( program.attributes(mAttributeInfo); } - program.descriptorBindings(0, mProgramDescriptorBindings[0]); - program.descriptorBindings(1, mProgramDescriptorBindings[1]); - program.descriptorBindings(2, mProgramDescriptorBindings[2]); + program.descriptorBindings(+DescriptorSetBindingPoints::PER_VIEW, + mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_VIEW]); + program.descriptorBindings(+DescriptorSetBindingPoints::PER_RENDERABLE, + mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_RENDERABLE]); + program.descriptorBindings(+DescriptorSetBindingPoints::PER_MATERIAL, + mProgramDescriptorBindings[+DescriptorSetBindingPoints::PER_MATERIAL]); program.specializationConstants(mSpecializationConstants); program.pushConstants(ShaderStage::VERTEX, mPushConstants[uint8_t(ShaderStage::VERTEX)]); @@ -1143,17 +1146,42 @@ void FMaterial::processDescriptorSets(FEngine& engine, MaterialParser const* con success = parser->getDescriptorBindings(&mProgramDescriptorBindings); assert_invariant(success); - std::array descriptorSetLayout; + backend::DescriptorSetLayout descriptorSetLayout; success = parser->getDescriptorSetLayout(&descriptorSetLayout); assert_invariant(success); + auto perMatLabel = mName; + perMatLabel.append("_perMat"); + descriptorSetLayout.label = std::move(perMatLabel); + + // get the PER_VIEW descriptor binding info + auto perViewDescriptorSetLayout = + descriptor_sets::getPerViewDescriptorSetLayout(mMaterialDomain, mVariantFilterMask, + mIsVariantLit || mHasShadowMultiplier, mReflectionMode, mRefractionMode); + auto perViewLabel = mName; + perViewLabel.append("_perView"); + perViewDescriptorSetLayout.label = std::move(perViewLabel); + + // get the PER_RENDERABLE and PER_VIEW descriptor binding info + for (auto&& [bindingPoint, descriptorSetLayout] : { + std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE, + descriptor_sets::getPerRenderableLayout() }, + std::pair{ DescriptorSetBindingPoints::PER_VIEW, + perViewDescriptorSetLayout }}) { + Program::DescriptorBindingsInfo& descriptors = mProgramDescriptorBindings[+bindingPoint]; + descriptors.reserve(descriptorSetLayout.bindings.size()); + for (auto const& entry: descriptorSetLayout.bindings) { + auto const& name = descriptor_sets::getDescriptorName(bindingPoint, entry.binding); + descriptors.push_back({ name, entry.type, entry.binding }); + } + } mDescriptorSetLayout = { engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(descriptorSetLayout[0]) }; + engine.getDriverApi(), std::move(descriptorSetLayout) }; mPerViewDescriptorSetLayout = { engine.getDescriptorSetLayoutFactory(), - engine.getDriverApi(), std::move(descriptorSetLayout[1]) }; + engine.getDriverApi(), perViewDescriptorSetLayout }; } descriptor_binding_t FMaterial::getSamplerBinding( diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index aa84a9aacd..0c0b721385 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.59.5" + spec.version = "1.60.0" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.5/filament-v1.59.5-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.60.0/filament-v1.60.0-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index abe3b1ba7d..c45f8a0acf 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 59; +static constexpr size_t MATERIAL_VERSION = 60; /** * Supported shading models diff --git a/libs/filabridge/include/private/filament/DescriptorSets.h b/libs/filabridge/include/private/filament/DescriptorSets.h index 171c8650d1..5ee10729b4 100644 --- a/libs/filabridge/include/private/filament/DescriptorSets.h +++ b/libs/filabridge/include/private/filament/DescriptorSets.h @@ -20,6 +20,7 @@ #include #include +#include #include @@ -39,8 +40,16 @@ backend::DescriptorSetLayout getPerViewDescriptorSetLayout( ReflectionMode reflectionMode, RefractionMode refractionMode) noexcept; +backend::DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( + Variant variant, + MaterialDomain domain, + UserVariantFilterMask variantFilter, + bool isLit, + ReflectionMode reflectionMode, + RefractionMode refractionMode) noexcept; + utils::CString getDescriptorName( - filament::DescriptorSetBindingPoints set, + DescriptorSetBindingPoints set, backend::descriptor_binding_t binding) noexcept; } // namespace filament::descriptor_sets diff --git a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h index 243f2dcfb6..28e5e8d4eb 100644 --- a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h +++ b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h @@ -23,6 +23,8 @@ #include #include +#include + #include #include #include @@ -128,6 +130,9 @@ public: static utils::CString generateUniformName(const char* group, const char* sampler) noexcept; + static SamplerInfoList filterSamplerList(SamplerInfoList list, + backend::DescriptorSetLayout const& descriptorSetLayout); + private: friend class Builder; diff --git a/libs/filabridge/src/DescriptorSets.cpp b/libs/filabridge/src/DescriptorSets.cpp index b313fcc056..b32bcdda5b 100644 --- a/libs/filabridge/src/DescriptorSets.cpp +++ b/libs/filabridge/src/DescriptorSets.cpp @@ -17,6 +17,7 @@ #include "private/filament/DescriptorSets.h" #include +#include #include @@ -26,34 +27,35 @@ #include #include -#include +#include #include +#include namespace filament::descriptor_sets { using namespace backend; -static DescriptorSetLayout const postProcessDescriptorSetLayout{{ +static constexpr std::initializer_list postProcessDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, -}}; +}; -static DescriptorSetLayout const depthVariantDescriptorSetLayout{{ +static constexpr std::initializer_list depthVariantDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, -}}; +}; // ssrVariantDescriptorSetLayout must match perViewDescriptorSetLayout's vertex stage. This is // because the SSR variant is always using the "standard" vertex shader (i.e. there is no // dedicated SSR vertex shader), which uses perViewDescriptorSetLayout. // This means that PerViewBindingPoints::SHADOWS must be in the layout even though it's not used // by the SSR variant. -static DescriptorSetLayout const ssrVariantDescriptorSetLayout{{ - { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, - { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, - { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE }, - { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, -}}; +static constexpr std::initializer_list ssrVariantDescriptorSetLayoutList = { + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, +}; -static DescriptorSetLayout perViewDescriptorSetLayout = {{ +static constexpr std::initializer_list perViewDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::LIGHTS }, @@ -66,16 +68,37 @@ static DescriptorSetLayout perViewDescriptorSetLayout = {{ { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSAO }, { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FOG }, -}}; +}; -static DescriptorSetLayout perRenderableDescriptorSetLayout = {{ +static constexpr std::initializer_list perRenderableDescriptorSetLayoutList = { { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET }, { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS }, { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS }, { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS }, { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS }, -}}; +}; + +// used for post-processing passes +static DescriptorSetLayout const postProcessDescriptorSetLayout{ utils::StaticString("postProcess"), + postProcessDescriptorSetLayoutList }; + +// used to generate shadow-maps +static DescriptorSetLayout const depthVariantDescriptorSetLayout{ + utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList +}; + +static DescriptorSetLayout const ssrVariantDescriptorSetLayout{ utils::StaticString("ssrVariant"), + ssrVariantDescriptorSetLayoutList }; + +// Used for generating the color pass (i.e. the main pass). This is in fact a template that gets +// declined into 8 different layouts, based on variants. +static DescriptorSetLayout perViewDescriptorSetLayout = { utils::StaticString("perView"), + perViewDescriptorSetLayoutList }; + +static DescriptorSetLayout perRenderableDescriptorSetLayout = { + utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList +}; DescriptorSetLayout const& getPostProcessLayout() noexcept { return postProcessDescriptorSetLayout; @@ -93,8 +116,8 @@ DescriptorSetLayout const& getPerRenderableLayout() noexcept { return perRenderableDescriptorSetLayout; } -utils::CString getDescriptorName(DescriptorSetBindingPoints set, - descriptor_binding_t binding) noexcept { +utils::CString getDescriptorName(DescriptorSetBindingPoints const set, + descriptor_binding_t const binding) noexcept { using namespace std::literals; static std::unordered_map const set0{{ @@ -140,11 +163,11 @@ utils::CString getDescriptorName(DescriptorSetBindingPoints set, } DescriptorSetLayout getPerViewDescriptorSetLayout( - MaterialDomain domain, - UserVariantFilterMask variantFilter, - bool isLit, - ReflectionMode reflectionMode, - RefractionMode refractionMode) noexcept { + MaterialDomain const domain, + UserVariantFilterMask const variantFilter, + bool const isLit, + ReflectionMode const reflectionMode, + RefractionMode const refractionMode) noexcept { bool const ssr = reflectionMode == ReflectionMode::SCREEN_SPACE || refractionMode == RefractionMode::SCREEN_SPACE; @@ -187,11 +210,65 @@ DescriptorSetLayout getPerViewDescriptorSetLayout( return layout; } case MaterialDomain::POST_PROCESS: - return descriptor_sets::getPostProcessLayout(); + return postProcessDescriptorSetLayout; case MaterialDomain::COMPUTE: // TODO: what's the layout for compute? - return descriptor_sets::getPostProcessLayout(); + return postProcessDescriptorSetLayout; } } +DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( + Variant const variant, + MaterialDomain domain, + UserVariantFilterMask const variantFilter, + bool const isLit, + ReflectionMode const reflectionMode, + RefractionMode const refractionMode) noexcept { + if (Variant::isValidDepthVariant(variant)) { + return depthVariantDescriptorSetLayout; + } + if (Variant::isSSRVariant(variant)) { + return ssrVariantDescriptorSetLayout; + } + // We need to filter out all the descriptors not included in the "resolved" layout below + return getPerViewDescriptorSetLayout(domain, variantFilter, + isLit, reflectionMode, refractionMode); +} + + + +template +constexpr static ITERATOR find_if(ITERATOR first, ITERATOR last, PREDICATE pred) { + for (; first != last; ++first) + if (pred(*first)) break; + return first; +} + +constexpr static bool checkConsistency() noexcept { + // check that all descriptors that apply to the vertex stage in perViewDescriptorSetLayout + // are present in ssrVariantDescriptorSetLayout; meaning that the latter is compatible + // with the former. + for (auto const& r: perViewDescriptorSetLayoutList) { + if (hasShaderType(r.stageFlags, ShaderStage::VERTEX)) { + auto const pos = find_if( + ssrVariantDescriptorSetLayoutList.begin(), + ssrVariantDescriptorSetLayoutList.end(), + [r](auto const& l) { + return l.count == r.count && + l.type == r.type && + l.binding == r.binding && + l.flags == r.flags && + l.stageFlags == r.stageFlags; + }); + if (pos == ssrVariantDescriptorSetLayoutList.end()) { + return false; + } + } + } + return true; +} + +static_assert(checkConsistency(), "ssrVariantDescriptorSetLayout is not compatible with " + "perViewDescriptorSetLayout"); + } // namespace filament::descriptor_sets diff --git a/libs/filabridge/src/SamplerInterfaceBlock.cpp b/libs/filabridge/src/SamplerInterfaceBlock.cpp index 1d1f4c387f..da2a1a76c4 100644 --- a/libs/filabridge/src/SamplerInterfaceBlock.cpp +++ b/libs/filabridge/src/SamplerInterfaceBlock.cpp @@ -16,6 +16,7 @@ #include "private/filament/SamplerInterfaceBlock.h" +#include #include @@ -102,7 +103,7 @@ const SamplerInterfaceBlock::SamplerInfo* SamplerInterfaceBlock::getSamplerInfo( return &mSamplersInfoList[pos->second]; } -utils::CString SamplerInterfaceBlock::generateUniformName(const char* group, const char* sampler) noexcept { +CString SamplerInterfaceBlock::generateUniformName(const char* group, const char* sampler) noexcept { char uniformName[256]; // sampler interface block name @@ -117,9 +118,27 @@ utils::CString SamplerInterfaceBlock::generateUniformName(const char* group, con std::min(sizeof(uniformName) / 2 - 2, strlen(sampler)), prefix + 1); *last++ = 0; // null terminator - assert(last <= std::end(uniformName)); + assert_invariant(last <= std::end(uniformName)); return CString{ uniformName, size_t(last - uniformName) - 1u }; } +SamplerInterfaceBlock::SamplerInfoList SamplerInterfaceBlock::filterSamplerList( + SamplerInfoList list, backend::DescriptorSetLayout const& descriptorSetLayout) { + // remove all the samplers that are not included in the descriptor-set layout + list.erase( + std::remove_if(list.begin(), list.end(), + [&](auto const& entry) { + auto pos = std::find_if( + descriptorSetLayout.bindings.begin(), + descriptorSetLayout.bindings.end(), + [&entry](const auto& item) { + return item.binding == entry.binding; + }); + return pos == descriptorSetLayout.bindings.end(); + }), list.end()); + + return list; +} + } // namespace filament diff --git a/libs/filamat/CMakeLists.txt b/libs/filamat/CMakeLists.txt index 4004106046..3d790b0e77 100644 --- a/libs/filamat/CMakeLists.txt +++ b/libs/filamat/CMakeLists.txt @@ -56,6 +56,7 @@ set(PRIVATE_HDRS src/MetalArgumentBuffer.h src/ShaderMinifier.h src/SpirvFixup.h + src/SpirvRemapWrapper.h src/sca/ASTHelpers.h src/sca/GLSLTools.h src/sca/builtinResource.h) @@ -71,7 +72,8 @@ set(SRCS src/sca/GLSLTools.cpp src/GLSLPostProcessor.cpp src/ShaderMinifier.cpp - src/SpirvFixup.cpp) + src/SpirvFixup.cpp + src/SpirvRemapWrapper.cpp) # ================================================================================================== # Include and target definitions diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index cea4a338ed..14ae424fec 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -603,7 +603,7 @@ public: * extension will be derived from the shader stage. For example, mymaterial_0x0e.frag, * mymaterial_0x18.vert, etc. */ - MaterialBuilder& saveRawVariants(bool saveVariants) noexcept; + MaterialBuilder& saveRawVariants(bool saveRawVariants) noexcept; //! If true, will include debugging information in generated SPIRV. MaterialBuilder& generateDebugInfo(bool generateDebugInfo) noexcept; @@ -635,7 +635,7 @@ public: * Build the material. If you are using the Filament engine with this library, you should use * the job system provided by Engine. */ - Package build(utils::JobSystem& jobSystem) noexcept; + Package build(utils::JobSystem& jobSystem); public: // The methods and types below are for internal use @@ -809,7 +809,7 @@ private: // Multiple calls to findProperties accumulate the property sets across fragment // and vertex shaders in mProperties. bool findProperties(filament::backend::ShaderStage type, - MaterialBuilder::PropertyList& allProperties, + MaterialBuilder::PropertyList const& allProperties, CodeGenParams const& semanticCodeGenParams) noexcept; bool runSemanticAnalysis(MaterialInfo* inOutInfo, diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 3283f2bd3f..34b1cac980 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -17,7 +17,6 @@ #include "GLSLPostProcessor.h" #include -#include #include #include @@ -34,14 +33,19 @@ #include "MetalArgumentBuffer.h" #include "SpirvFixup.h" -#include "utils/ostream.h" #include +#include +#include #include +#include +#include +#include #include #include +#include #include #ifdef FILAMENT_SUPPORTS_WEBGPU @@ -151,21 +155,16 @@ DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib return layout; } -static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, +static void collectDescriptorsForSet(DescriptorSetBindingPoints set, const GLSLPostProcessor::Config& config, DescriptorSetInfo& descriptors) { const MaterialInfo& material = *config.materialInfo; - DescriptorSetLayout const info = [&]() { + // get the descriptor set layout for the given pinding point + DescriptorSetLayout const descriptorSetLayout = [&] { switch (set) { case DescriptorSetBindingPoints::PER_VIEW: { - if (filament::Variant::isValidDepthVariant(config.variant)) { - return descriptor_sets::getDepthVariantLayout(); - } - if (filament::Variant::isSSRVariant(config.variant)) { - return descriptor_sets::getSsrVariantLayout(); - } - return descriptor_sets::getPerViewDescriptorSetLayout(config.domain, - config.variantFilter, + return descriptor_sets::getPerViewDescriptorSetLayoutWithVariant( + config.variant, config.domain, config.variantFilter, material.isLit || material.hasShadowMultiplier, material.reflectionMode, material.refractionMode); @@ -179,7 +178,8 @@ static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, } }(); - auto samplerList = [&]() { + // get the sampler list for this binding point + auto samplerList = [&] { switch (set) { case DescriptorSetBindingPoints::PER_VIEW: return SibGenerator::getPerViewSib(config.variant).getSamplerInfoList(); @@ -192,42 +192,34 @@ static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, } }(); - // remove all the samplers that are not included in the descriptor-set layout - samplerList.erase(std::remove_if(samplerList.begin(), samplerList.end(), - [&info](auto const& entry) { - auto pos = std::find_if(info.bindings.begin(), - info.bindings.end(), [&entry](const auto& item) { - return item.binding == entry.binding; - }); - return pos == info.bindings.end(); - }), - samplerList.end()); + // filter the list with the descriptor set layout + auto const descriptorSetSamplerList = + SamplerInterfaceBlock::filterSamplerList(std::move(samplerList), descriptorSetLayout); - auto getDescriptorName = [&](DescriptorSetBindingPoints set, descriptor_binding_t binding) { + // helper to get the name of a descriptor for this set, given a binding. + auto getDescriptorName = [set, &descriptorSetSamplerList](descriptor_binding_t binding) { if (set == DescriptorSetBindingPoints::PER_MATERIAL) { - auto pos = std::find_if(samplerList.begin(), samplerList.end(), + auto pos = std::find_if(descriptorSetSamplerList.begin(), descriptorSetSamplerList.end(), [&](const auto& entry) { return entry.binding == binding; }); - if (pos == samplerList.end()) { + if (pos == descriptorSetSamplerList.end()) { return descriptor_sets::getDescriptorName(set, binding); } - SamplerInterfaceBlock::SamplerInfo& sampler = *pos; - return sampler.uniformName; + return pos->uniformName; } return descriptor_sets::getDescriptorName(set, binding); }; - for (size_t i = 0; i < info.bindings.size(); i++) { - backend::descriptor_binding_t binding = info.bindings[i].binding; - auto name = getDescriptorName(set, binding); - if (info.bindings[i].type == DescriptorType::SAMPLER || - info.bindings[i].type == DescriptorType::SAMPLER_EXTERNAL) { - auto pos = std::find_if(samplerList.begin(), samplerList.end(), + for (auto descriptor : descriptorSetLayout.bindings) { + descriptor_binding_t binding = descriptor.binding; + auto name = getDescriptorName(binding); + if (descriptor.type == DescriptorType::SAMPLER || + descriptor.type == DescriptorType::SAMPLER_EXTERNAL) { + auto pos = std::find_if(descriptorSetSamplerList.begin(), descriptorSetSamplerList.end(), [&](const auto& entry) { return entry.binding == binding; }); - assert_invariant(pos != samplerList.end()); - SamplerInterfaceBlock::SamplerInfo& sampler = *pos; - descriptors.emplace_back(name, info.bindings[i], sampler); + assert_invariant(pos != descriptorSetSamplerList.end()); + descriptors.emplace_back(name, descriptor, *pos); } else { - descriptors.emplace_back(name, info.bindings[i], std::nullopt); + descriptors.emplace_back(name, descriptor, std::nullopt); } } @@ -296,24 +288,8 @@ GLSLPostProcessor::GLSLPostProcessor(MaterialBuilder::Optimization optimization, : mOptimization(optimization), mPrintShaders(flags & PRINT_SHADERS), mGenerateDebugInfo(flags & GENERATE_DEBUG_INFO) { - // SPIRV error handler registration needs to occur only once. To avoid a race we do it up here - // in the constructor, which gets invoked before MaterialBuilder kicks off jobs. - spv::spirvbin_t::registerErrorHandler([](const std::string& str) { - slog.e << str << io::endl; - }); - - // Similar to above, we need to do a no-op remap to init a static table in the remapper before - // the jobs start using remap(). - spv::spirvbin_t remapper(0); - // We need to provide at least a valid header to not crash. - SpirvBlob spirv { - 0x07230203,// MAGIC - 0, // VERSION - 0, // GENERATOR - 0, // BOUND - 0 // SCHEMA, must be 0 - }; - remapper.remap(spirv, 0); + // This should occur only once, to avoid races. + SpirvRemapWrapperSetUp(); } GLSLPostProcessor::~GLSLPostProcessor() = default; @@ -365,7 +341,7 @@ static std::string stringifySpvOptimizerMessage(spv_message_level_t level, const } void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl, - filament::backend::ShaderStage stage, filament::backend::ShaderModel shaderModel, + ShaderStage stage, ShaderModel shaderModel, bool useFramebufferFetch, const DescriptorSets& descriptorSets, const ShaderMinifier* minifier) { using namespace msl; @@ -674,7 +650,7 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co // SpvRules should be enough. // I think this could cause the compilation to fail on gl_VertexID. using Type = std::underlying_type_t; - msg = EShMessages(Type(msg) | Type(EShMessages::EShMsgVulkanRules)); + msg = EShMessages(Type(msg) | Type(EShMsgVulkanRules)); } bool const ok = tShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg); @@ -684,7 +660,7 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co } // add texture lod bias - if (config.shaderType == backend::ShaderStage::FRAGMENT && + if (config.shaderType == ShaderStage::FRAGMENT && config.domain == MaterialDomain::SURFACE) { GLSLTools::textureLodBias(tShader); } @@ -760,8 +736,8 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co return true; } -bool GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, - GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const { +bool GLSLPostProcessor::preprocessOptimization(TShader& tShader, + Config const& config, InternalConfig& internalConfig) const { using TargetApi = MaterialBuilder::TargetApi; assert_invariant(bool(internalConfig.spirvOutput) == (config.targetApi != TargetApi::OPENGL)); @@ -832,7 +808,7 @@ bool GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, } bool GLSLPostProcessor::fullOptimization(const TShader& tShader, - GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const { + Config const& config, InternalConfig& internalConfig) const { SpirvBlob spirv; bool const optimizeForSize = mOptimization == MaterialBuilderBase::Optimization::SIZE; @@ -928,7 +904,7 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader, #else try { *internalConfig.glslOutput = glslCompiler.compile(); - } catch (spirv_cross::CompilerError e) { + } catch (CompilerError e) { slog.e << "ERROR: " << e.what() << io::endl; return false; } @@ -948,8 +924,8 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader, return true; } -std::shared_ptr GLSLPostProcessor::createEmptyOptimizer() { - auto optimizer = std::make_shared(SPV_ENV_UNIVERSAL_1_3); +std::shared_ptr GLSLPostProcessor::createEmptyOptimizer() { + auto optimizer = std::make_shared(SPV_ENV_UNIVERSAL_1_3); optimizer->SetMessageConsumer([](spv_message_level_t level, const char* source, const spv_position_t& position, const char* message) { if (!filterSpvOptimizerMessage(level)) { @@ -961,7 +937,7 @@ std::shared_ptr GLSLPostProcessor::createEmptyOptimizer() { return optimizer; } -std::shared_ptr GLSLPostProcessor::createOptimizer( +std::shared_ptr GLSLPostProcessor::createOptimizer( MaterialBuilder::Optimization optimization, Config const& config) { auto optimizer = createEmptyOptimizer(); @@ -995,12 +971,11 @@ void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) } // Remove dead module-level objects: functions, types, vars - spv::spirvbin_t remapper(0); - remapper.remap(spirv, spv::spirvbin_base_t::DCE_ALL); + SpirvRemapWrapperRemap(spirv); } void GLSLPostProcessor::fixupClipDistance( - SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const { + SpirvBlob& spirv, Config const& config) const { if (!config.usesClipDistance) { return; } @@ -1040,7 +1015,7 @@ void GLSLPostProcessor::fixupClipDistance( void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config const& config) { - auto RegisterPass = [&](spvtools::Optimizer::PassToken&& pass, + auto RegisterPass = [&](Optimizer::PassToken&& pass, MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) { if (!(config.targetApi & apiFilter)) { return; @@ -1085,7 +1060,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c } void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& config) { - auto RegisterPass = [&](spvtools::Optimizer::PassToken&& pass, + auto RegisterPass = [&](Optimizer::PassToken&& pass, MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) { if (!(config.targetApi & apiFilter)) { return; diff --git a/libs/filamat/src/GLSLPostProcessor.h b/libs/filamat/src/GLSLPostProcessor.h index a93551a776..2a78c133bd 100644 --- a/libs/filamat/src/GLSLPostProcessor.h +++ b/libs/filamat/src/GLSLPostProcessor.h @@ -23,6 +23,7 @@ #include #include "ShaderMinifier.h" +#include "SpirvRemapWrapper.h" #include diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index d1e145aa31..a02e28f73a 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -17,58 +17,71 @@ #include "filamat/MaterialBuilder.h" #include +#include +#include #include "Includes.h" #include "MaterialVariants.h" #include "PushConstantDefinitions.h" -#include "shaders/SibGenerator.h" -#include "shaders/UibGenerator.h" - #include "GLSLPostProcessor.h" + #include "sca/GLSLTools.h" #include "shaders/MaterialInfo.h" #include "shaders/ShaderGenerator.h" +#include "shaders/UibGenerator.h" #include "eiff/BlobDictionary.h" +#include "eiff/ChunkContainer.h" +#include "eiff/DictionarySpirvChunk.h" +#include "eiff/DictionaryTextChunk.h" #include "eiff/LineDictionary.h" +#include "eiff/MaterialBinaryChunk.h" #include "eiff/MaterialInterfaceBlockChunk.h" #include "eiff/MaterialTextChunk.h" -#include "eiff/MaterialBinaryChunk.h" -#include "eiff/ChunkContainer.h" -#include "eiff/SimpleFieldChunk.h" -#include "eiff/DictionaryTextChunk.h" -#include "eiff/DictionarySpirvChunk.h" +#include "eiff/ShaderEntry.h" #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 #include #include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include #include #include +#include namespace filamat { @@ -77,34 +90,34 @@ using namespace filament; // Note: the VertexAttribute enum value must match the index in the array const MaterialBuilder::AttributeDatabase MaterialBuilder::sAttributeDatabase = {{ - { "position", AttributeType::FLOAT4, VertexAttribute::POSITION }, - { "tangents", AttributeType::FLOAT4, VertexAttribute::TANGENTS }, - { "color", AttributeType::FLOAT4, VertexAttribute::COLOR }, - { "uv0", AttributeType::FLOAT2, VertexAttribute::UV0 }, - { "uv1", AttributeType::FLOAT2, VertexAttribute::UV1 }, - { "bone_indices", AttributeType::UINT4, VertexAttribute::BONE_INDICES }, - { "bone_weights", AttributeType::FLOAT4, VertexAttribute::BONE_WEIGHTS }, + { "position", AttributeType::FLOAT4, POSITION }, + { "tangents", AttributeType::FLOAT4, TANGENTS }, + { "color", AttributeType::FLOAT4, COLOR }, + { "uv0", AttributeType::FLOAT2, UV0 }, + { "uv1", AttributeType::FLOAT2, UV1 }, + { "bone_indices", AttributeType::UINT4, BONE_INDICES }, + { "bone_weights", AttributeType::FLOAT4, BONE_WEIGHTS }, { }, - { "custom0", AttributeType::FLOAT4, VertexAttribute::CUSTOM0 }, - { "custom1", AttributeType::FLOAT4, VertexAttribute::CUSTOM1 }, - { "custom2", AttributeType::FLOAT4, VertexAttribute::CUSTOM2 }, - { "custom3", AttributeType::FLOAT4, VertexAttribute::CUSTOM3 }, - { "custom4", AttributeType::FLOAT4, VertexAttribute::CUSTOM4 }, - { "custom5", AttributeType::FLOAT4, VertexAttribute::CUSTOM5 }, - { "custom6", AttributeType::FLOAT4, VertexAttribute::CUSTOM6 }, - { "custom7", AttributeType::FLOAT4, VertexAttribute::CUSTOM7 }, + { "custom0", AttributeType::FLOAT4, CUSTOM0 }, + { "custom1", AttributeType::FLOAT4, CUSTOM1 }, + { "custom2", AttributeType::FLOAT4, CUSTOM2 }, + { "custom3", AttributeType::FLOAT4, CUSTOM3 }, + { "custom4", AttributeType::FLOAT4, CUSTOM4 }, + { "custom5", AttributeType::FLOAT4, CUSTOM5 }, + { "custom6", AttributeType::FLOAT4, CUSTOM6 }, + { "custom7", AttributeType::FLOAT4, CUSTOM7 }, }}; std::atomic MaterialBuilderBase::materialBuilderClients(0); -inline void assertSingleTargetApi(MaterialBuilderBase::TargetApi api) { +static void assertSingleTargetApi(MaterialBuilderBase::TargetApi api) { // Assert that a single bit is set. - UTILS_UNUSED uint8_t bits = (uint8_t)api; - assert(bits && !(bits & bits - 1u)); + UTILS_UNUSED uint8_t const bits = uint8_t(api); + assert_invariant(bits && !(bits & bits - 1u)); } -void MaterialBuilderBase::prepare(bool vulkanSemantics, - filament::backend::FeatureLevel featureLevel) { +void MaterialBuilderBase::prepare(bool const vulkanSemantics, + backend::FeatureLevel const featureLevel) { mCodeGenPermutations.clear(); mShaderModels.reset(); @@ -118,12 +131,12 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, } // OpenGL is a special case. If we're doing any optimization, then we need to go to Spir-V. - TargetLanguage glTargetLanguage = mOptimization > MaterialBuilder::Optimization::PREPROCESSOR ? + TargetLanguage glTargetLanguage = mOptimization > Optimization::PREPROCESSOR ? TargetLanguage::SPIRV : TargetLanguage::GLSL; if (vulkanSemantics) { // Currently GLSLPostProcessor.cpp is incapable of compiling SPIRV to GLSL without // running the optimizer. For now we just activate the optimizer in that case. - mOptimization = MaterialBuilder::Optimization::PERFORMANCE; + mOptimization = Optimization::PERFORMANCE; glTargetLanguage = TargetLanguage::SPIRV; } @@ -134,8 +147,8 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, // Generally build for a minimum of feature level 1. If feature level 0 is specified, an extra // permutation is specifically included for the OpenGL/mobile target. - MaterialBuilder::FeatureLevel effectiveFeatureLevel = - std::max(featureLevel, filament::backend::FeatureLevel::FEATURE_LEVEL_1); + MaterialBuilder::FeatureLevel const effectiveFeatureLevel = + std::max(featureLevel, backend::FeatureLevel::FEATURE_LEVEL_1); // Build a list of codegen permutations, which is useful across all types of material builders. static_assert(backend::SHADER_MODEL_COUNT == 2); @@ -153,13 +166,13 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics, effectiveFeatureLevel, }); if (mIncludeEssl1 - && featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0 + && featureLevel == backend::FeatureLevel::FEATURE_LEVEL_0 && shaderModel == ShaderModel::MOBILE) { mCodeGenPermutations.push_back({ shaderModel, TargetApi::OPENGL, glTargetLanguage, - filament::backend::FeatureLevel::FEATURE_LEVEL_0 + backend::FeatureLevel::FEATURE_LEVEL_0 }); } } @@ -200,12 +213,12 @@ MaterialBuilder::MaterialBuilder() : mMaterialName("Unnamed") { MaterialBuilder::~MaterialBuilder() = default; void MaterialBuilderBase::init() { - materialBuilderClients++; + ++materialBuilderClients; GLSLTools::init(); } void MaterialBuilderBase::shutdown() { - materialBuilderClients--; + --materialBuilderClients; GLSLTools::shutdown(); } @@ -214,12 +227,12 @@ MaterialBuilder& MaterialBuilder::name(const char* name) noexcept { return *this; } -MaterialBuilder& MaterialBuilder::fileName(const char* fileName) noexcept { - mFileName = CString(fileName); +MaterialBuilder& MaterialBuilder::fileName(const char* name) noexcept { + mFileName = CString(name); return *this; } -MaterialBuilder& MaterialBuilder::material(const char* code, size_t line) noexcept { +MaterialBuilder& MaterialBuilder::material(const char* code, size_t const line) noexcept { mMaterialFragmentCode.setUnresolved(CString(code)); mMaterialFragmentCode.setLineOffset(line); return *this; @@ -230,18 +243,18 @@ MaterialBuilder& MaterialBuilder::includeCallback(IncludeCallback callback) noex return *this; } -MaterialBuilder& MaterialBuilder::materialVertex(const char* code, size_t line) noexcept { +MaterialBuilder& MaterialBuilder::materialVertex(const char* code, size_t const line) noexcept { mMaterialVertexCode.setUnresolved(CString(code)); mMaterialVertexCode.setLineOffset(line); return *this; } -MaterialBuilder& MaterialBuilder::shading(Shading shading) noexcept { +MaterialBuilder& MaterialBuilder::shading(Shading const shading) noexcept { mShading = shading; return *this; } -MaterialBuilder& MaterialBuilder::interpolation(Interpolation interpolation) noexcept { +MaterialBuilder& MaterialBuilder::interpolation(Interpolation const interpolation) noexcept { mInterpolation = interpolation; return *this; } @@ -253,7 +266,7 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep case Variable::CUSTOM2: case Variable::CUSTOM3: case Variable::CUSTOM4: - assert(size_t(v) < MATERIAL_VARIABLES_COUNT); + assert_invariant(size_t(v) < MATERIAL_VARIABLES_COUNT); mVariables[size_t(v)] = { CString(name), Precision::DEFAULT, false }; break; } @@ -261,14 +274,14 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep } MaterialBuilder& MaterialBuilder::variable(Variable v, - const char* name, ParameterPrecision precision) noexcept { + const char* name, ParameterPrecision const precision) noexcept { switch (v) { case Variable::CUSTOM0: case Variable::CUSTOM1: case Variable::CUSTOM2: case Variable::CUSTOM3: case Variable::CUSTOM4: - assert(size_t(v) < MATERIAL_VARIABLES_COUNT); + assert_invariant(size_t(v) < MATERIAL_VARIABLES_COUNT); mVariables[size_t(v)] = { CString(name), precision, true }; break; } @@ -282,8 +295,8 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, size_t size, Unifo return *this; } -MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType type, - ParameterPrecision precision) noexcept { +MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const type, + ParameterPrecision const precision) noexcept { return parameter(name, 1, type, precision); } @@ -303,9 +316,9 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample } template -MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType type, T defaultValue) { +MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType const type, T defaultValue) { auto result = std::find_if(mConstants.begin(), mConstants.end(), [name](const Constant& c) { - return c.name == utils::CString(name); + return c.name == CString(name); }); FILAMENT_CHECK_POSTCONDITION(result == mConstants.end()) << "There is already a constant parameter present with the name " << name << "."; @@ -313,7 +326,7 @@ MaterialBuilder& MaterialBuilder::constant(const char* name, ConstantType type, .name = CString(name), .type = type, }; - auto toString = [](ConstantType t) { + auto toString = [](ConstantType const t) { switch (t) { case ConstantType::INT: return "INT"; case ConstantType::FLOAT: return "FLOAT"; @@ -352,7 +365,7 @@ template MaterialBuilder& MaterialBuilder::constant( MaterialBuilder& MaterialBuilder::buffer(BufferInterfaceBlock bib) noexcept { FILAMENT_CHECK_POSTCONDITION(mBuffers.size() < MAX_BUFFERS_COUNT) << "Too many buffers"; - mBuffers.emplace_back(std::make_unique(std::move(bib))); + mBuffers.emplace_back(std::make_unique(std::move(bib))); return *this; } @@ -366,32 +379,32 @@ MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, SamplerFormat return *this; } -MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, SamplerFormat format, +MaterialBuilder& MaterialBuilder::subpass(SubpassType const subpassType, SamplerFormat const format, const char* name) noexcept { return subpass(subpassType, format, ParameterPrecision::DEFAULT, name); } -MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, ParameterPrecision precision, +MaterialBuilder& MaterialBuilder::subpass(SubpassType const subpassType, ParameterPrecision const precision, const char* name) noexcept { return subpass(subpassType, SamplerFormat::FLOAT, precision, name); } -MaterialBuilder& MaterialBuilder::subpass(SubpassType subpassType, const char* name) noexcept { +MaterialBuilder& MaterialBuilder::subpass(SubpassType const subpassType, const char* name) noexcept { return subpass(subpassType, SamplerFormat::FLOAT, ParameterPrecision::DEFAULT, name); } -MaterialBuilder& MaterialBuilder::require(VertexAttribute attribute) noexcept { +MaterialBuilder& MaterialBuilder::require(VertexAttribute const attribute) noexcept { mRequiredAttributes.set(attribute); return *this; } -MaterialBuilder& MaterialBuilder::groupSize(filament::math::uint3 groupSize) noexcept { +MaterialBuilder& MaterialBuilder::groupSize(math::uint3 const groupSize) noexcept { mGroupSize = groupSize; return *this; } MaterialBuilder& MaterialBuilder::materialDomain( - MaterialBuilder::MaterialDomain materialDomain) noexcept { + MaterialDomain const materialDomain) noexcept { mMaterialDomain = materialDomain; if (mMaterialDomain == MaterialDomain::COMPUTE) { // compute implies feature level 2 @@ -402,34 +415,34 @@ MaterialBuilder& MaterialBuilder::materialDomain( return *this; } -MaterialBuilder& MaterialBuilder::refractionMode(RefractionMode refraction) noexcept { +MaterialBuilder& MaterialBuilder::refractionMode(RefractionMode const refraction) noexcept { mRefractionMode = refraction; return *this; } -MaterialBuilder& MaterialBuilder::refractionType(RefractionType refractionType) noexcept { +MaterialBuilder& MaterialBuilder::refractionType(RefractionType const refractionType) noexcept { mRefractionType = refractionType; return *this; } -MaterialBuilder& MaterialBuilder::quality(ShaderQuality quality) noexcept { +MaterialBuilder& MaterialBuilder::quality(ShaderQuality const quality) noexcept { mShaderQuality = quality; return *this; } -MaterialBuilder& MaterialBuilder::featureLevel(FeatureLevel featureLevel) noexcept { +MaterialBuilder& MaterialBuilder::featureLevel(FeatureLevel const featureLevel) noexcept { mFeatureLevel = featureLevel; return *this; } -MaterialBuilder& MaterialBuilder::blending(BlendingMode blending) noexcept { +MaterialBuilder& MaterialBuilder::blending(BlendingMode const blending) noexcept { mBlendingMode = blending; return *this; } MaterialBuilder& MaterialBuilder::customBlendFunctions( - BlendFunction srcRGB, BlendFunction srcA, - BlendFunction dstRGB, BlendFunction dstA) noexcept { + BlendFunction const srcRGB, BlendFunction const srcA, + BlendFunction const dstRGB, BlendFunction const dstA) noexcept { mCustomBlendFunctions[0] = srcRGB; mCustomBlendFunctions[1] = srcA; mCustomBlendFunctions[2] = dstRGB; @@ -437,162 +450,162 @@ MaterialBuilder& MaterialBuilder::customBlendFunctions( return *this; } -MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode blending) noexcept { +MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode const blending) noexcept { mPostLightingBlendingMode = blending; return *this; } -MaterialBuilder& MaterialBuilder::vertexDomain(VertexDomain domain) noexcept { +MaterialBuilder& MaterialBuilder::vertexDomain(VertexDomain const domain) noexcept { mVertexDomain = domain; return *this; } -MaterialBuilder& MaterialBuilder::culling(CullingMode culling) noexcept { +MaterialBuilder& MaterialBuilder::culling(CullingMode const culling) noexcept { mCullingMode = culling; return *this; } -MaterialBuilder& MaterialBuilder::colorWrite(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::colorWrite(bool const enable) noexcept { mColorWrite = enable; return *this; } -MaterialBuilder& MaterialBuilder::depthWrite(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::depthWrite(bool const enable) noexcept { mDepthWrite = enable; mDepthWriteSet = true; return *this; } -MaterialBuilder& MaterialBuilder::depthCulling(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::depthCulling(bool const enable) noexcept { mDepthTest = enable; return *this; } -MaterialBuilder& MaterialBuilder::instanced(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::instanced(bool const enable) noexcept { mInstanced = enable; return *this; } -MaterialBuilder& MaterialBuilder::doubleSided(bool doubleSided) noexcept { +MaterialBuilder& MaterialBuilder::doubleSided(bool const doubleSided) noexcept { mDoubleSided = doubleSided; mDoubleSidedCapability = true; return *this; } -MaterialBuilder& MaterialBuilder::maskThreshold(float threshold) noexcept { +MaterialBuilder& MaterialBuilder::maskThreshold(float const threshold) noexcept { mMaskThreshold = threshold; return *this; } -MaterialBuilder& MaterialBuilder::alphaToCoverage(bool enable) noexcept { +MaterialBuilder& MaterialBuilder::alphaToCoverage(bool const enable) noexcept { mAlphaToCoverage = enable; mAlphaToCoverageSet = true; return *this; } -MaterialBuilder& MaterialBuilder::shadowMultiplier(bool shadowMultiplier) noexcept { +MaterialBuilder& MaterialBuilder::shadowMultiplier(bool const shadowMultiplier) noexcept { mShadowMultiplier = shadowMultiplier; return *this; } -MaterialBuilder& MaterialBuilder::transparentShadow(bool transparentShadow) noexcept { +MaterialBuilder& MaterialBuilder::transparentShadow(bool const transparentShadow) noexcept { mTransparentShadow = transparentShadow; return *this; } -MaterialBuilder& MaterialBuilder::specularAntiAliasing(bool specularAntiAliasing) noexcept { +MaterialBuilder& MaterialBuilder::specularAntiAliasing(bool const specularAntiAliasing) noexcept { mSpecularAntiAliasing = specularAntiAliasing; return *this; } -MaterialBuilder& MaterialBuilder::specularAntiAliasingVariance(float screenSpaceVariance) noexcept { +MaterialBuilder& MaterialBuilder::specularAntiAliasingVariance(float const screenSpaceVariance) noexcept { mSpecularAntiAliasingVariance = screenSpaceVariance; return *this; } -MaterialBuilder& MaterialBuilder::specularAntiAliasingThreshold(float threshold) noexcept { +MaterialBuilder& MaterialBuilder::specularAntiAliasingThreshold(float const threshold) noexcept { mSpecularAntiAliasingThreshold = threshold; return *this; } -MaterialBuilder& MaterialBuilder::clearCoatIorChange(bool clearCoatIorChange) noexcept { +MaterialBuilder& MaterialBuilder::clearCoatIorChange(bool const clearCoatIorChange) noexcept { mClearCoatIorChange = clearCoatIorChange; return *this; } -MaterialBuilder& MaterialBuilder::flipUV(bool flipUV) noexcept { +MaterialBuilder& MaterialBuilder::flipUV(bool const flipUV) noexcept { mFlipUV = flipUV; return *this; } -MaterialBuilder& MaterialBuilder::customSurfaceShading(bool customSurfaceShading) noexcept { +MaterialBuilder& MaterialBuilder::customSurfaceShading(bool const customSurfaceShading) noexcept { mCustomSurfaceShading = customSurfaceShading; return *this; } -MaterialBuilder& MaterialBuilder::multiBounceAmbientOcclusion(bool multiBounceAO) noexcept { +MaterialBuilder& MaterialBuilder::multiBounceAmbientOcclusion(bool const multiBounceAO) noexcept { mMultiBounceAO = multiBounceAO; mMultiBounceAOSet = true; return *this; } -MaterialBuilder& MaterialBuilder::specularAmbientOcclusion(SpecularAmbientOcclusion specularAO) noexcept { +MaterialBuilder& MaterialBuilder::specularAmbientOcclusion(SpecularAmbientOcclusion const specularAO) noexcept { mSpecularAO = specularAO; mSpecularAOSet = true; return *this; } -MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode mode) noexcept { +MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode const mode) noexcept { mTransparencyMode = mode; return *this; } -MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType stereoscopicType) noexcept { +MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType const stereoscopicType) noexcept { mStereoscopicType = stereoscopicType; return *this; } -MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t eyeCount) noexcept { +MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t const eyeCount) noexcept { mStereoscopicEyeCount = eyeCount; return *this; } -MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept { +MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode const mode) noexcept { mReflectionMode = mode; return *this; } -MaterialBuilder& MaterialBuilder::platform(Platform platform) noexcept { +MaterialBuilder& MaterialBuilder::platform(Platform const platform) noexcept { mPlatform = platform; return *this; } -MaterialBuilder& MaterialBuilder::targetApi(TargetApi targetApi) noexcept { +MaterialBuilder& MaterialBuilder::targetApi(TargetApi const targetApi) noexcept { mTargetApi |= targetApi; return *this; } -MaterialBuilder& MaterialBuilder::optimization(Optimization optimization) noexcept { +MaterialBuilder& MaterialBuilder::optimization(Optimization const optimization) noexcept { mOptimization = optimization; return *this; } -MaterialBuilder& MaterialBuilder::printShaders(bool printShaders) noexcept { +MaterialBuilder& MaterialBuilder::printShaders(bool const printShaders) noexcept { mPrintShaders = printShaders; return *this; } -MaterialBuilder& MaterialBuilder::saveRawVariants(bool saveRawVariants) noexcept { +MaterialBuilder& MaterialBuilder::saveRawVariants(bool const saveRawVariants) noexcept { mSaveRawVariants = saveRawVariants; return *this; } -MaterialBuilder& MaterialBuilder::generateDebugInfo(bool generateDebugInfo) noexcept { +MaterialBuilder& MaterialBuilder::generateDebugInfo(bool const generateDebugInfo) noexcept { mGenerateDebugInfo = generateDebugInfo; return *this; } -MaterialBuilder& MaterialBuilder::variantFilter(UserVariantFilterMask variantFilter) noexcept { +MaterialBuilder& MaterialBuilder::variantFilter(UserVariantFilterMask const variantFilter) noexcept { mVariantFilter = variantFilter; return *this; } @@ -602,7 +615,7 @@ MaterialBuilder& MaterialBuilder::shaderDefine(const char* name, const char* val return *this; } -bool MaterialBuilder::hasSamplerType(SamplerType samplerType) const noexcept { +bool MaterialBuilder::hasSamplerType(SamplerType const samplerType) const noexcept { for (size_t i = 0, c = mParameterCount; i < c; i++) { auto const& param = mParameters[i]; if (param.isSampler() && param.samplerType == samplerType) { @@ -613,7 +626,7 @@ bool MaterialBuilder::hasSamplerType(SamplerType samplerType) const noexcept { } void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { - MaterialBuilderBase::prepare(mEnableFramebufferFetch, mFeatureLevel); + prepare(mEnableFramebufferFetch, mFeatureLevel); // Build the per-material sampler block and uniform block. SamplerInterfaceBlock::Builder sbb; @@ -643,10 +656,8 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { assert_invariant(param.isSubpass()); // For now, we only support a single subpass for attachment 0. // Subpasses belong to the "MaterialParams" block. - const uint8_t attachmentIndex = 0; - const uint8_t binding = 0; info.subpass = { CString("MaterialParams"), param.name, param.subpassType, - param.format, param.precision, attachmentIndex, binding }; + param.format, param.precision, 0, 0 }; } for (auto const& buffer : mBuffers) { @@ -668,9 +679,9 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { ibb.add({{ "_doubleSided", 0, UniformType::BOOL, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }}); } - mRequiredAttributes.set(VertexAttribute::POSITION); + mRequiredAttributes.set(POSITION); if (mShading != Shading::UNLIT || mShadowMultiplier) { - mRequiredAttributes.set(VertexAttribute::TANGENTS); + mRequiredAttributes.set(TANGENTS); } info.sib = sbb.name("MaterialParams").build(); @@ -714,7 +725,7 @@ void MaterialBuilder::initPushConstants() noexcept { mPushConstants.reserve(PUSH_CONSTANTS.size()); mPushConstants.resize(PUSH_CONSTANTS.size()); std::transform(PUSH_CONSTANTS.cbegin(), PUSH_CONSTANTS.cend(), mPushConstants.begin(), - [](filament::MaterialPushConstant const& inConstant) -> PushConstant { + [](MaterialPushConstant const& inConstant) -> PushConstant { return { .name = inConstant.name, .type = inConstant.type, @@ -723,11 +734,11 @@ void MaterialBuilder::initPushConstants() noexcept { }); } -bool MaterialBuilder::findProperties(backend::ShaderStage type, - MaterialBuilder::PropertyList& allProperties, +bool MaterialBuilder::findProperties(backend::ShaderStage const type, + PropertyList const& allProperties, CodeGenParams const& semanticCodeGenParams) noexcept { - GLSLTools glslTools; - std::string shaderCodeAllProperties = peek(type, semanticCodeGenParams, allProperties); + GLSLTools const glslTools; + std::string const shaderCodeAllProperties = peek(type, semanticCodeGenParams, allProperties); // Populate mProperties with the properties set in the shader. if (!glslTools.findProperties(type, shaderCodeAllProperties, mProperties, semanticCodeGenParams.targetApi, @@ -751,7 +762,7 @@ bool MaterialBuilder::findAllProperties(CodeGenParams const& semanticCodeGenPara // Some fields in MaterialInputs only exist if the property is set (e.g: normal, subsurface // for cloth shading model). Give our shader all properties. This will enable us to parse and // static code analyse the AST. - MaterialBuilder::PropertyList allProperties; + PropertyList allProperties; std::fill_n(allProperties, MATERIAL_PROPERTIES_COUNT, true); if (!findProperties(ShaderStage::FRAGMENT, allProperties, semanticCodeGenParams)) { return false; @@ -778,7 +789,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo, bool success = false; std::string shaderCode; ShaderModel const model = semanticCodeGenParams.shaderModel; - if (mMaterialDomain == filament::MaterialDomain::COMPUTE) { + if (mMaterialDomain == MaterialDomain::COMPUTE) { shaderCode = peek(ShaderStage::COMPUTE, semanticCodeGenParams, mProperties); success = GLSLTools::analyzeComputeShader(shaderCode, model, targetApi, targetLanguage); @@ -788,7 +799,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo, targetApi, targetLanguage); if (success) { shaderCode = peek(ShaderStage::FRAGMENT, semanticCodeGenParams, mProperties); - auto result = GLSLTools::analyzeFragmentShader(shaderCode, model, mMaterialDomain, + auto const result = GLSLTools::analyzeFragmentShader(shaderCode, model, mMaterialDomain, targetApi, targetLanguage, mCustomSurfaceShading); success = result.has_value(); if (success) { @@ -805,7 +816,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo* inOutInfo, bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback, const CString& fileName) noexcept { if (!mCode.empty()) { - ResolveOptions options { + ResolveOptions const options { .insertLineDirectives = true, .insertLineDirectiveCheck = true }; @@ -815,7 +826,7 @@ bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback, .lineNumberOffset = getLineOffset(), .name = CString("") }; - if (!::filamat::resolveIncludes(source, std::move(callback), options)) { + if (!filamat::resolveIncludes(source, std::move(callback), options)) { return false; } mCode = source.text; @@ -825,14 +836,14 @@ bool MaterialBuilder::ShaderCode::resolveIncludes(IncludeCallback callback, return true; } -static void showErrorMessage(const char* materialName, filament::Variant variant, - MaterialBuilder::TargetApi targetApi, backend::ShaderStage shaderType, - MaterialBuilder::FeatureLevel featureLevel, +static void showErrorMessage(const char* materialName, filament::Variant const variant, + MaterialBuilder::TargetApi const targetApi, backend::ShaderStage const shaderType, + MaterialBuilder::FeatureLevel const featureLevel, const std::string& shaderCode) { using ShaderStage = backend::ShaderStage; using TargetApi = MaterialBuilder::TargetApi; - const char* targetApiString; + const char* targetApiString = "unknown"; switch (targetApi) { case TargetApi::OPENGL: targetApiString = (featureLevel == MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0) @@ -848,11 +859,11 @@ static void showErrorMessage(const char* materialName, filament::Variant variant targetApiString = "WebGPU.\n"; break; case TargetApi::ALL: - assert(0); // Unreachable. + assert_invariant(false); // Unreachable. break; } - const char* shaderStageString; + const char* shaderStageString = "unknown"; switch (shaderType) { case ShaderStage::VERTEX: shaderStageString = "Vertex Shader\n"; @@ -900,7 +911,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector(ChunkType::MaterialHasCustomDepthShader, needsStandardDepthProgram()); + container.emplace(MaterialHasCustomDepthShader, needsStandardDepthProgram()); std::atomic_bool cancelJobs(false); bool firstJob = true; @@ -962,32 +973,33 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector const lock(entriesLock); + std::unique_lock const lock(entriesLock); // below we rely on casting ShaderStage to uint8_t - static_assert(sizeof(filament::backend::ShaderStage) == 1); + static_assert(sizeof(backend::ShaderStage) == 1); switch (targetApi) { case TargetApi::WEBGPU: - assert(!spirv.empty()); - assert(wgsl.length() > 0); + assert_invariant(!spirv.empty()); + assert_invariant(!wgsl.empty()); wgslEntry.stage = v.stage; wgslEntry.shader = wgsl; wgslEntries.push_back(wgslEntry); @@ -1071,8 +1083,8 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector d(reinterpret_cast(spirv.data()), + assert_invariant(!spirv.empty()); + std::vector d(reinterpret_cast(spirv.data()), reinterpret_cast(spirv.data() + spirv.size())); spirvEntry.stage = v.stage; spirvEntry.data = std::move(d); @@ -1080,8 +1092,8 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector 0); + assert_invariant(!spirv.empty()); + assert_invariant(!msl.empty()); metalEntry.stage = v.stage; metalEntry.shader = msl; metalEntries.push_back(metalEntry); @@ -1129,7 +1141,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector spirv = std::move(s.data); + std::vector const spirv{ std::move(s.data) }; s.dictionaryIndex = spirvDictionary.addBlob(spirv); } for (const auto& s : metalEntries) { @@ -1140,38 +1152,38 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector( - std::move(textDictionary), ChunkType::DictionaryText); + const auto& dictionaryChunk = container.push( + std::move(textDictionary), DictionaryText); // Emit GLSL chunk (MaterialTextChunk). if (!glslEntries.empty()) { container.push(std::move(glslEntries), - dictionaryChunk.getDictionary(), ChunkType::MaterialGlsl); + dictionaryChunk.getDictionary(), MaterialGlsl); } // Emit ESSL1 chunk (MaterialTextChunk). if (!essl1Entries.empty()) { container.push(std::move(essl1Entries), - dictionaryChunk.getDictionary(), ChunkType::MaterialEssl1); + dictionaryChunk.getDictionary(), MaterialEssl1); } // Emit SPIRV chunks (SpirvDictionaryReader and MaterialBinaryChunk). if (!spirvEntries.empty()) { const bool stripInfo = !mGenerateDebugInfo; - container.push(std::move(spirvDictionary), stripInfo); - container.push(std::move(spirvEntries), ChunkType::MaterialSpirv); + container.push(std::move(spirvDictionary), stripInfo); + container.push(std::move(spirvEntries), MaterialSpirv); } // Emit Metal chunk (MaterialTextChunk). if (!metalEntries.empty()) { container.push(std::move(metalEntries), - dictionaryChunk.getDictionary(), ChunkType::MaterialMetal); + dictionaryChunk.getDictionary(), MaterialMetal); } // Emit WGSL chunk (MaterialTextChunk). if (!wgslEntries.empty()) { container.push(std::move(wgslEntries), - dictionaryChunk.getDictionary(), ChunkType::MaterialWgsl); + dictionaryChunk.getDictionary(), MaterialWgsl); } return true; @@ -1213,7 +1225,7 @@ MaterialBuilder& MaterialBuilder::output(VariableQualifier qualifier, OutputTarg FILAMENT_CHECK_PRECONDITION(depthOutputCount <= MAX_DEPTH_OUTPUT) << "A maximum of " << MAX_DEPTH_OUTPUT << " DEPTH output is allowed."; - assert(mOutputs.size() <= MAX_COLOR_OUTPUT + MAX_DEPTH_OUTPUT); + assert_invariant(mOutputs.size() <= MAX_COLOR_OUTPUT + MAX_DEPTH_OUTPUT); return *this; } @@ -1225,7 +1237,7 @@ MaterialBuilder& MaterialBuilder::enableFramebufferFetch() noexcept { return *this; } -MaterialBuilder& MaterialBuilder::vertexDomainDeviceJittered(bool enabled) noexcept { +MaterialBuilder& MaterialBuilder::vertexDomainDeviceJittered(bool const enabled) noexcept { mVertexDomainDeviceJittered = enabled; return *this; } @@ -1235,8 +1247,7 @@ MaterialBuilder& MaterialBuilder::useLegacyMorphing() noexcept { return *this; } -Package MaterialBuilder::build(JobSystem& jobSystem) noexcept { - bool success; +Package MaterialBuilder::build(JobSystem& jobSystem) { if (materialBuilderClients == 0) { slog.e << "Error: MaterialBuilder::init() must be called before build()." << io::endl; // Return an empty package to signal a failure to build the material. @@ -1257,7 +1268,7 @@ error: } if (mMaterialDomain == MaterialDomain::SURFACE) { - if (mRequiredAttributes[VertexAttribute::COLOR] && + if (mRequiredAttributes[COLOR] && !mVariables[int(Variable::CUSTOM4)].name.empty()) { // both the color attribute and the custom4 variable are present, that's not supported slog.e << "Error: when the 'color' attribute is required 'Variable::CUSTOM4' is not supported." << io::endl; @@ -1310,7 +1321,7 @@ error: } // adjust variant-filter for feature level *before* we start writing into the container - if (mFeatureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0) { + if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) { // at feature level 0, many variants are not supported mVariantFilter |= uint32_t(UserVariantFilterBit::DIRECTIONAL_LIGHTING); mVariantFilter |= uint32_t(UserVariantFilterBit::DYNAMIC_LIGHTING); @@ -1343,7 +1354,7 @@ error: break; } - success = generateShaders(jobSystem, variants, container, info); + bool const success = generateShaders(jobSystem, variants, container, info); if (!success) { // Return an empty package to signal a failure to build the material. goto error; @@ -1358,7 +1369,7 @@ error: } using namespace backend; -static const char* to_string(ShaderStageFlags stageFlags) noexcept { +static const char* to_string(ShaderStageFlags const stageFlags) noexcept { switch (stageFlags) { case ShaderStageFlags::NONE: return "{ }"; case ShaderStageFlags::VERTEX: return "{ vertex }"; @@ -1404,7 +1415,7 @@ bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const break; } - auto const maxTextureCount = backend::FEATURE_LEVEL_CAPS[1].MAX_FRAGMENT_SAMPLER_COUNT; + constexpr auto maxTextureCount = FEATURE_LEVEL_CAPS[1].MAX_FRAGMENT_SAMPLER_COUNT; // count how many samplers filament uses based on the material properties // note: currently SSAO is not used with unlit, but we want to keep that possibility. @@ -1416,7 +1427,7 @@ bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const info.refractionMode == RefractionMode::SCREEN_SPACE) { textureUsedByFilamentCount += 1; // ssr } - if (mVariantFilter & (uint32_t)UserVariantFilterBit::FOG) { + if (mVariantFilter & uint32_t(UserVariantFilterBit::FOG)) { textureUsedByFilamentCount -= 1; // fog texture } @@ -1477,10 +1488,10 @@ bool MaterialBuilder::needsStandardDepthProgram() const noexcept { mBlendingMode == BlendingMode::FADE)); } -std::string MaterialBuilder::peek(backend::ShaderStage stage, +std::string MaterialBuilder::peek(backend::ShaderStage const stage, const CodeGenParams& params, const PropertyList& properties) noexcept { - ShaderGenerator sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants, + ShaderGenerator const sg(properties, mVariables, mOutputs, mDefines, mConstants, mPushConstants, mMaterialFragmentCode.getResolved(), mMaterialFragmentCode.getLineOffset(), mMaterialVertexCode.getResolved(), mMaterialVertexCode.getLineOffset(), mMaterialDomain); @@ -1489,19 +1500,20 @@ std::string MaterialBuilder::peek(backend::ShaderStage stage, prepareToBuild(info); switch (stage) { - case backend::ShaderStage::VERTEX: + case ShaderStage::VERTEX: return sg.createSurfaceVertexProgram( params.shaderModel, params.targetApi, params.targetLanguage, params.featureLevel, info, {}, mInterpolation, mVertexDomain); - case backend::ShaderStage::FRAGMENT: + case ShaderStage::FRAGMENT: return sg.createSurfaceFragmentProgram( params.shaderModel, params.targetApi, params.targetLanguage, params.featureLevel, info, {}, mInterpolation, mVariantFilter); - case backend::ShaderStage::COMPUTE: + case ShaderStage::COMPUTE: return sg.createSurfaceComputeProgram( params.shaderModel, params.targetApi, params.targetLanguage, params.featureLevel, info); } + return {}; } static Program::UniformInfo extractUniforms(BufferInterfaceBlock const& uib) noexcept { @@ -1532,22 +1544,22 @@ static Program::UniformInfo extractUniforms(BufferInterfaceBlock const& uib) noe } void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& info) const noexcept { - container.emplace(ChunkType::MaterialVersion, MATERIAL_VERSION); - container.emplace(ChunkType::MaterialFeatureLevel, (uint8_t)info.featureLevel); - container.emplace(ChunkType::MaterialName, mMaterialName.c_str_safe()); - container.emplace(ChunkType::MaterialShaderModels, mShaderModels.getValue()); + container.emplace(MaterialVersion, MATERIAL_VERSION); + container.emplace(MaterialFeatureLevel, (uint8_t)info.featureLevel); + container.emplace(MaterialName, mMaterialName.c_str_safe()); + container.emplace(MaterialShaderModels, mShaderModels.getValue()); container.emplace(ChunkType::MaterialDomain, static_cast(mMaterialDomain)); // if that ever needed to change, this would require a material version bump static_assert(sizeof(uint32_t) >= sizeof(UserVariantFilterMask)); - container.emplace(ChunkType::MaterialVariantFilterMask, mVariantFilter); + container.emplace(MaterialVariantFilterMask, mVariantFilter); using namespace filament; if (info.featureLevel == FeatureLevel::FEATURE_LEVEL_0) { // FIXME: don't hardcode this - FixedCapacityVector> list({ + FixedCapacityVector> list({ { 0, "FrameUniforms", extractUniforms(UibGenerator::getPerViewUib()) }, { 1, "ObjectUniforms", extractUniforms(UibGenerator::getPerRenderableUib()) }, { 2, "MaterialParams", extractUniforms(info.uib) }, @@ -1582,12 +1594,12 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& container.push(std::move(list)); - using Container = utils::FixedCapacityVector>; + using Container = FixedCapacityVector>; auto attributes = Container::with_capacity(sAttributeDatabase.size()); for (auto const& attribute: sAttributeDatabase) { std::string name("mesh_"); name.append(attribute.name); - attributes.emplace_back(utils::CString{ name.data(), name.size() }, attribute.location); + attributes.emplace_back(CString{ name.data(), name.size() }, attribute.location); } container.push(std::move(attributes)); } @@ -1598,29 +1610,23 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& // User texture parameters container.push(info.sib); - - backend::DescriptorSetLayout const perViewDescriptorSetLayout = - descriptor_sets::getPerViewDescriptorSetLayout( - mMaterialDomain, mVariantFilter, - info.isLit || info.hasShadowMultiplier, info.reflectionMode, info.refractionMode); - // Descriptor layout and descriptor name/binding mapping - container.push(info.sib, perViewDescriptorSetLayout); - container.push(info.sib, perViewDescriptorSetLayout); + container.push(info.sib); + container.push(info.sib); // User constant parameters - utils::FixedCapacityVector constantsEntry(mConstants.size()); + FixedCapacityVector constantsEntry(mConstants.size()); std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(), [](Constant const& c) { return MaterialConstant(c.name.c_str(), c.type); }); container.push(std::move(constantsEntry)); - utils::FixedCapacityVector pushConstantsEntry(mPushConstants.size()); + FixedCapacityVector pushConstantsEntry(mPushConstants.size()); std::transform(mPushConstants.begin(), mPushConstants.end(), pushConstantsEntry.begin(), [](PushConstant const& c) { return MaterialPushConstant(c.name.c_str(), c.type, c.stage); }); container.push( - utils::CString(PUSH_CONSTANT_STRUCT_VAR_NAME), std::move(pushConstantsEntry)); + CString(PUSH_CONSTANT_STRUCT_VAR_NAME), std::move(pushConstantsEntry)); // TODO: should we write the SSBO info? this would only be needed if we wanted to provide // an interface to set [get?] values in the buffer. But we can do that easily @@ -1630,9 +1636,9 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& // User Subpass container.push(info.subpass); - container.emplace(ChunkType::MaterialDoubleSidedSet, mDoubleSidedCapability); - container.emplace(ChunkType::MaterialDoubleSided, mDoubleSided); - container.emplace(ChunkType::MaterialBlendingMode, + container.emplace(MaterialDoubleSidedSet, mDoubleSidedCapability); + container.emplace(MaterialDoubleSided, mDoubleSided); + container.emplace(MaterialBlendingMode, static_cast(mBlendingMode)); if (mBlendingMode == BlendingMode::CUSTOM) { @@ -1641,21 +1647,21 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& (uint32_t(mCustomBlendFunctions[1]) << 16) | (uint32_t(mCustomBlendFunctions[2]) << 8) | (uint32_t(mCustomBlendFunctions[3]) << 0); - container.emplace< uint32_t >(ChunkType::MaterialBlendFunction, blendFunctions); + container.emplace< uint32_t >(MaterialBlendFunction, blendFunctions); } - container.emplace(ChunkType::MaterialTransparencyMode, + container.emplace(MaterialTransparencyMode, static_cast(mTransparencyMode)); - container.emplace(ChunkType::MaterialReflectionMode, + container.emplace(MaterialReflectionMode, static_cast(mReflectionMode)); - container.emplace(ChunkType::MaterialColorWrite, mColorWrite); - container.emplace(ChunkType::MaterialDepthWriteSet, mDepthWriteSet); - container.emplace(ChunkType::MaterialDepthWrite, mDepthWrite); - container.emplace(ChunkType::MaterialDepthTest, mDepthTest); - container.emplace(ChunkType::MaterialInstanced, mInstanced); - container.emplace(ChunkType::MaterialAlphaToCoverageSet, mAlphaToCoverageSet); - container.emplace(ChunkType::MaterialAlphaToCoverage, mAlphaToCoverage); - container.emplace(ChunkType::MaterialCullingMode, + container.emplace(MaterialColorWrite, mColorWrite); + container.emplace(MaterialDepthWriteSet, mDepthWriteSet); + container.emplace(MaterialDepthWrite, mDepthWrite); + container.emplace(MaterialDepthTest, mDepthTest); + container.emplace(MaterialInstanced, mInstanced); + container.emplace(MaterialAlphaToCoverageSet, mAlphaToCoverageSet); + container.emplace(MaterialAlphaToCoverage, mAlphaToCoverage); + container.emplace(MaterialCullingMode, static_cast(mCullingMode)); uint64_t properties = 0; @@ -1665,56 +1671,56 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& properties |= uint64_t(1u) << i; } } - container.emplace(ChunkType::MaterialProperties, properties); - container.emplace(ChunkType::MaterialStereoscopicType, static_cast(mStereoscopicType)); + container.emplace(MaterialProperties, properties); + container.emplace(MaterialStereoscopicType, static_cast(mStereoscopicType)); } // create a unique material id auto const& vert = mMaterialVertexCode.getResolved(); auto const& frag = mMaterialFragmentCode.getResolved(); std::hash const hasher; - size_t const materialId = utils::hash::combine( + size_t const materialId = hash::combine( MATERIAL_VERSION, - utils::hash::combine( + hash::combine( hasher({ vert.data(), vert.size() }), hasher({ frag.data(), frag.size() }))); - container.emplace(ChunkType::MaterialCacheId, materialId); + container.emplace(MaterialCacheId, materialId); } void MaterialBuilder::writeSurfaceChunks(ChunkContainer& container) const noexcept { if (mBlendingMode == BlendingMode::MASKED) { - container.emplace(ChunkType::MaterialMaskThreshold, mMaskThreshold); + container.emplace(MaterialMaskThreshold, mMaskThreshold); } - container.emplace(ChunkType::MaterialShading, static_cast(mShading)); + container.emplace(MaterialShading, static_cast(mShading)); if (mShading == Shading::UNLIT) { - container.emplace(ChunkType::MaterialShadowMultiplier, mShadowMultiplier); + container.emplace(MaterialShadowMultiplier, mShadowMultiplier); } - container.emplace(ChunkType::MaterialRefraction, static_cast(mRefractionMode)); - container.emplace(ChunkType::MaterialRefractionType, + container.emplace(MaterialRefraction, static_cast(mRefractionMode)); + container.emplace(MaterialRefractionType, static_cast(mRefractionType)); - container.emplace(ChunkType::MaterialClearCoatIorChange, mClearCoatIorChange); - container.emplace(ChunkType::MaterialRequiredAttributes, + container.emplace(MaterialClearCoatIorChange, mClearCoatIorChange); + container.emplace(MaterialRequiredAttributes, mRequiredAttributes.getValue()); - container.emplace(ChunkType::MaterialSpecularAntiAliasing, mSpecularAntiAliasing); - container.emplace(ChunkType::MaterialSpecularAntiAliasingVariance, + container.emplace(MaterialSpecularAntiAliasing, mSpecularAntiAliasing); + container.emplace(MaterialSpecularAntiAliasingVariance, mSpecularAntiAliasingVariance); - container.emplace(ChunkType::MaterialSpecularAntiAliasingThreshold, + container.emplace(MaterialSpecularAntiAliasingThreshold, mSpecularAntiAliasingThreshold); - container.emplace(ChunkType::MaterialVertexDomain, static_cast(mVertexDomain)); - container.emplace(ChunkType::MaterialInterpolation, + container.emplace(MaterialVertexDomain, static_cast(mVertexDomain)); + container.emplace(MaterialInterpolation, static_cast(mInterpolation)); } -MaterialBuilder& MaterialBuilder::noSamplerValidation(bool enabled) noexcept { +MaterialBuilder& MaterialBuilder::noSamplerValidation(bool const enabled) noexcept { mNoSamplerValidation = enabled; return *this; } -MaterialBuilder& MaterialBuilder::includeEssl1(bool enabled) noexcept { +MaterialBuilder& MaterialBuilder::includeEssl1(bool const enabled) noexcept { mIncludeEssl1 = enabled; return *this; } diff --git a/libs/filamat/src/MaterialVariants.cpp b/libs/filamat/src/MaterialVariants.cpp index b6ce7ccd74..2bccf95449 100644 --- a/libs/filamat/src/MaterialVariants.cpp +++ b/libs/filamat/src/MaterialVariants.cpp @@ -16,8 +16,6 @@ #include "MaterialVariants.h" -#include "shaders/ShaderGenerator.h" - #include #include @@ -25,16 +23,8 @@ #include -#include -#include -#include - -#include #include -#include -#include - namespace filamat { std::vector determineSurfaceVariants( @@ -62,58 +52,6 @@ std::vector determineSurfaceVariants( if (fragmentVariant == variant) { variants.emplace_back(variant, filament::backend::ShaderStage::FRAGMENT); } - - // Here we make sure that the combination of vertex and fragment variants have compatible - // PER_VIEW descriptor-set layouts. This could actually be a static/compile-time check - // because it is entirely decided in DescriptorSets.cpp. Unfortunately it's not possible - // to write this entirely as a constexpr. - - if (UTILS_UNLIKELY(vertexVariant != fragmentVariant)) { - // fragment and vertex variants are different, we need to check the layouts are - // compatible. - using filament::ReflectionMode; - using filament::RefractionMode; - using filament::backend::ShaderStage; - - // And we need to do that for all configurations of the "PER_VIEW" descriptor set - // layouts (there are eight). - // See ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant. - for (auto reflection: { - ReflectionMode::SCREEN_SPACE, - ReflectionMode::DEFAULT }) { - for (auto refraction: { - RefractionMode::SCREEN_SPACE, - RefractionMode::CUBEMAP, - RefractionMode::NONE }) { - auto const vdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - vertexVariant, userVariantFilter, isLit || shadowMultiplier, - reflection, refraction); - auto const fdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - fragmentVariant, userVariantFilter, isLit || shadowMultiplier, - reflection, refraction); - // Check that all bindings present in the vertex shader DescriptorSetLayout - // are also present in the fragment shader DescriptorSetLayout. - for (auto const& r: vdsl.bindings) { - if (!hasShaderType(r.stageFlags, ShaderStage::VERTEX)) { - // ignore descriptors that are of the fragment stage only - continue; - } - auto const pos = std::find_if(fdsl.bindings.begin(), fdsl.bindings.end(), - [r](auto const& l) { - return l.count == r.count && l.type == r.type && - l.binding == r.binding && l.flags == r.flags && - l.stageFlags == r.stageFlags; - }); - - // A mismatch is fatal. The material is ill-formed. This typically - // mean a bug / inconsistency in DescriptorsSets.cpp - FILAMENT_CHECK_POSTCONDITION(pos != fdsl.bindings.end()) - << "Variant " << +k << " has mismatched descriptorset layouts"; - } - } - } - } - } return variants; } @@ -133,7 +71,7 @@ std::vector determinePostProcessVariants() { std::vector determineComputeVariants() { // TODO: should we have variants for compute shaders? std::vector variants; - filament::Variant variant(0); + filament::Variant const variant(0); variants.emplace_back(variant, filament::backend::ShaderStage::COMPUTE); return variants; } diff --git a/libs/filamat/src/SamplerBindingMap.cpp b/libs/filamat/src/SamplerBindingMap.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/libs/filamat/src/SamplerBindingMap.h b/libs/filamat/src/SamplerBindingMap.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/libs/filamat/src/SpirvRemapWrapper.cpp b/libs/filamat/src/SpirvRemapWrapper.cpp new file mode 100644 index 0000000000..95fe6ae085 --- /dev/null +++ b/libs/filamat/src/SpirvRemapWrapper.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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 "SpirvRemapWrapper.h" + +#include +#include +#include + +#include + +namespace filamat { + +void SpirvRemapWrapperSetUp() { + // SPIRV error handler registration should occur only once. + // Construct this SpirvRemapWrapper object only once. + spv::spirvbin_t::registerErrorHandler([](const std::string& str) { + utils::slog.e << str << utils::io::endl; + }); + + // Similar to above, we need to do a no-op remap to init a static + // table in the remapper before the jobs start using remap(). + spv::spirvbin_t remapper(0); + // We need to provide at least a valid header to not crash. + std::vector spirv { + 0x07230203,// MAGIC + 0, // VERSION + 0, // GENERATOR + 0, // BOUND + 0 // SCHEMA, must be 0 + }; + remapper.remap(spirv, 0); +} + +void SpirvRemapWrapperRemap(std::vector& spirv) { + // Remove dead module-level objects: functions, types, vars + spv::spirvbin_t remapper(0); + remapper.remap(spirv, spv::spirvbin_base_t::DCE_ALL); +} + +} // namespace filamat diff --git a/libs/filamat/src/SpirvRemapWrapper.h b/libs/filamat/src/SpirvRemapWrapper.h new file mode 100644 index 0000000000..c03c0be728 --- /dev/null +++ b/libs/filamat/src/SpirvRemapWrapper.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_SPIRVREMAPWRAPPER_H +#define TNT_SPIRVREMAPWRAPPER_H + +#include +#include + +namespace filamat { + +void SpirvRemapWrapperSetUp(); +void SpirvRemapWrapperRemap(std::vector& spirv); + +} // namespace filamat + +#endif //TNT_SPIRVREMAPWRAPPER_H diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp index 838e76d810..cc24a55f09 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp @@ -39,7 +39,7 @@ namespace filamat { MaterialUniformInterfaceBlockChunk::MaterialUniformInterfaceBlockChunk( BufferInterfaceBlock const& uib) : - Chunk(ChunkType::MaterialUib), + Chunk(MaterialUib), mUib(uib) { } @@ -60,7 +60,7 @@ void MaterialUniformInterfaceBlockChunk::flatten(Flattener& f) { MaterialSamplerInterfaceBlockChunk::MaterialSamplerInterfaceBlockChunk( SamplerInterfaceBlock const& sib) : - Chunk(ChunkType::MaterialSib), + Chunk(MaterialSib), mSib(sib) { } @@ -81,7 +81,7 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialSubpassInterfaceBlockChunk::MaterialSubpassInterfaceBlockChunk(SubpassInfo const& subpass) : - Chunk(ChunkType::MaterialSubpass), + Chunk(MaterialSubpass), mSubpass(subpass) { } @@ -101,8 +101,8 @@ void MaterialSubpassInterfaceBlockChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialConstantParametersChunk::MaterialConstantParametersChunk( - utils::FixedCapacityVector constants) - : Chunk(ChunkType::MaterialConstants), mConstants(std::move(constants)) {} + FixedCapacityVector constants) + : Chunk(MaterialConstants), mConstants(std::move(constants)) {} void MaterialConstantParametersChunk::flatten(Flattener& f) { f.writeUint64(mConstants.size()); @@ -115,8 +115,8 @@ void MaterialConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialPushConstantParametersChunk::MaterialPushConstantParametersChunk( - CString const& structVarName, utils::FixedCapacityVector constants) - : Chunk(ChunkType::MaterialPushConstants), + CString const& structVarName, FixedCapacityVector constants) + : Chunk(MaterialPushConstants), mStructVarName(structVarName), mConstants(std::move(constants)) {} @@ -133,7 +133,7 @@ void MaterialPushConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialBindingUniformInfoChunk::MaterialBindingUniformInfoChunk(Container list) noexcept - : Chunk(ChunkType::MaterialBindingUniformInfo), + : Chunk(MaterialBindingUniformInfo), mBindingUniformInfo(std::move(list)) { } @@ -155,7 +155,7 @@ void MaterialBindingUniformInfoChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ MaterialAttributesInfoChunk::MaterialAttributesInfoChunk(Container list) noexcept - : Chunk(ChunkType::MaterialAttributeInfo), + : Chunk(MaterialAttributeInfo), mAttributeInfo(std::move(list)) { } @@ -170,11 +170,9 @@ void MaterialAttributesInfoChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ -MaterialDescriptorBindingsChuck::MaterialDescriptorBindingsChuck(Container const& sib, - backend::DescriptorSetLayout const& perViewLayout) noexcept - : Chunk(ChunkType::MaterialDescriptorBindingsInfo), - mSamplerInterfaceBlock(sib), - mPerViewLayout(perViewLayout) { +MaterialDescriptorBindingsChuck::MaterialDescriptorBindingsChuck(Container const& sib) noexcept + : Chunk(MaterialDescriptorBindingsInfo), + mSamplerInterfaceBlock(sib) { } void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { @@ -183,13 +181,6 @@ void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { using namespace backend; - - // number of descriptor-sets - f.writeUint8(3); - - // set - f.writeUint8(+DescriptorSetBindingPoints::PER_MATERIAL); - // samplers + 1 descriptor for the UBO f.writeUint8(mSamplerInterfaceBlock.getSize() + 1); @@ -210,37 +201,13 @@ void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { } f.writeUint8(entry.binding); } - - // set - f.writeUint8(+DescriptorSetBindingPoints::PER_RENDERABLE); - f.writeUint8(descriptor_sets::getPerRenderableLayout().bindings.size()); - for (auto const& entry: descriptor_sets::getPerRenderableLayout().bindings) { - auto const& name = descriptor_sets::getDescriptorName( - DescriptorSetBindingPoints::PER_RENDERABLE, entry.binding); - f.writeString({ name.data(), name.size() }); - f.writeUint8(uint8_t(entry.type)); - f.writeUint8(entry.binding); - } - - // set - f.writeUint8(+DescriptorSetBindingPoints::PER_VIEW); - f.writeUint8(mPerViewLayout.bindings.size()); - for (auto const& entry: mPerViewLayout.bindings) { - auto const& name = descriptor_sets::getDescriptorName( - DescriptorSetBindingPoints::PER_VIEW, entry.binding); - f.writeString({ name.data(), name.size() }); - f.writeUint8(uint8_t(entry.type)); - f.writeUint8(entry.binding); - } } // ------------------------------------------------------------------------------------------------ -MaterialDescriptorSetLayoutChunk::MaterialDescriptorSetLayoutChunk(Container const& sib, - backend::DescriptorSetLayout const& perViewLayout) noexcept - : Chunk(ChunkType::MaterialDescriptorSetLayoutInfo), - mSamplerInterfaceBlock(sib), - mPerViewLayout(perViewLayout) { +MaterialDescriptorSetLayoutChunk::MaterialDescriptorSetLayoutChunk(Container const& sib) noexcept + : Chunk(MaterialDescriptorSetLayoutInfo), + mSamplerInterfaceBlock(sib) { } void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) { @@ -271,18 +238,6 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) { f.writeUint8(uint8_t(DescriptorFlags::NONE)); f.writeUint16(0); } - - // samplers + 1 descriptor for the UBO - f.writeUint8(mPerViewLayout.bindings.size()); - - // all the material's sampler descriptors - for (auto const& entry: mPerViewLayout.bindings) { - f.writeUint8(uint8_t(entry.type)); - f.writeUint8(uint8_t(entry.stageFlags)); - f.writeUint8(entry.binding); - f.writeUint8(uint8_t(entry.flags)); - f.writeUint16(entry.count); - } } } // namespace filamat diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h index cbb3a85446..6d428400ab 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h @@ -19,16 +19,12 @@ #include "Chunk.h" -#include - -#include #include #include #include #include -#include #include @@ -45,10 +41,10 @@ namespace filamat { class MaterialUniformInterfaceBlockChunk final : public Chunk { public: explicit MaterialUniformInterfaceBlockChunk(filament::BufferInterfaceBlock const& uib); - ~MaterialUniformInterfaceBlockChunk() final = default; + ~MaterialUniformInterfaceBlockChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; filament::BufferInterfaceBlock const& mUib; }; @@ -58,10 +54,10 @@ private: class MaterialSamplerInterfaceBlockChunk final : public Chunk { public: explicit MaterialSamplerInterfaceBlockChunk(filament::SamplerInterfaceBlock const& sib); - ~MaterialSamplerInterfaceBlockChunk() final = default; + ~MaterialSamplerInterfaceBlockChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; filament::SamplerInterfaceBlock const& mSib; }; @@ -71,10 +67,10 @@ private: class MaterialSubpassInterfaceBlockChunk final : public Chunk { public: explicit MaterialSubpassInterfaceBlockChunk(filament::SubpassInfo const& subpass); - ~MaterialSubpassInterfaceBlockChunk() final = default; + ~MaterialSubpassInterfaceBlockChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; filament::SubpassInfo const& mSubpass; }; @@ -84,41 +80,41 @@ private: class MaterialConstantParametersChunk final : public Chunk { public: explicit MaterialConstantParametersChunk( - utils::FixedCapacityVector constants); - ~MaterialConstantParametersChunk() final = default; + FixedCapacityVector constants); + ~MaterialConstantParametersChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; - utils::FixedCapacityVector mConstants; + FixedCapacityVector mConstants; }; // ------------------------------------------------------------------------------------------------ class MaterialPushConstantParametersChunk final : public Chunk { public: - explicit MaterialPushConstantParametersChunk(utils::CString const& structVarName, - utils::FixedCapacityVector constants); - ~MaterialPushConstantParametersChunk() final = default; + explicit MaterialPushConstantParametersChunk(CString const& structVarName, + FixedCapacityVector constants); + ~MaterialPushConstantParametersChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; - utils::CString mStructVarName; - utils::FixedCapacityVector mConstants; + CString mStructVarName; + FixedCapacityVector mConstants; }; // ------------------------------------------------------------------------------------------------ class MaterialBindingUniformInfoChunk final : public Chunk { using Container = FixedCapacityVector>; + uint8_t, CString, filament::backend::Program::UniformInfo>>; public: explicit MaterialBindingUniformInfoChunk(Container list) noexcept; - ~MaterialBindingUniformInfoChunk() final = default; + ~MaterialBindingUniformInfoChunk() override = default; private: - void flatten(Flattener &) final; + void flatten(Flattener &) override; Container mBindingUniformInfo; }; @@ -126,13 +122,13 @@ private: // ------------------------------------------------------------------------------------------------ class MaterialAttributesInfoChunk final : public Chunk { - using Container = FixedCapacityVector>; + using Container = FixedCapacityVector>; public: explicit MaterialAttributesInfoChunk(Container list) noexcept; - ~MaterialAttributesInfoChunk() final = default; + ~MaterialAttributesInfoChunk() override = default; private: - void flatten(Flattener &) final; + void flatten(Flattener &) override; Container mAttributeInfo; }; @@ -142,15 +138,13 @@ private: class MaterialDescriptorBindingsChuck final : public Chunk { using Container = filament::SamplerInterfaceBlock; public: - explicit MaterialDescriptorBindingsChuck(Container const& sib, - filament::backend::DescriptorSetLayout const& perViewLayout) noexcept; - ~MaterialDescriptorBindingsChuck() final = default; + explicit MaterialDescriptorBindingsChuck(Container const& sib) noexcept; + ~MaterialDescriptorBindingsChuck() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; Container const& mSamplerInterfaceBlock; - filament::backend::DescriptorSetLayout mPerViewLayout; }; // ------------------------------------------------------------------------------------------------ @@ -158,15 +152,13 @@ private: class MaterialDescriptorSetLayoutChunk final : public Chunk { using Container = filament::SamplerInterfaceBlock; public: - explicit MaterialDescriptorSetLayoutChunk(Container const& sib, - filament::backend::DescriptorSetLayout const& perViewLayout) noexcept; - ~MaterialDescriptorSetLayoutChunk() final = default; + explicit MaterialDescriptorSetLayoutChunk(Container const& sib) noexcept; + ~MaterialDescriptorSetLayoutChunk() override = default; private: - void flatten(Flattener&) final; + void flatten(Flattener&) override; Container const& mSamplerInterfaceBlock; - filament::backend::DescriptorSetLayout mPerViewLayout; }; } // namespace filamat diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 3c7cd4f7a5..e6fe317385 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -604,27 +604,13 @@ std::string ShaderGenerator::createSurfaceFragmentProgram(ShaderModel shaderMode if (featureLevel >= FeatureLevel::FEATURE_LEVEL_1) { assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); - - auto const perViewDescriptorSetLayout = getPerViewDescriptorSetLayoutWithVariant( - variant, variantFilter, - material.isLit || material.hasShadowMultiplier, - material.reflectionMode, material.refractionMode); - // this is the list of samplers we need to filter - auto list = SibGenerator::getPerViewSib(variant).getSamplerInfoList(); - - // remove all the samplers that are not included in the descriptor-set layout - list.erase( - std::remove_if(list.begin(), list.end(), - [&perViewDescriptorSetLayout](auto const& entry) { - auto pos = std::find_if( - perViewDescriptorSetLayout.bindings.begin(), - perViewDescriptorSetLayout.bindings.end(), - [&entry](const auto& item) { - return item.binding == entry.binding; - }); - return pos == perViewDescriptorSetLayout.bindings.end(); - }), list.end()); + auto const list = SamplerInterfaceBlock::filterSamplerList( + SibGenerator::getPerViewSib(variant).getSamplerInfoList(), + descriptor_sets::getPerViewDescriptorSetLayoutWithVariant( + variant, mMaterialDomain, variantFilter, + material.isLit || material.hasShadowMultiplier, + material.reflectionMode, material.refractionMode)); cg.generateCommonSamplers(fs, DescriptorSetBindingPoints::PER_VIEW, list); } @@ -841,22 +827,4 @@ bool ShaderGenerator::hasStereo( && featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0; } -backend::DescriptorSetLayout ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - filament::Variant variant, - UserVariantFilterMask variantFilter, - bool isLit, - ReflectionMode reflectionMode, - RefractionMode refractionMode) { - if (filament::Variant::isValidDepthVariant(variant)) { - return descriptor_sets::getDepthVariantLayout(); - } - if (filament::Variant::isSSRVariant(variant)) { - return descriptor_sets::getSsrVariantLayout(); - } - // We need to filter out all the descriptors not included in the "resolved" layout below - return descriptor_sets::getPerViewDescriptorSetLayout( - MaterialDomain::SURFACE, variantFilter, - isLit, reflectionMode, refractionMode); -} - } // namespace filament diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index fd597b13ca..9d9b470a91 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -88,13 +88,6 @@ public: MaterialBuilder::FeatureLevel featureLevel, MaterialInfo const& material) noexcept; - static filament::backend::DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( - filament::Variant variant, - filament::UserVariantFilterMask variantFilter, - bool isLit, - filament::ReflectionMode reflectionMode, - filament::RefractionMode refractionMode); - private: static void generateVertexDomainDefines(utils::io::sstream& out, filament::VertexDomain domain) noexcept; diff --git a/libs/utils/include/utils/CString.h b/libs/utils/include/utils/CString.h index b799172d3a..bea466ae29 100644 --- a/libs/utils/include/utils/CString.h +++ b/libs/utils/include/utils/CString.h @@ -128,6 +128,7 @@ public: CString& replace(size_type pos, size_type len, const CString& str) noexcept; CString& insert(size_type pos, const CString& str) noexcept { return replace(pos, 0, str); } + CString& append(const CString& str) noexcept { return insert(length(), str); } const_reference operator[](size_type pos) const noexcept { assert(pos < size()); diff --git a/test/renderdiff/src/golden_manager.py b/test/renderdiff/src/golden_manager.py new file mode 100644 index 0000000000..6daabc1fda --- /dev/null +++ b/test/renderdiff/src/golden_manager.py @@ -0,0 +1,80 @@ +import os +import shutil + +from utils import execute, ArgParseImpl + +GOLDENS_DIR = 'renderdiff' + +class GoldenManager: + def __init__(self, working_dir, access_token=None): + self.working_dir_ = working_dir + self.access_token_ = access_token + assert os.path.isdir(self.working_dir_),\ + f"working directory {self.working_dir_} does not exist" + self._prepare() + + def _assets_dir(self): + return os.path.join(self.working_dir_, "filament-assets") + + def _prepare(self): + assets_dir = self._assets_dir() + if not os.path.exists(assets_dir): + access_token_part = '' + if self.access_token_: + access_token_part = f'x-access-token:{self.access_token_}@' + execute( + f'git clone --depth=1 https://{access_token_part}github.com/google/filament-assets.git', + cwd=self.working_dir_) + else: + self.update() + + def update(self): + self._git_exec('fetch') + self._git_exec('checkout main') + self._git_exec('rebase') + + def _git_exec(self, cmd): + execute(f'git {cmd}', cwd=self._assets_dir(), capture_output=False) + + def merge_to_main(self, branch, push_to_remote=False): + self.update() + assets_dir = self._assets_dir() + self._git_exec(f'checkout main') + self._git_exec(f'merge --no-ff {branch}') + if push_to_remote and self.access_token_: + self._git_exec(f'push origin main') + + def source_from_and_commit(self, src_dir, commit_msg, branch, push_to_remote=False): + assets_dir = self._assets_dir() + self._git_exec(f'checkout main') + # Force create the branch (note will overwrite the old branch) + self._git_exec(f'switch -C {branch}') + rdiff_dir = os.path.join(assets_dir, GOLDENS_DIR) + execute(f'rm -rf {rdiff_dir}') + execute(f'mkdir -p {rdiff_dir}') + shutil.copytree(src_dir, rdiff_dir, dirs_exist_ok=True) + self._git_exec(f'add {GOLDENS_DIR}') + + TMP_GOLDEN_COMMIT_FILE = '/tmp/golden_commit.txt' + + with open(TMP_GOLDEN_COMMIT_FILE, 'w') as f: + f.write(commit_msg) + self._git_exec(f'commit -F {TMP_GOLDEN_COMMIT_FILE}') + if push_to_remote and self.access_token_: + self._git_exec(f'push -f origin ${branch}') + + def download_to(self, dest_dir, branch='main'): + assets_dir = self._assets_dir() + execute(f'mkdir -p {dest_dir}') + rdiff_dir = os.path.join(assets_dir, GOLDENS_DIR) + shutil.copytree(rdiff_dir, dest_dir, dirs_exist_ok=True) + +# For testing only +if __name__ == "__main__": + golden_manager = GoldenManager(os.getcwd()) + # golden_manager.source_from_and_commit( + # os.path.join(os.getcwd(), 'out/renderdiff_tests'), + # 'First commit (local)', + # branch='branch-test') + # golden_manager.merge_to_main('branch-test', push_to_remote=True) + # golden_manager.download_to(os.path.join(os.getcwd(), 'tmp/goldens')) diff --git a/test/renderdiff/src/image_diff.py b/test/renderdiff/src/image_diff.py new file mode 100644 index 0000000000..b4c3d3eedb --- /dev/null +++ b/test/renderdiff/src/image_diff.py @@ -0,0 +1,29 @@ +import tifffile +import numpy + +def same_image(tiff_file_a, tiff_file_b): + try: + img1_data = tifffile.imread(tiff_file_a) + img2_data = tifffile.imread(tiff_file_b) + + # If the dimensions (height, width, number of channels, number of pages/frames) + # are different, the images are not the same. + if img1_data.shape != img2_data.shape: + print(f"Images have different shapes: {img1_data.shape} vs {img2_data.shape}") + return False + + # numpy.array_equal() checks if two arrays have the same shape and elements. + if numpy.array_equal(img1_data, img2_data): + return True + else: + return False + + except FileNotFoundError: + print(f"Error: One or both files not found ('{file_path1}', '{file_path2}').") + return False + except tifffile.TiffFileError as e: + print(f"Error: One or both files are not valid TIFF files or could not be read. Details: {e}") + return False + except Exception as e: + print(f"An unexpected error occurred: {e}") + return False diff --git a/test/renderdiff/src/run.py b/test/renderdiff/src/run.py index 15c5663eba..63334faa03 100644 --- a/test/renderdiff/src/run.py +++ b/test/renderdiff/src/run.py @@ -14,9 +14,13 @@ import sys import os +import json +import glob from utils import execute, ArgParseImpl from parse_test_json import parse_test_config_from_path +from golden_manager import GoldenManager +from image_diff import same_image def important_print(msg): lines = msg.split('\n') @@ -28,8 +32,16 @@ def important_print(msg): print(information) print('-' * (max_len + 8)) -def render_test(gltf_viewer, test_config, output_dir, - opengl_lib=None, vk_icd=None): +RESULT_OK = 'ok' +RESULT_FAILED_TO_RENDER = 'failed-to-render' +RESULT_FAILED_IMAGE_DIFF = 'failed-image-diff' +RESULT_FAILED_NO_GOLDEN = 'failed-no-golden' + +def run_test(gltf_viewer, + test_config, + output_dir, + opengl_lib=None, + vk_icd=None): assert os.path.isdir(output_dir), f"output directory {output_dir} does not exist" assert os.access(gltf_viewer, os.X_OK) @@ -60,43 +72,46 @@ def render_test(gltf_viewer, test_config, output_dir, important_print(f'Rendering {test_desc}') - res, _ = execute(f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless', - env=env, capture_output=False) + out_code, _ = execute( + f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless', + env=env, capture_output=False + ) - if res == 0: - execute(f'mv -f {test.name}0.tif {named_output_dir}/{out_name}.tif', capture_output=False) - execute(f'mv -f {test.name}0.json {named_output_dir}/{test.name}.json', capture_output=False) + result = '' + if out_code == 0: + result = RESULT_OK + out_tif_basename = f'{out_name}.tif' + out_tif_name = f'{named_output_dir}/{out_tif_basename}' + execute(f'mv -f {test.name}0.tif {out_tif_name}', capture_output=False) + execute(f'mv -f {test.name}0.json {named_output_dir}/{test.name}.json', + capture_output=False) else: - important_print(f'{test_desc} failed with error={res}') - print('') + result = RESULT_FAILED_TO_RENDER + important_print(f'{test_desc} rendering failed with error={out_code}') - results.append((out_name, res)) - return results + results.append({ + 'name': out_name, + 'result': result, + 'result_code': out_code, + }) + return named_output_dir, results -GOLDENS_DIR = 'renderdiff_goldens' +def compare_goldens(render_results, output_dir, goldens): + for result in render_results: + if result['result'] != RESULT_OK: + continue -# We pull the goldens from the filament-assets repo -def pull_goldens(output_dir): - assert os.path.isdir(output_dir), f"output directory {output_dir} does not exist" - golden_dir = os.path.join(output_dir, "golden") - assets_dir = os.path.join(output_dir, "filament-assets") + out_tif_basename = f"{result['name']}.tif" + out_tif_name = f'{output_dir}/{out_tif_basename}' + golden_path = goldens.get(out_tif_basename) + if not golden_path: + result['result'] = RESULT_FAILED_NO_GOLDEN + result['result_code'] = 1 + elif not same_image(golden_path, out_tif_name): + result['result'] = RESULT_FAILED_IMAGE_DIFF + result['result_code'] = 1 - if not os.path.exists(assets_dir): - execute('git clone --depth 1 git@github.com:google/filament-assets.git', cwd=output_dir) - else: - execute('git fetch', cwd=assets_dir) - execute('git checkout main ', cwd=assets_dir) - execute('git rebase', cwd=assets_dir) - - if os.path.exists(golden_dir): - execute('rm -f goldens/*', cwd=output_dir) - execute(f'cp filament-assets/{GOLDENS_DIR}/* goldens', cwd=output_dir) - -def push_goldens(output_dir, test_name, filter_func=lambda a:True): - for test in test_config.tests: - for backend in test_config.backends: - for model in test.models: - pass + return render_results if __name__ == "__main__": parser = ArgParseImpl() @@ -105,12 +120,40 @@ if __name__ == "__main__": parser.add_argument('--output_dir', help='Output Directory', required=True) parser.add_argument('--opengl_lib', help='Path to the folder containing OpenGL driver lib (for LD_LIBRARY_PATH)') parser.add_argument('--vk_icd', help='Path to VK ICD file') + parser.add_argument('--golden_branch', help='Branch of the golden repo to compare against') args, _ = parser.parse_known_args(sys.argv[1:]) test = parse_test_config_from_path(args.test) - render_result = render_test(args.gltf_viewer, test, args.output_dir, opengl_lib=args.opengl_lib, vk_icd=args.vk_icd) - failed = [f' {tname}' for tname, res in render_result if res != 0] - success_count = len(render_result) - len(failed ) - important_print(f'Successfully rendered {success_count} / {len(render_result)}' + + output_dir, results = \ + run_test(args.gltf_viewer, + test, + args.output_dir, + opengl_lib=args.opengl_lib, + vk_icd=args.vk_icd) + + # The presence of this argument indicates comparison against a set of goldens. + if args.golden_branch: + # prepare goldens working directory + tmp_golden_dir = '/tmp/renderdiff-goldens' + execute(f'mkdir -p {tmp_golden_dir}') + + # Download the golden repo into the current working directory + golden_manager = GoldenManager(os.getcwd()) + golden_manager.download_to(tmp_golden_dir, branch=args.golden_branch) + + goldens = { + os.path.basename(fpath) : fpath for fpath in \ + glob.glob(f'{os.path.join(tmp_golden_dir, test.name)}/**/*.tif', recursive=True) + } + results = compare_goldens(results, output_dir, goldens) + + + with open(f'{output_dir}/results.json', 'w') as f: + f.write(json.dumps(results)) + execute(f'cp {args.test} {output_dir}/test.json') + + failed = [f" {k['name']}" for k in results if k['result'] != RESULT_OK] + success_count = len(results) - len(failed) + important_print(f'Successfully tested {success_count} / {len(results)}' + ('\nFailed:\n' + ('\n'.join(failed)) if len(failed) > 0 else '')) diff --git a/test/renderdiff/src/workflow_presubmit_msg.py b/test/renderdiff/src/workflow_presubmit_msg.py index 06d8975364..e971aa7b33 100644 --- a/test/renderdiff/src/workflow_presubmit_msg.py +++ b/test/renderdiff/src/workflow_presubmit_msg.py @@ -25,7 +25,8 @@ def get_last_commit(): return ( commit.split(' ')[1], title.strip(), - desc) + desc + ) def sanitized_split(line, split_atom='\n'): return list(filter(lambda x: len(x) > 0, map(lambda x: x.strip(), line.split(split_atom)))) diff --git a/test/renderdiff/test.sh b/test/renderdiff/test.sh index 354cdf6236..d219419a58 100755 --- a/test/renderdiff/test.sh +++ b/test/renderdiff/test.sh @@ -43,9 +43,10 @@ function prepare_mesa() { set -ex && prepare_mesa && \ mkdir -p ${OUTPUT_DIR} && \ - CXX=`which clang++` CC=`which clang` ./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \ + CXX=`which clang++` CC=`which clang` ./build.sh -f -X ${MESA_DIR} -p desktop debug gltf_viewer && \ python3 ${RENDERDIFF_TEST_DIR}/src/run.py \ --gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \ --test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \ --output_dir=${OUTPUT_DIR} \ - --opengl_lib=${MESA_LIB_DIR} + --opengl_lib=${MESA_LIB_DIR} \ + --golden_branch=main diff --git a/third_party/dawn/src/dawn/native/CommandValidation.cpp b/third_party/dawn/src/dawn/native/CommandValidation.cpp index 7fa92a301a..65b37c8f27 100644 --- a/third_party/dawn/src/dawn/native/CommandValidation.cpp +++ b/third_party/dawn/src/dawn/native/CommandValidation.cpp @@ -229,10 +229,10 @@ MaybeError ValidateWriteBuffer(const DeviceBase* device, uint64_t size) { DAWN_TRY(device->ValidateObject(buffer)); - DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.", - bufferOffset); - - DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size); + // DAWN_INVALID_IF(bufferOffset % 4 != 0, "BufferOffset (%u) is not a multiple of 4.", + // bufferOffset); + // + // DAWN_INVALID_IF(size % 4 != 0, "Size (%u) is not a multiple of 4.", size); uint64_t bufferSize = buffer->GetSize(); DAWN_INVALID_IF(bufferOffset > bufferSize || size > (bufferSize - bufferOffset), diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 111edbd45a..6700a39fac 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.59.5", + "version": "1.60.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js",