diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index aa22a226ec..53fb602b02 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -25,7 +25,7 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: Android diff --git a/.github/workflows/build-apple.yml b/.github/workflows/build-apple.yml index 05783ced9c..fba0b0cfa1 100644 --- a/.github/workflows/build-apple.yml +++ b/.github/workflows/build-apple.yml @@ -57,7 +57,7 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: ${{ matrix.platform }} @@ -71,20 +71,20 @@ jobs: - name: Configure CMake if: success() - uses: DiligentGraphics/github-action/configure-cmake@v14 + uses: DiligentGraphics/github-action/configure-cmake@v21 with: build-type: ${{ matrix.build_type }} cmake-args: ${{ matrix.cmake_args }} - name: Build if: success() - uses: DiligentGraphics/github-action/build@v14 + uses: DiligentGraphics/github-action/build@v21 with: target: install - name: DiligentCoreTest if: ${{ success() && matrix.platform == 'MacOS' }} - uses: DiligentGraphics/github-action/run-core-tests@v14 + uses: DiligentGraphics/github-action/run-core-tests@v21 - name: Upload artifact uses: actions/upload-artifact@v7 diff --git a/.github/workflows/build-emscripten.yml b/.github/workflows/build-emscripten.yml index ab2b36f91e..fcf659377e 100644 --- a/.github/workflows/build-emscripten.yml +++ b/.github/workflows/build-emscripten.yml @@ -38,17 +38,17 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: Web - name: Configure CMake if: success() - uses: DiligentGraphics/github-action/configure-cmake@v14 + uses: DiligentGraphics/github-action/configure-cmake@v21 with: build-type: ${{ matrix.build_type }} cmake-args: ${{ matrix.cmake_args }} - name: Build if: success() - uses: DiligentGraphics/github-action/build@v14 + uses: DiligentGraphics/github-action/build@v21 diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index eb1650bde6..7d00b20a7c 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -61,6 +61,22 @@ jobs: cc: "clang-18" cxx: "clang++-18" + - name: "Clang-ASAN" + build_type: "RelWithDebInfo" + cmake_generator: "Ninja" + cmake_args: "-DDILIGENT_BUILD_CORE_TESTS=ON" + sanitizer: "address" + cc: "clang-18" + cxx: "clang++-18" + + - name: "Clang-TSAN" + build_type: "RelWithDebInfo" + cmake_generator: "Ninja" + cmake_args: "-DDILIGENT_BUILD_CORE_TESTS=ON" + sanitizer: "thread" + cc: "clang-18" + cxx: "clang++-18" + - name: "Clang-NO_GLSLANG" build_type: "Debug" cmake_generator: "Ninja" @@ -85,7 +101,7 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: Linux cmake-generator: ${{ matrix.cmake_generator }} @@ -100,57 +116,58 @@ jobs: - name: Configure CMake if: success() - uses: DiligentGraphics/github-action/configure-cmake@v14 + uses: DiligentGraphics/github-action/configure-cmake@v21 with: cc: ${{ matrix.cc }} cxx: ${{ matrix.cxx }} generator: ${{ matrix.cmake_generator }} build-type: ${{ matrix.build_type }} cmake-args: ${{ matrix.cmake_args }} + sanitizer: ${{ matrix.sanitizer }} - name: Build id: build if: success() - uses: DiligentGraphics/github-action/build@v14 + uses: DiligentGraphics/github-action/build@v21 with: target: install - name: DiligentCoreTest if: success() - uses: DiligentGraphics/github-action/run-core-tests@v14 + uses: DiligentGraphics/github-action/run-core-tests@v21 - name: DiligentCoreAPITest VK # NB: it is essential to include failure() to override the default status check of success() # that is automatically applied to if conditions that don't contain a status check function. - if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC' || matrix.name == 'Clang-ASAN' || matrix.name == 'Clang-TSAN') }} + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: vk_sw - name: DiligentCoreAPITest VK Compatibility - if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC' || matrix.name == 'Clang-ASAN' || matrix.name == 'Clang-TSAN') }} + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: vk_sw vk-compatibility: true - name: DiligentCoreAPITest GL - if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC' || matrix.name == 'Clang-ASAN' || matrix.name == 'Clang-TSAN') }} + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: gl - name: DiligentCoreAPITest GL with Non-Separable Programs - if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Clang' || matrix.name == 'GCC' || matrix.name == 'Clang-ASAN' || matrix.name == 'Clang-TSAN') }} + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: gl non-separable-progs: true - name: Upload artifact uses: actions/upload-artifact@v7 - if: ${{ success() && matrix.build_type != 'Debug' }} + if: ${{ success() && matrix.build_type != 'Debug' && matrix.name != 'Clang-ASAN' && matrix.name != 'Clang-TSAN' }} with: name: DiligentCore-Linux-${{ matrix.name }}-x64-${{ matrix.build_type }} path: | diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 1064eb4f5a..b59e17eaaf 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -77,7 +77,7 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: ${{ matrix.platform }} cmake-generator: ${{ matrix.cmake_generator }} @@ -93,7 +93,7 @@ jobs: - name: Configure CMake if: success() - uses: DiligentGraphics/github-action/configure-cmake@v14 + uses: DiligentGraphics/github-action/configure-cmake@v21 with: generator: ${{ matrix.cmake_generator }} vs-arch: ${{ matrix.toolset }} @@ -103,31 +103,31 @@ jobs: - name: Build id: build if: success() - uses: DiligentGraphics/github-action/build@v14 + uses: DiligentGraphics/github-action/build@v21 with: target: install - name: DiligentCoreTest if: ${{ success() && matrix.name != 'UWP'}} - uses: DiligentGraphics/github-action/run-core-tests@v14 + uses: DiligentGraphics/github-action/run-core-tests@v21 - name: DiligentCoreAPITest D3D11 # NB: it is essential to include failure() to override the default status check of success() # that is automatically applied to if conditions that don't contain a status check function. if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Win10' || matrix.name == 'Win10-Ninja') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: d3d11_sw - name: DiligentCoreAPITest D3D12 if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Win10' || matrix.name == 'Win10-Ninja') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: d3d12_sw - name: DiligentCoreAPITest D3D12 DXC if: ${{ (success() || failure() && steps.build.outcome == 'success') && (matrix.name == 'Win10' || matrix.name == 'Win10-Ninja') }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: d3d12_sw use-dxc: true @@ -136,7 +136,7 @@ jobs: - name: DiligentCoreAPITest WebGPU if: ${{ (success() || failure() && steps.build.outcome == 'success') && matrix.name == 'Win10' }} - uses: DiligentGraphics/github-action/run-core-gpu-tests@v14 + uses: DiligentGraphics/github-action/run-core-gpu-tests@v21 with: mode: wgpu diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8be322ff4e..99c89036ed 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,14 +41,14 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: ${{ matrix.platform }} cmake-generator: ${{ matrix.cmake_generator }} - name: Configure CMake if: success() - uses: DiligentGraphics/github-action/configure-cmake@v14 + uses: DiligentGraphics/github-action/configure-cmake@v21 with: cc: ${{ matrix.cc }} cxx: ${{ matrix.cxx }} @@ -65,7 +65,7 @@ jobs: - name: Build if: success() - uses: DiligentGraphics/github-action/build@v14 + uses: DiligentGraphics/github-action/build@v21 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/msvc_analysis.yml b/.github/workflows/msvc_analysis.yml index 0681f0b520..d28e2d304e 100644 --- a/.github/workflows/msvc_analysis.yml +++ b/.github/workflows/msvc_analysis.yml @@ -32,14 +32,14 @@ jobs: - name: Set up build environment if: success() - uses: DiligentGraphics/github-action/setup-build-env@v14 + uses: DiligentGraphics/github-action/setup-build-env@v21 with: platform: ${{ matrix.platform }} cmake-generator: ${{ matrix.cmake_generator }} - name: Configure CMake if: success() - uses: DiligentGraphics/github-action/configure-cmake@v14 + uses: DiligentGraphics/github-action/configure-cmake@v21 with: generator: ${{ matrix.cmake_generator }} vs-arch: ${{ matrix.toolset }} @@ -48,7 +48,7 @@ jobs: - name: Build if: success() - uses: DiligentGraphics/github-action/build@v14 + uses: DiligentGraphics/github-action/build@v21 - name: Run MSVC Code Analysis uses: DiligentGraphics/msvc-code-analysis-action@main diff --git a/BuildTools/CMake/BuildUtils.cmake b/BuildTools/CMake/BuildUtils.cmake index 4c8c6f7488..a0feacf5ff 100644 --- a/BuildTools/CMake/BuildUtils.cmake +++ b/BuildTools/CMake/BuildUtils.cmake @@ -295,6 +295,21 @@ function(set_common_target_properties TARGET) custom_post_configure_target(${TARGET}) endif() + # Mark generated files as such to avoid "file not found" warnings in some IDEs in configuration stage + if (WEBGPU_SUPPORTED AND (NOT PLATFORM_WEB)) + set(DAWN_GENERATED_HEADERS + ${DAWN_BUILD_GEN_DIR}/include/dawn/webgpu.h + ${DAWN_BUILD_GEN_DIR}/include/dawn/webgpu_cpp.h + ${DAWN_BUILD_GEN_DIR}/include/dawn/webgpu_cpp_print.h + ${DAWN_BUILD_GEN_DIR}/include/dawn/wire/client/webgpu.h + ${DAWN_BUILD_GEN_DIR}/include/dawn/wire/client/webgpu_cpp.h + ${DAWN_BUILD_GEN_DIR}/include/dawn/wire/client/webgpu_cpp_print.h + ${DAWN_BUILD_GEN_DIR}/include/dawn/dawn_proc_table.h + ${DAWN_BUILD_GEN_DIR}/include/webgpu/webgpu_cpp_chained_struct.h + ) + set_source_files_properties(${DAWN_GENERATED_HEADERS} PROPERTIES GENERATED TRUE) + endif() + endfunction() function(find_targets_in_directory _RESULT _DIR) @@ -518,24 +533,33 @@ function(add_format_validation_target MODULE_NAME MODULE_ROOT_PATH IDE_FOLDER) endfunction() -# FetchContent's GIT_SHALLOW option is buggy and does not actually do a shallow -# clone. This macro takes care of it. -macro(FetchContent_DeclareShallowGit Name GIT_REPOSITORY GitRepository GIT_TAG GitTag) +# The GIT_SHALLOW option in FetchContent is buggy and does not actually perform +# a shallow clone. This macro fixes that. Also, we do not want to clone all +# submodules to reduce download time, therefore an optional GIT_SUBMODULES +# argument specifies which submodules should be cloned (if omitted, all of them). +macro(FetchContent_DeclareShallowGit Name) + set(oneValueArgs GIT_REPOSITORY GIT_TAG) + set(multiValueArgs GIT_SUBMODULES) + cmake_parse_arguments(ARG "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + include(FetchContent) - FetchContent_Declare( - "${Name}" - # This is what it'd look like if GIT_SHALLOW was indeed working: - #GIT_REPOSITORY "${GitRepository}" - #GIT_TAG "${GitTag}" - #GIT_SHALLOW ON + # This is what it'd look like if GIT_SHALLOW was indeed working: + #GIT_REPOSITORY "${ARG_GIT_REPOSITORY}" + #GIT_TAG "${ARG_GIT_TAG}" + #GIT_SHALLOW ON + #GIT_SUBMODULES "${ARG_GIT_SUBMODULES}" - # Manual download mode instead: + # Manual download mode instead: + FetchContent_Declare( + ${Name} DOWNLOAD_COMMAND cd "${FETCHCONTENT_BASE_DIR}/${Name}-src" && git init && - git fetch --depth=1 "${GitRepository}" "${GitTag}" && - git reset --hard FETCH_HEAD + git remote add origin "${ARG_GIT_REPOSITORY}" && + git fetch --depth=1 origin "${ARG_GIT_TAG}" && + git checkout FETCH_HEAD && + git submodule update --init --recursive --depth=1 ${ARG_GIT_SUBMODULES} ) endmacro() diff --git a/BuildTools/Sanitizers/tsan.supp b/BuildTools/Sanitizers/tsan.supp new file mode 100644 index 0000000000..9f83d7e906 --- /dev/null +++ b/BuildTools/Sanitizers/tsan.supp @@ -0,0 +1,14 @@ +# Mesa lavapipe/llvmpipe internal synchronization during Vulkan teardown. +race:libvulkan_lvp.so + +# Mesa lavapipe LLVM internals during Vulkan shader compilation. +race:libLLVM.so + +# Mesa Gallium/llvmpipe internal synchronization during OpenGL readback/present. +race:libgallium + +# Vulkan validation layer object lifetime tracking may trigger lock-order-inversion reports. +deadlock:libVkLayer_khronos_validation.so + +# DXC/glibc locale conversion internals during parallel shader compilation. +race:DXCompilerImpl::Compile diff --git a/CMakeLists.txt b/CMakeLists.txt index 947c391e67..9c72af9ea6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,11 +204,13 @@ elseif(PLATFORM_ANDROID) elseif(PLATFORM_LINUX) set(GL_SUPPORTED TRUE CACHE INTERNAL "OpenGL is supported on Linux platform") set(VULKAN_SUPPORTED TRUE CACHE INTERNAL "Vulkan is supported on Linux platform") + set(WEBGPU_SUPPORTED TRUE CACHE INTERNAL "WebGPU is supported on Linux platform via Dawn") set(ARCHIVER_SUPPORTED TRUE CACHE INTERNAL "Archiver is supported on Linux platform") target_compile_definitions(Diligent-PublicBuildSettings INTERFACE PLATFORM_LINUX=1) elseif(PLATFORM_MACOS) set(GL_SUPPORTED TRUE CACHE INTERNAL "OpenGL is supported on macOS platform") set(VULKAN_SUPPORTED TRUE CACHE INTERNAL "Vulkan is enabled through MoltenVK on macOS platform") + set(WEBGPU_SUPPORTED TRUE CACHE INTERNAL "WebGPU is supported on macOS platform via Dawn") set(ARCHIVER_SUPPORTED TRUE CACHE INTERNAL "Archiver is supported on macOS platform") target_compile_definitions(Diligent-PublicBuildSettings INTERFACE PLATFORM_MACOS=1 PLATFORM_APPLE=1) elseif(PLATFORM_IOS) @@ -316,6 +318,8 @@ endif() option(DILIGENT_NO_ARCHIVER "Do not build archiver" OFF) option(DILIGENT_NO_SUPER_RESOLUTION "Do not build super resolution" OFF) +set(DILIGENT_SANITIZER "" CACHE STRING "Enable sanitizer: address or thread") +set_property(CACHE DILIGENT_SANITIZER PROPERTY STRINGS "" address thread) option(DILIGENT_EMSCRIPTEN_STRIP_DEBUG_INFO "Strip debug information from WebAsm binaries" OFF) @@ -392,6 +396,48 @@ endif() target_link_libraries(Diligent-BuildSettings INTERFACE Diligent-PublicBuildSettings) +string(TOLOWER "${DILIGENT_SANITIZER}" DILIGENT_SANITIZER_NORMALIZED) +if(NOT "${DILIGENT_SANITIZER}" STREQUAL "${DILIGENT_SANITIZER_NORMALIZED}") + set(DILIGENT_SANITIZER "${DILIGENT_SANITIZER_NORMALIZED}" CACHE STRING "Enable sanitizer: address or thread" FORCE) +endif() + +set(DILIGENT_SANITIZER_COMPILE_OPTIONS "" CACHE INTERNAL "Diligent sanitizer compile options") +set(DILIGENT_SANITIZER_LINK_OPTIONS "" CACHE INTERNAL "Diligent sanitizer link options") + +if(DILIGENT_SANITIZER) + if(DILIGENT_SANITIZER STREQUAL "address") + message(STATUS "DILIGENT_SANITIZER is 'address': AddressSanitizer is enabled") + if(MSVC) + set(DILIGENT_SANITIZER_COMPILE_OPTIONS /fsanitize=address CACHE INTERNAL "Diligent sanitizer compile options") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + set(DILIGENT_SANITIZER_COMPILE_OPTIONS -fsanitize=address -fno-omit-frame-pointer CACHE INTERNAL "Diligent sanitizer compile options") + set(DILIGENT_SANITIZER_LINK_OPTIONS -fsanitize=address CACHE INTERNAL "Diligent sanitizer link options") + else() + message(FATAL_ERROR "DILIGENT_SANITIZER=address is not supported by ${CMAKE_CXX_COMPILER_ID}") + endif() + elseif(DILIGENT_SANITIZER STREQUAL "thread") + message(STATUS "DILIGENT_SANITIZER is 'thread': ThreadSanitizer is enabled") + if(MSVC) + message(FATAL_ERROR "DILIGENT_SANITIZER=thread is not supported by MSVC") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + set(DILIGENT_SANITIZER_COMPILE_OPTIONS -fsanitize=thread -fno-omit-frame-pointer CACHE INTERNAL "Diligent sanitizer compile options") + set(DILIGENT_SANITIZER_LINK_OPTIONS -fsanitize=thread CACHE INTERNAL "Diligent sanitizer link options") + else() + message(FATAL_ERROR "DILIGENT_SANITIZER=thread is not supported by ${CMAKE_CXX_COMPILER_ID}") + endif() + else() + message(FATAL_ERROR "Unsupported DILIGENT_SANITIZER value: '${DILIGENT_SANITIZER}'. Supported values: address, thread.") + endif() + + message(STATUS " DILIGENT_SANITIZER_COMPILE_OPTIONS: ${DILIGENT_SANITIZER_COMPILE_OPTIONS}") + message(STATUS " DILIGENT_SANITIZER_LINK_OPTIONS: ${DILIGENT_SANITIZER_LINK_OPTIONS}") + + target_compile_options(Diligent-BuildSettings INTERFACE ${DILIGENT_SANITIZER_COMPILE_OPTIONS}) + if(DILIGENT_SANITIZER_LINK_OPTIONS) + target_link_libraries(Diligent-BuildSettings INTERFACE ${DILIGENT_SANITIZER_LINK_OPTIONS}) + endif() +endif() + foreach(DBG_CONFIG ${DEBUG_CONFIGURATIONS}) target_compile_definitions(Diligent-BuildSettings INTERFACE "$<$:_DEBUG;DEBUG>") endforeach() @@ -413,12 +459,15 @@ if(MSVC) endif() # Enable whole program optimization - set(DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS /GL) + set(DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS "") + if(NOT DILIGENT_SANITIZER) + list(APPEND DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS /GL) + endif() if("${TARGET_CPU}" STREQUAL "x86_64") # Enable AVX2 - set(DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS ${DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS} /arch:AVX2) + list(APPEND DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS /arch:AVX2) endif() - set(DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS ${DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS} CACHE STRING "Additional MSVC compile options for release configurations") + set(DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS "${DEFAULT_DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS}" CACHE STRING "Additional MSVC compile options for release configurations") if (DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS) message("Additional MSVC compile options for release configurations: " ${DILIGENT_MSVC_RELEASE_COMPILE_OPTIONS}) endif() diff --git a/Common/interface/ObjectsRegistry.hpp b/Common/interface/ObjectsRegistry.hpp index de1ad453c9..5ca3bbe732 100644 --- a/Common/interface/ObjectsRegistry.hpp +++ b/Common/interface/ObjectsRegistry.hpp @@ -1,4 +1,4 @@ -/* Copyright 2023-2025 Diligent Graphics LLC +/* Copyright 2023-2026 Diligent Graphics LLC * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "../../Platforms/Basic/interface/DebugUtilities.hpp" #include "RefCntAutoPtr.hpp" @@ -29,48 +31,53 @@ namespace Diligent { -template -struct _StrongPtrHelper; +namespace Details +{ + +template +struct StrongPtrHelper; -// _StrongPtrHelper specialization for RefCntAutoPtr +// StrongPtrHelper specialization for RefCntAutoPtr template -struct _StrongPtrHelper> +struct StrongPtrHelper> { using WeakPtrType = RefCntWeakPtr; }; -// _StrongPtrHelper specialization for std::shared_ptr +// StrongPtrHelper specialization for std::shared_ptr template -struct _StrongPtrHelper> +struct StrongPtrHelper> { using WeakPtrType = std::weak_ptr; }; template -auto _LockWeakPtr(RefCntWeakPtr& pWeakPtr) +auto LockWeakPtr(RefCntWeakPtr& pWeakPtr) { return pWeakPtr.Lock(); } template -auto _LockWeakPtr(std::weak_ptr& pWeakPtr) +auto LockWeakPtr(std::weak_ptr& pWeakPtr) { return pWeakPtr.lock(); } template -auto _IsWeakPtrExpired(RefCntWeakPtr& pWeakPtr) +auto IsWeakPtrExpired(RefCntWeakPtr& pWeakPtr) { return !pWeakPtr.IsValid(); } template -auto _IsWeakPtrExpired(std::weak_ptr& pWeakPtr) +auto IsWeakPtrExpired(std::weak_ptr& pWeakPtr) { return pWeakPtr.expired(); } +} // namespace Details + /// A thread-safe and exception-safe object registry that works with std::shared_ptr or RefCntAutoPtr. /// The registry keeps weak pointers to the objects and returns strong pointers if the requested object exits. /// An application should keep strong pointers to the objects to keep them alive. @@ -106,6 +113,11 @@ auto _IsWeakPtrExpired(std::weak_ptr& pWeakPtr) /// If the object is found, the initializer function is not called. /// /// It is guaranteed, that the Object will only be initialized once, even if multiple threads call Get() simultaneously. +/// This guarantee does not apply to Get() calls that overlap with Clear(): such calls are safe, but an +/// in-flight Get() may still return an object removed from the cache, and another Get() may initialize +/// a different object for the same key after Clear(). +/// CreateObject may re-enter the registry for independent keys. Recursive creation of the same key, or +/// cyclic creation dependencies between keys, may deadlock because each key is protected by a non-recursive mutex. /// template ::WeakPtrType; + using WeakPtrType = typename Details::StrongPtrHelper::WeakPtrType; explicit ObjectsRegistry(Uint32 NumRequestsToPurge = 1024) noexcept : m_NumRequestsToPurge{NumRequestsToPurge} {} +#ifdef DILIGENT_OBJECTS_REGISTRY_TEST_HOOKS + using BeforeGetObjectCallbackType = void (*)(void* pUserData); + + // Invoked after an ObjectWrapper has been copied from m_Cache and before locking it. + void SetBeforeGetObjectCallback(BeforeGetObjectCallbackType Callback, void* pUserData = nullptr) + { + m_BeforeGetObjectCallback = Callback; + m_pBeforeGetObjectCallbackCtx = pUserData; + } +#endif + /// Finds the object in the registry and returns strong pointer to it (std::shared_ptr or RefCntAutoPtr). /// If the object is not found, it is atomically created using the provided initializer. /// @@ -132,9 +155,8 @@ class ObjectsRegistry /// CreateObject function may throw in case of an error. /// /// It is guaranteed, that the Object will only be initialized once, even if multiple threads call Get() simultaneously. - /// However, if another thread runs an overloaded Get() without the initializer function with the same key, it may - /// remove the entry from the registry, and the object will be initialized multiple times. - /// This is OK as only one object will be added to the registry. + /// CreateObject may re-enter the registry for independent keys, but must not recursively request the same key + /// or form a cyclic dependency with another thread creating a different key. template StrongPtrType Get(const KeyType& Key, CreateObjectType&& CreateObject // May throw @@ -157,30 +179,35 @@ class ObjectsRegistry StrongPtrType pObject; try { +#ifdef DILIGENT_OBJECTS_REGISTRY_TEST_HOOKS + if (m_BeforeGetObjectCallback != nullptr) + m_BeforeGetObjectCallback(m_pBeforeGetObjectCallbackCtx); +#endif pObject = pObjectWrpr->Get(std::forward(CreateObject)); } catch (...) { - std::lock_guard Guard{m_CacheMtx}; + // Do not take ObjectWrapper's mutex while holding m_CacheMtx: CreateObject may re-enter + // the registry while another thread is waiting on this wrapper. + pObject = pObjectWrpr->Lock(); + if (pObject) + return pObject; - auto it = m_Cache.find(Key); - if (it != m_Cache.end()) { - pObject = it->second->Lock(); - if (pObject) - { - // The object was created by another thread while we were waiting for the lock - return pObject; - } - else - { - m_Cache.erase(it); - } + std::lock_guard Guard{m_CacheMtx}; + EraseObjectWrapperIfExpired(Key, pObjectWrpr); } throw; } + if (!pObject) + { + // The initializer may have returned an empty pointer, but another thread may create + // the same object before we clean up the wrapper. + pObject = pObjectWrpr->Lock(); + } + { std::lock_guard Guard{m_CacheMtx}; @@ -196,13 +223,7 @@ class ObjectsRegistry } else { - if (it != m_Cache.end()) - { - pObject = it->second->Lock(); - // Note that the object may have been created by another thread while we were waiting for the lock - if (!pObject) - m_Cache.erase(it); - } + EraseObjectWrapperIfExpired(Key, pObjectWrpr); } if (m_NumRequestsSinceLastPurge.fetch_add(1) + 1 >= m_NumRequestsToPurge) @@ -221,26 +242,34 @@ class ObjectsRegistry /// or empty pointer otherwise. StrongPtrType Get(const KeyType& Key) { - std::lock_guard Guard{m_CacheMtx}; + std::shared_ptr pObjectWrpr; + { + std::lock_guard Guard{m_CacheMtx}; - if (m_NumRequestsSinceLastPurge.fetch_add(1) + 1 >= m_NumRequestsToPurge) - PurgeUnguarded(); + if (m_NumRequestsSinceLastPurge.fetch_add(1) + 1 >= m_NumRequestsToPurge) + PurgeUnguarded(); - auto it = m_Cache.find(Key); - if (it != m_Cache.end()) - { - auto pObject = it->second->Lock(); - if (!pObject) - { - // Note that we may remove the entry from the cache while another thread is creating the object. - // This is OK as it will be added back to the cache. - m_Cache.erase(it); - } + auto it = m_Cache.find(Key); + if (it == m_Cache.end()) + return {}; - return pObject; + pObjectWrpr = it->second; + } + +#ifdef DILIGENT_OBJECTS_REGISTRY_TEST_HOOKS + if (m_BeforeGetObjectCallback != nullptr) + m_BeforeGetObjectCallback(m_pBeforeGetObjectCallbackCtx); +#endif + auto pObject = pObjectWrpr->Lock(); + if (!pObject) + { + std::lock_guard Guard{m_CacheMtx}; + // An empty wrapper may still be used by Get(Key, CreateObject) after it + // copies the wrapper from m_Cache and before it enters ObjectWrapper::Get(). + EraseObjectWrapperIfExpired(Key, pObjectWrpr); } - return {}; + return pObject; } /// Removes all expired pointers from the cache @@ -254,17 +283,26 @@ class ObjectsRegistry template void ProcessElements(HandlerType&& Handler) { - std::lock_guard Guard{m_CacheMtx}; - for (auto& Entry : m_Cache) + std::vector>> Snapshot; + + { + std::lock_guard Guard{m_CacheMtx}; + + Snapshot.reserve(m_Cache.size()); + for (auto& Entry : m_Cache) + Snapshot.emplace_back(Entry.first, Entry.second); + } + + for (auto& Entry : Snapshot) { if (auto pObject = Entry.second->Lock()) - { Handler(Entry.first, *pObject); - } } } /// Removes all objects from the cache. + /// This method is safe to call concurrently with Get(), but it does not synchronize with in-flight + /// object creation. The create-once guarantee does not apply to Get() calls that overlap with Clear(). void Clear() { std::lock_guard Guard{m_CacheMtx}; @@ -277,12 +315,12 @@ class ObjectsRegistry { public: template - const StrongPtrType Get(CreateObjectType&& CreateObject) noexcept(false) + StrongPtrType Get(CreateObjectType&& CreateObject) noexcept(false) { StrongPtrType pObject; std::lock_guard Guard{m_CreateObjectMtx}; - pObject = _LockWeakPtr(m_wpObject); + pObject = Details::LockWeakPtr(m_wpObject); if (!pObject) { pObject = CreateObject(); // May throw @@ -294,12 +332,14 @@ class ObjectsRegistry StrongPtrType Lock() { - return _LockWeakPtr(m_wpObject); + std::lock_guard Guard{m_CreateObjectMtx}; + return Details::LockWeakPtr(m_wpObject); } bool IsExpired() { - return _IsWeakPtrExpired(m_wpObject); + std::lock_guard Guard{m_CreateObjectMtx}; + return Details::IsWeakPtrExpired(m_wpObject); } private: @@ -311,7 +351,9 @@ class ObjectsRegistry { for (auto it = m_Cache.begin(); it != m_Cache.end();) { - if (it->second->IsExpired()) + // Skip empty wrappers that are still referenced by Get(Key, CreateObject): + // removing them would allow another wrapper to be inserted for the same key. + if (!IsObjectWrapperInUse(it->second) && it->second->IsExpired()) { it = m_Cache.erase(it); } @@ -324,6 +366,34 @@ class ObjectsRegistry m_NumRequestsSinceLastPurge.store(0); } + bool EraseObjectWrapperIfExpired(const KeyType& Key, const std::shared_ptr& pObjectWrpr) + { + auto it = m_Cache.find(Key); + if (it != m_Cache.end() && + it->second == pObjectWrpr && + !IsObjectWrapperInUse(it->second, pObjectWrpr.get()) && + it->second->IsExpired()) + { + m_Cache.erase(it); + return true; + } + + return false; + } + + static bool IsObjectWrapperInUse(const std::shared_ptr& pObjectWrpr, + const ObjectWrapper* pCurrentObjectWrpr = nullptr) + { + // m_CacheMtx must be held: ObjectWrapper references are copied from m_Cache under this mutex, + // so use_count() cannot grow while we make the erase decision. + // With no current Get(Key, CreateObject) call, m_Cache should be the only owner. + // When that call checks its own wrapper, it also holds pObjectWrpr locally, so the + // expected use count is 2. Any larger count means another thread may be using or + // initializing the wrapper, and erasing it could break the create-once guarantee. + const auto ExpectedUseCount = pObjectWrpr.get() == pCurrentObjectWrpr ? 2 : 1; + return pObjectWrpr.use_count() > ExpectedUseCount; + } + private: using CacheType = std::unordered_map, KeyHasher, KeyEqual>; @@ -333,6 +403,11 @@ class ObjectsRegistry std::mutex m_CacheMtx; CacheType m_Cache; + +#ifdef DILIGENT_OBJECTS_REGISTRY_TEST_HOOKS + BeforeGetObjectCallbackType m_BeforeGetObjectCallback = nullptr; + void* m_pBeforeGetObjectCallbackCtx = nullptr; +#endif }; } // namespace Diligent diff --git a/Common/interface/ParsingTools.hpp b/Common/interface/ParsingTools.hpp index 214c6ee1a8..585211c3e1 100644 --- a/Common/interface/ParsingTools.hpp +++ b/Common/interface/ParsingTools.hpp @@ -956,7 +956,6 @@ TokenIterType FindMatchingBracket(const TokenIterType& Start, ClosingBracketType = TokenType::OpenSquareBracket; SearchForward = false; break; - case TokenType::ClosingAngleBracket: ClosingBracketType = TokenType::OpenAngleBracket; SearchForward = false; diff --git a/Graphics/Archiver/CMakeLists.txt b/Graphics/Archiver/CMakeLists.txt index 4e20f8de5b..fe14051629 100644 --- a/Graphics/Archiver/CMakeLists.txt +++ b/Graphics/Archiver/CMakeLists.txt @@ -171,6 +171,13 @@ if(WEBGPU_SUPPORTED) target_link_libraries(Diligent-Archiver-static PRIVATE Diligent-GraphicsEngineWebGPU-static) if (NOT PLATFORM_WEB) target_link_libraries(Diligent-Archiver-static PRIVATE dawn_native dawn_proc) + # dawn_version's generated header propagates here as a source; re-mark it GENERATED in this scope (CMP0118 is OLD). + if (TARGET dawn_version) + get_target_property(DAWN_VERSION_HEADERS dawn_version INTERFACE_SOURCES) + if (DAWN_VERSION_HEADERS) + set_source_files_properties(${DAWN_VERSION_HEADERS} PROPERTIES GENERATED TRUE) + endif() + endif() endif() target_include_directories(Diligent-Archiver-static PRIVATE diff --git a/Graphics/Archiver/src/SerializationDeviceImpl.cpp b/Graphics/Archiver/src/SerializationDeviceImpl.cpp index b5c2cd13b8..80dd14faad 100644 --- a/Graphics/Archiver/src/SerializationDeviceImpl.cpp +++ b/Graphics/Archiver/src/SerializationDeviceImpl.cpp @@ -31,6 +31,7 @@ #include "SerializedRenderPassImpl.hpp" #include "SerializedResourceSignatureImpl.hpp" #include "SerializedPipelineStateImpl.hpp" +#include "SPIRVUtils.hpp" #include "EngineMemory.h" namespace Diligent @@ -91,6 +92,11 @@ SerializationDeviceImpl::SerializationDeviceImpl(IReferenceCounters* pRefCounter { m_GLProps.OptimizeShaders = CreateInfo.GL.OptimizeShaders; m_GLProps.ZeroToOneClipZ = CreateInfo.GL.ZeroToOneClipZ; + +#if !DILIGENT_NO_GLSLANG + if (m_GLProps.OptimizeShaders) + WarmUpSPIRVCrossGLSLCompiler(); +#endif } if (m_ValidDeviceFlags & ARCHIVE_DEVICE_DATA_FLAG_VULKAN) diff --git a/Graphics/GraphicsEngine/include/ShaderResourceVariableBase.hpp b/Graphics/GraphicsEngine/include/ShaderResourceVariableBase.hpp index 0469d518f3..61b153a1f0 100644 --- a/Graphics/GraphicsEngine/include/ShaderResourceVariableBase.hpp +++ b/Graphics/GraphicsEngine/include/ShaderResourceVariableBase.hpp @@ -840,7 +840,7 @@ struct ShaderVariableBase : public ResourceVariableBaseInterface const PipelineResourceDesc& ResDesc = pThis->GetDesc(); const SHADER_RESOURCE_VARIABLE_TYPE_FLAGS VarTypeFlag = static_cast(1u << ResDesc.VarType); - if ((Flags & VarTypeFlag) == 0) + if ((static_cast(Flags) & static_cast(VarTypeFlag)) == 0) return; // This variable type is not being processed if ((StaleVarTypes & VarTypeFlag) != 0) diff --git a/Graphics/GraphicsEngineNextGenBase/include/DynamicHeap.hpp b/Graphics/GraphicsEngineNextGenBase/include/DynamicHeap.hpp index 8857d7e667..d6eb858b61 100644 --- a/Graphics/GraphicsEngineNextGenBase/include/DynamicHeap.hpp +++ b/Graphics/GraphicsEngineNextGenBase/include/DynamicHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -154,11 +154,7 @@ class MasterBlockListBasedManager { if (Mgr != nullptr) { - std::lock_guard Lock{Mgr->m_AllocationsMgrMtx}; -#ifdef DILIGENT_DEVELOPMENT - --Mgr->m_MasterBlockCounter; -#endif - Mgr->m_AllocationsMgr.Free(std::move(Block)); + Mgr->FreeMasterBlock(std::move(Block)); } } }; @@ -169,10 +165,9 @@ class MasterBlockListBasedManager } } - // clang-format off - OffsetType GetSize() const { return m_AllocationsMgr.GetMaxSize(); } - OffsetType GetUsedSize() const { return m_AllocationsMgr.GetUsedSize();} - // clang-format on + OffsetType GetSize() const { return m_AllocationsMgr.GetMaxSize(); } + + OffsetType GetUsedSize() const { return m_UsedSize.load(std::memory_order_relaxed); } #ifdef DILIGENT_DEVELOPMENT Int32 GetMasterBlockCounter() const @@ -186,19 +181,34 @@ class MasterBlockListBasedManager { std::lock_guard Lock{m_AllocationsMgrMtx}; MasterBlock NewBlock = m_AllocationsMgr.Allocate(SizeInBytes, Alignment); -#ifdef DILIGENT_DEVELOPMENT if (NewBlock.IsValid()) { +#ifdef DILIGENT_DEVELOPMENT ++m_MasterBlockCounter; - } #endif + m_UsedSize.store(m_AllocationsMgr.GetUsedSize(), std::memory_order_relaxed); + } return NewBlock; } private: + void FreeMasterBlock(MasterBlock&& Block) + { + std::lock_guard Lock{m_AllocationsMgrMtx}; +#ifdef DILIGENT_DEVELOPMENT + --m_MasterBlockCounter; +#endif + m_AllocationsMgr.Free(std::move(Block)); + m_UsedSize.store(m_AllocationsMgr.GetUsedSize(), std::memory_order_relaxed); + } + std::mutex m_AllocationsMgrMtx; VariableSizeAllocationsManager m_AllocationsMgr; + // Mirrors m_AllocationsMgr.GetUsedSize() and is updated under m_AllocationsMgrMtx. + // This lets statistics queries read the current value without locking the allocation manager. + std::atomic m_UsedSize{0}; + #ifdef DILIGENT_DEVELOPMENT std::atomic m_MasterBlockCounter; #endif diff --git a/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.hpp b/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.hpp index 8a45bf7532..3056f8c5b2 100644 --- a/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.hpp +++ b/Graphics/GraphicsEngineVulkan/include/VulkanDynamicHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ #pragma once +#include #include #include "VulkanUtilities/VulkanHeaders.h" #include "VulkanUtilities/MemoryManager.hpp" @@ -158,7 +159,8 @@ class VulkanDynamicMemoryManager : public DynamicHeap::MasterBlockListBasedManag Uint8* m_CPUAddress; const VkDeviceSize m_DefaultAlignment; const Uint64 m_CommandQueueMask; - OffsetType m_TotalPeakSize = 0; + // Updated by dynamic heaps from multiple device contexts when they allocate master blocks. + std::atomic m_TotalPeakSize{0}; }; diff --git a/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp b/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp index c5ae2ec8d6..4cffdc922d 100644 --- a/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp +++ b/Graphics/GraphicsEngineVulkan/src/VulkanDynamicHeap.cpp @@ -32,6 +32,7 @@ #include #include +#include "Atomics.hpp" #include "RenderDeviceVkImpl.hpp" namespace Diligent @@ -128,13 +129,14 @@ void VulkanDynamicMemoryManager::Destroy() VulkanDynamicMemoryManager::~VulkanDynamicMemoryManager() { VERIFY(m_BufferMemory == VK_NULL_HANDLE && m_VkBuffer == VK_NULL_HANDLE, "Vulkan resources must be explicitly released with Destroy()"); - OffsetType Size = GetSize(); + OffsetType Size = GetSize(); + OffsetType TotalPeakSize = m_TotalPeakSize.load(std::memory_order_relaxed); LOG_INFO_MESSAGE("Dynamic memory manager usage stats:\n" " Total size: ", FormatMemorySize(Size, 2), - ". Peak allocated size: ", FormatMemorySize(m_TotalPeakSize, 2, Size), + ". Peak allocated size: ", FormatMemorySize(TotalPeakSize, 2, Size), ". Peak utilization: ", - std::fixed, std::setprecision(1), static_cast(m_TotalPeakSize) / static_cast(std::max(Size, size_t{1})) * 100.0, '%'); + std::fixed, std::setprecision(1), static_cast(TotalPeakSize) / static_cast(std::max(Size, size_t{1})) * 100.0, '%'); } @@ -213,7 +215,7 @@ VulkanDynamicMemoryManager::MasterBlock VulkanDynamicMemoryManager::AllocateMast if (Block.IsValid()) { - m_TotalPeakSize = std::max(m_TotalPeakSize, GetUsedSize()); + AtomicMax(m_TotalPeakSize, GetUsedSize(), std::memory_order_relaxed); } return Block; diff --git a/Graphics/GraphicsEngineWebGPU/CMakeLists.txt b/Graphics/GraphicsEngineWebGPU/CMakeLists.txt index 518e0a80fa..553af40c0e 100644 --- a/Graphics/GraphicsEngineWebGPU/CMakeLists.txt +++ b/Graphics/GraphicsEngineWebGPU/CMakeLists.txt @@ -136,6 +136,14 @@ endif() if (NOT PLATFORM_WEB) list(APPEND PRIVATE_DEPENDENCIES dawn_native dawn_proc) + + # dawn_version's generated header propagates here as a source; re-mark it GENERATED in this scope (CMP0118 is OLD). + if (TARGET dawn_version) + get_target_property(DAWN_VERSION_HEADERS dawn_version INTERFACE_SOURCES) + if (DAWN_VERSION_HEADERS) + set_source_files_properties(${DAWN_VERSION_HEADERS} PROPERTIES GENERATED TRUE) + endif() + endif() endif() target_link_libraries(Diligent-GraphicsEngineWebGPU-static @@ -179,7 +187,12 @@ PRIVATE ) if (PLATFORM_WEB) - target_link_options(Diligent-GraphicsEngineWebGPU-static PUBLIC "SHELL: -s USE_WEBGPU=1 -s USE_PTHREADS=1") + target_link_options(Diligent-GraphicsEngineWebGPU-static PUBLIC "SHELL: --use-port=emdawnwebgpu -s USE_PTHREADS=1") + target_compile_options(Diligent-GraphicsEngineWebGPU-static PUBLIC "--use-port=emdawnwebgpu") + # This is required only for IntelliSense to detect webgpu.h. + target_include_directories(Diligent-GraphicsEngineWebGPU-static PUBLIC + ${EMSCRIPTEN_ROOT_PATH}/cache/ports/emdawnwebgpu/emdawnwebgpu_pkg/webgpu/include + ) endif() target_compile_definitions(Diligent-GraphicsEngineWebGPU-shared PUBLIC DILIGENT_WEBGPU_SHARED=1) diff --git a/Graphics/GraphicsEngineWebGPU/include/DynamicMemoryManagerWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/DynamicMemoryManagerWebGPU.hpp index 826b37b8de..f790580546 100644 --- a/Graphics/GraphicsEngineWebGPU/include/DynamicMemoryManagerWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/DynamicMemoryManagerWebGPU.hpp @@ -30,6 +30,7 @@ /// Declaration of Diligent::DynamicMemoryManagerWebGPU class #include +#include #include #include "WebGPUObjectWrappers.hpp" @@ -111,9 +112,9 @@ class DynamicMemoryManagerWebGPU size_t m_CurrentOffset = 0; WebGPUBufferWrapper m_wgpuBuffer; - std::mutex m_AvailablePagesMtx; - std::vector m_AvailablePages; - std::vector m_MappedData; + std::mutex m_AvailablePagesMtx; + std::vector m_AvailablePages; + std::unique_ptr m_MappedData; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp index 7a18d19cb0..a88355fd3e 100644 --- a/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/PipelineStateWebGPUImpl.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "EngineWebGPUImplTraits.hpp" #include "PipelineStateBase.hpp" @@ -85,11 +86,9 @@ class PipelineStateWebGPUImpl final : public PipelineStateBase SpecConstEntries; + // Per-stage specialization constants as (override @id string, value) + // pairs, materialized into WGPUConstantEntry[] at pipeline creation. + std::vector> SpecConstEntries; ShaderStageInfo(ShaderWebGPUImpl* _pShader) : Type{_pShader->GetDesc().ShaderType}, diff --git a/Graphics/GraphicsEngineWebGPU/include/SwapChainWebGPUImpl.hpp b/Graphics/GraphicsEngineWebGPU/include/SwapChainWebGPUImpl.hpp index 40a2783143..b4371f6491 100644 --- a/Graphics/GraphicsEngineWebGPU/include/SwapChainWebGPUImpl.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/SwapChainWebGPUImpl.hpp @@ -81,22 +81,23 @@ class SwapChainWebGPUImpl final : public SwapChainBase void ConfigureSurface(); - void CreateBuffersAndViews(); + void CreateDepthBufferView(); + + void RecreateSwapChain(); void ReleaseSwapChainResources(); - void RecreateSwapChain(); + void RequestAnimationFrame(Uint32 SyncInterval); -private: - class PresentCommand; + WGPUSurfaceGetCurrentTextureStatus AcquireSurfaceTexture(); +private: NativeWindow m_NativeWindow; WebGPUSurfaceWrapper m_wgpuSurface; RefCntAutoPtr m_pBackBufferRTV; - RefCntAutoPtr m_pBackBufferSRV; RefCntAutoPtr m_pDepthBufferDSV; - std::unique_ptr m_pCmdPresent; - bool m_VSyncEnabled = true; + bool m_VSyncEnabled = false; + Uint32 m_RequestedSyncInterval = 0; }; } // namespace Diligent diff --git a/Graphics/GraphicsEngineWebGPU/include/UploadMemoryManagerWebGPU.hpp b/Graphics/GraphicsEngineWebGPU/include/UploadMemoryManagerWebGPU.hpp index b7ea082d77..29dc09ddf2 100644 --- a/Graphics/GraphicsEngineWebGPU/include/UploadMemoryManagerWebGPU.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/UploadMemoryManagerWebGPU.hpp @@ -30,8 +30,9 @@ /// Declaration of Diligent::UploadMemoryManagerWebGPU class #include -#include +#include #include +#include #include "WebGPUObjectWrappers.hpp" #include "BasicTypes.h" @@ -83,13 +84,14 @@ class UploadMemoryManagerWebGPU size_t GetSize() const { - return m_Data.size(); + return m_DataSize; } private: UploadMemoryManagerWebGPU* m_pMgr = nullptr; WebGPUBufferWrapper m_wgpuBuffer; - std::vector m_Data; + std::unique_ptr m_Data; + size_t m_DataSize = 0; size_t m_CurrOffset = 0; }; diff --git a/Graphics/GraphicsEngineWebGPU/include/WebGPUStubs.hpp b/Graphics/GraphicsEngineWebGPU/include/WebGPUStubs.hpp index d4dec4d798..939b2d6932 100644 --- a/Graphics/GraphicsEngineWebGPU/include/WebGPUStubs.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/WebGPUStubs.hpp @@ -29,16 +29,5 @@ #if PLATFORM_WEB inline constexpr WGPUFeatureName WGPUFeatureName_ChromiumExperimentalTimestampQueryInsidePasses = static_cast(0x000003EE); -inline constexpr WGPUFeatureName WGPUFeatureName_Unorm16TextureFormats = static_cast(0x000003FB); -inline constexpr WGPUFeatureName WGPUFeatureName_Snorm16TextureFormats = static_cast(0x000003FC); - -inline constexpr WGPUTextureFormat WGPUTextureFormat_R16Unorm = static_cast(0x00000060); -inline constexpr WGPUTextureFormat WGPUTextureFormat_R16Snorm = static_cast(0x00000063); -inline constexpr WGPUTextureFormat WGPUTextureFormat_RG16Unorm = static_cast(0x00000061); -inline constexpr WGPUTextureFormat WGPUTextureFormat_RG16Snorm = static_cast(0x00000064); -inline constexpr WGPUTextureFormat WGPUTextureFormat_RGBA16Unorm = static_cast(0x00000062); -inline constexpr WGPUTextureFormat WGPUTextureFormat_RGBA16Snorm = static_cast(0x00000065); - -inline constexpr WGPUSurfaceGetCurrentTextureStatus WGPUSurfaceGetCurrentTextureStatus_Error = static_cast(0x00000007); #endif diff --git a/Graphics/GraphicsEngineWebGPU/include/WebGPUTypeConversions.hpp b/Graphics/GraphicsEngineWebGPU/include/WebGPUTypeConversions.hpp index 0949427259..3d5930ec46 100644 --- a/Graphics/GraphicsEngineWebGPU/include/WebGPUTypeConversions.hpp +++ b/Graphics/GraphicsEngineWebGPU/include/WebGPUTypeConversions.hpp @@ -35,6 +35,7 @@ #include "DepthStencilState.h" #include "RasterizerState.h" #include "InputLayout.h" +#include "TextureView.h" namespace Diligent { @@ -81,6 +82,10 @@ WGPUShaderStage ShaderStagesToWGPUShaderStageFlags(SHADER_TYPE Stages); WGPUVertexStepMode InputElementFrequencyToWGPUVertexStepMode(INPUT_ELEMENT_FREQUENCY StepRate); +WGPUComponentSwizzle TextureComponentSwizzleToWGPUComponentSwizzle(TEXTURE_COMPONENT_SWIZZLE Swizzle); + +WGPUTextureComponentSwizzle TextureComponentMappingToWGPUTextureComponentSwizzle(const TextureComponentMapping& Mapping); + inline WGPUOptionalBool BoolToWGPUOptionalBool(bool Value) { return Value ? WGPUOptionalBool_True : WGPUOptionalBool_False; diff --git a/Graphics/GraphicsEngineWebGPU/include/pch.h b/Graphics/GraphicsEngineWebGPU/include/pch.h index 8ace9d3b41..3bb73d7685 100644 --- a/Graphics/GraphicsEngineWebGPU/include/pch.h +++ b/Graphics/GraphicsEngineWebGPU/include/pch.h @@ -32,50 +32,18 @@ #pragma once #include +#include +#include #include #if PLATFORM_WEB -using WGPUOptionalBool = bool; -using WGPUShaderSourceWGSL = WGPUShaderModuleWGSLDescriptor; -using WGPUStringView = const char*; -using WGPUSurfaceSourceCanvasHTMLSelector_Emscripten = WGPUSurfaceDescriptorFromCanvasHTMLSelector; +using WGPUSurfaceSourceCanvasHTMLSelector_Emscripten = WGPUEmscriptenSurfaceSourceCanvasHTMLSelector; -constexpr bool WGPUOptionalBool_True = true; -constexpr bool WGPUOptionalBool_False = false; +constexpr WGPUSType WGPUSType_SurfaceSourceCanvasHTMLSelector_Emscripten = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; -constexpr WGPUSType WGPUSType_ShaderSourceWGSL = WGPUSType_ShaderModuleWGSLDescriptor; -constexpr WGPUSType WGPUSType_SurfaceSourceCanvasHTMLSelector_Emscripten = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; - -constexpr WGPUBufferBindingType WGPUBufferBindingType_BindingNotUsed = WGPUBufferBindingType_Undefined; -constexpr WGPUSamplerBindingType WGPUSamplerBindingType_BindingNotUsed = WGPUSamplerBindingType_Undefined; -constexpr WGPUTextureSampleType WGPUTextureSampleType_BindingNotUsed = WGPUTextureSampleType_Undefined; -constexpr WGPUStorageTextureAccess WGPUStorageTextureAccess_BindingNotUsed = WGPUStorageTextureAccess_Undefined; - -constexpr WGPUFeatureName WGPUFeatureName_DualSourceBlending = static_cast(0x00050008); - -inline bool WGPUStringViewValid(WGPUStringView Str) -{ - return Str != nullptr && Str[0] != '\0'; -} - -inline const char* WGPUStringViewToString(WGPUStringView Str) -{ - return Str; -} - -inline WGPUStringView GetWGPUStringView(const std::string& Str) -{ - return Str.c_str(); -} - -inline WGPUStringView GetWGPUStringView(const char* Str) -{ - return Str; -} - -#else +#endif inline bool WGPUStringViewValid(const WGPUStringView& Str) { @@ -102,5 +70,3 @@ inline WGPUStringView GetWGPUStringView(const char* Str) { return {Str, Str ? strlen(Str) : 0}; } - -#endif diff --git a/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp index ed0e66e838..3c428e4490 100644 --- a/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp @@ -919,13 +919,13 @@ void DeviceContextWebGPUImpl::UpdateTexture(ITexture* pText BufferWebGPUImpl* const pSrcBufferWebGPU = ClassPtrCast(SubresData.pSrcBuffer); const BufferDesc& SrcBuffDesc = pSrcBufferWebGPU->GetDesc(); - WGPUImageCopyTexture wgpuImageCopyDst{}; - wgpuImageCopyDst.texture = pDstTextureWebGPU->GetWebGPUTexture(); - wgpuImageCopyDst.aspect = WGPUTextureAspect_All; - wgpuImageCopyDst.origin.x = DstBox.MinX; - wgpuImageCopyDst.origin.y = DstBox.MinY; - wgpuImageCopyDst.origin.z = Slice != 0 ? Slice : DstBox.MinZ; - wgpuImageCopyDst.mipLevel = MipLevel; + WGPUTexelCopyTextureInfo wgpuTexelCopyDst{}; + wgpuTexelCopyDst.texture = pDstTextureWebGPU->GetWebGPUTexture(); + wgpuTexelCopyDst.aspect = WGPUTextureAspect_All; + wgpuTexelCopyDst.origin.x = DstBox.MinX; + wgpuTexelCopyDst.origin.y = DstBox.MinY; + wgpuTexelCopyDst.origin.z = Slice != 0 ? Slice : DstBox.MinZ; + wgpuTexelCopyDst.mipLevel = MipLevel; const TextureFormatAttribs& FmtAttribs = GetTextureFormatAttribs(pDstTextureWebGPU->GetDesc().Format); @@ -951,13 +951,13 @@ void DeviceContextWebGPUImpl::UpdateTexture(ITexture* pText } } - WGPUImageCopyBuffer wgpuImageCopySrc{}; - wgpuImageCopySrc.buffer = pSrcStagingBuffer != nullptr ? pSrcStagingBuffer->wgpuBuffer : pSrcBufferWebGPU->GetWebGPUBuffer(); - wgpuImageCopySrc.layout.offset = SubresData.SrcOffset; - wgpuImageCopySrc.layout.bytesPerRow = static_cast(SubresData.Stride); - wgpuImageCopySrc.layout.rowsPerImage = wgpuCopySize.height; + WGPUTexelCopyBufferInfo wgpuTexelCopySrc{}; + wgpuTexelCopySrc.buffer = pSrcStagingBuffer != nullptr ? pSrcStagingBuffer->wgpuBuffer : pSrcBufferWebGPU->GetWebGPUBuffer(); + wgpuTexelCopySrc.layout.offset = SubresData.SrcOffset; + wgpuTexelCopySrc.layout.bytesPerRow = static_cast(SubresData.Stride); + wgpuTexelCopySrc.layout.rowsPerImage = wgpuCopySize.height; - wgpuCommandEncoderCopyBufferToTexture(GetCommandEncoder(), &wgpuImageCopySrc, &wgpuImageCopyDst, &wgpuCopySize); + wgpuCommandEncoderCopyBufferToTexture(GetCommandEncoder(), &wgpuTexelCopySrc, &wgpuTexelCopyDst, &wgpuCopySize); if (pSrcStagingBuffer != nullptr) { @@ -988,19 +988,19 @@ void DeviceContextWebGPUImpl::UpdateTexture(ITexture* pText } } - WGPUImageCopyBuffer wgpuImageCopySrc{}; - wgpuImageCopySrc.buffer = UploadAlloc.wgpuBuffer; - wgpuImageCopySrc.layout.offset = UploadAlloc.Offset; - wgpuImageCopySrc.layout.bytesPerRow = static_cast(CopyInfo.RowStride); - wgpuImageCopySrc.layout.rowsPerImage = static_cast(CopyInfo.DepthStride / CopyInfo.RowStride); + WGPUTexelCopyBufferInfo wgpuTexelCopySrc{}; + wgpuTexelCopySrc.buffer = UploadAlloc.wgpuBuffer; + wgpuTexelCopySrc.layout.offset = UploadAlloc.Offset; + wgpuTexelCopySrc.layout.bytesPerRow = static_cast(CopyInfo.RowStride); + wgpuTexelCopySrc.layout.rowsPerImage = static_cast(CopyInfo.DepthStride / CopyInfo.RowStride); - WGPUImageCopyTexture wgpuImageCopyDst{}; - wgpuImageCopyDst.texture = pTextureWebGPU->GetWebGPUTexture(); - wgpuImageCopyDst.aspect = WGPUTextureAspect_All; - wgpuImageCopyDst.origin.x = DstBox.MinX; - wgpuImageCopyDst.origin.y = DstBox.MinY; - wgpuImageCopyDst.origin.z = Slice != 0 ? Slice : DstBox.MinZ; - wgpuImageCopyDst.mipLevel = MipLevel; + WGPUTexelCopyTextureInfo wgpuTexelCopyDst{}; + wgpuTexelCopyDst.texture = pTextureWebGPU->GetWebGPUTexture(); + wgpuTexelCopyDst.aspect = WGPUTextureAspect_All; + wgpuTexelCopyDst.origin.x = DstBox.MinX; + wgpuTexelCopyDst.origin.y = DstBox.MinY; + wgpuTexelCopyDst.origin.z = Slice != 0 ? Slice : DstBox.MinZ; + wgpuTexelCopyDst.mipLevel = MipLevel; const TextureFormatAttribs& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format); @@ -1015,7 +1015,7 @@ void DeviceContextWebGPUImpl::UpdateTexture(ITexture* pText wgpuCopySize.height = AlignUp(wgpuCopySize.height, FmtAttribs.BlockHeight); } - wgpuCommandEncoderCopyBufferToTexture(GetCommandEncoder(), &wgpuImageCopySrc, &wgpuImageCopyDst, &wgpuCopySize); + wgpuCommandEncoderCopyBufferToTexture(GetCommandEncoder(), &wgpuTexelCopySrc, &wgpuTexelCopyDst, &wgpuCopySize); } } @@ -1056,21 +1056,21 @@ void DeviceContextWebGPUImpl::CopyTexture(const CopyTextureAttribs& CopyAttribs) else wgpuAspectMask = WGPUTextureAspect_All; - WGPUImageCopyTexture wgpuImageCopySrc{}; - wgpuImageCopySrc.texture = pSrcTexWebGPU->GetWebGPUTexture(); - wgpuImageCopySrc.aspect = wgpuAspectMask; - wgpuImageCopySrc.origin.x = pSrcBox->MinX; - wgpuImageCopySrc.origin.y = pSrcBox->MinY; - wgpuImageCopySrc.origin.z = CopyAttribs.SrcSlice != 0 ? CopyAttribs.SrcSlice : pSrcBox->MinZ; - wgpuImageCopySrc.mipLevel = CopyAttribs.SrcMipLevel; - - WGPUImageCopyTexture wgpuImageCopyDst{}; - wgpuImageCopyDst.texture = pDstTexWebGPU->GetWebGPUTexture(); - wgpuImageCopyDst.aspect = wgpuAspectMask; - wgpuImageCopyDst.origin.x = CopyAttribs.DstX; - wgpuImageCopyDst.origin.y = CopyAttribs.DstY; - wgpuImageCopyDst.origin.z = CopyAttribs.DstSlice != 0 ? CopyAttribs.DstSlice : CopyAttribs.DstZ; - wgpuImageCopyDst.mipLevel = CopyAttribs.DstMipLevel; + WGPUTexelCopyTextureInfo wgpuTexelCopySrc{}; + wgpuTexelCopySrc.texture = pSrcTexWebGPU->GetWebGPUTexture(); + wgpuTexelCopySrc.aspect = wgpuAspectMask; + wgpuTexelCopySrc.origin.x = pSrcBox->MinX; + wgpuTexelCopySrc.origin.y = pSrcBox->MinY; + wgpuTexelCopySrc.origin.z = CopyAttribs.SrcSlice != 0 ? CopyAttribs.SrcSlice : pSrcBox->MinZ; + wgpuTexelCopySrc.mipLevel = CopyAttribs.SrcMipLevel; + + WGPUTexelCopyTextureInfo wgpuTexelCopyDst{}; + wgpuTexelCopyDst.texture = pDstTexWebGPU->GetWebGPUTexture(); + wgpuTexelCopyDst.aspect = wgpuAspectMask; + wgpuTexelCopyDst.origin.x = CopyAttribs.DstX; + wgpuTexelCopyDst.origin.y = CopyAttribs.DstY; + wgpuTexelCopyDst.origin.z = CopyAttribs.DstSlice != 0 ? CopyAttribs.DstSlice : CopyAttribs.DstZ; + wgpuTexelCopyDst.mipLevel = CopyAttribs.DstMipLevel; WGPUExtent3D wgpuCopySize{}; wgpuCopySize.width = std::max(pSrcBox->Width(), 1u); @@ -1083,7 +1083,7 @@ void DeviceContextWebGPUImpl::CopyTexture(const CopyTextureAttribs& CopyAttribs) wgpuCopySize.height = AlignUp(wgpuCopySize.height, DstFmtAttribs.BlockHeight); } - wgpuCommandEncoderCopyTextureToTexture(wgpuCmdEncoder, &wgpuImageCopySrc, &wgpuImageCopyDst, &wgpuCopySize); + wgpuCommandEncoderCopyTextureToTexture(wgpuCmdEncoder, &wgpuTexelCopySrc, &wgpuTexelCopyDst, &wgpuCopySize); } else if (SrcTexDesc.Usage == USAGE_STAGING && DstTexDesc.Usage != USAGE_STAGING) { @@ -1106,19 +1106,19 @@ void DeviceContextWebGPUImpl::CopyTexture(const CopyTextureAttribs& CopyAttribs) return; } - WGPUImageCopyBuffer wgpuImageCopySrc{}; - wgpuImageCopySrc.buffer = pSrcStagingBuffer->wgpuBuffer; - wgpuImageCopySrc.layout.offset = SrcBufferOffset; - wgpuImageCopySrc.layout.bytesPerRow = static_cast(AlignUp(SrcMipLevelAttribs.RowSize, TextureWebGPUImpl::ImageCopyBufferRowAlignment)); - wgpuImageCopySrc.layout.rowsPerImage = SrcMipLevelAttribs.StorageHeight / DstFmtAttribs.BlockHeight; + WGPUTexelCopyBufferInfo wgpuTexelCopySrc{}; + wgpuTexelCopySrc.buffer = pSrcStagingBuffer->wgpuBuffer; + wgpuTexelCopySrc.layout.offset = SrcBufferOffset; + wgpuTexelCopySrc.layout.bytesPerRow = static_cast(AlignUp(SrcMipLevelAttribs.RowSize, TextureWebGPUImpl::ImageCopyBufferRowAlignment)); + wgpuTexelCopySrc.layout.rowsPerImage = SrcMipLevelAttribs.StorageHeight / DstFmtAttribs.BlockHeight; - WGPUImageCopyTexture wgpuImageCopyDst{}; - wgpuImageCopyDst.texture = pDstTexWebGPU->GetWebGPUTexture(); - wgpuImageCopyDst.aspect = wgpuAspectMask; - wgpuImageCopyDst.origin.x = CopyAttribs.DstX; - wgpuImageCopyDst.origin.y = CopyAttribs.DstY; - wgpuImageCopyDst.origin.z = CopyAttribs.DstSlice != 0 ? CopyAttribs.DstSlice : CopyAttribs.DstZ; - wgpuImageCopyDst.mipLevel = CopyAttribs.DstMipLevel; + WGPUTexelCopyTextureInfo wgpuTexelCopyDst{}; + wgpuTexelCopyDst.texture = pDstTexWebGPU->GetWebGPUTexture(); + wgpuTexelCopyDst.aspect = wgpuAspectMask; + wgpuTexelCopyDst.origin.x = CopyAttribs.DstX; + wgpuTexelCopyDst.origin.y = CopyAttribs.DstY; + wgpuTexelCopyDst.origin.z = CopyAttribs.DstSlice != 0 ? CopyAttribs.DstSlice : CopyAttribs.DstZ; + wgpuTexelCopyDst.mipLevel = CopyAttribs.DstMipLevel; WGPUExtent3D wgpuCopySize{}; wgpuCopySize.width = std::max(pSrcBox->Width(), 1u); @@ -1131,7 +1131,7 @@ void DeviceContextWebGPUImpl::CopyTexture(const CopyTextureAttribs& CopyAttribs) wgpuCopySize.height = AlignUp(wgpuCopySize.height, DstFmtAttribs.BlockHeight); } - wgpuCommandEncoderCopyBufferToTexture(wgpuCmdEncoder, &wgpuImageCopySrc, &wgpuImageCopyDst, &wgpuCopySize); + wgpuCommandEncoderCopyBufferToTexture(wgpuCmdEncoder, &wgpuTexelCopySrc, &wgpuTexelCopyDst, &wgpuCopySize); m_PendingStagingWrites.emplace(pSrcStagingBuffer, RefCntAutoPtr{pSrcTexWebGPU}); } @@ -1156,19 +1156,19 @@ void DeviceContextWebGPUImpl::CopyTexture(const CopyTextureAttribs& CopyAttribs) return; } - WGPUImageCopyTexture wgpuImageCopySrc{}; - wgpuImageCopySrc.texture = pSrcTexWebGPU->GetWebGPUTexture(); - wgpuImageCopySrc.aspect = wgpuAspectMask; - wgpuImageCopySrc.origin.x = pSrcBox->MinX; - wgpuImageCopySrc.origin.y = pSrcBox->MinY; - wgpuImageCopySrc.origin.z = CopyAttribs.SrcSlice != 0 ? CopyAttribs.SrcSlice : pSrcBox->MinZ; - wgpuImageCopySrc.mipLevel = CopyAttribs.SrcMipLevel; + WGPUTexelCopyTextureInfo wgpuTexelCopySrc{}; + wgpuTexelCopySrc.texture = pSrcTexWebGPU->GetWebGPUTexture(); + wgpuTexelCopySrc.aspect = wgpuAspectMask; + wgpuTexelCopySrc.origin.x = pSrcBox->MinX; + wgpuTexelCopySrc.origin.y = pSrcBox->MinY; + wgpuTexelCopySrc.origin.z = CopyAttribs.SrcSlice != 0 ? CopyAttribs.SrcSlice : pSrcBox->MinZ; + wgpuTexelCopySrc.mipLevel = CopyAttribs.SrcMipLevel; - WGPUImageCopyBuffer wgpuImageCopyDst{}; - wgpuImageCopyDst.buffer = pDstStagingBuffer->wgpuBuffer; - wgpuImageCopyDst.layout.offset = DstBufferOffset; - wgpuImageCopyDst.layout.bytesPerRow = static_cast(AlignUp(DstMipLevelAttribs.RowSize, TextureWebGPUImpl::ImageCopyBufferRowAlignment)); - wgpuImageCopyDst.layout.rowsPerImage = DstMipLevelAttribs.StorageHeight / SrcFmtAttribs.BlockHeight; + WGPUTexelCopyBufferInfo wgpuTexelCopyDst{}; + wgpuTexelCopyDst.buffer = pDstStagingBuffer->wgpuBuffer; + wgpuTexelCopyDst.layout.offset = DstBufferOffset; + wgpuTexelCopyDst.layout.bytesPerRow = static_cast(AlignUp(DstMipLevelAttribs.RowSize, TextureWebGPUImpl::ImageCopyBufferRowAlignment)); + wgpuTexelCopyDst.layout.rowsPerImage = DstMipLevelAttribs.StorageHeight / SrcFmtAttribs.BlockHeight; WGPUExtent3D wgpuCopySize{}; wgpuCopySize.width = std::max(pSrcBox->Width(), 1u); @@ -1181,7 +1181,7 @@ void DeviceContextWebGPUImpl::CopyTexture(const CopyTextureAttribs& CopyAttribs) wgpuCopySize.height = AlignUp(wgpuCopySize.height, SrcFmtAttribs.BlockHeight); } - wgpuCommandEncoderCopyTextureToBuffer(wgpuCmdEncoder, &wgpuImageCopySrc, &wgpuImageCopyDst, &wgpuCopySize); + wgpuCommandEncoderCopyTextureToBuffer(wgpuCmdEncoder, &wgpuTexelCopySrc, &wgpuTexelCopyDst, &wgpuCopySize); m_PendingStagingReads.emplace(pDstStagingBuffer, RefCntAutoPtr{pDstTexWebGPU}); } @@ -1300,19 +1300,19 @@ void DeviceContextWebGPUImpl::UnmapTextureSubresource(ITexture* pTexture, Uint32 const UploadMemoryManagerWebGPU::Allocation& Allocation = UploadSpaceIt->second.Allocation; const BufferToTextureCopyInfo& CopyInfo = UploadSpaceIt->second.CopyInfo; - WGPUImageCopyBuffer wgpuImageCopySrc{}; - wgpuImageCopySrc.buffer = Allocation.wgpuBuffer; - wgpuImageCopySrc.layout.offset = Allocation.Offset; - wgpuImageCopySrc.layout.bytesPerRow = static_cast(CopyInfo.RowStride); - wgpuImageCopySrc.layout.rowsPerImage = static_cast(CopyInfo.DepthStride / CopyInfo.RowStride); + WGPUTexelCopyBufferInfo wgpuTexelCopySrc{}; + wgpuTexelCopySrc.buffer = Allocation.wgpuBuffer; + wgpuTexelCopySrc.layout.offset = Allocation.Offset; + wgpuTexelCopySrc.layout.bytesPerRow = static_cast(CopyInfo.RowStride); + wgpuTexelCopySrc.layout.rowsPerImage = static_cast(CopyInfo.DepthStride / CopyInfo.RowStride); - WGPUImageCopyTexture wgpuImageCopyDst{}; - wgpuImageCopyDst.texture = pTextureWebGPU->GetWebGPUTexture(); - wgpuImageCopyDst.aspect = WGPUTextureAspect_All; - wgpuImageCopyDst.origin.x = CopyInfo.Region.MinX; - wgpuImageCopyDst.origin.y = CopyInfo.Region.MinY; - wgpuImageCopyDst.origin.z = CopyInfo.Region.MinZ; - wgpuImageCopyDst.mipLevel = UploadSpaceIt->first.MipLevel; + WGPUTexelCopyTextureInfo wgpuTexelCopyDst{}; + wgpuTexelCopyDst.texture = pTextureWebGPU->GetWebGPUTexture(); + wgpuTexelCopyDst.aspect = WGPUTextureAspect_All; + wgpuTexelCopyDst.origin.x = CopyInfo.Region.MinX; + wgpuTexelCopyDst.origin.y = CopyInfo.Region.MinY; + wgpuTexelCopyDst.origin.z = CopyInfo.Region.MinZ; + wgpuTexelCopyDst.mipLevel = UploadSpaceIt->first.MipLevel; WGPUExtent3D wgpuCopySize{}; wgpuCopySize.width = CopyInfo.Region.Width(); @@ -1325,7 +1325,7 @@ void DeviceContextWebGPUImpl::UnmapTextureSubresource(ITexture* pTexture, Uint32 wgpuCopySize.height = AlignUp(wgpuCopySize.height, FmtAttribs.BlockHeight); } - wgpuCommandEncoderCopyBufferToTexture(GetCommandEncoder(), &wgpuImageCopySrc, &wgpuImageCopyDst, &wgpuCopySize); + wgpuCommandEncoderCopyBufferToTexture(GetCommandEncoder(), &wgpuTexelCopySrc, &wgpuTexelCopyDst, &wgpuCopySize); m_MappedTextures.erase(UploadSpaceIt); } else @@ -1499,9 +1499,9 @@ void DeviceContextWebGPUImpl::Flush() if (m_wgpuCommandEncoder || !m_SignaledFences.empty()) { - auto WorkDoneCallback = [](WGPUQueueWorkDoneStatus Status, void* pUserData) { - VERIFY_EXPR(pUserData != nullptr); - SyncPointWebGPUImpl* pSyncPoint = static_cast(pUserData); + auto WorkDoneCallback = [](WGPUQueueWorkDoneStatus wgpuStatus, WGPUStringView Message, void* pUserData1, void* pUserData2) { + VERIFY_EXPR(pUserData1 != nullptr); + SyncPointWebGPUImpl* pSyncPoint = static_cast(pUserData1); pSyncPoint->Trigger(); pSyncPoint->Release(); }; @@ -1524,9 +1524,13 @@ void DeviceContextWebGPUImpl::Flush() WGPUCommandBufferDescriptor wgpuCmdBufferDesc{}; WebGPUCommandBufferWrapper wgpuCmdBuffer{wgpuCommandEncoderFinish(GetCommandEncoder(), &wgpuCmdBufferDesc)}; DEV_CHECK_ERR(wgpuCmdBuffer != nullptr, "Failed to finish command encoder"); - wgpuQueueSubmit(m_wgpuQueue, 1, &wgpuCmdBuffer.Get()); - wgpuQueueOnSubmittedWorkDone(m_wgpuQueue, WorkDoneCallback, pWorkDoneSyncPoint.Detach()); + + WGPUQueueWorkDoneCallbackInfo wgpuWorkDoneCallbackInfo{}; + wgpuWorkDoneCallbackInfo.callback = WorkDoneCallback; + wgpuWorkDoneCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + wgpuWorkDoneCallbackInfo.userdata1 = pWorkDoneSyncPoint.Detach(); + wgpuQueueOnSubmittedWorkDone(m_wgpuQueue, wgpuWorkDoneCallbackInfo); m_wgpuCommandEncoder.Reset(nullptr); for (auto& PendingReadIt : m_PendingStagingReads) diff --git a/Graphics/GraphicsEngineWebGPU/src/DynamicMemoryManagerWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/DynamicMemoryManagerWebGPU.cpp index e970efcd5f..e3081fbddb 100644 --- a/Graphics/GraphicsEngineWebGPU/src/DynamicMemoryManagerWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/DynamicMemoryManagerWebGPU.cpp @@ -133,7 +133,7 @@ DynamicMemoryManagerWebGPU::DynamicMemoryManagerWebGPU(WGPUDevice wgpuDevice, si WGPUBufferUsage_Index | WGPUBufferUsage_Indirect; m_wgpuBuffer.Reset(wgpuDeviceCreateBuffer(wgpuDevice, &wgpuBufferDesc)); - m_MappedData.resize(BufferSize); + m_MappedData.reset(new Uint8[BufferSize]); LOG_INFO_MESSAGE("Created dynamic buffer: ", BufferSize >> 10, " KB"); } diff --git a/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp index abd299ed49..fd8d0ab735 100644 --- a/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/EngineFactoryWebGPU.cpp @@ -147,19 +147,19 @@ std::vector FindCompatibleAdapters(WGPUInstance wgpuInstan struct CallbackUserData { - WGPUAdapter Adapter = nullptr; - WGPURequestAdapterStatus RequestStatus = {}; - String Message = {}; - bool IsReady = {}; + WGPUAdapter wgpuAdapter = nullptr; + WGPURequestAdapterStatus wgpuRequestStatus = {}; + String Message = {}; + bool IsReady = {}; }; - auto OnAdapterRequestEnded = [](WGPURequestAdapterStatus Status, WGPUAdapter Adapter, WGPUStringView Message, void* pCallbackUserData) { - if (pCallbackUserData != nullptr) + auto OnAdapterRequestEnded = [](WGPURequestAdapterStatus wgpuStatus, WGPUAdapter wgpuAdapter, WGPUStringView Message, void* pUserData1, void* pUserData2) { + if (pUserData1 != nullptr) { - CallbackUserData* pUserData = static_cast(pCallbackUserData); - pUserData->Adapter = Adapter; - pUserData->RequestStatus = Status; - pUserData->IsReady = true; + CallbackUserData* pUserData = static_cast(pUserData1); + pUserData->wgpuAdapter = wgpuAdapter; + pUserData->wgpuRequestStatus = wgpuStatus; + pUserData->IsReady = true; if (WGPUStringViewValid(Message)) pUserData->Message = WGPUStringViewToString(Message); } @@ -169,27 +169,31 @@ std::vector FindCompatibleAdapters(WGPUInstance wgpuInstan WGPUPowerPreference_HighPerformance, WGPUPowerPreference_LowPower}; - for (const WGPUPowerPreference& powerPreference : PowerPreferences) + for (const WGPUPowerPreference& PowerPreference : PowerPreferences) { CallbackUserData UserData{}; - WGPURequestAdapterOptions Options{}; - Options.powerPreference = powerPreference; - Options.backendType = WGPUBackendType_Undefined; - Options.forceFallbackAdapter = false; - Options.compatibilityMode = false; - wgpuInstanceRequestAdapter(wgpuInstance, &Options, OnAdapterRequestEnded, &UserData); + WGPURequestAdapterOptions wgpuAdapterRequestOptions{}; + wgpuAdapterRequestOptions.powerPreference = PowerPreference; + wgpuAdapterRequestOptions.backendType = WGPUBackendType_Undefined; + wgpuAdapterRequestOptions.forceFallbackAdapter = false; + + WGPURequestAdapterCallbackInfo wgpuAdapterRequestCallbackInfo{}; + wgpuAdapterRequestCallbackInfo.callback = OnAdapterRequestEnded; + wgpuAdapterRequestCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + wgpuAdapterRequestCallbackInfo.userdata1 = &UserData; + wgpuInstanceRequestAdapter(wgpuInstance, &wgpuAdapterRequestOptions, wgpuAdapterRequestCallbackInfo); while (!UserData.IsReady) InstancePoolEvents(wgpuInstance); - if (UserData.RequestStatus == WGPURequestAdapterStatus_Success) + if (UserData.wgpuRequestStatus == WGPURequestAdapterStatus_Success) { - auto adapter_it = std::find_if(wgpuAdapters.begin(), wgpuAdapters.end(), - [&](const auto& wgpuAdapter) { return wgpuAdapter.Get() == UserData.Adapter; }); + auto AdapterIter = std::find_if(wgpuAdapters.begin(), wgpuAdapters.end(), + [&](const auto& wgpuAdapter) { return wgpuAdapter.Get() == UserData.wgpuAdapter; }); - if (adapter_it == wgpuAdapters.end()) - wgpuAdapters.emplace_back(UserData.Adapter); + if (AdapterIter == wgpuAdapters.end()) + wgpuAdapters.emplace_back(UserData.wgpuAdapter); } else { @@ -200,47 +204,25 @@ std::vector FindCompatibleAdapters(WGPUInstance wgpuInstan return wgpuAdapters; } -static void DeviceLostCallback(WGPUDeviceLostReason Reason, +static void DeviceLostCallback(WGPUDevice const* wgpuDevice, + WGPUDeviceLostReason wgpuReason, WGPUStringView Message, - void* userdata) -{ - bool Expression = Reason != WGPUDeviceLostReason_Destroyed; -#if !PLATFORM_WEB - Expression &= (Reason != WGPUDeviceLostReason_InstanceDropped); -#endif - if (Expression && WGPUStringViewValid(Message)) - { - LOG_DEBUG_MESSAGE(DEBUG_MESSAGE_SEVERITY_ERROR, "WebGPU: ", WGPUStringViewToString(Message)); - } -} - -#if !PLATFORM_WEB -static void DeviceLostCallback2(WGPUDevice const* device, - WGPUDeviceLostReason Reason, - WGPUStringView Message, - void* userdata1, - void* userdata2) + void* userdata, + void* userdata2) { - DeviceLostCallback(Reason, Message, userdata1); -} + bool Expression = wgpuReason != WGPUDeviceLostReason_Destroyed; + Expression &= (wgpuReason != WGPUDeviceLostReason_CallbackCancelled); -static void UncapturedErrorCallback2(WGPUDevice const* device, - WGPUErrorType MessageType, - WGPUStringView Message, - void* userdata1, - void* userdata2) -{ - if (WGPUStringViewValid(Message)) + if (Expression && WGPUStringViewValid(Message)) { LOG_DEBUG_MESSAGE(DEBUG_MESSAGE_SEVERITY_ERROR, "WebGPU: ", WGPUStringViewToString(Message)); } } -#endif WebGPUDeviceWrapper CreateDeviceForAdapter(const DeviceFeatures& Features, WGPUInstance wgpuInstance, WGPUAdapter wgpuAdapter) { - WGPUSupportedLimits SupportedLimits{}; - wgpuAdapterGetLimits(wgpuAdapter, &SupportedLimits); + WGPULimits wgpuSupportedLimits{}; + wgpuAdapterGetLimits(wgpuAdapter, &wgpuSupportedLimits); std::vector wgpuFeatures{}; { @@ -255,6 +237,7 @@ WebGPUDeviceWrapper CreateDeviceForAdapter(const DeviceFeatures& Features, WGPUI AddWGPUFeature(Features.TextureCompressionBC, WGPUFeatureName_TextureCompressionBC); AddWGPUFeature(Features.TextureCompressionETC2, WGPUFeatureName_TextureCompressionETC2); AddWGPUFeature(Features.ShaderFloat16, WGPUFeatureName_ShaderF16); + AddWGPUFeature(Features.TextureComponentSwizzle, WGPUFeatureName_TextureComponentSwizzle); if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_Depth32FloatStencil8)) wgpuFeatures.push_back(WGPUFeatureName_Depth32FloatStencil8); @@ -262,6 +245,9 @@ WebGPUDeviceWrapper CreateDeviceForAdapter(const DeviceFeatures& Features, WGPUI if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_Float32Filterable)) wgpuFeatures.push_back(WGPUFeatureName_Float32Filterable); + if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_Float32Blendable)) + wgpuFeatures.push_back(WGPUFeatureName_Float32Blendable); + if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_IndirectFirstInstance)) wgpuFeatures.push_back(WGPUFeatureName_IndirectFirstInstance); @@ -271,33 +257,45 @@ WebGPUDeviceWrapper CreateDeviceForAdapter(const DeviceFeatures& Features, WGPUI if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_BGRA8UnormStorage)) wgpuFeatures.push_back(WGPUFeatureName_BGRA8UnormStorage); - if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_Unorm16TextureFormats)) - wgpuFeatures.push_back(WGPUFeatureName_Unorm16TextureFormats); + if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_TextureFormatsTier1)) + wgpuFeatures.push_back(WGPUFeatureName_TextureFormatsTier1); - if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_Snorm16TextureFormats)) - wgpuFeatures.push_back(WGPUFeatureName_Snorm16TextureFormats); + if (wgpuAdapterHasFeature(wgpuAdapter, WGPUFeatureName_TextureFormatsTier2)) + wgpuFeatures.push_back(WGPUFeatureName_TextureFormatsTier2); } struct CallbackUserData { - WGPUDevice Device = nullptr; - WGPURequestDeviceStatus RequestStatus = {}; - String Message = {}; - bool IsReady = {}; + WGPUDevice wgpuDevice = nullptr; + WGPURequestDeviceStatus wgpuRequestStatus = {}; + String Message = {}; + bool IsReady = {}; } UserData; - auto OnDeviceRequestEnded = [](WGPURequestDeviceStatus Status, WGPUDevice Device, WGPUStringView Message, void* pCallbackUserData) { - if (pCallbackUserData != nullptr) + auto OnDeviceRequestEnded = [](WGPURequestDeviceStatus wgpuStatus, WGPUDevice wgpuDevice, WGPUStringView Message, void* pUserData1, void* pUserData2) { + if (pUserData1 != nullptr) { - CallbackUserData* pUserData = static_cast(pCallbackUserData); - pUserData->Device = Device; - pUserData->RequestStatus = Status; - pUserData->IsReady = true; + CallbackUserData* pUserData = static_cast(pUserData1); + pUserData->wgpuDevice = wgpuDevice; + pUserData->wgpuRequestStatus = wgpuStatus; + pUserData->IsReady = true; if (WGPUStringViewValid(Message)) pUserData->Message = WGPUStringViewToString(Message); } }; + auto UncapturedErrorCallback = [](WGPUDevice const* wgpuDevice, WGPUErrorType wgpuTypeError, WGPUStringView Message, void* pUserdata1, void* pUserdata2) { + LOG_ERROR_MESSAGE("Uncaptured WebGPU error (type ", wgpuTypeError, "): ", WGPUStringViewToString(Message)); + }; + + WGPUDeviceDescriptor wgpuDeviceDesc{}; + wgpuDeviceDesc.requiredLimits = &wgpuSupportedLimits; + wgpuDeviceDesc.requiredFeatureCount = wgpuFeatures.size(); + wgpuDeviceDesc.requiredFeatures = wgpuFeatures.data(); + + wgpuDeviceDesc.uncapturedErrorCallbackInfo.callback = UncapturedErrorCallback; + wgpuDeviceDesc.deviceLostCallbackInfo.callback = DeviceLostCallback; + wgpuDeviceDesc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; #if !PLATFORM_WEB const char* ToggleNames[] = { "disable_timestamp_query_conversion", @@ -308,30 +306,24 @@ WebGPUDeviceWrapper CreateDeviceForAdapter(const DeviceFeatures& Features, WGPUI wgpuDawnTogglesDesc.chain.sType = WGPUSType_DawnTogglesDescriptor; wgpuDawnTogglesDesc.enabledToggleCount = _countof(ToggleNames); wgpuDawnTogglesDesc.enabledToggles = ToggleNames; -#endif - - WGPURequiredLimits RequiredLimits{nullptr, SupportedLimits.limits}; - WGPUDeviceDescriptor DeviceDesc{}; - DeviceDesc.requiredLimits = &RequiredLimits; - DeviceDesc.requiredFeatureCount = wgpuFeatures.size(); - DeviceDesc.requiredFeatures = wgpuFeatures.data(); -#if PLATFORM_WEB - DeviceDesc.deviceLostCallback = DeviceLostCallback; -#else - DeviceDesc.deviceLostCallbackInfo2 = {nullptr, WGPUCallbackMode_AllowSpontaneous, DeviceLostCallback2}; - DeviceDesc.uncapturedErrorCallbackInfo2 = {nullptr, UncapturedErrorCallback2}; - DeviceDesc.nextInChain = reinterpret_cast(&wgpuDawnTogglesDesc); + wgpuDeviceDesc.nextInChain = reinterpret_cast(&wgpuDawnTogglesDesc); #endif - wgpuAdapterRequestDevice(wgpuAdapter, &DeviceDesc, OnDeviceRequestEnded, &UserData); + + WGPURequestDeviceCallbackInfo wgpuDeviceRequestCallbackInfo{}; + wgpuDeviceRequestCallbackInfo.nextInChain = nullptr; + wgpuDeviceRequestCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + wgpuDeviceRequestCallbackInfo.callback = OnDeviceRequestEnded; + wgpuDeviceRequestCallbackInfo.userdata1 = &UserData; + wgpuAdapterRequestDevice(wgpuAdapter, &wgpuDeviceDesc, wgpuDeviceRequestCallbackInfo); while (!UserData.IsReady) InstancePoolEvents(wgpuInstance); - if (UserData.RequestStatus != WGPURequestDeviceStatus_Success) + if (UserData.wgpuRequestStatus != WGPURequestDeviceStatus_Success) LOG_ERROR_AND_THROW(UserData.Message); - return WebGPUDeviceWrapper{UserData.Device}; + return WebGPUDeviceWrapper{UserData.wgpuDevice}; } bool FeatureSupported(WGPUAdapter wgpuAdapter, WGPUDevice wgpuDevice, WGPUFeatureName Feature) @@ -398,7 +390,7 @@ DeviceFeatures GetSupportedFeatures(WGPUAdapter wgpuAdapter, WGPUDevice wgpuDevi Features.VariableRateShading = DEVICE_FEATURE_STATE_DISABLED; Features.SparseResources = DEVICE_FEATURE_STATE_DISABLED; Features.SubpassFramebufferFetch = DEVICE_FEATURE_STATE_DISABLED; - Features.TextureComponentSwizzle = DEVICE_FEATURE_STATE_DISABLED; + Features.TextureComponentSwizzle = CheckFeature(WGPUFeatureName_TextureComponentSwizzle); Features.TextureSubresourceViews = DEVICE_FEATURE_STATE_ENABLED; Features.NativeMultiDraw = DEVICE_FEATURE_STATE_DISABLED; Features.AsyncShaderCompilation = DEVICE_FEATURE_STATE_ENABLED; @@ -457,7 +449,7 @@ GraphicsAdapterInfo GetGraphicsAdapterInfo(WGPUAdapter wgpuAdapter, WGPUDevice w AdapterInfo.Features = GetSupportedFeatures(wgpuAdapter, wgpuDevice); - WGPUSupportedLimits wgpuSupportedLimits{}; + WGPULimits wgpuSupportedLimits{}; if (wgpuAdapter) wgpuAdapterGetLimits(wgpuAdapter, &wgpuSupportedLimits); else @@ -494,16 +486,16 @@ GraphicsAdapterInfo GetGraphicsAdapterInfo(WGPUAdapter wgpuAdapter, WGPUDevice w { ComputeShaderProperties& ComputeShaderInfo{AdapterInfo.ComputeShader}; - ComputeShaderInfo.MaxThreadGroupSizeX = wgpuSupportedLimits.limits.maxComputeWorkgroupSizeX; - ComputeShaderInfo.MaxThreadGroupSizeY = wgpuSupportedLimits.limits.maxComputeWorkgroupSizeY; - ComputeShaderInfo.MaxThreadGroupSizeZ = wgpuSupportedLimits.limits.maxComputeWorkgroupSizeZ; + ComputeShaderInfo.MaxThreadGroupSizeX = wgpuSupportedLimits.maxComputeWorkgroupSizeX; + ComputeShaderInfo.MaxThreadGroupSizeY = wgpuSupportedLimits.maxComputeWorkgroupSizeY; + ComputeShaderInfo.MaxThreadGroupSizeZ = wgpuSupportedLimits.maxComputeWorkgroupSizeZ; - ComputeShaderInfo.MaxThreadGroupCountX = wgpuSupportedLimits.limits.maxComputeWorkgroupsPerDimension; - ComputeShaderInfo.MaxThreadGroupCountY = wgpuSupportedLimits.limits.maxComputeWorkgroupsPerDimension; - ComputeShaderInfo.MaxThreadGroupCountZ = wgpuSupportedLimits.limits.maxComputeWorkgroupsPerDimension; + ComputeShaderInfo.MaxThreadGroupCountX = wgpuSupportedLimits.maxComputeWorkgroupsPerDimension; + ComputeShaderInfo.MaxThreadGroupCountY = wgpuSupportedLimits.maxComputeWorkgroupsPerDimension; + ComputeShaderInfo.MaxThreadGroupCountZ = wgpuSupportedLimits.maxComputeWorkgroupsPerDimension; - ComputeShaderInfo.SharedMemorySize = wgpuSupportedLimits.limits.maxComputeWorkgroupStorageSize; - ComputeShaderInfo.MaxThreadGroupInvocations = wgpuSupportedLimits.limits.maxComputeInvocationsPerWorkgroup; + ComputeShaderInfo.SharedMemorySize = wgpuSupportedLimits.maxComputeWorkgroupStorageSize; + ComputeShaderInfo.MaxThreadGroupInvocations = wgpuSupportedLimits.maxComputeInvocationsPerWorkgroup; } // Set texture info @@ -511,11 +503,11 @@ GraphicsAdapterInfo GetGraphicsAdapterInfo(WGPUAdapter wgpuAdapter, WGPUDevice w TextureProperties& TextureInfo{AdapterInfo.Texture}; TextureInfo.MaxTexture1DArraySlices = 0; // Not supported in WebGPU - TextureInfo.MaxTexture2DArraySlices = wgpuSupportedLimits.limits.maxTextureArrayLayers; + TextureInfo.MaxTexture2DArraySlices = wgpuSupportedLimits.maxTextureArrayLayers; - TextureInfo.MaxTexture1DDimension = wgpuSupportedLimits.limits.maxTextureDimension1D; - TextureInfo.MaxTexture2DDimension = wgpuSupportedLimits.limits.maxTextureDimension2D; - TextureInfo.MaxTexture3DDimension = wgpuSupportedLimits.limits.maxTextureDimension3D; + TextureInfo.MaxTexture1DDimension = wgpuSupportedLimits.maxTextureDimension1D; + TextureInfo.MaxTexture2DDimension = wgpuSupportedLimits.maxTextureDimension2D; + TextureInfo.MaxTexture3DDimension = wgpuSupportedLimits.maxTextureDimension3D; TextureInfo.Texture2DMSSupported = True; TextureInfo.Texture2DMSArraySupported = False; @@ -527,8 +519,8 @@ GraphicsAdapterInfo GetGraphicsAdapterInfo(WGPUAdapter wgpuAdapter, WGPUDevice w // Set buffer info { BufferProperties& BufferInfo{AdapterInfo.Buffer}; - BufferInfo.ConstantBufferOffsetAlignment = wgpuSupportedLimits.limits.minUniformBufferOffsetAlignment; - BufferInfo.StructuredBufferOffsetAlignment = wgpuSupportedLimits.limits.minStorageBufferOffsetAlignment; + BufferInfo.ConstantBufferOffsetAlignment = wgpuSupportedLimits.minUniformBufferOffsetAlignment; + BufferInfo.StructuredBufferOffsetAlignment = wgpuSupportedLimits.minStorageBufferOffsetAlignment; BufferInfo.TextureUpdateOffsetAlignment = 256; // Where is this specified? BufferInfo.TextureUpdateStrideAlignment = 256; // From spec ASSERT_SIZEOF(BufferInfo, 16, "Did you add a new member to BufferProperties? Please initialize it here."); diff --git a/Graphics/GraphicsEngineWebGPU/src/GenerateMipsHelperWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/GenerateMipsHelperWebGPU.cpp index bb72acf8b5..2736d8689c 100644 --- a/Graphics/GraphicsEngineWebGPU/src/GenerateMipsHelperWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/GenerateMipsHelperWebGPU.cpp @@ -247,10 +247,24 @@ std::string_view ConvertWebGPUFormatToString(WGPUTextureFormat TexFmt) case WGPUTextureFormat_RGBA16Float: return "rgba16float"; + case WGPUTextureFormat_RGBA16Unorm: + return "rgba16unorm"; + case WGPUTextureFormat_RGBA16Snorm: + return "rgba16snorm"; + case WGPUTextureFormat_RG16Float: return "rg16float"; + case WGPUTextureFormat_RG16Unorm: + return "rg16unorm"; + case WGPUTextureFormat_RG16Snorm: + return "rg16snorm"; + case WGPUTextureFormat_R16Float: return "r16float"; + case WGPUTextureFormat_R16Unorm: + return "r16unorm"; + case WGPUTextureFormat_R16Snorm: + return "r16snorm"; case WGPUTextureFormat_RGBA32Float: return "rgba32float"; @@ -259,8 +273,13 @@ std::string_view ConvertWebGPUFormatToString(WGPUTextureFormat TexFmt) case WGPUTextureFormat_R32Float: return "r32float"; + case WGPUTextureFormat_RG11B10Ufloat: + return "rg11b10ufloat"; + case WGPUTextureFormat_RGB10A2Unorm: + return "rgb10a2unorm"; + default: - UNEXPECTED("Unsupported texture format"); + UNEXPECTED("Unsupported texture format: ", TexFmt); return "rgba8unorm"; } } diff --git a/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp index 112209d205..9767020fa4 100644 --- a/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp @@ -86,6 +86,20 @@ double ConvertSpecConstantToDouble(const void* pData, Uint32 DataSize, SHADER_CO } } +std::vector GetWGPUConstantEntries(const std::vector>& SpecConsts) +{ + std::vector Entries; + Entries.reserve(SpecConsts.size()); + for (const std::pair& SpecConst : SpecConsts) + { + WGPUConstantEntry Entry{}; + Entry.key = GetWGPUStringView(SpecConst.first); + Entry.value = SpecConst.second; + Entries.push_back(Entry); + } + return Entries; +} + void BuildSpecializationDataWebGPU(PipelineStateWebGPUImpl::TShaderStages& ShaderStages, Uint32 NumSpecializationConstants, const SpecializationConstant* pSpecializationConstants, @@ -103,6 +117,8 @@ void BuildSpecializationDataWebGPU(PipelineStateWebGPUImpl::TShaderStages& Shade if (!pShaderResources) continue; + Stage.SpecConstEntries.reserve(pShaderResources->GetNumSpecConstants()); + for (Uint32 r = 0; r < pShaderResources->GetNumSpecConstants(); ++r) { const WGSLSpecializationConstantAttribs& ReflectedSC = pShaderResources->GetSpecConstant(r); @@ -140,11 +156,8 @@ void BuildSpecializationDataWebGPU(PipelineStateWebGPUImpl::TShaderStages& Shade " (", ReflectedSize, " bytes)."); } - WGPUConstantEntry Entry{}; - Entry.key = GetWGPUStringView(ReflectedSC.Name); - Entry.value = ConvertSpecConstantToDouble(pUserConst->pData, pUserConst->Size, ReflectedType); - - Stage.SpecConstEntries.push_back(Entry); + Stage.SpecConstEntries.emplace_back(std::to_string(ReflectedSC.OverrideId), + ConvertSpecConstantToDouble(pUserConst->pData, pUserConst->Size, ReflectedType)); } } } @@ -422,35 +435,19 @@ struct PipelineStateWebGPUImpl::AsyncPipelineBuilder : public ObjectBase(pUserData)->InitializePipelines(Status, Pipeline, nullptr, Message); - } - - static void CreateRenderPipelineCallback2(WGPUCreatePipelineAsyncStatus Status, - WGPURenderPipeline Pipeline, - WGPUStringView Message, - void* pUserData1, - void* pUserData2) + void* pUserData1, + void* pUserData2) { - CreateRenderPipelineCallback(Status, Pipeline, Message, pUserData1); + static_cast(pUserData1)->InitializePipelines(Status, Pipeline, nullptr, Message); } static void CreateComputePipelineCallback(WGPUCreatePipelineAsyncStatus Status, WGPUComputePipeline Pipeline, WGPUStringView Message, - void* pUserData) - { - static_cast(pUserData)->InitializePipelines(Status, nullptr, Pipeline, Message); - } - - static void CreateComputePipelineCallback2(WGPUCreatePipelineAsyncStatus Status, - WGPUComputePipeline Pipeline, - WGPUStringView Message, - void* pUserData1, - void* pUserData2) + void* pUserData1, + void* pUserData2) { - CreateComputePipelineCallback(Status, Pipeline, Message, pUserData1); + static_cast(pUserData1)->InitializePipelines(Status, nullptr, Pipeline, Message); } }; @@ -514,7 +511,9 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages WGPUFragmentState wgpuFragmentState{}; - std::vector wgpuShaderModules{ShaderStages.size()}; + std::vector wgpuShaderModules{ShaderStages.size()}; + std::vector> wgpuStageConstants(ShaderStages.size()); + for (size_t ShaderIdx = 0; ShaderIdx < ShaderStages.size(); ++ShaderIdx) { const ShaderStageInfo& Stage = ShaderStages[ShaderIdx]; @@ -533,18 +532,20 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages { case SHADER_TYPE_VERTEX: VERIFY(wgpuRenderPipelineDesc.vertex.module == nullptr, "Only one vertex shader is allowed"); + wgpuStageConstants[ShaderIdx] = GetWGPUConstantEntries(Stage.SpecConstEntries); wgpuRenderPipelineDesc.vertex.module = wgpuShaderModules[ShaderIdx].Get(); wgpuRenderPipelineDesc.vertex.entryPoint = GetWGPUStringView(Stage.pShader->GetEntryPoint()); - wgpuRenderPipelineDesc.vertex.constantCount = Stage.SpecConstEntries.size(); - wgpuRenderPipelineDesc.vertex.constants = Stage.SpecConstEntries.empty() ? nullptr : Stage.SpecConstEntries.data(); + wgpuRenderPipelineDesc.vertex.constantCount = wgpuStageConstants[ShaderIdx].size(); + wgpuRenderPipelineDesc.vertex.constants = wgpuStageConstants[ShaderIdx].empty() ? nullptr : wgpuStageConstants[ShaderIdx].data(); break; case SHADER_TYPE_PIXEL: VERIFY(wgpuFragmentState.module == nullptr, "Only one vertex shader is allowed"); + wgpuStageConstants[ShaderIdx] = GetWGPUConstantEntries(Stage.SpecConstEntries); wgpuFragmentState.module = wgpuShaderModules[ShaderIdx].Get(); wgpuFragmentState.entryPoint = GetWGPUStringView(Stage.pShader->GetEntryPoint()); - wgpuFragmentState.constantCount = Stage.SpecConstEntries.size(); - wgpuFragmentState.constants = Stage.SpecConstEntries.empty() ? nullptr : Stage.SpecConstEntries.data(); + wgpuFragmentState.constantCount = wgpuStageConstants[ShaderIdx].size(); + wgpuFragmentState.constants = wgpuStageConstants[ShaderIdx].empty() ? nullptr : wgpuStageConstants[ShaderIdx].data(); wgpuRenderPipelineDesc.fragment = &wgpuFragmentState; break; @@ -579,7 +580,7 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages for (size_t Idx = 0; Idx < MaxBufferSlot + 1; ++Idx) { - wgpuVertexBufferLayouts[Idx].stepMode = !wgpuVertexAttributes[Idx].empty() ? wgpuVertexBufferLayouts[Idx].stepMode : WGPUVertexStepMode_VertexBufferNotUsed; + wgpuVertexBufferLayouts[Idx].stepMode = !wgpuVertexAttributes[Idx].empty() ? wgpuVertexBufferLayouts[Idx].stepMode : WGPUVertexStepMode_Undefined; wgpuVertexBufferLayouts[Idx].attributeCount = static_cast(wgpuVertexAttributes[Idx].size()); wgpuVertexBufferLayouts[Idx].attributes = wgpuVertexAttributes[Idx].data(); } @@ -655,9 +656,6 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages wgpuRenderPipelineDesc.depthStencil = &wgpuDepthStencilState; } -#if PLATFORM_WEB - WGPUPrimitiveDepthClipControl wgpuDepthClipControl{}; -#endif { const RasterizerStateDesc& RasterizerDesc = GraphicsPipeline.RasterizerDesc; @@ -679,13 +677,7 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages { if (m_pDevice->GetDeviceInfo().Features.DepthClamp) { -#if PLATFORM_WEB - wgpuDepthClipControl.chain.sType = WGPUSType_PrimitiveDepthClipControl; - wgpuDepthClipControl.unclippedDepth = true; - wgpuPrimitiveState.nextInChain = reinterpret_cast(&wgpuDepthClipControl); -#else wgpuPrimitiveState.unclippedDepth = true; -#endif } else { @@ -706,18 +698,14 @@ void PipelineStateWebGPUImpl::InitializeWebGPURenderPipeline(const TShaderStages { // The reference will be released from the callback. AsyncBuilder->AddRef(); -#if PLATFORM_WEB - wgpuDeviceCreateRenderPipelineAsync(m_pDevice->GetWebGPUDevice(), &wgpuRenderPipelineDesc, AsyncPipelineBuilder::CreateRenderPipelineCallback, AsyncBuilder); -#else - wgpuDeviceCreateRenderPipelineAsync2(m_pDevice->GetWebGPUDevice(), &wgpuRenderPipelineDesc, - { - nullptr, - WGPUCallbackMode_AllowSpontaneous, - AsyncPipelineBuilder::CreateRenderPipelineCallback2, - AsyncBuilder, - nullptr, - }); -#endif + wgpuDeviceCreateRenderPipelineAsync(m_pDevice->GetWebGPUDevice(), &wgpuRenderPipelineDesc, + { + nullptr, + WGPUCallbackMode_AllowSpontaneous, + AsyncPipelineBuilder::CreateRenderPipelineCallback, + AsyncBuilder, + nullptr, + }); } else { @@ -750,26 +738,23 @@ void PipelineStateWebGPUImpl::InitializeWebGPUComputePipeline(const TShaderStage wgpuComputePipelineDesc.compute.entryPoint = GetWGPUStringView(pShaderWebGPU->GetEntryPoint()); wgpuComputePipelineDesc.layout = m_PipelineLayout.GetWebGPUPipelineLayout(); - const std::vector& SpecConstEntries = ShaderStages[0].SpecConstEntries; - wgpuComputePipelineDesc.compute.constantCount = SpecConstEntries.size(); - wgpuComputePipelineDesc.compute.constants = SpecConstEntries.empty() ? nullptr : SpecConstEntries.data(); + const std::vector wgpuConstants = GetWGPUConstantEntries(ShaderStages[0].SpecConstEntries); + wgpuComputePipelineDesc.compute.constantCount = wgpuConstants.size(); + wgpuComputePipelineDesc.compute.constants = wgpuConstants.empty() ? nullptr : wgpuConstants.data(); if (AsyncBuilder) { // The reference will be released from the callback. AsyncBuilder->AddRef(); -#if PLATFORM_WEB - wgpuDeviceCreateComputePipelineAsync(m_pDevice->GetWebGPUDevice(), &wgpuComputePipelineDesc, AsyncPipelineBuilder::CreateComputePipelineCallback, AsyncBuilder); -#else - wgpuDeviceCreateComputePipelineAsync2(m_pDevice->GetWebGPUDevice(), &wgpuComputePipelineDesc, - { - nullptr, - WGPUCallbackMode_AllowSpontaneous, - AsyncPipelineBuilder::CreateComputePipelineCallback2, - AsyncBuilder, - nullptr, - }); -#endif + + wgpuDeviceCreateComputePipelineAsync(m_pDevice->GetWebGPUDevice(), &wgpuComputePipelineDesc, + { + nullptr, + WGPUCallbackMode_AllowSpontaneous, + AsyncPipelineBuilder::CreateComputePipelineCallback, + AsyncBuilder, + nullptr, + }); } else { diff --git a/Graphics/GraphicsEngineWebGPU/src/RenderDeviceWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/RenderDeviceWebGPUImpl.cpp index 2f264be81d..dc3bdc7379 100644 --- a/Graphics/GraphicsEngineWebGPU/src/RenderDeviceWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/RenderDeviceWebGPUImpl.cpp @@ -67,14 +67,6 @@ class ShaderBindingTableWebGPUImpl class DeviceMemoryWebGPUImpl {}; -#if PLATFORM_WEB -static void DebugMessengerCallback(WGPUErrorType MessageType, const char* Message, void* pUserData) -{ - if (Message != nullptr) - LOG_DEBUG_MESSAGE(DEBUG_MESSAGE_SEVERITY_ERROR, "WebGPU: ", Message); -} -#endif - RenderDeviceWebGPUImpl::RenderDeviceWebGPUImpl(IReferenceCounters* pRefCounters, const CreateInfo& CI) : @@ -92,13 +84,8 @@ RenderDeviceWebGPUImpl::RenderDeviceWebGPUImpl(IReferenceCounters* pRefCounters, m_wgpuDevice{CI.wgpuDevice} // clang-format on { - WGPUSupportedLimits wgpuSupportedLimits{}; - wgpuDeviceGetLimits(m_wgpuDevice, &wgpuSupportedLimits); - m_wgpuLimits = wgpuSupportedLimits.limits; -#if PLATFORM_WEB - wgpuDeviceSetUncapturedErrorCallback(m_wgpuDevice, DebugMessengerCallback, nullptr); -#endif + wgpuDeviceGetLimits(m_wgpuDevice, &m_wgpuLimits); FindSupportedTextureFormats(); m_DeviceInfo.Type = RENDER_DEVICE_TYPE_WEBGPU; @@ -386,9 +373,12 @@ void RenderDeviceWebGPUImpl::FindSupportedTextureFormats() constexpr BIND_FLAGS BIND_SRU = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET | BIND_UNORDERED_ACCESS; constexpr BIND_FLAGS BIND_SR = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET; - constexpr BIND_FLAGS BIND_S = BIND_SHADER_RESOURCE; constexpr BIND_FLAGS BIND_SU = BIND_SHADER_RESOURCE | BIND_UNORDERED_ACCESS; + constexpr BIND_FLAGS BIND_S = BIND_SHADER_RESOURCE; + constexpr BIND_FLAGS BIND_R = BIND_RENDER_TARGET; constexpr BIND_FLAGS BIND_D = BIND_DEPTH_STENCIL; + constexpr BIND_FLAGS BIND_U = BIND_UNORDERED_ACCESS; + SAMPLE_COUNT SupportedSampleCounts = SAMPLE_COUNT_1 | SAMPLE_COUNT_4; // We can't query supported sample counts in WebGPU @@ -431,14 +421,24 @@ void RenderDeviceWebGPUImpl::FindSupportedTextureFormats() } }; + auto UpdateTexFormatInfo = [&](std::initializer_list Formats, BIND_FLAGS BindFlags, Uint32 FmtFlags) { + for (TEXTURE_FORMAT Fmt : Formats) + { + TextureFormatInfoExt& FmtInfo = m_TextureFormatsInfo[Fmt]; + VERIFY(FmtInfo.Supported, "The format hasn't been initialized"); + FmtInfo.BindFlags |= BindFlags; + FmtInfo.SampleCounts |= (FmtFlags & FMT_FLAG_MSAA) != 0 ? SupportedSampleCounts : SAMPLE_COUNT_1; + FmtInfo.Filterable |= (FmtFlags & FMT_FLAG_FILTER) != 0; + } + }; + const bool BGRA8UnormStorageSupported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_BGRA8UnormStorage); const bool Float32FilterableSupported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_Float32Filterable); const bool RG11B10UfloatRenderableSupported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_RG11B10UfloatRenderable); const bool Depth32FloatStencil8Supported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_Depth32FloatStencil8); const bool TextureCompressionBCSupported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_TextureCompressionBC); const bool TextureCompressionETC2Supported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_TextureCompressionETC2); - const bool R16UnormSupported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_Unorm16TextureFormats); - const bool R16SnormSupported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_Snorm16TextureFormats); + const bool TextureTier1Supported = wgpuDeviceHasFeature(m_wgpuDevice, WGPUFeatureName_TextureFormatsTier1); // https://www.w3.org/TR/webgpu/#texture-format-caps @@ -521,18 +521,40 @@ void RenderDeviceWebGPUImpl::FindSupportedTextureFormats() BIND_S, FMT_FLAG_FILTER); } - if (R16UnormSupported) - { - SetTexFormatInfo({TEX_FORMAT_R16_UNORM}, BIND_SR, FMT_FLAG_FILTER | FMT_FLAG_MSAA); - SetTexFormatInfo({TEX_FORMAT_RG16_UNORM}, BIND_SR, FMT_FLAG_FILTER | FMT_FLAG_MSAA); - SetTexFormatInfo({TEX_FORMAT_RGBA16_UNORM}, BIND_SR, FMT_FLAG_FILTER | FMT_FLAG_MSAA); - } - - if (R16SnormSupported) + if (TextureTier1Supported) { - SetTexFormatInfo({TEX_FORMAT_R16_SNORM}, BIND_SR, FMT_FLAG_FILTER | FMT_FLAG_MSAA); - SetTexFormatInfo({TEX_FORMAT_RG16_SNORM}, BIND_SR, FMT_FLAG_FILTER | FMT_FLAG_MSAA); - SetTexFormatInfo({TEX_FORMAT_RGBA16_SNORM}, BIND_SR, FMT_FLAG_FILTER | FMT_FLAG_MSAA); + // https://www.w3.org/TR/webgpu/#texture-formats-tier1 + SetTexFormatInfo({TEX_FORMAT_R16_UNORM, + TEX_FORMAT_R16_SNORM, + TEX_FORMAT_RG16_UNORM, + TEX_FORMAT_RG16_SNORM, + TEX_FORMAT_RGBA16_UNORM, + TEX_FORMAT_RGBA16_SNORM}, + BIND_SRU, FMT_FLAG_FILTER | FMT_FLAG_MSAA); + + UpdateTexFormatInfo({TEX_FORMAT_R8_SNORM, + TEX_FORMAT_RG8_SNORM, + TEX_FORMAT_RGBA8_SNORM}, + BIND_R, FMT_FLAG_MSAA); + + UpdateTexFormatInfo({TEX_FORMAT_R8_UNORM, + TEX_FORMAT_R8_SNORM, + TEX_FORMAT_R8_UINT, + TEX_FORMAT_R8_SINT, + TEX_FORMAT_RG8_UNORM, + TEX_FORMAT_RG8_SNORM, + TEX_FORMAT_RG8_UINT, + TEX_FORMAT_RG8_SINT, + TEX_FORMAT_R16_UINT, + TEX_FORMAT_R16_SINT, + TEX_FORMAT_R16_FLOAT, + TEX_FORMAT_RG16_UINT, + TEX_FORMAT_RG16_SINT, + TEX_FORMAT_RG16_FLOAT, + TEX_FORMAT_RGB10A2_UNORM, + TEX_FORMAT_RGB10A2_UINT, + TEX_FORMAT_R11G11B10_FLOAT}, + BIND_U, 0); } if (TextureCompressionETC2Supported) diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp index bb756b685b..bd363eaf64 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp @@ -93,12 +93,12 @@ std::vector CompileShaderToSPIRV(const ShaderCreateInfo& S const std::string HLSLSource = BuildHLSLSourceString(ShaderCI); if (!HLSLSource.empty()) { - // Extract image formats from special comments in HLSL code: - // Texture2D g_RWTexture; - const auto ImageFormats = Parsing::ExtractGLSLImageFormatsFromHLSL(HLSLSource); + // Extract image formats and access modes from special comments in HLSL code: + // Texture2D g_RWTexture; + const auto ImageFormats = Parsing::ExtractGLSLImageFormatsAndAccessModeFromHLSL(HLSLSource); if (!ImageFormats.empty()) { - SPIRV = PatchImageFormats(SPIRV, ImageFormats); + SPIRV = PatchImageFormatsAndAccessModes(SPIRV, ImageFormats); } } } @@ -205,6 +205,8 @@ void ShaderWebGPUImpl::Initialize(const ShaderCreateInfo& ShaderCI, LOG_ERROR_AND_THROW("Shader source must be provided through one of the 'Source', 'FilePath' or 'ByteCode' members"); } + StripGoogleHlslFunctionality(SPIRV); + AddStorageImageExtendedFormats(SPIRV); m_WGSL = ConvertSPIRVtoWGSL(SPIRV); if (m_WGSL.empty()) { diff --git a/Graphics/GraphicsEngineWebGPU/src/SwapChainWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/SwapChainWebGPUImpl.cpp index 90a7d74fca..4f7007f0ec 100644 --- a/Graphics/GraphicsEngineWebGPU/src/SwapChainWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/SwapChainWebGPUImpl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 Diligent Graphics LLC + * Copyright 2023-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ #include "TextureViewWebGPU.h" #include "WebGPUTypeConversions.hpp" #include "WebGPUStubs.hpp" +#include "webgpu/webgpu.h" +#include #ifdef PLATFORM_WIN32 # include @@ -47,60 +49,6 @@ namespace Diligent namespace { -constexpr char VSSource[] = R"( -struct VertexOutput -{ - @builtin(position) Position: vec4f, -} - -@vertex -fn VSMain(@builtin(vertex_index) VertexId: u32) -> VertexOutput -{ - let Texcoord: vec2f = vec2f(f32((VertexId << 1u) & 2u), f32(VertexId & 2u)); - let Position: vec4f = vec4f(Texcoord * vec2f(2.0f, -2.0f) + vec2f(-1.0f, 1.0f), 1.0f, 1.0f); - return VertexOutput(Position); -} -)"; - -constexpr char PSSource[] = R"( -@group(0) @binding(0) var TextureSrc: texture_2d; - -struct VertexOutput -{ - @builtin(position) Position: vec4f, -} - -@fragment -fn PSMain(Input: VertexOutput) -> @location(0) vec4f -{ - return textureLoad(TextureSrc, vec2i(Input.Position.xy), 0); -} -)"; - -constexpr char PSSourceGamma[] = R"( -@group(0) @binding(0) var TextureSrc: texture_2d; - -struct VertexOutput -{ - @builtin(position) Position: vec4f, -} - -fn LinearToSRGB(RGB: vec3) -> vec3 { - let threshold = vec3(0.0031308); - let bGreater = step(threshold, RGB); - let linearPart = RGB * 12.92; - let sRGBPart = (pow(RGB, vec3(1.0 / 2.4)) * 1.055) - vec3(0.055); - return mix(linearPart, sRGBPart, bGreater); -} - -@fragment -fn PSMain(Input: VertexOutput) -> @location(0) vec4f -{ - var Color: vec4f = textureLoad(TextureSrc, vec2i(Input.Position.xy), 0); - return vec4f(LinearToSRGB(Color.rgb), Color.a); -} -)"; - WGPUTextureFormat WGPUConvertUnormToSRGB(WGPUTextureFormat Format) { switch (Format) @@ -117,237 +65,7 @@ WGPUTextureFormat WGPUConvertUnormToSRGB(WGPUTextureFormat Format) } // namespace -class SwapChainWebGPUImpl::PresentCommand -{ -public: - PresentCommand(IRenderDeviceWebGPU* pRenderDevice) : - m_pRenderDevice{pRenderDevice} - { - } - - bool InitializePipelineState(WGPUTextureFormat wgpuFormat, bool ConvertToGamma) - { - if (m_wgpuRenderPipeline) - return true; - - WGPUDevice wgpuDevice = m_pRenderDevice->GetWebGPUDevice(); - - WebGPUShaderModuleWrapper wgpuVSShaderModule{}; - { - WGPUShaderSourceWGSL wgpuShaderCodeDesc{}; - wgpuShaderCodeDesc.chain.sType = WGPUSType_ShaderSourceWGSL; - wgpuShaderCodeDesc.code = GetWGPUStringView(VSSource); - - WGPUShaderModuleDescriptor wgpuShaderModuleDesc{}; - wgpuShaderModuleDesc.nextInChain = reinterpret_cast(&wgpuShaderCodeDesc); - wgpuVSShaderModule.Reset(wgpuDeviceCreateShaderModule(wgpuDevice, &wgpuShaderModuleDesc)); - if (!wgpuVSShaderModule) - { - LOG_ERROR_MESSAGE("Failed to create shader module for swap chain present command"); - return false; - } - } - - WebGPUShaderModuleWrapper wgpuPSShaderModule{}; - { - WGPUShaderSourceWGSL wgpuShaderCodeDesc{}; - wgpuShaderCodeDesc.chain.sType = WGPUSType_ShaderSourceWGSL; - wgpuShaderCodeDesc.code = GetWGPUStringView(ConvertToGamma ? PSSourceGamma : PSSource); - - WGPUShaderModuleDescriptor wgpuShaderModuleDesc{}; - wgpuShaderModuleDesc.nextInChain = reinterpret_cast(&wgpuShaderCodeDesc); - wgpuPSShaderModule.Reset(wgpuDeviceCreateShaderModule(wgpuDevice, &wgpuShaderModuleDesc)); - if (!wgpuPSShaderModule) - { - LOG_ERROR_MESSAGE("Failed to create shader module for swap chain present command"); - return false; - } - } - WGPUBindGroupLayoutEntry wgpuBindGroupLayoutEntries[1]{}; - wgpuBindGroupLayoutEntries[0].binding = 0; - wgpuBindGroupLayoutEntries[0].visibility = WGPUShaderStage_Fragment; - wgpuBindGroupLayoutEntries[0].texture.sampleType = WGPUTextureSampleType_Float; - wgpuBindGroupLayoutEntries[0].texture.viewDimension = WGPUTextureViewDimension_2D; - - WGPUBindGroupLayoutDescriptor wgpuBindGroupLayoutDesc{}; - wgpuBindGroupLayoutDesc.entryCount = _countof(wgpuBindGroupLayoutEntries); - wgpuBindGroupLayoutDesc.entries = wgpuBindGroupLayoutEntries; - m_wgpuBindGroupLayout.Reset(wgpuDeviceCreateBindGroupLayout(wgpuDevice, &wgpuBindGroupLayoutDesc)); - if (!m_wgpuBindGroupLayout) - { - LOG_ERROR_MESSAGE("Failed to create bind group layout for swap chain present command"); - return false; - } - - WGPUPipelineLayoutDescriptor wgpuPipelineLayoutDesc{}; - wgpuPipelineLayoutDesc.bindGroupLayoutCount = 1; - wgpuPipelineLayoutDesc.bindGroupLayouts = &m_wgpuBindGroupLayout.Get(); - m_wgpuPipelineLayout.Reset(wgpuDeviceCreatePipelineLayout(wgpuDevice, &wgpuPipelineLayoutDesc)); - if (!m_wgpuPipelineLayout) - { - LOG_ERROR_MESSAGE("Failed to create pipeline layout for swap chain present command"); - return false; - } - - WGPUColorTargetState wgpuColorTargetState{}; - wgpuColorTargetState.format = wgpuFormat; - wgpuColorTargetState.blend = nullptr; - wgpuColorTargetState.writeMask = WGPUColorWriteMask_All; - - WGPUFragmentState wgpuFragmentState{}; - wgpuFragmentState.module = wgpuPSShaderModule; - wgpuFragmentState.entryPoint = GetWGPUStringView("PSMain"); - wgpuFragmentState.targets = &wgpuColorTargetState; - wgpuFragmentState.targetCount = 1; - - WGPURenderPipelineDescriptor wgpuRenderPipelineDesc{}; - wgpuRenderPipelineDesc.label = GetWGPUStringView("SwapChainPresentPSO"); - wgpuRenderPipelineDesc.layout = m_wgpuPipelineLayout; - wgpuRenderPipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; - wgpuRenderPipelineDesc.primitive.cullMode = WGPUCullMode_None; - wgpuRenderPipelineDesc.vertex.module = wgpuVSShaderModule; - wgpuRenderPipelineDesc.vertex.entryPoint = GetWGPUStringView("VSMain"); - wgpuRenderPipelineDesc.fragment = &wgpuFragmentState; - wgpuRenderPipelineDesc.multisample.count = 1; - wgpuRenderPipelineDesc.multisample.mask = 0xFFFFFFFF; - m_wgpuRenderPipeline.Reset(wgpuDeviceCreateRenderPipeline(wgpuDevice, &wgpuRenderPipelineDesc)); - if (!m_wgpuPipelineLayout) - { - LOG_ERROR_MESSAGE("Failed to create render pipeline for swap chain present command"); - return false; - } - - return true; - } - - WGPUSurfaceGetCurrentTextureStatus Execute(ITextureViewWebGPU* pTexture, ISwapChainWebGPU* pSwapChain, IDeviceContextWebGPU* pDeviceContext) - { - WGPUSurfaceTexture wgpuSurfaceTexture{}; - wgpuSurfaceGetCurrentTexture(pSwapChain->GetWebGPUSurface(), &wgpuSurfaceTexture); - WebGPUTextureWrapper wgpuTexture{wgpuSurfaceTexture.texture}; - - switch (wgpuSurfaceTexture.status) - { - case WGPUSurfaceGetCurrentTextureStatus_Success: - case WGPUSurfaceGetCurrentTextureStatus_Outdated: - break; - - case WGPUSurfaceGetCurrentTextureStatus_Timeout: - break; - - case WGPUSurfaceGetCurrentTextureStatus_Lost: - LOG_WARNING_MESSAGE("Unable to present: swap chain surface is lost"); - return wgpuSurfaceTexture.status; - - case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory: - LOG_ERROR_MESSAGE("Unable to present: out of memory"); - return wgpuSurfaceTexture.status; - - case WGPUSurfaceGetCurrentTextureStatus_DeviceLost: - LOG_ERROR_MESSAGE("Unable to present: device is lost"); - return wgpuSurfaceTexture.status; - - case WGPUSurfaceGetCurrentTextureStatus_Error: - LOG_ERROR_MESSAGE("Unable to present: unknown error"); - return wgpuSurfaceTexture.status; - - default: - UNEXPECTED("Unexpected status"); - return wgpuSurfaceTexture.status; - } - - WGPUTextureFormat ViewFormat = wgpuTextureGetFormat(wgpuTexture); - - // Simplify this code once the bug for sRGB texture view is fixed in Dawn - bool ConvertToGamma = false; -#if !PLATFORM_WEB - if (IsSRGBFormat(pSwapChain->GetDesc().ColorBufferFormat)) - ViewFormat = WGPUConvertUnormToSRGB(ViewFormat); -#else - ConvertToGamma = IsSRGBFormat(pSwapChain->GetDesc().ColorBufferFormat); -#endif - if (!InitializePipelineState(ViewFormat, ConvertToGamma)) - return WGPUSurfaceGetCurrentTextureStatus_Error; - - WGPUTextureViewDescriptor wgpuTextureViewDesc{}; - wgpuTextureViewDesc.nextInChain = nullptr; - wgpuTextureViewDesc.label = GetWGPUStringView("SwapChainPresentTextureView"); - wgpuTextureViewDesc.format = ViewFormat; - wgpuTextureViewDesc.dimension = WGPUTextureViewDimension_2D; - wgpuTextureViewDesc.baseMipLevel = 0; - wgpuTextureViewDesc.mipLevelCount = 1; - wgpuTextureViewDesc.baseArrayLayer = 0; - wgpuTextureViewDesc.arrayLayerCount = 1; - wgpuTextureViewDesc.aspect = WGPUTextureAspect_All; - - WebGPUTextureViewWrapper wgpuTextureView{wgpuTextureCreateView(wgpuTexture, &wgpuTextureViewDesc)}; - if (!wgpuTextureView) - { - LOG_ERROR_MESSAGE("Failed to create texture view for WGPU surface texture"); - return WGPUSurfaceGetCurrentTextureStatus_Error; - } - - if (!m_wgpuBindGroup) - { - WGPUBindGroupEntry wgpuBindGroupEntries[1]{}; - wgpuBindGroupEntries[0].binding = 0; - wgpuBindGroupEntries[0].textureView = pTexture->GetWebGPUTextureView(); - - WGPUBindGroupDescriptor wgpuBindGroupDesc{}; - wgpuBindGroupDesc.entries = wgpuBindGroupEntries; - wgpuBindGroupDesc.entryCount = _countof(wgpuBindGroupEntries); - wgpuBindGroupDesc.layout = m_wgpuBindGroupLayout; - - m_wgpuBindGroup.Reset(wgpuDeviceCreateBindGroup(m_pRenderDevice->GetWebGPUDevice(), &wgpuBindGroupDesc)); - } - - WGPUCommandEncoderDescriptor wgpuCmdEncoderDesc{}; - WebGPUCommandEncoderWrapper wgpuCmdEncoder{wgpuDeviceCreateCommandEncoder(m_pRenderDevice->GetWebGPUDevice(), &wgpuCmdEncoderDesc)}; - - WGPURenderPassColorAttachment wgpuRenderPassColorAttachments[1]{}; - wgpuRenderPassColorAttachments[0].clearValue = WGPUColor{0.0, 0.0, 0.0, 1.0}; - wgpuRenderPassColorAttachments[0].loadOp = WGPULoadOp_Clear; - wgpuRenderPassColorAttachments[0].storeOp = WGPUStoreOp_Store; - wgpuRenderPassColorAttachments[0].view = wgpuTextureView; - wgpuRenderPassColorAttachments[0].depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; - - WGPURenderPassDescriptor wgpuRenderPassDesc{}; - wgpuRenderPassDesc.colorAttachmentCount = _countof(wgpuRenderPassColorAttachments); - wgpuRenderPassDesc.colorAttachments = wgpuRenderPassColorAttachments; - - WebGPURenderPassEncoderWrapper wgpuRenderPassEncoder{wgpuCommandEncoderBeginRenderPass(wgpuCmdEncoder, &wgpuRenderPassDesc)}; - wgpuRenderPassEncoderSetPipeline(wgpuRenderPassEncoder, m_wgpuRenderPipeline); - wgpuRenderPassEncoderSetBindGroup(wgpuRenderPassEncoder, 0, m_wgpuBindGroup, 0, nullptr); - wgpuRenderPassEncoderDraw(wgpuRenderPassEncoder, 3, 1, 0, 0); - wgpuRenderPassEncoderEnd(wgpuRenderPassEncoder); - - WGPUCommandBufferDescriptor wgpuCmdBufferDesc{}; - WebGPUCommandBufferWrapper wgpuCmdBuffer{wgpuCommandEncoderFinish(wgpuCmdEncoder, &wgpuCmdBufferDesc)}; - - wgpuQueueSubmit(pDeviceContext->GetWebGPUQueue(), 1, &wgpuCmdBuffer.Get()); - -#if PLATFORM_WEB - emscripten_request_animation_frame([](double Time, void* pUserData) -> EM_BOOL { return EM_FALSE; }, nullptr); -#else - wgpuSurfacePresent(pSwapChain->GetWebGPUSurface()); -#endif - - return wgpuSurfaceTexture.status; - } - - void ReleaseSwapChainResources() - { - m_wgpuBindGroup.Reset(); - } - -private: - RefCntAutoPtr m_pRenderDevice; - WebGPUBindGroupLayoutWrapper m_wgpuBindGroupLayout; - WebGPUBindGroupWrapper m_wgpuBindGroup; - WebGPUPipelineLayoutWrapper m_wgpuPipelineLayout; - WebGPURenderPipelineWrapper m_wgpuRenderPipeline; -}; SwapChainWebGPUImpl::SwapChainWebGPUImpl(IReferenceCounters* pRefCounters, const SwapChainDesc& SCDesc, @@ -362,8 +80,7 @@ SwapChainWebGPUImpl::SwapChainWebGPUImpl(IReferenceCounters* pRefCounters, pDeviceContext, SCDesc }, - m_NativeWindow{Window}, - m_pCmdPresent{std::make_unique(pRenderDevice)} + m_NativeWindow{Window} // clang-format on { if (m_DesiredPreTransform != SURFACE_TRANSFORM_OPTIMAL && m_DesiredPreTransform != SURFACE_TRANSFORM_IDENTITY) @@ -377,43 +94,42 @@ SwapChainWebGPUImpl::SwapChainWebGPUImpl(IReferenceCounters* pRefCounters, CreateSurface(); ConfigureSurface(); - CreateBuffersAndViews(); + CreateDepthBufferView(); + + WGPUSurfaceGetCurrentTextureStatus wgpuStatus = AcquireSurfaceTexture(); + if (wgpuStatus != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && + wgpuStatus != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) + { + LOG_ERROR_MESSAGE("Failed to acquire the initial swap chain surface texture"); + } } SwapChainWebGPUImpl::~SwapChainWebGPUImpl() = default; void SwapChainWebGPUImpl::Present(Uint32 SyncInterval) { + m_RequestedSyncInterval = SyncInterval; if (SyncInterval != 0 && SyncInterval != 1) LOG_WARNING_MESSAGE_ONCE("WebGPU only supports 0 and 1 present intervals"); RefCntAutoPtr pDeviceContext = m_wpDeviceContext.Lock(); - RenderDeviceWebGPUImpl* pRenderDevice = m_pRenderDevice.RawPtr(); if (!pDeviceContext) { LOG_ERROR_MESSAGE("Immediate context has been released"); return; } + pDeviceContext->Flush(); - DeviceContextWebGPUImpl* pImmediateCtxWebGPU = pDeviceContext.RawPtr(); - - pImmediateCtxWebGPU->Flush(); - WGPUSurfaceGetCurrentTextureStatus SurfaceStatus = m_pCmdPresent->Execute(m_pBackBufferSRV, this, pImmediateCtxWebGPU); - - if (m_SwapChainDesc.IsPrimary) - { - pImmediateCtxWebGPU->FinishFrame(); - pRenderDevice->ReleaseStaleResources(); - } +#if PLATFORM_WEB - const bool EnableVSync = SyncInterval != 0; - if (SurfaceStatus == WGPUSurfaceGetCurrentTextureStatus_Outdated || - SurfaceStatus == WGPUSurfaceGetCurrentTextureStatus_Lost || - m_VSyncEnabled != EnableVSync) - { - m_VSyncEnabled = EnableVSync; - RecreateSwapChain(); - } + emscripten_request_animation_frame([](double Time, void* pUserData) -> EM_BOOL { + auto* const pSwapChain = static_cast(pUserData); + pSwapChain->RequestAnimationFrame(pSwapChain->m_RequestedSyncInterval); + return EM_FALSE; }, this); +#else + wgpuSurfacePresent(m_wgpuSurface); + RequestAnimationFrame(m_RequestedSyncInterval); +#endif } void SwapChainWebGPUImpl::Resize(Uint32 NewWidth, @@ -445,17 +161,17 @@ void SwapChainWebGPUImpl::CreateSurface() wgpuSurfaceNativeDesc.hinstance = GetModuleHandle(nullptr); #elif PLATFORM_LINUX WGPUSurfaceSourceXCBWindow wgpuSurfaceNativeDesc{}; - wgpuSurfaceNativeDesc.chain = {nullptr, WGPUSType_SurfaceSourceXCBWindow}; + wgpuSurfaceNativeDesc.chain = {nullptr, WGPUSType_SurfaceSourceXCBWindow}; wgpuSurfaceNativeDesc.connection = m_NativeWindow.pXCBConnection; - wgpuSurfaceNativeDesc.window = m_NativeWindow.WindowId; -#elif PLATFROM_MACOS + wgpuSurfaceNativeDesc.window = m_NativeWindow.WindowId; +#elif PLATFORM_MACOS WGPUSurfaceSourceMetalLayer wgpuSurfaceNativeDesc{}; - wgpuSurfaceNativeDesc.chain = {nullptr, WGPUSType_SurfaceSourceMetalLayer}; - wgpuSurfaceNativeDesc.window = m_NativeWindow.MetalLayer; + wgpuSurfaceNativeDesc.chain = {nullptr, WGPUSType_SurfaceSourceMetalLayer}; + wgpuSurfaceNativeDesc.layer = m_NativeWindow.GetLayer(); #elif PLATFORM_WEB WGPUSurfaceSourceCanvasHTMLSelector_Emscripten wgpuSurfaceNativeDesc{}; wgpuSurfaceNativeDesc.chain = {nullptr, WGPUSType_SurfaceSourceCanvasHTMLSelector_Emscripten}; - wgpuSurfaceNativeDesc.selector = m_NativeWindow.pCanvasId; + wgpuSurfaceNativeDesc.selector = GetWGPUStringView(m_NativeWindow.pCanvasId); #endif WGPUSurfaceDescriptor wgpuSurfaceDesc{}; @@ -521,6 +237,23 @@ void SwapChainWebGPUImpl::ConfigureSurface() return Result; }; + auto SelectFormat = [&](TEXTURE_FORMAT Format) { + WGPUTextureFormat wgpuFormat = TextureFormatToWGPUFormat(SRGBFormatToUnorm(Format)); + + const WGPUTextureFormat* FindBegin = wgpuSurfaceCapabilities.formats; + const WGPUTextureFormat* FindEnd = wgpuSurfaceCapabilities.formats + wgpuSurfaceCapabilities.formatCount; + + if (std::find(FindBegin, FindEnd, wgpuFormat) != FindEnd) + { + return wgpuFormat; + } + else + { + LOG_ERROR_MESSAGE("Failed to find the requested format in the surface capabilities. Using the first available format instead."); + return wgpuSurfaceCapabilities.formats[0]; + } + }; + if (m_SwapChainDesc.Width == 0 || m_SwapChainDesc.Height == 0) { #if PLATFORM_WIN32 @@ -529,12 +262,14 @@ void SwapChainWebGPUImpl::ConfigureSurface() m_SwapChainDesc.Width = WindowRect.right - WindowRect.left; m_SwapChainDesc.Height = WindowRect.bottom - WindowRect.top; +#elif PLATFORM_MACOS + m_NativeWindow.GetBackingSize(m_SwapChainDesc.Width, m_SwapChainDesc.Height); #elif PLATFORM_WEB - int32_t CanvasWidth = 0; + int32_t CanvasWidth = 0; int32_t CanvasHeight = 0; emscripten_get_canvas_element_size(m_NativeWindow.pCanvasId, &CanvasWidth, &CanvasHeight); - m_SwapChainDesc.Width = static_cast(CanvasWidth); + m_SwapChainDesc.Width = static_cast(CanvasWidth); m_SwapChainDesc.Height = static_cast(CanvasHeight); #endif @@ -542,15 +277,21 @@ void SwapChainWebGPUImpl::ConfigureSurface() m_SwapChainDesc.Height = (std::max)(m_SwapChainDesc.Height, 1u); } - const WGPUTextureFormat wgpuPreferredFormat = wgpuSurfaceCapabilities.formats[0]; + const WGPUTextureFormat wgpuPreferredFormat = SelectFormat(m_SwapChainDesc.ColorBufferFormat); - WGPUTextureFormat wgpuRTVFormats[] = { - wgpuPreferredFormat, - WGPUConvertUnormToSRGB(wgpuPreferredFormat), - }; + std::vector wgpuRTVFormats; + wgpuRTVFormats.push_back(wgpuPreferredFormat); + if (IsSRGBFormat(m_SwapChainDesc.ColorBufferFormat)) + wgpuRTVFormats.push_back(WGPUConvertUnormToSRGB(wgpuPreferredFormat)); + + WGPUSurfaceColorManagement wgpuColorManagement{}; + wgpuColorManagement.chain.sType = WGPUSType_SurfaceColorManagement; + wgpuColorManagement.chain.next = nullptr; + wgpuColorManagement.colorSpace = WGPUPredefinedColorSpace_DisplayP3; + wgpuColorManagement.toneMappingMode = WGPUToneMappingMode_Extended; WGPUSurfaceConfiguration wgpuSurfaceConfig{}; - wgpuSurfaceConfig.nextInChain = nullptr; + wgpuSurfaceConfig.nextInChain = wgpuPreferredFormat != WGPUTextureFormat_RGBA16Float ? nullptr : &wgpuColorManagement.chain; wgpuSurfaceConfig.device = pRenderDeviceWebGPU->GetWebGPUDevice(); wgpuSurfaceConfig.usage = SelectUsage(m_SwapChainDesc.Usage); wgpuSurfaceConfig.width = m_SwapChainDesc.Width; @@ -558,36 +299,110 @@ void SwapChainWebGPUImpl::ConfigureSurface() wgpuSurfaceConfig.format = wgpuPreferredFormat; wgpuSurfaceConfig.presentMode = SelectPresentMode(m_VSyncEnabled); wgpuSurfaceConfig.alphaMode = WGPUCompositeAlphaMode_Auto; - wgpuSurfaceConfig.viewFormats = wgpuRTVFormats; - wgpuSurfaceConfig.viewFormatCount = _countof(wgpuRTVFormats); + wgpuSurfaceConfig.viewFormats = wgpuRTVFormats.data(); + wgpuSurfaceConfig.viewFormatCount = static_cast(wgpuRTVFormats.size()); wgpuSurfaceConfigure(m_wgpuSurface, &wgpuSurfaceConfig); wgpuSurfaceCapabilitiesFreeMembers(wgpuSurfaceCapabilities); } -void SwapChainWebGPUImpl::CreateBuffersAndViews() +WGPUSurfaceGetCurrentTextureStatus SwapChainWebGPUImpl::AcquireSurfaceTexture() { + m_pBackBufferRTV.Release(); + + WGPUSurfaceTexture wgpuSurfaceTexture{}; + wgpuSurfaceGetCurrentTexture(m_wgpuSurface, &wgpuSurfaceTexture); + + WebGPUTextureWrapper wgpuTextureWrapper{wgpuSurfaceTexture.texture}; + + switch (wgpuSurfaceTexture.status) + { + case WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal: + case WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal: + break; + + case WGPUSurfaceGetCurrentTextureStatus_Outdated: + case WGPUSurfaceGetCurrentTextureStatus_Timeout: + return wgpuSurfaceTexture.status; + + case WGPUSurfaceGetCurrentTextureStatus_Lost: + LOG_WARNING_MESSAGE("Unable to present: swap chain surface is lost"); + return wgpuSurfaceTexture.status; + + case WGPUSurfaceGetCurrentTextureStatus_Error: + LOG_ERROR_MESSAGE("Unable to present: unknown error"); + return wgpuSurfaceTexture.status; + + default: + UNEXPECTED("Unexpected status"); + return wgpuSurfaceTexture.status; + } + + WGPUTextureFormat wgpuFormat = wgpuTextureGetFormat(wgpuTextureWrapper.Get()); + uint32_t wgpuSurfaceWidth = wgpuTextureGetWidth(wgpuTextureWrapper.Get()); + uint32_t wgpuSurfaceHeight = wgpuTextureGetHeight(wgpuTextureWrapper.Get()); + TextureDesc BackBufferDesc{}; - BackBufferDesc.Type = RESOURCE_DIM_TEX_2D; - BackBufferDesc.Width = m_SwapChainDesc.Width; - BackBufferDesc.Height = m_SwapChainDesc.Height; - BackBufferDesc.Format = m_SwapChainDesc.ColorBufferFormat; - BackBufferDesc.SampleCount = 1; - BackBufferDesc.Usage = USAGE_DEFAULT; - BackBufferDesc.BindFlags = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET; - BackBufferDesc.Name = "Main back buffer"; - - RefCntAutoPtr pBackBufferTex; - m_pRenderDevice->CreateTexture(BackBufferDesc, nullptr, &pBackBufferTex); - m_pBackBufferRTV = RefCntAutoPtr{pBackBufferTex->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET), IID_TextureViewWebGPU}; - m_pBackBufferSRV = RefCntAutoPtr{pBackBufferTex->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE), IID_TextureViewWebGPU}; + BackBufferDesc.Name = "Main back buffer"; + BackBufferDesc.Type = RESOURCE_DIM_TEX_2D; + BackBufferDesc.Width = wgpuSurfaceWidth; + BackBufferDesc.Height = wgpuSurfaceHeight; + BackBufferDesc.Format = WGPUFormatToTextureFormat(wgpuFormat); + BackBufferDesc.Usage = USAGE_DEFAULT; + BackBufferDesc.BindFlags = BIND_RENDER_TARGET; + + RefCntAutoPtr pBackBufferTexture; + m_pRenderDevice.RawPtr()->CreateTextureFromWebGPUTexture(wgpuTextureWrapper.Get(), BackBufferDesc, RESOURCE_STATE_UNDEFINED, &pBackBufferTexture); + + if (IsSRGBFormat(m_SwapChainDesc.ColorBufferFormat)) + { + TextureViewDesc BackBufferRTVDesc{}; + BackBufferRTVDesc.ViewType = TEXTURE_VIEW_RENDER_TARGET; + BackBufferRTVDesc.Format = m_SwapChainDesc.ColorBufferFormat; + + RefCntAutoPtr pBackBufferRTV; + pBackBufferTexture->CreateView(BackBufferRTVDesc, &pBackBufferRTV); + m_pBackBufferRTV = RefCntAutoPtr{pBackBufferRTV, IID_TextureViewWebGPU}; + } + else + { + m_pBackBufferRTV = RefCntAutoPtr{pBackBufferTexture->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET), IID_TextureViewWebGPU}; + } + + return wgpuSurfaceTexture.status; +} +void SwapChainWebGPUImpl::RequestAnimationFrame(Uint32 SyncInterval) +{ + RefCntAutoPtr pDeviceContext = m_wpDeviceContext.Lock(); + + if (m_SwapChainDesc.IsPrimary) + { + pDeviceContext->FinishFrame(); + m_pRenderDevice->ReleaseStaleResources(); + } + + WGPUSurfaceGetCurrentTextureStatus wgpuStatus = AcquireSurfaceTexture(); + + const bool EnableVSync = SyncInterval != 0; + if (wgpuStatus == WGPUSurfaceGetCurrentTextureStatus_Outdated || + wgpuStatus == WGPUSurfaceGetCurrentTextureStatus_Lost || + m_VSyncEnabled != EnableVSync) + { + m_VSyncEnabled = EnableVSync; + RecreateSwapChain(); + } +} + +void SwapChainWebGPUImpl::CreateDepthBufferView() +{ if (m_SwapChainDesc.DepthBufferFormat != TEX_FORMAT_UNKNOWN) { TextureDesc DepthBufferDesc{}; + DepthBufferDesc.Name = "Main depth buffer"; DepthBufferDesc.Type = RESOURCE_DIM_TEX_2D; - DepthBufferDesc.Width = BackBufferDesc.Width; - DepthBufferDesc.Height = BackBufferDesc.Height; + DepthBufferDesc.Width = m_SwapChainDesc.Width; + DepthBufferDesc.Height = m_SwapChainDesc.Height; DepthBufferDesc.Format = m_SwapChainDesc.DepthBufferFormat; DepthBufferDesc.SampleCount = 1; DepthBufferDesc.Usage = USAGE_DEFAULT; @@ -596,7 +411,7 @@ void SwapChainWebGPUImpl::CreateBuffersAndViews() DepthBufferDesc.ClearValue.Format = DepthBufferDesc.Format; DepthBufferDesc.ClearValue.DepthStencil.Depth = m_SwapChainDesc.DefaultDepthValue; DepthBufferDesc.ClearValue.DepthStencil.Stencil = m_SwapChainDesc.DefaultStencilValue; - DepthBufferDesc.Name = "Main depth buffer"; + RefCntAutoPtr pDepthBufferTex; m_pRenderDevice->CreateTexture(DepthBufferDesc, nullptr, &pDepthBufferTex); m_pDepthBufferDSV = RefCntAutoPtr{pDepthBufferTex->GetDefaultView(TEXTURE_VIEW_DEPTH_STENCIL), IID_TextureViewWebGPU}; @@ -608,8 +423,6 @@ void SwapChainWebGPUImpl::ReleaseSwapChainResources() if (!m_wgpuSurface) return; - m_pCmdPresent->ReleaseSwapChainResources(); - m_pBackBufferSRV.Release(); m_pBackBufferRTV.Release(); m_pDepthBufferDSV.Release(); } @@ -620,7 +433,14 @@ void SwapChainWebGPUImpl::RecreateSwapChain() { ReleaseSwapChainResources(); ConfigureSurface(); - CreateBuffersAndViews(); + CreateDepthBufferView(); + + WGPUSurfaceGetCurrentTextureStatus wgpuStatus = AcquireSurfaceTexture(); + if (wgpuStatus != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && + wgpuStatus != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) + { + LOG_ERROR("Failed to acquire the swap chain surface texture after recreation"); + } } catch (const std::runtime_error&) { diff --git a/Graphics/GraphicsEngineWebGPU/src/TextureWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/TextureWebGPUImpl.cpp index 3162d2d9c3..9728a7942c 100644 --- a/Graphics/GraphicsEngineWebGPU/src/TextureWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/TextureWebGPUImpl.cpp @@ -138,7 +138,6 @@ WGPUTextureDescriptor TextureDescToWGPUTextureDescriptor(const TextureDesc& return wgpuTextureDesc; } -#if !PLATFORM_EMSCRIPTEN static WGPUTextureUsage TextureViewTypeToWGPUTextureUsage(TEXTURE_VIEW_TYPE ViewType) { static_assert(TEXTURE_VIEW_NUM_VIEWS == 7, "Please update the switch below to handle the new view type"); @@ -166,7 +165,6 @@ static WGPUTextureUsage TextureViewTypeToWGPUTextureUsage(TEXTURE_VIEW_TYPE View return WGPUTextureUsage_None; } } -#endif WGPUTextureViewDescriptor TextureViewDescToWGPUTextureViewDescriptor(const TextureDesc& TexDesc, TextureViewDesc& ViewDesc, @@ -229,14 +227,10 @@ WGPUTextureViewDescriptor TextureViewDescToWGPUTextureViewDescriptor(const Textu wgpuTextureViewDesc.aspect = WGPUTextureAspect_All; } - // TODO: enable this as soon as Emscripten adds the usage member to WGPUTextureViewDescriptor - // https://github.com/emscripten-core/emscripten/issues/23945 -#if !PLATFORM_EMSCRIPTEN - if (ViewDesc.Format != TexDesc.Format) + if (ViewDesc.Format != TexDesc.Format || (static_cast(TexDesc.MiscFlags) & static_cast(TEXTURE_VIEW_FLAG_ALLOW_MIP_MAP_GENERATION))) { wgpuTextureViewDesc.usage = TextureViewTypeToWGPUTextureUsage(ViewDesc.ViewType); } -#endif return wgpuTextureViewDesc; } @@ -385,13 +379,14 @@ TextureWebGPUImpl::TextureWebGPUImpl(IReferenceCounters* pRefCounters, DstDepthStride // DstDepthStride ); - WGPUImageCopyBuffer wgpuSourceCopyInfo{}; + + WGPUTexelCopyBufferInfo wgpuSourceCopyInfo{}; wgpuSourceCopyInfo.layout.offset = DstSubResOffset; wgpuSourceCopyInfo.layout.bytesPerRow = static_cast(DstRawStride); wgpuSourceCopyInfo.layout.rowsPerImage = static_cast(DstDepthStride / DstRawStride); wgpuSourceCopyInfo.buffer = wgpuUploadBuffer.Get(); - WGPUImageCopyTexture wgpuDestinationCopyInfo{}; + WGPUTexelCopyTextureInfo wgpuDestinationCopyInfo{}; wgpuDestinationCopyInfo.texture = m_wgpuTexture.Get(); wgpuDestinationCopyInfo.mipLevel = MipIdx; wgpuDestinationCopyInfo.origin = {0, 0, LayerIdx}; @@ -527,6 +522,14 @@ void TextureWebGPUImpl::CreateViewInternal(const TextureViewDesc& ViewDesc, ITex ValidatedAndCorrectTextureViewDesc(m_Desc, UpdatedViewDesc); WGPUTextureViewDescriptor wgpuTextureViewDesc = TextureViewDescToWGPUTextureViewDescriptor(m_Desc, UpdatedViewDesc, m_pDevice); + WGPUTextureComponentSwizzleDescriptor wgpuSwizzleDesc{}; + if (!IsIdentityComponentMapping(UpdatedViewDesc.Swizzle)) + { + wgpuSwizzleDesc.chain.sType = WGPUSType_TextureComponentSwizzleDescriptor; + wgpuSwizzleDesc.swizzle = TextureComponentMappingToWGPUTextureComponentSwizzle(UpdatedViewDesc.Swizzle); + wgpuTextureViewDesc.nextInChain = &wgpuSwizzleDesc.chain; + } + WebGPUTextureViewWrapper wgpuTextureView{wgpuTextureCreateView(m_wgpuTexture.Get(), &wgpuTextureViewDesc)}; if (!wgpuTextureView) LOG_ERROR_AND_THROW("Failed to create WebGPU texture view ", " '", ViewDesc.Name ? ViewDesc.Name : "", '\''); diff --git a/Graphics/GraphicsEngineWebGPU/src/UploadMemoryManagerWebGPU.cpp b/Graphics/GraphicsEngineWebGPU/src/UploadMemoryManagerWebGPU.cpp index e43e7e206a..44405b09da 100644 --- a/Graphics/GraphicsEngineWebGPU/src/UploadMemoryManagerWebGPU.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/UploadMemoryManagerWebGPU.cpp @@ -36,7 +36,8 @@ namespace Diligent UploadMemoryManagerWebGPU::Page::Page(UploadMemoryManagerWebGPU& Mgr, size_t Size) : m_pMgr{&Mgr}, - m_Data(Size) + m_Data{new Uint8[Size]}, + m_DataSize{Size} { WGPUBufferDescriptor wgpuBufferDesc{}; wgpuBufferDesc.label = GetWGPUStringView("Upload memory page"); @@ -58,10 +59,13 @@ UploadMemoryManagerWebGPU::Page::Page(Page&& RHS) noexcept : m_pMgr{RHS.m_pMgr}, m_wgpuBuffer{std::move(RHS.m_wgpuBuffer)}, m_Data{std::move(RHS.m_Data)}, + m_DataSize{RHS.m_DataSize}, m_CurrOffset{RHS.m_CurrOffset} // clang-format on { - RHS = Page{}; + RHS.m_pMgr = nullptr; + RHS.m_DataSize = 0; + RHS.m_CurrOffset = 0; } UploadMemoryManagerWebGPU::Page& UploadMemoryManagerWebGPU::Page::operator=(Page&& RHS) noexcept @@ -72,9 +76,11 @@ UploadMemoryManagerWebGPU::Page& UploadMemoryManagerWebGPU::Page::operator=(Page m_pMgr = RHS.m_pMgr; m_wgpuBuffer = std::move(RHS.m_wgpuBuffer); m_Data = std::move(RHS.m_Data); + m_DataSize = RHS.m_DataSize; m_CurrOffset = RHS.m_CurrOffset; RHS.m_pMgr = nullptr; + RHS.m_DataSize = 0; RHS.m_CurrOffset = 0; return *this; @@ -91,7 +97,7 @@ UploadMemoryManagerWebGPU::Allocation UploadMemoryManagerWebGPU::Page::Allocate( Allocation Alloc; Alloc.Offset = AlignUp(m_CurrOffset, Alignment); Alloc.Size = AlignUp(Size, Alignment); - if (Alloc.Offset + Alloc.Size <= m_Data.size()) + if (Alloc.Offset + Alloc.Size <= m_DataSize) { Alloc.wgpuBuffer = m_wgpuBuffer; Alloc.pData = &m_Data[Alloc.Offset]; @@ -105,7 +111,7 @@ void UploadMemoryManagerWebGPU::Page::FlushWrites(WGPUQueue wgpuQueue) { if (m_CurrOffset > 0) { - wgpuQueueWriteBuffer(wgpuQueue, m_wgpuBuffer, 0, m_Data.data(), m_CurrOffset); + wgpuQueueWriteBuffer(wgpuQueue, m_wgpuBuffer, 0, m_Data.get(), m_CurrOffset); } } diff --git a/Graphics/GraphicsEngineWebGPU/src/WebGPUResourceBase.cpp b/Graphics/GraphicsEngineWebGPU/src/WebGPUResourceBase.cpp index 588d660410..f6a7aa2e3b 100644 --- a/Graphics/GraphicsEngineWebGPU/src/WebGPUResourceBase.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/WebGPUResourceBase.cpp @@ -183,11 +183,11 @@ void WebGPUResourceBase::FlushPendingWrites(StagingBufferInfo& Buffer) void WebGPUResourceBase::ProcessAsyncReadback(StagingBufferInfo& Buffer) { - auto MapAsyncCallback = [](WGPUBufferMapAsyncStatus MapStatus, void* pUserData) { - VERIFY_EXPR(pUserData != nullptr); - StagingBufferInfo& BufferInfo = *static_cast(pUserData); + auto MapAsyncCallback = [](WGPUMapAsyncStatus wgpuMapStatus, WGPUStringView Message, void* pUserData1, void* pUserData2) { + VERIFY_EXPR(pUserData1 != nullptr); + StagingBufferInfo& BufferInfo = *static_cast(pUserData1); - if (MapStatus == WGPUBufferMapAsyncStatus_Success) + if (wgpuMapStatus == WGPUMapAsyncStatus_Success) { // Do NOT use WGPU_WHOLE_MAP_SIZE due to https://github.com/emscripten-core/emscripten/issues/20538 const size_t MappedDataSize = BufferInfo.Resource.m_MappedData.size(); @@ -201,6 +201,10 @@ void WebGPUResourceBase::ProcessAsyncReadback(StagingBufferInfo& Buffer) } wgpuBufferUnmap(BufferInfo.wgpuBuffer.Get()); } + else + { + LOG_DEBUG_MESSAGE(DEBUG_MESSAGE_SEVERITY_ERROR, "WebGPU: ", WGPUStringViewToString(Message)); + } BufferInfo.pSyncPoint->Trigger(); @@ -211,8 +215,12 @@ void WebGPUResourceBase::ProcessAsyncReadback(StagingBufferInfo& Buffer) // Keep the resource alive until the callback is called m_Owner.AddRef(); // Do NOT use WGPU_WHOLE_MAP_SIZE due to https://github.com/emscripten-core/emscripten/issues/20538 - const size_t MappedDataSize = Buffer.Resource.m_MappedData.size(); - wgpuBufferMapAsync(Buffer.wgpuBuffer, WGPUMapMode_Read, 0, AlignUp(MappedDataSize, MappedRangeAlignment), MapAsyncCallback, &Buffer); + const size_t MappedDataSize = Buffer.Resource.m_MappedData.size(); + WGPUBufferMapCallbackInfo wgpuBufferMapCallbackInfo{}; + wgpuBufferMapCallbackInfo.callback = MapAsyncCallback; + wgpuBufferMapCallbackInfo.userdata1 = &Buffer; + wgpuBufferMapCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous; + wgpuBufferMapAsync(Buffer.wgpuBuffer, WGPUMapMode_Read, 0, AlignUp(MappedDataSize, MappedRangeAlignment), wgpuBufferMapCallbackInfo); } } // namespace Diligent diff --git a/Graphics/GraphicsEngineWebGPU/src/WebGPUTypeConversions.cpp b/Graphics/GraphicsEngineWebGPU/src/WebGPUTypeConversions.cpp index ffbff1b0bc..8462349fb9 100644 --- a/Graphics/GraphicsEngineWebGPU/src/WebGPUTypeConversions.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/WebGPUTypeConversions.cpp @@ -31,7 +31,7 @@ #include "DebugUtilities.hpp" #include "GraphicsAccessories.hpp" -#define WEBGPU_FORMAT_RANGE_SIZE (WGPUTextureFormat_RGBA16Snorm - WGPUTextureFormat_Undefined + 1) +#define WEBGPU_FORMAT_RANGE_SIZE (WGPUTextureFormat_ASTC12x12UnormSrgb - WGPUTextureFormat_Undefined + 1) namespace Diligent { @@ -954,4 +954,29 @@ WGPUVertexStepMode InputElementFrequencyToWGPUVertexStepMode(INPUT_ELEMENT_FREQU } } +WGPUComponentSwizzle TextureComponentSwizzleToWGPUComponentSwizzle(TEXTURE_COMPONENT_SWIZZLE Swizzle) +{ + static_assert(TEXTURE_COMPONENT_SWIZZLE_COUNT == 7, "Did you add a new swizzle type? Please handle it here."); + // clang-format off + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_IDENTITY) == WGPUComponentSwizzle_Undefined, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_IDENTITY enum."); + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_ZERO) == WGPUComponentSwizzle_Zero, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_ZERO enum."); + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_ONE) == WGPUComponentSwizzle_One, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_ONE enum."); + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_R) == WGPUComponentSwizzle_R, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_R enum."); + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_G) == WGPUComponentSwizzle_G, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_G enum."); + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_B) == WGPUComponentSwizzle_B, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_B enum."); + static_assert(static_cast(TEXTURE_COMPONENT_SWIZZLE_A) == WGPUComponentSwizzle_A, "Unexpected value of TEXTURE_COMPONENT_SWIZZLE_A enum."); + // clang-format on + return static_cast(Swizzle); +} + +WGPUTextureComponentSwizzle TextureComponentMappingToWGPUTextureComponentSwizzle(const TextureComponentMapping& Mapping) +{ + return WGPUTextureComponentSwizzle{ + TextureComponentSwizzleToWGPUComponentSwizzle(Mapping.R), + TextureComponentSwizzleToWGPUComponentSwizzle(Mapping.G), + TextureComponentSwizzleToWGPUComponentSwizzle(Mapping.B), + TextureComponentSwizzleToWGPUComponentSwizzle(Mapping.A) // + }; +} + } // namespace Diligent diff --git a/Graphics/GraphicsTools/include/GPUUploadManagerImpl.hpp b/Graphics/GraphicsTools/include/GPUUploadManagerImpl.hpp index 1b92e2ab29..e01a315e70 100644 --- a/Graphics/GraphicsTools/include/GPUUploadManagerImpl.hpp +++ b/Graphics/GraphicsTools/include/GPUUploadManagerImpl.hpp @@ -59,6 +59,8 @@ class GPUUploadManagerImpl final : public ObjectBase GPUUploadManagerImpl(IReferenceCounters* pRefCounters, const GPUUploadManagerCreateInfo& CI); ~GPUUploadManagerImpl(); + IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_GPUUploadManager, TBase); + virtual void DILIGENT_CALL_TYPE RenderThreadUpdate(IDeviceContext* pContext) override final; virtual bool DILIGENT_CALL_TYPE ScheduleBufferUpdate(const ScheduleBufferUpdateInfo& UpdateInfo) override final; @@ -149,7 +151,8 @@ class GPUUploadManagerImpl final : public ObjectBase void Seal(); void ExecutePendingOps(IDeviceContext* pContext, Uint64 FenceValue); - void Reset(IDeviceContext* pContext); + bool Reset(IDeviceContext* pContext); + bool IsValid() const; // Tries to set the page as enqueued for execution. // Returns true if the page was not previously enqueued, false otherwise. @@ -212,6 +215,7 @@ class GPUUploadManagerImpl final : public ObjectBase void* Map(IDeviceContext* pContext); void Unmap(IDeviceContext* pContext); void Reset(); + bool IsValid() const { return pTex != nullptr; } DynamicAtlasManager::Region Allocate(Uint32 Width, Uint32 Height); @@ -348,14 +352,14 @@ class GPUUploadManagerImpl final : public ObjectBase bool TryEnqueuePage(Page* P); void ProcessPagesToRelease(IDeviceContext* pContext); void AddFreePages(IDeviceContext* pContext); - void AddFreePage(Page* pPage) { m_FreePages.Push(pPage); } + void ReturnFreePage(Page* pPage); bool ScheduleUpdate(IDeviceContext* pContext, Uint32 UpdateSize, const void* pUpdateInfo, bool ScheduleUpdate(Page::Writer& Writer, const void* pUpdateInfo)); void ReleaseStagingBuffers(IDeviceContext* pContext); - void SignalPageRotated() { m_PageRotatedSignal.Tick(); } + void SignalPageRotated() { m_PagePoolChangedSignal.Tick(); } void SignalStop(); Uint32 GetPageSize() const { return m_PageSize; } @@ -370,7 +374,7 @@ class GPUUploadManagerImpl final : public ObjectBase std::atomic m_pCurrentPage{nullptr}; - Threading::TickSignal m_PageRotatedSignal; + Threading::TickSignal m_PagePoolChangedSignal; std::unordered_map> m_Pages; std::map m_PageSizeToCount; @@ -444,6 +448,9 @@ class GPUUploadManagerImpl final : public ObjectBase bool TryBeginScheduleUpdate() noexcept; void EndScheduleUpdate() noexcept; bool SetStopping() noexcept; + // Only call from context-owning paths: RenderThreadUpdate(), Stop(), or Schedule*Update() with non-null pContext. + bool SetOrValidateContext(IDeviceContext* pContext, const char* MethodName); + void StopInternal(IDeviceContext* pContext); static constexpr Uint32 SCHEDULE_STOP_BIT = 0x80000000u; static constexpr Uint32 SCHEDULE_COUNT_MASK = ~SCHEDULE_STOP_BIT; diff --git a/Graphics/GraphicsTools/interface/GPUUploadManager.h b/Graphics/GraphicsTools/interface/GPUUploadManager.h index b631eeffff..254b8a86e3 100644 --- a/Graphics/GraphicsTools/interface/GPUUploadManager.h +++ b/Graphics/GraphicsTools/interface/GPUUploadManager.h @@ -82,6 +82,7 @@ typedef struct GPUUploadManagerCreateInfo GPUUploadManagerCreateInfo; /// \warning Reentrancy / thread-safety: /// The callback is executed from inside IGPUUploadManager::ScheduleBufferUpdate(). /// The callback MUST NOT call back into the same IGPUUploadManager instance. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. typedef void (*WriteStagingBufferDataCallbackType)(void* pDstData, Uint32 NumBytes, void* pUserData); @@ -105,6 +106,7 @@ typedef void (*WriteStagingBufferDataCallbackType)(void* pDstData, Uint32 NumByt /// perform actions that may synchronously trigger RenderThreadUpdate() or otherwise /// re-enter the manager, as this may lead to deadlocks, unbounded recursion, or /// inconsistent internal state. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. /// /// If follow-up work is required, the callback should only enqueue work to be /// processed later (e.g. push a task into a user-owned queue) and return promptly. @@ -143,6 +145,7 @@ typedef void (*GPUBufferUploadEnqueuedCallbackType)(IBuffer* pDstBuffer, /// perform actions that may synchronously trigger RenderThreadUpdate() or otherwise /// re-enter the manager, as this may lead to deadlocks, unbounded recursion, or /// inconsistent internal state. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. /// /// If follow-up work is required, the callback should only enqueue work to be /// processed later (e.g. push a task into a user-owned queue) and return promptly. @@ -157,6 +160,8 @@ typedef void (*CopyStagingBufferCallbackType)(IDeviceContext* pContext, struct ScheduleBufferUpdateInfo { /// If calling ScheduleBufferUpdate() from the render thread, a pointer to the device context. + /// If no manager context has been set yet, this context becomes the manager context. + /// Otherwise, it must be the same as the manager context. /// If calling ScheduleBufferUpdate() from a worker thread, this parameter must be null. IDeviceContext* pContext DEFAULT_INITIALIZER(nullptr); @@ -280,6 +285,7 @@ typedef struct ScheduleBufferUpdateInfo ScheduleBufferUpdateInfo; /// \warning Reentrancy / thread-safety: /// The callback is executed from inside IGPUUploadManager::ScheduleTextureUpdate(). /// The callback MUST NOT call back into the same IGPUUploadManager instance. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. typedef void (*WriteStagingTextureDataCallbackType)(void* pDstData, Uint32 Stride, Uint32 DepthStride, @@ -307,6 +313,7 @@ typedef void (*WriteStagingTextureDataCallbackType)(void* pDstData, /// perform actions that may synchronously trigger RenderThreadUpdate() or otherwise /// re-enter the manager, as this may lead to deadlocks, unbounded recursion, or /// inconsistent internal state. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. /// /// If follow-up work is required, the callback should only enqueue work to be /// processed later (e.g. push a task into a user-owned queue) and return promptly. @@ -348,6 +355,7 @@ typedef void (*GPUTextureUploadEnqueuedCallbackType)(ITexture* pDstTexture, /// perform actions that may synchronously trigger RenderThreadUpdate() or otherwise /// re-enter the manager, as this may lead to deadlocks, unbounded recursion, or /// inconsistent internal state. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. /// /// If follow-up work is required, the callback should only enqueue work to be /// processed later (e.g. push a task into a user-owned queue) and return promptly. @@ -382,6 +390,7 @@ typedef void (*CopyStagingTextureCallbackType)(IDeviceContext* pCont /// If scheduling is abandoned, it may be executed with a null context pointer /// from inside ScheduleTextureUpdate() or the manager stop/destruction path. /// The callback MUST NOT call back into the same IGPUUploadManager instance. +/// The callback MUST NOT throw exceptions. It must handle any errors internally. typedef void (*CopyStagingD3D11TextureCallbackType)(IDeviceContext* pContext, Uint32 DstMipLevel, Uint32 DstSlice, @@ -396,6 +405,8 @@ typedef void (*CopyStagingD3D11TextureCallbackType)(IDeviceContext* pContext, struct ScheduleTextureUpdateInfo { /// If calling ScheduleTextureUpdate() from the render thread, a pointer to the device context. + /// If no manager context has been set yet, this context becomes the manager context. + /// Otherwise, it must be the same as the manager context. /// If calling ScheduleTextureUpdate() from a worker thread, this parameter must be null. IDeviceContext* pContext DEFAULT_INITIALIZER(nullptr); @@ -547,11 +558,15 @@ typedef struct GPUUploadManagerStats GPUUploadManagerStats; // clang-format off +// {1C5CF903-9E24-4B2C-9D63-FE63D49BE1F6} +static DILIGENT_CONSTEXPR INTERFACE_ID IID_GPUUploadManager = + { 0x1c5cf903, 0x9e24, 0x4b2c, { 0x9d, 0x63, 0xfe, 0x63, 0xd4, 0x9b, 0xe1, 0xf6 } }; + #define DILIGENT_INTERFACE_NAME IGPUUploadManager #include "../../../Primitives/interface/DefineInterfaceHelperMacros.h" #define IGPUUploadManagerInclusiveMethods \ - IDeviceObjectInclusiveMethods; \ + IObjectInclusiveMethods; \ IGPUUploadManagerMethods GPUUploadManager /// Asynchronous GPU upload manager @@ -563,6 +578,9 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// from worker threads, but only one thread is allowed to call RenderThreadUpdate() at a time. /// RenderThreadUpdate() must not be called concurrently with Stop(), and the device context /// used by the manager must not be used concurrently by other threads. + /// pContext must not be null. If no context was provided at creation, the first context + /// passed to RenderThreadUpdate() becomes the manager context. Otherwise, it must be + /// the same as the manager context. /// /// The method must be called periodically to process pending updates. If the method is not called, /// ScheduleBufferUpdate() or ScheduleTextureUpdate() may block indefinitely when there are no free @@ -578,8 +596,10 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// due to invalid parameters, a stopped manager, or an internal scheduling failure. /// If false is returned, any abandoned-scheduling callback is invoked before the method returns. /// - /// The method is thread-safe and can be called from multiple threads simultaneously with other calls to ScheduleBufferUpdate() - /// and RenderThreadUpdate(). + /// The method is thread-safe for worker-thread calls that use a null pContext. These calls can be made from multiple threads + /// simultaneously with other worker-thread ScheduleBufferUpdate() calls and RenderThreadUpdate(). + /// Calls that provide a non-null pContext use the device context and must be externally serialized with RenderThreadUpdate(), + /// Stop(), GetStats(), and other use of the same context. /// The caller must keep the upload manager alive for the entire duration of this call. Worker threads should /// hold their own strong reference to the manager if the manager may be stopped or released concurrently. /// Calls admitted before Stop() may complete; calls admitted after Stop() are ignored and return false. @@ -595,8 +615,9 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// call RenderThreadUpdate() to process pending buffer updates. If RenderThreadUpdate() is not called, the method may block indefinitely /// when there are no free pages available for new updates. /// - /// If the method is called from the render thread, the pContext parameter must be a pointer to the device context used to create the - /// GPU upload manager. If the method is called from the render thread with null pContext, it may never return. + /// If the method is called from the render thread, the pContext parameter must be a pointer + /// to the manager context. If no manager context has been set yet, pContext becomes the manager + /// context. If the method is called from the render thread with null pContext, it may never return. VIRTUAL bool METHOD(ScheduleBufferUpdate)(THIS_ const ScheduleBufferUpdateInfo REF UpdateInfo) PURE; @@ -609,8 +630,10 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// due to invalid parameters, a stopped manager, or an internal scheduling failure. /// If false is returned, any abandoned-scheduling callback is invoked before the method returns. /// - /// The method is thread-safe and can be called from multiple threads simultaneously with other calls to ScheduleTextureUpdate() - /// and RenderThreadUpdate(). + /// The method is thread-safe for worker-thread calls that use a null pContext. These calls can be made from multiple threads + /// simultaneously with other worker-thread ScheduleTextureUpdate() calls and RenderThreadUpdate(). + /// Calls that provide a non-null pContext use the device context and must be externally serialized with RenderThreadUpdate(), + /// Stop(), GetStats(), and other use of the same context. /// The caller must keep the upload manager alive for the entire duration of this call. Worker threads should /// hold their own strong reference to the manager if the manager may be stopped or released concurrently. /// Calls admitted before Stop() may complete; calls admitted after Stop() are ignored and return false. @@ -626,8 +649,9 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// call RenderThreadUpdate() to process pending texture updates. If RenderThreadUpdate() is not called, the method may block indefinitely /// when there are no free pages available for new updates. /// - /// If the method is called from the render thread, the pContext parameter must be a pointer to the device context used to create the - /// GPU upload manager. If the method is called from the render thread with null pContext, it may never return. + /// If the method is called from the render thread, the pContext parameter must be a pointer + /// to the manager context. If no manager context has been set yet, pContext becomes the manager + /// context. If the method is called from the render thread with null pContext, it may never return. VIRTUAL bool METHOD(ScheduleTextureUpdate)(THIS_ const ScheduleTextureUpdateInfo REF UpdateInfo) PURE; @@ -645,8 +669,7 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// Permanently stops the upload manager. /// - /// \param [in] pContext - Device context used to release staging resources. If null, the manager - /// uses the context provided at creation or during RenderThreadUpdate(), if any. + /// \param [in] pContext - Device context used to release staging resources. Must not be null. /// /// The method wakes any threads blocked in ScheduleBufferUpdate() or ScheduleTextureUpdate(). /// After this call, the manager must not be used for new upload scheduling, render-thread @@ -657,23 +680,27 @@ DILIGENT_BEGIN_INTERFACE(IGPUUploadManager, IObject) /// will invoke their callbacks with null handles during manager teardown. RenderThreadUpdate() /// and GetStats() calls after Stop() are misuse. /// - /// Stop() waits for admitted scheduling calls to return and releases staging resources using pContext - /// or the manager's stored context. The context must be the same context used by RenderThreadUpdate(), - /// if any, and must not be used concurrently while Stop() is executing. Internal stream/page objects - /// remain alive until the manager is destroyed. + /// Stop() waits for admitted scheduling calls to return and releases staging resources using pContext. + /// If the manager context has not been set yet, pContext becomes the manager context. + /// Otherwise, pContext must be the same as the manager context. The context must not be used + /// concurrently while Stop() is executing. Internal stream/page objects remain alive until the + /// manager is destroyed. /// - /// The intended use is to call Stop() once, normally from the render thread. - /// Multiple and parallel Stop() calls are allowed, but only the first call performs - /// the stop, wait, and staging-resource release. Any subsequent Stop() call from - /// any thread is a no-op and returns immediately, potentially before the first - /// Stop() call has completed. + /// Stop() is a render-thread/context-owning call. The first Stop() call must be made + /// from the render thread and must not race with RenderThreadUpdate(), GetStats(), + /// another Stop() call, or ScheduleBufferUpdate()/ScheduleTextureUpdate() calls that + /// provide a non-null device context. After Stop() has completed, subsequent Stop() + /// calls are no-ops and return immediately. /// /// The method may be called while worker threads are inside ScheduleBufferUpdate() or /// ScheduleTextureUpdate(). The manager must remain alive until these calls have returned. - /// Stop() must not be called concurrently with RenderThreadUpdate() or GetStats(). + /// The first Stop() call must not be made while other render-thread/context-owning + /// upload-manager calls are running. /// - /// If Stop() is not called explicitly, it is called by the manager destructor using the stored - /// context from whichever thread releases the last reference. + /// If Stop() is not called explicitly, the manager destructor performs the same internal stop + /// sequence using the stored manager context from whichever thread releases the last reference. + /// If no context has ever been provided to the manager, destructor cleanup proceeds without a + /// context; in this case no staging resources have been mapped through the manager. VIRTUAL void METHOD(Stop)(THIS_ IDeviceContext* pContext) PURE; }; diff --git a/Graphics/GraphicsTools/src/GPUUploadManagerImpl.cpp b/Graphics/GraphicsTools/src/GPUUploadManagerImpl.cpp index 8e2585bc05..eb13d1790b 100644 --- a/Graphics/GraphicsTools/src/GPUUploadManagerImpl.cpp +++ b/Graphics/GraphicsTools/src/GPUUploadManagerImpl.cpp @@ -359,7 +359,10 @@ GPUUploadManagerImpl::Page::StagingTextureAtlas::StagingTextureAtlas(IRenderDevi TexDesc.CPUAccessFlags = CPU_ACCESS_WRITE; pDevice->CreateTexture(TexDesc, nullptr, &pTex); - VERIFY_EXPR(pTex); + if (!pTex) + { + LOG_ERROR_MESSAGE("Failed to create GPU upload manager staging texture '", Name, "'"); + } } GPUUploadManagerImpl::Page::StagingTextureAtlas::~StagingTextureAtlas() @@ -369,6 +372,9 @@ GPUUploadManagerImpl::Page::StagingTextureAtlas::~StagingTextureAtlas() void* GPUUploadManagerImpl::Page::StagingTextureAtlas::Map(IDeviceContext* pContext) { + if (!pTex) + return nullptr; + pContext->TransitionResourceState({pTex, RESOURCE_STATE_UNKNOWN, RESOURCE_STATE_COPY_SOURCE, STATE_TRANSITION_FLAG_UPDATE_STATE}); MappedTextureSubresource MappedData; pContext->MapTextureSubresource(pTex, 0, 0, MAP_WRITE, MAP_FLAG_NONE, nullptr, MappedData); @@ -426,7 +432,10 @@ GPUUploadManagerImpl::Page::Page(UploadStream* pStream, IRenderDevice* pDevice, Desc.Usage = USAGE_STAGING; Desc.CPUAccessFlags = CPU_ACCESS_WRITE; pDevice->CreateBuffer(Desc, nullptr, &m_pStagingBuffer); - VERIFY_EXPR(m_pStagingBuffer != nullptr); + if (!m_pStagingBuffer) + { + LOG_ERROR_MESSAGE("Failed to create GPU upload manager staging buffer '", Name, "'"); + } } GPUUploadManagerImpl::Page::Page(UploadStream* pStream, IRenderDevice* pDevice, Uint32 Size, TEXTURE_FORMAT Format) : @@ -445,6 +454,11 @@ GPUUploadManagerImpl::Page::Page(UploadStream* pStream, IRenderDevice* pDevice, { } +bool GPUUploadManagerImpl::Page::IsValid() const +{ + return (m_pStagingBuffer != nullptr) || (m_pStagingAtlas != nullptr && m_pStagingAtlas->IsValid()); +} + GPUUploadManagerImpl::Page::~Page() { for (PendingOp Op; m_PendingOps.Dequeue(Op);) @@ -868,7 +882,7 @@ void GPUUploadManagerImpl::Page::ExecutePendingOps(IDeviceContext* pContext, Uin m_FenceValue = FenceValue; } -void GPUUploadManagerImpl::Page::Reset(IDeviceContext* pContext) +bool GPUUploadManagerImpl::Page::Reset(IDeviceContext* pContext) { VERIFY(DbgGetWriterCount() == 0, "All writers must finish before resetting the page"); VERIFY(m_PendingOps.IsEmpty(), "All pending operations must be executed before resetting the page"); @@ -898,8 +912,14 @@ void GPUUploadManagerImpl::Page::Reset(IDeviceContext* pContext) m_pData = m_pStagingAtlas->Map(pContext); } - VERIFY_EXPR(m_pData != nullptr); + if (m_pData == nullptr) + { + LOG_ERROR_MESSAGE("Failed to map GPU upload manager staging page"); + return false; + } } + + return true; } bool GPUUploadManagerImpl::Page::TryEnqueue() @@ -913,7 +933,7 @@ bool GPUUploadManagerImpl::Page::TryEnqueue() void GPUUploadManagerImpl::Page::Recycle() { - m_pStream->AddFreePage(this); + m_pStream->ReturnFreePage(this); } void GPUUploadManagerImpl::Page::ReleaseStagingBuffer(IDeviceContext* pContext) @@ -1151,7 +1171,10 @@ GPUUploadManagerImpl::GPUUploadManagerImpl(IReferenceCounters* pRefCounters, con Desc.Name = "GPU upload manager fence"; Desc.Type = FENCE_TYPE_CPU_WAIT_ONLY; m_pDevice->CreateFence(Desc, &m_pFence); - VERIFY_EXPR(m_pFence != nullptr); + if (!m_pFence) + { + LOG_ERROR_AND_THROW("Failed to create GPU upload manager fence"); + } if (m_DeviceType == RENDER_DEVICE_TYPE_D3D11) { @@ -1172,7 +1195,15 @@ void GPUUploadManagerImpl::UploadStream::ReleaseStagingBuffers(IDeviceContext* p void GPUUploadManagerImpl::UploadStream::SignalStop() { - m_PageRotatedSignal.RequestStop(); + m_PagePoolChangedSignal.RequestStop(); +} + +void GPUUploadManagerImpl::UploadStream::ReturnFreePage(Page* pPage) +{ + // Publish the page before waking schedulers so a waiter that observes the tick + // can immediately acquire the returned page. + m_FreePages.Push(pPage); + m_PagePoolChangedSignal.Tick(); } bool GPUUploadManagerImpl::TryBeginScheduleUpdate() noexcept @@ -1233,25 +1264,44 @@ GPUUploadManagerImpl::ScheduleUpdateGuard::~ScheduleUpdateGuard() m_pMgr->EndScheduleUpdate(); } -void GPUUploadManagerImpl::Stop(IDeviceContext* pContext) +bool GPUUploadManagerImpl::SetOrValidateContext(IDeviceContext* pContext, const char* MethodName) { - if (!SetStopping()) - return; - - m_Stopping.store(true, std::memory_order_release); + if (pContext == nullptr) + { + LOG_ERROR_MESSAGE("A valid context must be provided to ", MethodName, "()"); + return false; + } - if (pContext != nullptr) + if (!m_pContext) { - if (!m_pContext) - m_pContext = pContext; - else - DEV_CHECK_ERR(pContext == m_pContext, "The context provided to Stop must be the same as the one used to create the GPUUploadManagerImpl"); + m_pContext = pContext; + return true; } - else + + if (pContext != m_pContext) { - pContext = m_pContext; + LOG_ERROR_MESSAGE("The context provided to ", MethodName, "() must be the same as the one already used by the GPUUploadManagerImpl"); + return false; } + return true; +} + +void GPUUploadManagerImpl::Stop(IDeviceContext* pContext) +{ + if (!SetOrValidateContext(pContext, "Stop")) + return; + + StopInternal(pContext); +} + +void GPUUploadManagerImpl::StopInternal(IDeviceContext* pContext) +{ + if (!SetStopping()) + return; + + m_Stopping.store(true, std::memory_order_release); + if (m_pTextureStreams) { m_pTextureStreams->SetStopping(); @@ -1274,15 +1324,18 @@ void GPUUploadManagerImpl::Stop(IDeviceContext* pContext) // the destructor so the application controls the thread/phase where the manager's device // context is touched. Keep streams and pages alive until destruction, because pending // operations still own callback payloads that must be released during teardown. - for (UploadStreamUniquePtr& Stream : m_Streams) + if (pContext != nullptr) { - Stream->ReleaseStagingBuffers(pContext); + for (UploadStreamUniquePtr& Stream : m_Streams) + { + Stream->ReleaseStagingBuffers(pContext); + } } } GPUUploadManagerImpl::~GPUUploadManagerImpl() { - Stop(m_pContext); + StopInternal(m_pContext); // Pending page pointers are owned by the streams below. The manager is terminally // destroyed, so discard the queue nodes before destroying the pages. @@ -1293,22 +1346,14 @@ GPUUploadManagerImpl::~GPUUploadManagerImpl() void GPUUploadManagerImpl::RenderThreadUpdate(IDeviceContext* pContext) { - DEV_CHECK_ERR(pContext != nullptr, "A valid context must be provided to RenderThreadUpdate"); if (m_Stopping.load(std::memory_order_acquire)) { DEV_ERROR("GPU upload manager has been stopped"); return; } - if (!m_pContext) - { - // If no context was provided at creation, we can accept any context in RenderThreadUpdate, but it must be the same across calls. - m_pContext = pContext; - } - else - { - DEV_CHECK_ERR(pContext == m_pContext, "The context provided to RenderThreadUpdate must be the same as the one used to create the GPUUploadManagerImpl"); - } + if (!SetOrValidateContext(pContext, "RenderThreadUpdate")) + return; if (m_pTextureStreams) { @@ -1359,19 +1404,21 @@ bool GPUUploadManagerImpl::UploadStream::ScheduleUpdate(IDeviceContext* pContext const void* pUpdateInfo, bool ScheduleUpdate(Page::Writer& Writer, const void* pUpdateInfo)) { - DEV_CHECK_ERR(pContext == nullptr || pContext == m_Mgr.m_pContext, - "If a context is provided to ScheduleBufferUpdate/ScheduleTextureUpdate, it must be the same as the " - "one used to create the GPUUploadManagerImpl"); - bool IsFirstAttempt = true; bool AbortUpdate = false; bool UpdateScheduled = false; auto UpdatePendingSizeAndTryRotate = [&](Page* P) { - Uint64 PageEpoch = m_PageRotatedSignal.CurrentEpoch(); + Uint64 PageEpoch = m_PagePoolChangedSignal.CurrentEpoch(); // Note that for texture pages, UpdateSize is the texture dimension. if (!TryRotatePage(pContext, P, UpdateSize)) { + if (pContext != nullptr) + { + AbortUpdate = true; + return; + } + // Atomically update the max pending update size to ensure the next page is large enough AtomicMax(m_MaxPendingUpdateSize, UpdateSize, std::memory_order_acq_rel); if (IsFirstAttempt) @@ -1379,7 +1426,7 @@ bool GPUUploadManagerImpl::UploadStream::ScheduleUpdate(IDeviceContext* pContext m_TotalPendingUpdateSize.fetch_add(UpdateSize, std::memory_order_acq_rel); IsFirstAttempt = false; } - AbortUpdate = m_PageRotatedSignal.WaitNext(PageEpoch) == 0; + AbortUpdate = m_PagePoolChangedSignal.WaitNext(PageEpoch) == 0; } }; @@ -1449,6 +1496,9 @@ bool GPUUploadManagerImpl::ScheduleBufferUpdate(const ScheduleBufferUpdateInfo& return false; } + if (UpdateInfo.pContext != nullptr && !SetOrValidateContext(UpdateInfo.pContext, "ScheduleBufferUpdate")) + return false; + if (!ValidateBufferUpdate(UpdateInfo)) return false; @@ -1486,6 +1536,9 @@ bool GPUUploadManagerImpl::ScheduleTextureUpdate(const ScheduleTextureUpdateInfo return false; } + if (UpdateInfo.pContext != nullptr && !SetOrValidateContext(UpdateInfo.pContext, "ScheduleTextureUpdate")) + return false; + const bool HasCopyCallback = UseD3D11TextureCallback ? UpdateInfo.CopyD3D11Texture != nullptr : @@ -1570,11 +1623,18 @@ GPUUploadManagerImpl::Page* GPUUploadManagerImpl::UploadStream::CreatePage(IDevi std::make_unique(this, m_Mgr.m_pDevice, PageSize, m_Format) : std::make_unique(this, m_Mgr.m_pDevice, PageSize); + if (!NewPage->IsValid()) + return nullptr; + Page* P = NewPage.get(); if (pContext != nullptr) { - P->Reset(pContext); + if (!P->Reset(pContext)) + { + return nullptr; + } } + m_Pages.emplace(P, std::move(NewPage)); m_PageSizeToCount[PageSize]++; m_PeakPageCount = std::max(m_PeakPageCount, static_cast(m_Pages.size())); @@ -1621,7 +1681,7 @@ bool GPUUploadManagerImpl::UploadStream::TryRotatePage(IDeviceContext* pContext, // free list only if sealing observes no active writers; otherwise the // last writer will recycle the empty page through TryEnqueuePage(). if (Fresh->TrySeal() == Page::SealStatus::Ready) - m_FreePages.Push(Fresh); + ReturnFreePage(Fresh); return true; // Rotation happened by someone else } @@ -1629,7 +1689,7 @@ bool GPUUploadManagerImpl::UploadStream::TryRotatePage(IDeviceContext* pContext, if (ExpectedCurrent != nullptr && ExpectedCurrent->TrySeal() == Page::SealStatus::Ready) TryEnqueuePage(ExpectedCurrent); - m_PageRotatedSignal.Tick(); + m_PagePoolChangedSignal.Tick(); return true; } @@ -1646,7 +1706,7 @@ bool GPUUploadManagerImpl::UploadStream::TryEnqueuePage(Page* P) else { P->Reset(nullptr); - m_FreePages.Push(P); + ReturnFreePage(P); } return true; } @@ -1666,8 +1726,14 @@ void GPUUploadManagerImpl::ReclaimCompletedPages(IDeviceContext* pContext) Page* P = m_InFlightPages[i]; if (P->GetFenceValue() <= CompletedFenceValue) { - P->Reset(pContext); - m_TmpPages.push_back(P); + if (P->Reset(pContext)) + { + m_TmpPages.push_back(P); + } + else + { + m_InFlightPages[NewInFlightPageCount++] = P; + } } else { diff --git a/Graphics/ShaderTools/CMakeLists.txt b/Graphics/ShaderTools/CMakeLists.txt index 0187749cc2..8a31f2d966 100644 --- a/Graphics/ShaderTools/CMakeLists.txt +++ b/Graphics/ShaderTools/CMakeLists.txt @@ -174,6 +174,14 @@ if(ENABLE_SPIRV) PRIVATE spirv-cross-core ) + if(TARGET spirv-cross-glsl) + target_link_libraries(Diligent-ShaderTools + PRIVATE + spirv-cross-glsl + ) + target_compile_definitions(Diligent-ShaderTools PRIVATE DILIGENT_USE_SPIRV_CROSS_GLSL=1) + endif() + if(SPIRV_CROSS_NAMESPACE_OVERRIDE) target_compile_definitions(Diligent-ShaderTools PUBLIC DILIGENT_SPIRV_CROSS_NAMESPACE=${SPIRV_CROSS_NAMESPACE_OVERRIDE}) endif() @@ -210,9 +218,7 @@ if(ENABLE_WGSL) target_link_libraries(Diligent-ShaderTools PRIVATE libtint - # We include this library because when building for Emscripten, libtint does not include this library in the dependency list - tint_lang_wgsl_inspector - ) + ) endif() set(HLSL_DEFINITIONS include/HLSLDefinitions.fxh) @@ -241,7 +247,11 @@ add_custom_command(OUTPUT ${HLSL_DEFINITIONS_INC} # We must use full path here! VERBATIM ) -set_common_target_properties(Diligent-ShaderTools) +if(ENABLE_WGSL) + set_common_target_properties(Diligent-ShaderTools 20) +else() + set_common_target_properties(Diligent-ShaderTools) +endif() source_group("src" FILES ${SOURCE}) source_group("include" FILES ${INCLUDE}) diff --git a/Graphics/ShaderTools/include/GLSLParsingTools.hpp b/Graphics/ShaderTools/include/GLSLParsingTools.hpp index 6ba4f85360..e63678c8e8 100644 --- a/Graphics/ShaderTools/include/GLSLParsingTools.hpp +++ b/Graphics/ShaderTools/include/GLSLParsingTools.hpp @@ -28,6 +28,7 @@ #include "ParsingTools.hpp" #include "GraphicsTypes.h" +#include "SPIRVUtils.hpp" namespace Diligent { @@ -111,6 +112,69 @@ std::string ExtractGLSLImageFormatFromComment(const InteratorType& Start, const /// \return Texture format, or TEXTURE_FORMAT_UNKNOWN if the format is not recognized. TEXTURE_FORMAT ParseGLSLImageFormat(const std::string& Format); + +/// Extracts GLSL access mode from the comment, e.g.: +/// /* access = read_write */ +/// ^ ^ +/// Start End +/// The function returns "read_write" +/// If the comment does not contain access mode specifier, the function returns an empty string. +/// /// \param[in] Start - iterator to the beginning of the comment +/// /// \param[in] End - iterator to the end of the comment +/// /// \return GLSL access mode +template +std::string ExtractGLSLAccessModeFromComment(const IteratorType& Start, const IteratorType& End) +{ + // /* access = read_write */ + // ^ + auto Pos = SkipDelimiters(Start, End); + if (Pos == End) + return {}; + // /* access = read_write */ + // ^ + if (*Pos != '/') + return {}; + ++Pos; + // /* access = read_write */ + // ^ + // // access = read_write + // ^ + if (Pos == End || (*Pos != '/' && *Pos != '*')) + return {}; + ++Pos; + // /* access = read_write */ + // ^ + Pos = SkipDelimiters(Pos, End); + if (Pos == End) + return {}; + // /* access = read_write */ + // ^ + if (!SkipString(Pos, End, "access", Pos)) + return {}; + // /* access = read_write */ + // ^ + Pos = SkipDelimiters(Pos, End); + if (Pos == End) + return {}; + // /* access = read_write */ + // ^ + if (*Pos != '=') + return {}; + ++Pos; + // /* access = read_write */ + // ^ + Pos = SkipDelimiters(Pos, End); + if (Pos == End) + return {}; + // /* access = read_write */ + // ^ + auto AccessModeEndPos = SkipIdentifier(Pos, End); + return {Pos, AccessModeEndPos}; +} + + +IMAGE_ACCESS_MODE ParseGLSLImageAccessMode(const std::string& AccessModeStr); + } // namespace Parsing } // namespace Diligent diff --git a/Graphics/ShaderTools/include/HLSLParsingTools.hpp b/Graphics/ShaderTools/include/HLSLParsingTools.hpp index c3d51c504c..0a0ba939a9 100644 --- a/Graphics/ShaderTools/include/HLSLParsingTools.hpp +++ b/Graphics/ShaderTools/include/HLSLParsingTools.hpp @@ -31,6 +31,7 @@ #include "ParsingTools.hpp" #include "GraphicsTypes.h" #include "HashUtils.hpp" +#include "SPIRVUtils.hpp" namespace Diligent { @@ -38,21 +39,36 @@ namespace Diligent namespace Parsing { -/// Parses HLSL source code and extracts image formats from RWTexture comments, for example: +/// Parses HLSL source code and extracts image formats and access modes +/// from RWTexture comments. Annotations are expected inside the RWTexture +/// template argument list, for example: +/// /// HLSL: -/// RWTexture2D g_Tex2D; -/// RWTexture3D g_Tex3D; +/// RWTexture2D g_Tex2D; +/// RWTexture3D g_Tex3D; +/// RWTexture2D g_Tex2D_Read; +/// /// Output: /// { -/// {"g_Tex2D", TEX_FORMAT_RGBA8_UNORM}, -/// {"g_Tex3D", TEX_FORMAT_RG16_FLOAT} -/// } +/// { "g_Tex2D", { TEX_FORMAT_RGBA8_UNORM, IMAGE_ACCESS_MODE_READ_WRITE } }, +/// { "g_Tex3D", { TEX_FORMAT_RG16_FLOAT, IMAGE_ACCESS_MODE_WRITE } }, +/// { "g_Tex2D_Read", { TEX_FORMAT_RGBA8_UNORM, IMAGE_ACCESS_MODE_READ } } +/// } +/// +/// The following comment patterns are recognized: +/// /*format=*/ +/// /*access=read*/ +/// /*access=write*/ +/// /*access=read_write*/ +/// +/// If no access comment is present, the access mode defaults to IMAGE_ACCESS_MODE_UNKNOWN. /// /// \param[in] HLSLSource - HLSL source code. -/// \return A map containing image formats extracted from RWTexture comments. +/// \return A map that associates RWTexture variable names with the image format +/// and access mode extracted from their comments. /// -/// \remarks Only texture declarations in global scope are processed. -std::unordered_map ExtractGLSLImageFormatsFromHLSL(const std::string& HLSLSource); +/// \remarks Only RWTexture declarations in global scope are processed. +std::unordered_map ExtractGLSLImageFormatsAndAccessModeFromHLSL(const std::string& HLSLSource); } // namespace Parsing diff --git a/Graphics/ShaderTools/include/SPIRVUtils.hpp b/Graphics/ShaderTools/include/SPIRVUtils.hpp index 8d9a2af3e6..0f43ca352c 100644 --- a/Graphics/ShaderTools/include/SPIRVUtils.hpp +++ b/Graphics/ShaderTools/include/SPIRVUtils.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 Diligent Graphics LLC + * Copyright 2024-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,13 +35,48 @@ namespace Diligent { +/// Defines image access modes in HLSL. +enum IMAGE_ACCESS_MODE +{ + IMAGE_ACCESS_MODE_UNKNOWN, + IMAGE_ACCESS_MODE_READ, + IMAGE_ACCESS_MODE_WRITE, + IMAGE_ACCESS_MODE_READ_WRITE +}; + +/// Stores the image format and access mode for an RWTexture declared in HLSL. +struct ImageFormatAndAccess +{ + TEXTURE_FORMAT Format = TEX_FORMAT_UNKNOWN; + IMAGE_ACCESS_MODE AccessMode = IMAGE_ACCESS_MODE_UNKNOWN; + + ImageFormatAndAccess() noexcept = default; + + constexpr ImageFormatAndAccess(TEXTURE_FORMAT _Format, + IMAGE_ACCESS_MODE _AccessMode = IMAGE_ACCESS_MODE_UNKNOWN) noexcept : + Format{_Format}, AccessMode{_AccessMode} {} + + bool operator==(const ImageFormatAndAccess& rhs) const + { + return Format == rhs.Format && AccessMode == rhs.AccessMode; + } + + bool operator!=(const ImageFormatAndAccess& rhs) const + { + return !(*this == rhs); + } +}; + /// Patches image format declarations in the SPIRV code using the provided mapping. /// /// \param [in] SPIRV - SPIRV code. /// \param [in] ImageFormats - Mapping from image format names to texture formats. /// /// \return Patched SPIRV code. -std::vector PatchImageFormats(const std::vector& SPIRV, - const std::unordered_map& ImageFormats); +std::vector PatchImageFormatsAndAccessModes(const std::vector& SPIRV, + const std::unordered_map& ImageInfos); + +/// Warms up SPIRV-Cross GLSL compiler to initialize internal static data before concurrent use. +void WarmUpSPIRVCrossGLSLCompiler(); } // namespace Diligent diff --git a/Graphics/ShaderTools/include/WGSLShaderResources.hpp b/Graphics/ShaderTools/include/WGSLShaderResources.hpp index 9266b31436..302e02f0c2 100644 --- a/Graphics/ShaderTools/include/WGSLShaderResources.hpp +++ b/Graphics/ShaderTools/include/WGSLShaderResources.hpp @@ -37,6 +37,7 @@ #include #include +#include #include "Shader.h" #include "PipelineState.h" diff --git a/Graphics/ShaderTools/include/WGSLUtils.hpp b/Graphics/ShaderTools/include/WGSLUtils.hpp index e4304a2a12..13c3a3b9e0 100644 --- a/Graphics/ShaderTools/include/WGSLUtils.hpp +++ b/Graphics/ShaderTools/include/WGSLUtils.hpp @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -56,6 +57,11 @@ std::string RemapWGSLResourceBindings(const std::string& WGSL, const WGSLResourceMapping& ResMapping, const char* EmulatedArrayIndexSuffix); +/// New version of Tint can't translate SPIR-V to WGSL with certain HLSL-specific functionality, +void StripGoogleHlslFunctionality(std::vector& SPIRV); + +/// Ensures the module declares `OpCapability StorageImageExtendedFormats`, which is required when using extended storage image formats +void AddStorageImageExtendedFormats(std::vector& SPIRV); /// When WGSL is generated from SPIR-V, the names of resources may be mangled /// diff --git a/Graphics/ShaderTools/src/GLSLParsingTools.cpp b/Graphics/ShaderTools/src/GLSLParsingTools.cpp index c23dd0bb7e..6d4ec389df 100644 --- a/Graphics/ShaderTools/src/GLSLParsingTools.cpp +++ b/Graphics/ShaderTools/src/GLSLParsingTools.cpp @@ -236,6 +236,18 @@ TEXTURE_FORMAT ParseGLSLImageFormat(const std::string& Format) return TEX_FORMAT_UNKNOWN; } +IMAGE_ACCESS_MODE ParseGLSLImageAccessMode(const std::string& AccessMode) +{ + if (AccessMode == "read") + return IMAGE_ACCESS_MODE_READ; + else if (AccessMode == "write") + return IMAGE_ACCESS_MODE_WRITE; + else if (AccessMode == "read_write") + return IMAGE_ACCESS_MODE_READ_WRITE; + else + return IMAGE_ACCESS_MODE_UNKNOWN; +} + } // namespace Parsing } // namespace Diligent diff --git a/Graphics/ShaderTools/src/HLSLParsingTools.cpp b/Graphics/ShaderTools/src/HLSLParsingTools.cpp index f31da3c871..3518c53849 100644 --- a/Graphics/ShaderTools/src/HLSLParsingTools.cpp +++ b/Graphics/ShaderTools/src/HLSLParsingTools.cpp @@ -34,40 +34,79 @@ namespace Diligent namespace Parsing { -static std::pair ParseRWTextureDefinition(HLSLTokenizer::TokenListType::const_iterator& Token, - HLSLTokenizer::TokenListType::const_iterator End) + +static void ExtractAnnotationsFromDelimiter(const std::string& Delim, ImageFormatAndAccess& Info) +{ + size_t Pos = 0; + while (Pos < Delim.size()) + { + size_t Begin = Delim.find("/*", Pos); + if (Begin == std::string::npos) + break; + + size_t End = Delim.find("*/", Begin + 2); + if (End == std::string::npos) + break; + + auto CmtBeg = Delim.begin() + Begin; + auto CmtEnd = Delim.begin() + End + 2; + + + // Try to extract image format from a comment like: + // /*format=rg8*/ + if (Info.Format == TEX_FORMAT_UNKNOWN) + { + std::string Format = ExtractGLSLImageFormatFromComment(CmtBeg, CmtEnd); + if (!Format.empty()) + Info.Format = ParseGLSLImageFormat(Format); + } + + + // Try to extract access mode from a comment like: + // /*access=read*/ + // /*access=write*/ + // /*access=read_write*/ + std::string Access = ExtractGLSLAccessModeFromComment(CmtBeg, CmtEnd); + if (!Access.empty()) + Info.AccessMode = ParseGLSLImageAccessMode(Access); + + Pos = End + 2; + } +} + +static std::pair ParseRWTextureDefinition( + HLSLTokenizer::TokenListType::const_iterator& Token, + HLSLTokenizer::TokenListType::const_iterator End) { - // RWTexture2D g_RWTex; - // ^ + // RWTexture2D g_RWTex; + // ^ - RWTexture* keyword ++Token; - // RWTexture2D g_RWTex; - // ^ + // RWTexture2D g_RWTex; + // ^ - '<' after RWTexture* if (Token == End || Token->Literal != "<") return {}; - TEXTURE_FORMAT Fmt = TEX_FORMAT_UNKNOWN; + ImageFormatAndAccess Info; /// Format = UNKNOWN, AccessMode = UNKNOWN by default + + // Walk through all tokens inside the '<' ... '>' list and look for + // comments that annotate format and access mode. while (Token != End && Token->Literal != ">") { ++Token; if (Token != End) { - // RWTexture2D< /*format=rg8*/ unorm float4> g_RWTex; - // ^ - // RWTexture2D< unorm /*format=rg8*/ float4> g_RWTex; - // ^ - // RWTexture2D< unorm float4 /*format=rg8*/> g_RWTex; - // ^ - std::string FormatStr = ExtractGLSLImageFormatFromComment(Token->Delimiter.begin(), Token->Delimiter.end()); - if (!FormatStr.empty()) + /// Any comments preceding the current token are stored in Delimiter. + if (!Token->Delimiter.empty()) { - Fmt = ParseGLSLImageFormat(FormatStr); + ExtractAnnotationsFromDelimiter(Token->Delimiter, Info); } } } - // RWTexture2D g_RWTex; - // ^ + // RWTexture2D g_RWTex; + // ^ - '>' reached + if (Token == End) return {}; @@ -75,20 +114,21 @@ static std::pair ParseRWTextureDefinition(HLSLToken if (Token == End) return {}; - // RWTexture2D g_RWTex; - // ^ + // RWTexture2D g_RWTex; + // ^ - texture variable identifier if (Token->Type != HLSLTokenType::Identifier) return {}; - return {Token->Literal, Fmt}; + return {Token->Literal, Info}; } -std::unordered_map ExtractGLSLImageFormatsFromHLSL(const std::string& HLSLSource) + +std::unordered_map ExtractGLSLImageFormatsAndAccessModeFromHLSL(const std::string& HLSLSource) { HLSLTokenizer Tokenizer; const HLSLTokenizer::TokenListType Tokens = Tokenizer.Tokenize(HLSLSource); - std::unordered_map ImageFormats; + std::unordered_map ImageFormats; auto Token = Tokens.begin(); int ScopeLevel = 0; @@ -119,13 +159,29 @@ std::unordered_map ExtractGLSLImageFormatsFrom Token->Type == HLSLTokenType::kw_RWTexture2DArray || Token->Type == HLSLTokenType::kw_RWTexture3D)) { - auto NameAndFmt = ParseRWTextureDefinition(Token, Tokens.end()); - if (NameAndFmt.second != TEX_FORMAT_UNKNOWN) + auto NameAndInfo = ParseRWTextureDefinition(Token, Tokens.end()); + const auto& Name = NameAndInfo.first; + const auto& Info = NameAndInfo.second; + + const bool HasFormat = (Info.Format != TEX_FORMAT_UNKNOWN); + const bool HasNonDefaultAccess = (Info.AccessMode != IMAGE_ACCESS_MODE_UNKNOWN); + + if (HasFormat || HasNonDefaultAccess) { - auto it_inserted = ImageFormats.emplace(NameAndFmt); - if (!it_inserted.second && it_inserted.first->second != NameAndFmt.second) + auto InsertedIt = ImageFormats.emplace(Name, Info); + if (!InsertedIt.second) { - LOG_WARNING_MESSAGE("Different formats are specified for the same RWTexture '", NameAndFmt.first, "'. Note that the parser does not support preprocessing."); + const auto& Existing = InsertedIt.first->second; + + if (Existing.Format != Info.Format) + { + LOG_WARNING_MESSAGE("Different formats are specified for the same RWTexture '", Name, "'. Note that the parser does not support preprocessing."); + } + + if (Existing.AccessMode != Info.AccessMode) + { + LOG_WARNING_MESSAGE("Different access modes are specified for the same RWTexture '", Name, "'. Note that the parser does not support preprocessing."); + } } } } diff --git a/Graphics/ShaderTools/src/SPIRVUtils.cpp b/Graphics/ShaderTools/src/SPIRVUtils.cpp index 587a66422a..f48ce74a43 100644 --- a/Graphics/ShaderTools/src/SPIRVUtils.cpp +++ b/Graphics/ShaderTools/src/SPIRVUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 Diligent Graphics LLC + * Copyright 2024-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,16 @@ #include "SPIRVUtils.hpp" #include "SPIRVShaderResources.hpp" // required for diligent_spirv_cross +#include "HLSLParsingTools.hpp" #include "spirv_cross.hpp" +#if DILIGENT_USE_SPIRV_CROSS_GLSL +# include "spirv_glsl.hpp" + +# include +#endif + namespace Diligent { @@ -95,64 +102,184 @@ static spv::ImageFormat TextureFormatToSpvImageFormat(TEXTURE_FORMAT Format) } } -std::vector PatchImageFormats(const std::vector& SPIRV, - const std::unordered_map& ImageFormats) -{ - diligent_spirv_cross::Compiler Compiler{SPIRV}; +std::vector PatchImageFormatsAndAccessModes(const std::vector& SPIRV, const std::unordered_map& ImageInfos) +{ + diligent_spirv_cross::Compiler Compiler{SPIRV}; diligent_spirv_cross::ShaderResources resources = Compiler.get_shader_resources(); std::unordered_map ImageTypeIdToFormat; - for (uint32_t i = 0; i < SPIRV.size(); ++i) + ImageTypeIdToFormat.reserve(resources.storage_images.size()); + + uint32_t FirstFunctionWordIndex = 0; + uint32_t LastDecorateEnd = 0; + + constexpr size_t SpirvHeaderSize = 5; + uint32_t InstructionPointer = static_cast(SpirvHeaderSize); + while (InstructionPointer < SPIRV.size()) { // OpTypeImage // 0 1 2 3 4 5 6 7 8 9 // | OpCode | Result | Sampled Type | Dim | Depth | Arrayed | MS | Sampled | Image Format | Access Qualifier constexpr uint32_t ImageFormatOffset = 8; - uint32_t OpCode = SPIRV[i] & 0xFFFF; - if (OpCode == spv::OpTypeImage && i + ImageFormatOffset < SPIRV.size()) + const uint32_t Instruction = SPIRV[InstructionPointer]; + const uint16_t WordCount = static_cast(Instruction >> 16); + const uint16_t OpCode = static_cast(Instruction & 0xFFFFu); + + if (WordCount == 0 || InstructionPointer + WordCount > SPIRV.size()) + break; + + if (OpCode == spv::OpTypeImage) + { + const uint32_t ImageTypeId = SPIRV[InstructionPointer + 1]; + if (WordCount > ImageFormatOffset) + ImageTypeIdToFormat[ImageTypeId] = InstructionPointer + ImageFormatOffset; + } + else if (OpCode == spv::OpFunction && FirstFunctionWordIndex == 0) { - const uint32_t ImageTypeId = SPIRV[i + 1]; - ImageTypeIdToFormat[ImageTypeId] = i + ImageFormatOffset; + FirstFunctionWordIndex = InstructionPointer; } + else if (OpCode == spv::OpDecorate) + { + LastDecorateEnd = InstructionPointer + WordCount; + } + + InstructionPointer += WordCount; } std::vector PatchedSPIRV = SPIRV; + + std::vector NewDecorations; + NewDecorations.reserve(resources.storage_images.size() * 3); + for (const diligent_spirv_cross::Resource& Img : resources.storage_images) { - const diligent_spirv_cross::SPIRType& type = Compiler.get_type(Img.type_id); - if (type.image.dim == spv::Dim1D || - type.image.dim == spv::Dim2D || - type.image.dim == spv::Dim3D) + const diligent_spirv_cross::SPIRType& SpvType = Compiler.get_type(Img.type_id); + if (SpvType.image.dim == spv::Dim1D || + SpvType.image.dim == spv::Dim2D || + SpvType.image.dim == spv::Dim3D) { - auto FormatIt = ImageFormats.find(HashMapStringKey{Img.name.c_str()}); - if (FormatIt != ImageFormats.end()) + auto InfoIt = ImageInfos.find(HashMapStringKey{Img.name.c_str()}); + if (InfoIt == ImageInfos.end()) + continue; + + const spv::ImageFormat SpvFormat = TextureFormatToSpvImageFormat(InfoIt->second.Format); + if (SpvFormat != spv::ImageFormatUnknown) { - spv::ImageFormat spvFormat = TextureFormatToSpvImageFormat(FormatIt->second); - if (spvFormat != spv::ImageFormatUnknown) + auto FormatOffsetIt = ImageTypeIdToFormat.find(Img.base_type_id); + if (FormatOffsetIt != ImageTypeIdToFormat.end() && + FormatOffsetIt->second < PatchedSPIRV.size()) { - auto FormatOffsetIt = ImageTypeIdToFormat.find(Img.base_type_id); - if (FormatOffsetIt != ImageTypeIdToFormat.end()) + const uint32_t ImageFormatOffset = FormatOffsetIt->second; + uint32_t& FormatWord = PatchedSPIRV[ImageFormatOffset]; + if (FormatWord != static_cast(SpvType.image.format) && + FormatWord != static_cast(SpvFormat)) { - const uint32_t ImageFormatOffset = FormatOffsetIt->second; - uint32_t& FormatWord = PatchedSPIRV[ImageFormatOffset]; - if (FormatWord != static_cast(type.image.format) && - FormatWord != static_cast(spvFormat)) - { - LOG_ERROR_MESSAGE("Inconsistent formats encountered while patching format for image '", Img.name, - "'.\nThis likely is the result of the same-format textures using inconsistent format specifiers in HLSL, for example:" - "\n RWTexture2D g_RWTex1;" - "\n RWTexture2D g_RWTex2;"); - } - FormatWord = spvFormat; + LOG_ERROR_MESSAGE("Inconsistent formats encountered while patching format for image '", Img.name, + "'.\nThis likely is the result of the same-format textures using inconsistent format specifiers in HLSL, for example:" + "\n RWTexture2D g_RWTex1;" + "\n RWTexture2D g_RWTex2;"); } + FormatWord = static_cast(SpvFormat); } } + + switch (InfoIt->second.AccessMode) + { + case IMAGE_ACCESS_MODE_READ: + { + constexpr uint32_t WordCount = 3; + constexpr uint32_t OpCode = static_cast(spv::OpDecorate); + + NewDecorations.push_back((WordCount << 16) | OpCode); + NewDecorations.push_back(Img.id); + NewDecorations.push_back(static_cast(spv::DecorationNonWritable)); + break; + } + + case IMAGE_ACCESS_MODE_WRITE: + { + constexpr uint32_t WordCount = 3; + constexpr uint32_t OpCode = static_cast(spv::OpDecorate); + + NewDecorations.push_back((WordCount << 16) | OpCode); + NewDecorations.push_back(Img.id); + NewDecorations.push_back(static_cast(spv::DecorationNonReadable)); + break; + } + + case IMAGE_ACCESS_MODE_READ_WRITE: + case IMAGE_ACCESS_MODE_UNKNOWN: + default: + break; + } + } + } + + if (!NewDecorations.empty()) + { + size_t InsertPos = PatchedSPIRV.size(); + + if (LastDecorateEnd != 0 && LastDecorateEnd <= PatchedSPIRV.size()) + { + InsertPos = static_cast(LastDecorateEnd); + } + else if (FirstFunctionWordIndex != 0 && FirstFunctionWordIndex < PatchedSPIRV.size()) + { + InsertPos = static_cast(FirstFunctionWordIndex); } + + PatchedSPIRV.insert(PatchedSPIRV.begin() + InsertPos, + NewDecorations.begin(), NewDecorations.end()); } return PatchedSPIRV; } +void WarmUpSPIRVCrossGLSLCompiler() +{ +#if DILIGENT_USE_SPIRV_CROSS_GLSL + static std::once_flag WarmUpFlag; + + std::call_once(WarmUpFlag, []() { + // Force SPIRV-Cross to initialize its GLSL keyword tables before async shader tasks use them. + static const uint32_t TrivialComputeShaderSPIRV[] = + { + 0x07230203, 0x00010000, 0, 5, 0, // Header + 0x00020011, 1, // OpCapability Shader + 0x0003000e, 0, 1, // OpMemoryModel Logical GLSL450 + 0x0005000f, 5, 3, 0x6e69616d, 0, // OpEntryPoint GLCompute %3 "main" + 0x00060010, 3, 17, 1, 1, 1, // OpExecutionMode %3 LocalSize 1 1 1 + 0x00020013, 1, // %void = OpTypeVoid + 0x00030021, 2, 1, // %fn = OpTypeFunction %void + 0x00050036, 1, 3, 0, 2, // %3 = OpFunction %void None %fn + 0x000200f8, 4, // %4 = OpLabel + 0x000100fd, // OpReturn + 0x00010038 // OpFunctionEnd + }; + + try + { + diligent_spirv_cross::CompilerGLSL Compiler{ + TrivialComputeShaderSPIRV, + sizeof(TrivialComputeShaderSPIRV) / sizeof(TrivialComputeShaderSPIRV[0])}; + + diligent_spirv_cross::CompilerGLSL::Options Options; + Options.version = 450; + Compiler.set_common_options(Options); + (void)Compiler.compile(); + } + catch (const std::exception& e) + { + LOG_WARNING_MESSAGE("Failed to warm up SPIRV-Cross GLSL compiler: ", e.what()); + } + catch (...) + { + LOG_WARNING_MESSAGE("Failed to warm up SPIRV-Cross GLSL compiler."); + } + }); +#endif +} + } // namespace Diligent diff --git a/Graphics/ShaderTools/src/WGSLShaderResources.cpp b/Graphics/ShaderTools/src/WGSLShaderResources.cpp index 2ad22f43bb..3e86be66d1 100644 --- a/Graphics/ShaderTools/src/WGSLShaderResources.cpp +++ b/Graphics/ShaderTools/src/WGSLShaderResources.cpp @@ -25,6 +25,10 @@ */ #include "WGSLShaderResources.hpp" + +#include +#include + #include "Align.hpp" #include "StringPool.hpp" #include "WGSLUtils.hpp" @@ -82,10 +86,11 @@ SHADER_TYPE TintPipelineStageToShaderType(tint::inspector::PipelineStage Stage) } } -WGSLShaderResourceAttribs::ResourceType TintResourceTypeToWGSLShaderAttribsResourceType(tint::inspector::ResourceBinding::ResourceType TintResType) +WGSLShaderResourceAttribs::ResourceType TintResourceTypeToWGSLShaderAttribsResourceType(const tint::inspector::ResourceBinding& TintBinding) { using TintResourceType = tint::inspector::ResourceBinding::ResourceType; - switch (TintResType) + using TintSamplerType = tint::inspector::ResourceBinding::SamplerType; + switch (TintBinding.resource_type) { case TintResourceType::kUniformBuffer: return WGSLShaderResourceAttribs::ResourceType::UniformBuffer; @@ -97,10 +102,9 @@ WGSLShaderResourceAttribs::ResourceType TintResourceTypeToWGSLShaderAttribsResou return WGSLShaderResourceAttribs::ResourceType::ROStorageBuffer; case TintResourceType::kSampler: - return WGSLShaderResourceAttribs::ResourceType::Sampler; - - case TintResourceType::kComparisonSampler: - return WGSLShaderResourceAttribs::ResourceType::ComparisonSampler; + return TintBinding.sampler_type == TintSamplerType::kComparison ? + WGSLShaderResourceAttribs::ResourceType::ComparisonSampler : + WGSLShaderResourceAttribs::ResourceType::Sampler; case TintResourceType::kSampledTexture: return WGSLShaderResourceAttribs::ResourceType::Texture; @@ -151,6 +155,8 @@ WGSLShaderResourceAttribs::TextureSampleType TintSampleKindToWGSLShaderAttribsSa switch (TintBinding.sampled_kind) { case TintSampledKind::kFloat: + case TintSampledKind::kFilterable: + case TintSampledKind::kUnknownFilterable: return WGSLShaderResourceAttribs::TextureSampleType::Float; case TintSampledKind::kSInt: @@ -159,8 +165,8 @@ WGSLShaderResourceAttribs::TextureSampleType TintSampleKindToWGSLShaderAttribsSa case TintSampledKind::kUInt: return WGSLShaderResourceAttribs::TextureSampleType::UInt; - case TintSampledKind::kUnknown: - return WGSLShaderResourceAttribs::TextureSampleType::Unknown; + case TintSampledKind::kUnfilterable: + return WGSLShaderResourceAttribs::TextureSampleType::UnfilterableFloat; default: UNEXPECTED("Unexpected sample kind"); @@ -221,7 +227,6 @@ RESOURCE_DIMENSION TintBindingToResourceDimension(const tint::inspector::Resourc return RESOURCE_DIM_BUFFER; case TintResourceType::kSampler: - case TintResourceType::kComparisonSampler: return RESOURCE_DIM_UNDEFINED; case TintResourceType::kSampledTexture: @@ -259,25 +264,47 @@ TEXTURE_FORMAT TintTexelFormatToTextureFormat(const tint::inspector::ResourceBin switch (TintBinding.image_format) { // clang-format off - case TintTexelFormat::kBgra8Unorm: return TEX_FORMAT_BGRA8_UNORM; - case TintTexelFormat::kRgba8Unorm: return TEX_FORMAT_RGBA8_UNORM; - case TintTexelFormat::kRgba8Snorm: return TEX_FORMAT_RGBA8_SNORM; - case TintTexelFormat::kRgba8Uint: return TEX_FORMAT_RGBA8_UINT; - case TintTexelFormat::kRgba8Sint: return TEX_FORMAT_RGBA8_SINT; - case TintTexelFormat::kRgba16Uint: return TEX_FORMAT_RGBA16_UINT; - case TintTexelFormat::kRgba16Sint: return TEX_FORMAT_RGBA16_SINT; - case TintTexelFormat::kRgba16Float: return TEX_FORMAT_RGBA16_FLOAT; - case TintTexelFormat::kR32Uint: return TEX_FORMAT_R32_UINT; - case TintTexelFormat::kR32Sint: return TEX_FORMAT_R32_SINT; - case TintTexelFormat::kR32Float: return TEX_FORMAT_R32_FLOAT; - case TintTexelFormat::kRg32Uint: return TEX_FORMAT_RG32_UINT; - case TintTexelFormat::kRg32Sint: return TEX_FORMAT_RG32_SINT; - case TintTexelFormat::kRg32Float: return TEX_FORMAT_RG32_FLOAT; - case TintTexelFormat::kRgba32Uint: return TEX_FORMAT_RGBA32_UINT; - case TintTexelFormat::kRgba32Sint: return TEX_FORMAT_RGBA32_SINT; - case TintTexelFormat::kRgba32Float: return TEX_FORMAT_RGBA32_FLOAT; - case TintTexelFormat::kR8Unorm: return TEX_FORMAT_R8_UNORM; - case TintTexelFormat::kNone: return TEX_FORMAT_UNKNOWN; + case TintTexelFormat::kBgra8Unorm: return TEX_FORMAT_BGRA8_UNORM; + case TintTexelFormat::kRgba8Unorm: return TEX_FORMAT_RGBA8_UNORM; + case TintTexelFormat::kRgba8Snorm: return TEX_FORMAT_RGBA8_SNORM; + case TintTexelFormat::kRgba8Uint: return TEX_FORMAT_RGBA8_UINT; + case TintTexelFormat::kRgba8Sint: return TEX_FORMAT_RGBA8_SINT; + case TintTexelFormat::kRgba16Uint: return TEX_FORMAT_RGBA16_UINT; + case TintTexelFormat::kRgba16Sint: return TEX_FORMAT_RGBA16_SINT; + case TintTexelFormat::kRgba16Float: return TEX_FORMAT_RGBA16_FLOAT; + case TintTexelFormat::kRgba16Unorm: return TEX_FORMAT_RGBA16_UNORM; + case TintTexelFormat::kRgba16Snorm: return TEX_FORMAT_RGBA16_SNORM; + case TintTexelFormat::kR32Uint: return TEX_FORMAT_R32_UINT; + case TintTexelFormat::kR32Sint: return TEX_FORMAT_R32_SINT; + case TintTexelFormat::kR32Float: return TEX_FORMAT_R32_FLOAT; + case TintTexelFormat::kRg32Uint: return TEX_FORMAT_RG32_UINT; + case TintTexelFormat::kRg32Sint: return TEX_FORMAT_RG32_SINT; + case TintTexelFormat::kRg32Float: return TEX_FORMAT_RG32_FLOAT; + case TintTexelFormat::kRgba32Uint: return TEX_FORMAT_RGBA32_UINT; + case TintTexelFormat::kRgba32Sint: return TEX_FORMAT_RGBA32_SINT; + case TintTexelFormat::kRgba32Float: return TEX_FORMAT_RGBA32_FLOAT; + case TintTexelFormat::kR8Unorm: return TEX_FORMAT_R8_UNORM; + case TintTexelFormat::kR8Snorm: return TEX_FORMAT_R8_SNORM; + case TintTexelFormat::kR8Uint: return TEX_FORMAT_R8_UINT; + case TintTexelFormat::kR8Sint: return TEX_FORMAT_R8_SINT; + case TintTexelFormat::kRg8Unorm: return TEX_FORMAT_RG8_UNORM; + case TintTexelFormat::kRg8Snorm: return TEX_FORMAT_RG8_SNORM; + case TintTexelFormat::kRg8Uint: return TEX_FORMAT_RG8_UINT; + case TintTexelFormat::kRg8Sint: return TEX_FORMAT_RG8_SINT; + case TintTexelFormat::kR16Uint: return TEX_FORMAT_R16_UINT; + case TintTexelFormat::kR16Sint: return TEX_FORMAT_R16_SINT; + case TintTexelFormat::kR16Float: return TEX_FORMAT_R16_FLOAT; + case TintTexelFormat::kR16Unorm: return TEX_FORMAT_R16_UNORM; + case TintTexelFormat::kR16Snorm: return TEX_FORMAT_R16_SNORM; + case TintTexelFormat::kRg16Uint: return TEX_FORMAT_RG16_UINT; + case TintTexelFormat::kRg16Sint: return TEX_FORMAT_RG16_SINT; + case TintTexelFormat::kRg16Float: return TEX_FORMAT_RG16_FLOAT; + case TintTexelFormat::kRg16Unorm: return TEX_FORMAT_RG16_UNORM; + case TintTexelFormat::kRg16Snorm: return TEX_FORMAT_RG16_SNORM; + case TintTexelFormat::kRg11B10Ufloat: return TEX_FORMAT_R11G11B10_FLOAT; + case TintTexelFormat::kRgb10A2Uint: return TEX_FORMAT_RGB10A2_UINT; + case TintTexelFormat::kRgb10A2Unorm: return TEX_FORMAT_RGB10A2_UNORM; + case TintTexelFormat::kNone: return TEX_FORMAT_UNKNOWN; // clang-format on default: UNEXPECTED("Unexpected texel format"); @@ -347,7 +374,7 @@ WGSLShaderResourceAttribs::WGSLShaderResourceAttribs(const char* // clang-format off Name {_Name}, ArraySize {static_cast(_ArraySize)}, - Type {TintResourceTypeToWGSLShaderAttribsResourceType(TintBinding.resource_type)}, + Type {TintResourceTypeToWGSLShaderAttribsResourceType(TintBinding)}, ResourceDim {TintBindingToResourceDimension(TintBinding)}, Format {TintTexelFormatToTextureFormat(TintBinding)}, BindGroup {static_cast(TintBinding.bind_group)}, @@ -875,7 +902,6 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, break; case TintResourceType::kSampler: - case TintResourceType::kComparisonSampler: ++ResCounters.NumSamplers; break; @@ -900,6 +926,11 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, UNSUPPORTED("Input attachments are not currently supported"); break; + case TintResourceType::kReadOnlyTexelBuffer: + case TintResourceType::kReadWriteTexelBuffer: + UNSUPPORTED("Texel buffers are not currently supported"); + break; + default: UNEXPECTED("Unexpected resource type"); } @@ -960,7 +991,6 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, break; case TintResourceType::kSampler: - case TintResourceType::kComparisonSampler: { new (&GetSampler(CurrRes.NumSamplers++)) WGSLShaderResourceAttribs{Name, Binding, ArraySize}; } @@ -993,6 +1023,11 @@ WGSLShaderResources::WGSLShaderResources(IMemoryAllocator& Allocator, UNSUPPORTED("Input attachments are not currently supported"); break; + case TintResourceType::kReadOnlyTexelBuffer: + case TintResourceType::kReadWriteTexelBuffer: + UNSUPPORTED("Texel buffers are not currently supported"); + break; + default: UNEXPECTED("Unexpected resource type"); } diff --git a/Graphics/ShaderTools/src/WGSLUtils.cpp b/Graphics/ShaderTools/src/WGSLUtils.cpp index f528f126f7..8b8447a160 100644 --- a/Graphics/ShaderTools/src/WGSLUtils.cpp +++ b/Graphics/ShaderTools/src/WGSLUtils.cpp @@ -25,6 +25,10 @@ */ #include "WGSLUtils.hpp" + +#include +#include + #include "DebugUtilities.hpp" #include "ParsingTools.hpp" #include "ShaderToolsCommon.hpp" @@ -44,11 +48,15 @@ #include "src/tint/lang/core/type/atomic.h" #include "src/tint/lang/core/type/array.h" #include "src/tint/lang/core/type/struct.h" - +#include "src/tint/lang/core/ir/transform/binding_remapper.h" +#include "src/tint/lang/wgsl/inspector/inspector.h" +#include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h" +#include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h" #ifdef _MSC_VER # pragma warning(pop) #endif +#include namespace Diligent { @@ -89,25 +97,135 @@ WGSLEmulatedResourceArrayElement GetWGSLEmulatedArrayElement(const std::string& return {Name, -1}; } -std::string ConvertSPIRVtoWGSL(const std::vector& SPIRV) +std::string DecodeSpirvLiteralString(const std::vector& SPIRV, + size_t StartWord, + uint16_t WordCount) { - tint::spirv::reader::Options SPIRVReaderOptions{true, {tint::wgsl::AllowedFeatures::Everything()}}; - tint::Program Program = Read(SPIRV, SPIRVReaderOptions); + std::string Result; + Result.reserve(static_cast(WordCount) * 4); - if (!Program.IsValid()) + const size_t EndWord = StartWord + WordCount; + for (size_t Idx = StartWord; Idx < EndWord; ++Idx) { - LOG_ERROR_MESSAGE("Tint SPIR-V reader failure:\nParser: " + Program.Diagnostics().Str() + "\n"); - return {}; + uint32_t Word = SPIRV[Idx]; + + for (int32_t ByteIndex = 0; ByteIndex < 4; ++ByteIndex) + { + char c = static_cast((Word >> (8 * ByteIndex)) & 0xFF); + if (c == '\0') + return Result; + Result.push_back(c); + } } - auto GenerationResult = tint::wgsl::writer::Generate(Program, {}); - if (GenerationResult != tint::Success) + return Result; +} + +void AddStorageImageExtendedFormats(std::vector& SPIRV) +{ + if (SPIRV.size() <= 5) + return; + + constexpr uint16_t OpCapability = 17; + constexpr uint32_t StorageImageExtendedFormats = 49; + + bool HasCapability = false; + size_t InsertAfterCapability = 5; + + size_t InstructionPointer = 5; // Skip SPIR-V header + while (InstructionPointer < SPIRV.size()) { - LOG_ERROR_MESSAGE("Tint WGSL writer failure:\nGeneate: " + GenerationResult.Failure().reason.Str() + "\n"); + uint32_t Instruction = SPIRV[InstructionPointer]; + uint16_t WordCount = Instruction >> 16; + uint16_t OpCode = Instruction & 0xFFFF; + + if (WordCount == 0 || InstructionPointer + WordCount > SPIRV.size()) + break; + + if (OpCode == OpCapability) + { + InsertAfterCapability = InstructionPointer + WordCount; + + if (WordCount >= 2 && SPIRV[InstructionPointer + 1] == StorageImageExtendedFormats) + { + HasCapability = true; + break; + } + + InstructionPointer += WordCount; + continue; + } + break; + } + + if (HasCapability) + return; + + + const uint32_t FirstWord = (2u << 16) | uint32_t(OpCapability); + SPIRV.insert(SPIRV.begin() + InsertAfterCapability, {FirstWord, StorageImageExtendedFormats}); +} + +void StripGoogleHlslFunctionality(std::vector& SPIRV) +{ + if (SPIRV.size() <= 5) + return; + + constexpr uint16_t OpExtension = 10; + constexpr uint16_t OpDecorateStringGOOGLE = 5632; + constexpr uint16_t OpMemberDecorateStringGOOGLE = 5633; + + size_t InstructionPointer = 5; // Skip SPIR-V header + + auto EraseCurrent = [&](size_t Count) { + SPIRV.erase(SPIRV.begin() + InstructionPointer, + SPIRV.begin() + InstructionPointer + Count); + }; + + while (InstructionPointer < SPIRV.size()) + { + uint32_t Instruction = SPIRV[InstructionPointer]; + uint16_t WordCount = Instruction >> 16; + uint16_t OpCode = Instruction & 0xFFFF; + + if (WordCount == 0 || InstructionPointer + WordCount > SPIRV.size()) + break; + + + if (OpCode == OpExtension) + { + std::string Extension = DecodeSpirvLiteralString(SPIRV, InstructionPointer + 1, static_cast(WordCount - 1)); + + if (Extension == "SPV_GOOGLE_hlsl_functionality1") + { + EraseCurrent(WordCount); + continue; + } + } + else if (OpCode == OpDecorateStringGOOGLE || OpCode == OpMemberDecorateStringGOOGLE) + { + EraseCurrent(WordCount); + continue; + } + InstructionPointer += WordCount; + } +} + +std::string ConvertSPIRVtoWGSL(const std::vector& SPIRV) +{ + tint::wgsl::writer::Options WGSLWriterOptions{}; + WGSLWriterOptions.allow_non_uniform_derivatives = true; + WGSLWriterOptions.allowed_features = tint::wgsl::AllowedFeatures::Everything(); + WGSLWriterOptions.minify = false; + + auto Result = tint::SpirvToWgsl(SPIRV, WGSLWriterOptions); + if (Result != tint::Success) + { + LOG_ERROR_MESSAGE("Tint SPIR-V -> WGSL failed:\n" + Result.Failure().reason + "\n"); return {}; } - return GenerationResult->wgsl; + return Result.Get(); } static bool IsAtomic(const tint::core::type::Type* WGSLType) @@ -155,6 +273,24 @@ static bool IsAtomic(const tint::core::type::Type* WGSLType) } } +static std::string StripNumericSuffix(std::string Name) +{ + const auto UnderscorePos = Name.find_last_of('_'); + if (UnderscorePos == std::string::npos || UnderscorePos + 1 >= Name.length()) + return Name; + + const bool AllDigits = + std::all_of(Name.begin() + static_cast(UnderscorePos + 1), + Name.end(), + [](char c) { return c >= '0' && c <= '9'; }); + + if (!AllDigits) + return Name; + + Name.erase(UnderscorePos); + return Name; +} + std::string GetWGSLResourceAlternativeName(const tint::Program& Program, const tint::inspector::ResourceBinding& Binding) { if (Binding.resource_type != tint::inspector::ResourceBinding::ResourceType::kUniformBuffer && @@ -196,7 +332,7 @@ std::string GetWGSLResourceAlternativeName(const tint::Program& Program, const t // g_Data0 : vec4f, // } // @group(0) @binding(0) var x_13 : CB0; - return TypeName; + return StripNumericSuffix(std::move(TypeName)); } else { @@ -253,14 +389,14 @@ static WGSLResourceMapping::const_iterator FindResourceAsArrayElement(const WGSL if (WGSLEmulatedResourceArrayElement ArrayElem = GetWGSLEmulatedArrayElement(Name, EmulatedArrayIndexSuffix)) { - auto BindigIt = ResMapping.find(ArrayElem.Name); - if (BindigIt == ResMapping.end()) + auto BindingIt = ResMapping.find(ArrayElem.Name); + if (BindingIt == ResMapping.end()) return ResMapping.end(); - if (ArrayElem.Index < static_cast(BindigIt->second.ArraySize)) + if (ArrayElem.Index < static_cast(BindingIt->second.ArraySize)) { ArrayIndex = static_cast(ArrayElem.Index); - return BindigIt; + return BindingIt; } } @@ -271,47 +407,50 @@ std::string RemapWGSLResourceBindings(const std::string& WGSL, const WGSLResourceMapping& ResMapping, const char* EmulatedArrayIndexSuffix) { - tint::Source::File srcFile("", WGSL); - tint::Program Program = tint::wgsl::reader::Parse(&srcFile, {tint::wgsl::AllowedFeatures::Everything()}); + tint::Source::File SrcFile("", WGSL); + + tint::wgsl::reader::Options WGSLReaderOptions{}; + WGSLReaderOptions.allowed_features = tint::wgsl::AllowedFeatures::Everything(); + + tint::Program Program = tint::wgsl::reader::Parse(&SrcFile, WGSLReaderOptions); if (!Program.IsValid()) { - LOG_ERROR_MESSAGE("Tint WGSL reader failure:\nParser: ", Program.Diagnostics().Str(), "\n"); + LOG_ERROR_MESSAGE("Tint WGSL parse failed:\n", Program.Diagnostics().Str(), "\n"); return {}; } - tint::ast::transform::BindingRemapper::BindingPoints BindingPoints; - - tint::inspector::Inspector Inspector{Program}; + std::unordered_map BindingPoints; + tint::inspector::Inspector Inspector{Program}; for (tint::inspector::EntryPoint& EntryPoint : Inspector.GetEntryPoints()) { for (tint::inspector::ResourceBinding& Binding : Inspector.GetResourceBindings(EntryPoint.name)) { Uint32 ArrayIndex = 0; - auto DstBindigIt = ResMapping.find(Binding.variable_name); - if (EmulatedArrayIndexSuffix != nullptr && DstBindigIt == ResMapping.end()) + auto DstBindingIt = ResMapping.find(Binding.variable_name); + if (EmulatedArrayIndexSuffix != nullptr && DstBindingIt == ResMapping.end()) { - DstBindigIt = FindResourceAsArrayElement(ResMapping, EmulatedArrayIndexSuffix, Binding.variable_name, ArrayIndex); + DstBindingIt = FindResourceAsArrayElement(ResMapping, EmulatedArrayIndexSuffix, Binding.variable_name, ArrayIndex); } - if (DstBindigIt == ResMapping.end()) + if (DstBindingIt == ResMapping.end()) { const std::string AltName = GetWGSLResourceAlternativeName(Program, Binding); if (!AltName.empty()) { - DstBindigIt = ResMapping.find(AltName); - if (EmulatedArrayIndexSuffix != nullptr && DstBindigIt == ResMapping.end()) + DstBindingIt = ResMapping.find(AltName); + if (EmulatedArrayIndexSuffix != nullptr && DstBindingIt == ResMapping.end()) { - DstBindigIt = FindResourceAsArrayElement(ResMapping, EmulatedArrayIndexSuffix, AltName, ArrayIndex); + DstBindingIt = FindResourceAsArrayElement(ResMapping, EmulatedArrayIndexSuffix, AltName, ArrayIndex); } } } - if (DstBindigIt != ResMapping.end()) + if (DstBindingIt != ResMapping.end()) { - const WGSLResourceBindingInfo& DstBindig = DstBindigIt->second; - BindingPoints.emplace(tint::ast::transform::BindingPoint{Binding.bind_group, Binding.binding}, tint::ast::transform::BindingPoint{DstBindig.Group, DstBindig.Index + ArrayIndex}); + const WGSLResourceBindingInfo& DstBinding = DstBindingIt->second; + BindingPoints.emplace(tint::BindingPoint{Binding.bind_group, Binding.binding}, tint::BindingPoint{DstBinding.Group, DstBinding.Index + ArrayIndex}); } else { @@ -320,23 +459,32 @@ std::string RemapWGSLResourceBindings(const std::string& WGSL, } } - tint::ast::transform::Manager Manager; - tint::ast::transform::DataMap Inputs; - tint::ast::transform::DataMap Outputs; + auto Module = tint::wgsl::reader::ProgramToIR(Program); + if (Module != tint::Success) + { + LOG_ERROR_MESSAGE("Tint WGSL → IR failed:\n", Module.Failure().reason, "\n"); + return {}; + } - Inputs.Add(BindingPoints, tint::ast::transform::BindingRemapper::AccessControls{}, false); - Manager.Add(); - tint::ast::transform::Output TransformResult = Manager.Run(Program, Inputs, Outputs); + if (auto Result = tint::core::ir::transform::BindingRemapper(Module.Get(), BindingPoints); Result != tint::Success) + { + LOG_ERROR_MESSAGE("Tint binding remap failed:\n", Result.Failure().reason, "\n"); + return {}; + } - auto GenerationResult = tint::wgsl::writer::Generate(TransformResult.program, {}); + tint::wgsl::writer::Options WGSLWriterOptions{}; + WGSLWriterOptions.allow_non_uniform_derivatives = true; + WGSLWriterOptions.allowed_features = WGSLReaderOptions.allowed_features; + WGSLWriterOptions.minify = false; - if (GenerationResult != tint::Success) + auto Result = tint::wgsl::writer::WgslFromIR(Module.Get(), WGSLWriterOptions); + if (Result != tint::Success) { - LOG_ERROR_MESSAGE("Tint WGSL writer failure:\nGeneate: ", GenerationResult.Failure().reason.Str(), "\n"); + LOG_ERROR_MESSAGE("Tint IR -> WGSL failed:\n", Result.Failure().reason, "\n"); return {}; } - std::string PatchedWGSL = std::move(GenerationResult->wgsl); + std::string PatchedWGSL = std::move(Result->wgsl); // If original WGSL contains shader source language definition, append it to the patched WGSL SHADER_SOURCE_LANGUAGE SrcLang = ParseShaderSourceLanguageDefinition(WGSL); diff --git a/Platforms/Apple/interface/MacOSNativeWindow.h b/Platforms/Apple/interface/MacOSNativeWindow.h index 6dde6fb574..1f87617deb 100644 --- a/Platforms/Apple/interface/MacOSNativeWindow.h +++ b/Platforms/Apple/interface/MacOSNativeWindow.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2024 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ #pragma once #include "../../../Primitives/interface/CommonDefinitions.h" +#include "../../../Primitives/interface/BasicTypes.h" DILIGENT_BEGIN_NAMESPACE(Diligent) @@ -44,6 +45,8 @@ struct MacOSNativeWindow {} void* GetLayer() const; + + void GetBackingSize(Uint32& Width, Uint32& Height) const; #endif }; diff --git a/Platforms/Apple/src/MacOSNativeWindow.mm b/Platforms/Apple/src/MacOSNativeWindow.mm index 37d0edae1e..0ec2d25469 100644 --- a/Platforms/Apple/src/MacOSNativeWindow.mm +++ b/Platforms/Apple/src/MacOSNativeWindow.mm @@ -23,6 +23,7 @@ */ #include "MacOSNativeWindow.h" +#include "BasicTypes.h" #import @@ -34,4 +35,20 @@ return pNSView ? (__bridge void*)((__bridge NSView*)pNSView).layer : nullptr; } +void MacOSNativeWindow::GetBackingSize(Uint32& Width, Uint32& Height) const +{ + if (pNSView) + { + NSView* nsView = (__bridge NSView*)pNSView; + NSRect viewRectPixels = [nsView convertRectToBacking:[nsView bounds]]; + Width = static_cast(viewRectPixels.size.width); + Height = static_cast(viewRectPixels.size.height); + } + else + { + Width = 0; + Height = 0; + } +} + } // namespace Diligent diff --git a/Platforms/Basic/interface/Atomics.hpp b/Platforms/Basic/interface/Atomics.hpp index 5efc023119..c16326069e 100644 --- a/Platforms/Basic/interface/Atomics.hpp +++ b/Platforms/Basic/interface/Atomics.hpp @@ -36,8 +36,8 @@ namespace Diligent template T AtomicMax(std::atomic& Val, T Candidate, - std::memory_order Success = std::memory_order_seq_cst, - std::memory_order Failure = std::memory_order_relaxed) + std::memory_order SuccessOrder = std::memory_order_seq_cst, + std::memory_order FailureOrder = std::memory_order_relaxed) { static_assert(std::atomic::is_always_lock_free, "AtomicMax requires a lock-free atomic type for performance."); @@ -45,7 +45,7 @@ T AtomicMax(std::atomic& Val, "AtomicMax only supports arithmetic or pointer types."); T Cur = Val.load(std::memory_order_relaxed); - while (Cur < Candidate && !Val.compare_exchange_weak(Cur, Candidate, Success, Failure)) + while (Cur < Candidate && !Val.compare_exchange_weak(Cur, Candidate, SuccessOrder, FailureOrder)) { // Cur is updated to the latest value by compare_exchange_weak on failure } @@ -57,8 +57,8 @@ T AtomicMax(std::atomic& Val, template T AtomicMin(std::atomic& Val, T Candidate, - std::memory_order Success = std::memory_order_seq_cst, - std::memory_order Failure = std::memory_order_relaxed) + std::memory_order SuccessOrder = std::memory_order_seq_cst, + std::memory_order FailureOrder = std::memory_order_relaxed) { static_assert(std::atomic::is_always_lock_free, "AtomicMax requires a lock-free atomic type for performance."); @@ -66,7 +66,7 @@ T AtomicMin(std::atomic& Val, "AtomicMax only supports arithmetic or pointer types."); T Cur = Val.load(std::memory_order_relaxed); - while (Cur > Candidate && !Val.compare_exchange_weak(Cur, Candidate, Success, Failure)) + while (Cur > Candidate && !Val.compare_exchange_weak(Cur, Candidate, SuccessOrder, FailureOrder)) { // Cur is updated to the latest value by compare_exchange_weak on failure } diff --git a/Primitives/interface/Errors.hpp b/Primitives/interface/Errors.hpp index fec2c8870a..07d5f461e2 100644 --- a/Primitives/interface/Errors.hpp +++ b/Primitives/interface/Errors.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +31,7 @@ #include #include #include +#include #include "DebugOutput.h" #include "FormatString.hpp" @@ -87,15 +88,14 @@ void LogError(bool IsFatal, const char* Function, const char* FullFilePath, int Diligent::LogError(/*IsFatal=*/true, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \ } while (false) -#define LOG_ERROR_ONCE(...) \ - do \ - { \ - static bool IsFirstTime = true; \ - if (IsFirstTime) \ - { \ - LOG_ERROR(##__VA_ARGS__); \ - IsFirstTime = false; \ - } \ +#define LOG_ERROR_ONCE(...) \ + do \ + { \ + static std::atomic_bool IsFirstTime{true}; \ + if (IsFirstTime.exchange(false, std::memory_order_relaxed)) \ + { \ + LOG_ERROR(##__VA_ARGS__); \ + } \ } while (false) @@ -148,15 +148,14 @@ void LogError(bool IsFatal, const char* Function, const char* FullFilePath, int # define LOG_DVP_INFO_MESSAGE(...) #endif -#define LOG_DEBUG_MESSAGE_ONCE(Severity, ...) \ - do \ - { \ - static bool IsFirstTime = true; \ - if (IsFirstTime) \ - { \ - LOG_DEBUG_MESSAGE(Severity, ##__VA_ARGS__); \ - IsFirstTime = false; \ - } \ +#define LOG_DEBUG_MESSAGE_ONCE(Severity, ...) \ + do \ + { \ + static std::atomic_bool IsFirstTime{true}; \ + if (IsFirstTime.exchange(false, std::memory_order_relaxed)) \ + { \ + LOG_DEBUG_MESSAGE(Severity, ##__VA_ARGS__); \ + } \ } while (false) #define LOG_FATAL_ERROR_MESSAGE_ONCE(...) LOG_DEBUG_MESSAGE_ONCE(Diligent::DEBUG_MESSAGE_SEVERITY_FATAL_ERROR, ##__VA_ARGS__) diff --git a/Tests/DiligentCoreAPITest/assets/shaders/RenderStateCache/ComputeShader.csh b/Tests/DiligentCoreAPITest/assets/shaders/RenderStateCache/ComputeShader.csh index aee688c6ec..d28abff2e8 100644 --- a/Tests/DiligentCoreAPITest/assets/shaders/RenderStateCache/ComputeShader.csh +++ b/Tests/DiligentCoreAPITest/assets/shaders/RenderStateCache/ComputeShader.csh @@ -1,6 +1,6 @@ #include "Defines.h" -VK_IMAGE_FORMAT("rgba8") RWTexture2D g_tex2DUAV; +VK_IMAGE_FORMAT("rgba8") RWTexture2D g_tex2DUAV; cbuffer cbConstants { diff --git a/Tests/DiligentCoreAPITest/include/InlineShaders/ComputeShaderTestHLSL.h b/Tests/DiligentCoreAPITest/include/InlineShaders/ComputeShaderTestHLSL.h index 3b2baf66af..0fee5d6be9 100644 --- a/Tests/DiligentCoreAPITest/include/InlineShaders/ComputeShaderTestHLSL.h +++ b/Tests/DiligentCoreAPITest/include/InlineShaders/ComputeShaderTestHLSL.h @@ -125,7 +125,7 @@ cbuffer Constants float4 g_Color; }; -VK_IMAGE_FORMAT("rgba8") RWTexture2D g_tex2DUAV : register(u0); +VK_IMAGE_FORMAT("rgba8") RWTexture2D g_tex2DUAV : register(u0); void main(in VSOutput VSOut) { diff --git a/Tests/DiligentCoreAPITest/src/ArchiveTest.cpp b/Tests/DiligentCoreAPITest/src/ArchiveTest.cpp index e266e8a5f6..cf3f13be12 100644 --- a/Tests/DiligentCoreAPITest/src/ArchiveTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ArchiveTest.cpp @@ -1296,7 +1296,7 @@ namespace HLSL // Test shader source as string const std::string ComputePSOTest_CS{R"( -VK_IMAGE_FORMAT("rgba8") RWTexture2D g_tex2DUAV : register(u0); +VK_IMAGE_FORMAT("rgba8") RWTexture2D g_tex2DUAV : register(u0); cbuffer cbConstants { diff --git a/Tests/DiligentCoreAPITest/src/DrawCommandTest.cpp b/Tests/DiligentCoreAPITest/src/DrawCommandTest.cpp index 2008455797..5d609b3e24 100644 --- a/Tests/DiligentCoreAPITest/src/DrawCommandTest.cpp +++ b/Tests/DiligentCoreAPITest/src/DrawCommandTest.cpp @@ -2943,7 +2943,7 @@ TEST_F(DrawCommandTest, DynamicUniformBufferUpdatesWithSignatures) } const Uint32 Indices[] = {0, 1, 2, 3, 4, 5}; - RefCntAutoPtr pIndexBuffer = CreateIndexBuffer(Indices, sizeof(Indices)); + RefCntAutoPtr pIndexBuffer = CreateIndexBuffer(Indices, _countof(Indices)); TestDynamicBufferUpdatesWithSignaturesAttribs Attribs{ pVS, diff --git a/Tests/DiligentCoreAPITest/src/GPUUploadManagerTest.cpp b/Tests/DiligentCoreAPITest/src/GPUUploadManagerTest.cpp index d367d56bff..d61fc0613e 100644 --- a/Tests/DiligentCoreAPITest/src/GPUUploadManagerTest.cpp +++ b/Tests/DiligentCoreAPITest/src/GPUUploadManagerTest.cpp @@ -87,6 +87,12 @@ TEST(GPUUploadManagerTest, Creation) GPUUploadManagerCreateInfo CreateInfo{pDevice, pContext}; CreateGPUUploadManager(CreateInfo, &pUploadManager); ASSERT_TRUE(pUploadManager != nullptr); + + RefCntAutoPtr pObject{pUploadManager, IID_Unknown}; + ASSERT_TRUE(pObject != nullptr); + + RefCntAutoPtr pQueriedManager{pObject, IID_GPUUploadManager}; + EXPECT_EQ(pQueriedManager.RawPtr(), pUploadManager.RawPtr()); } void VerifyBufferContents(IBuffer* pBuffer, const std::vector& ExpectedData) @@ -794,8 +800,8 @@ TEST(GPUUploadManagerTest, StopReleasesBlockedBufferUpdates) RefCntAutoPtr pBuffer = CreateUploadTestBuffer(pDevice, BufferData.size()); ASSERT_NE(pBuffer, nullptr); - constexpr size_t kNumThreads = 4; - constexpr size_t kNumUpdates = kNumThreads * 2; + static constexpr size_t kNumThreads = 4; + static constexpr size_t kNumUpdates = kNumThreads * 2; std::vector Threads; std::atomic NumUpdatesRunning{0}; std::atomic NumCopyCallbacks{0}; @@ -918,8 +924,8 @@ TEST(GPUUploadManagerTest, StopReleasesBlockedTextureUpdates) CreateGPUUploadManager(CreateInfo, &pUploadManager); ASSERT_TRUE(pUploadManager != nullptr); - constexpr size_t kNumThreads = 4; - constexpr size_t kNumUpdates = kNumThreads * 2; + static constexpr size_t kNumThreads = 4; + static constexpr size_t kNumUpdates = kNumThreads * 2; std::vector Threads; std::atomic NumUpdatesRunning{0}; std::atomic NumCopyCallbacks{0}; diff --git a/Tests/DiligentCoreAPITest/src/ShaderVariableAccessTest.cpp b/Tests/DiligentCoreAPITest/src/ShaderVariableAccessTest.cpp index ab62da79a9..bf4e39619d 100644 --- a/Tests/DiligentCoreAPITest/src/ShaderVariableAccessTest.cpp +++ b/Tests/DiligentCoreAPITest/src/ShaderVariableAccessTest.cpp @@ -157,12 +157,14 @@ TEST(ShaderResourceLayout, VariableAccess) constexpr auto RTVFormat = TEX_FORMAT_RGBA8_UNORM; constexpr auto DSVFormat = TEX_FORMAT_D32_FLOAT; + TexDesc.Name = "Render target"; TexDesc.Format = RTVFormat; TexDesc.BindFlags = BIND_RENDER_TARGET | BIND_SHADER_RESOURCE; RefCntAutoPtr pRenderTarget; pDevice->CreateTexture(TexDesc, nullptr, &pRenderTarget); auto* pRTV = pRenderTarget->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET); + TexDesc.Name = "Depth texture"; TexDesc.Format = DSVFormat; TexDesc.BindFlags = BIND_DEPTH_STENCIL; RefCntAutoPtr pDepthTex; diff --git a/Tests/DiligentCoreTest/CMakeLists.txt b/Tests/DiligentCoreTest/CMakeLists.txt index 1cde824899..ad6befd379 100644 --- a/Tests/DiligentCoreTest/CMakeLists.txt +++ b/Tests/DiligentCoreTest/CMakeLists.txt @@ -38,7 +38,12 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() add_executable(DiligentCoreTest ${SOURCE} ${SHADERS}) -set_common_target_properties(DiligentCoreTest) + +if (WEBGPU_SUPPORTED) + set_common_target_properties(DiligentCoreTest 20) +else() + set_common_target_properties(DiligentCoreTest) +endif() if (PLATFORM_WEB) set(RESOURCE_PATH "${PROJECT_SOURCE_DIR}/assets/") diff --git a/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextureArrays.psh b/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextureArrays.psh index 80ceac47d9..09eb9660a1 100644 --- a/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextureArrays.psh +++ b/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextureArrays.psh @@ -1,14 +1,14 @@ -RWTexture2D g_WOTex2DArr0_3; -RWTexture2D g_WOTex2DArr0_1; +RWTexture2D g_WOTex2DArr0_3; +RWTexture2D g_WOTex2DArr0_1; RWTexture2D g_RWTex2DArr0_2; RWTexture2D g_RWTex2DArr0_0; -RWTexture2D g_WOTex2DNotArr1_2; -RWTexture2D g_WOTex2DNotArr1_4; +RWTexture2D g_WOTex2DNotArr1_2; +RWTexture2D g_WOTex2DNotArr1_4; -RWTexture2D g_RWTex2DNotArr2_5; -RWTexture2D g_RWTex2DNotArr2_9; +RWTexture2D g_RWTex2DNotArr2_5; +RWTexture2D g_RWTex2DNotArr2_9; float4 main() : SV_Target { diff --git a/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextures.psh b/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextures.psh index f3e2a73220..da08cb61eb 100644 --- a/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextures.psh +++ b/Tests/DiligentCoreTest/assets/shaders/WGSL/RWTextures.psh @@ -1,17 +1,17 @@ -RWTexture1D g_WOTex1D; -RWTexture2D g_WOTex2D; -RWTexture2DArray g_WOTex2DArr; -RWTexture3D g_WOTex3D; +RWTexture1D g_WOTex1D; +RWTexture2D g_WOTex2D; +RWTexture2DArray g_WOTex2DArr; +RWTexture3D g_WOTex3D; -RWTexture1D g_ROTex1D; -RWTexture2D g_ROTex2D; -RWTexture2DArray g_ROTex2DArr; -RWTexture3D g_ROTex3D; +RWTexture1D g_ROTex1D; +RWTexture2D g_ROTex2D; +RWTexture2DArray g_ROTex2DArr; +RWTexture3D g_ROTex3D; -RWTexture1D g_RWTex1D; -RWTexture2D g_RWTex2D; -RWTexture2DArray g_RWTex2DArr; -RWTexture3D g_RWTex3D; +RWTexture1D g_RWTex1D; +RWTexture2D g_RWTex2D; +RWTexture2DArray g_RWTex2DArr; +RWTexture3D g_RWTex3D; float4 main() : SV_Target { diff --git a/Tests/DiligentCoreTest/src/Common/ObjectsRegistryTest.cpp b/Tests/DiligentCoreTest/src/Common/ObjectsRegistryTest.cpp index 52f74c790e..644266a9e6 100644 --- a/Tests/DiligentCoreTest/src/Common/ObjectsRegistryTest.cpp +++ b/Tests/DiligentCoreTest/src/Common/ObjectsRegistryTest.cpp @@ -24,12 +24,17 @@ * of the possibility of such damages. */ +#define DILIGENT_OBJECTS_REGISTRY_TEST_HOOKS 1 #include "ObjectsRegistry.hpp" +#undef DILIGENT_OBJECTS_REGISTRY_TEST_HOOKS #include "gtest/gtest.h" +#include #include #include +#include +#include #include "ObjectBase.hpp" #include "ThreadSignal.hpp" @@ -168,6 +173,235 @@ TEST(Common_ObjectsRegistry, CreateDestroyRace_RefCntAutoPtr) TestObjectRegistryCreateDestroyRace(); } +enum class ObjectWrapperRemovalType +{ + Purge, + EmptyGet, +}; + +struct BeforeGetObjectHook +{ + Threading::Signal FirstCallStarted; + Threading::Signal ReleaseFirstCall; + std::atomic CallCount{0}; +}; + +template