From b97221b8de95940c87791eed3e73800651f1bed6 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 25 Sep 2024 10:43:19 -0700 Subject: [PATCH 01/36] Improve setFrameScheduled threading (#8139) --- .../include/private/backend/DriverAPI.inc | 3 +- filament/backend/src/metal/MetalDriver.mm | 12 ++++-- filament/backend/src/metal/MetalHandles.h | 4 +- filament/backend/src/metal/MetalHandles.mm | 40 +++++++++++++------ filament/backend/src/noop/NoopDriver.cpp | 2 +- filament/backend/src/opengl/OpenGLDriver.cpp | 4 +- filament/backend/src/vulkan/VulkanDriver.cpp | 4 +- filament/backend/test/test_Callbacks.cpp | 4 +- filament/include/filament/SwapChain.h | 15 ++++++- filament/src/SwapChain.cpp | 4 +- filament/src/details/SwapChain.cpp | 5 ++- filament/src/details/SwapChain.h | 2 +- 12 files changed, 68 insertions(+), 31 deletions(-) diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 295229809a6..0316339563a 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -139,7 +139,8 @@ DECL_DRIVER_API_N(beginFrame, DECL_DRIVER_API_N(setFrameScheduledCallback, backend::SwapChainHandle, sch, backend::CallbackHandler*, handler, - backend::FrameScheduledCallback&&, callback) + backend::FrameScheduledCallback&&, callback, + uint64_t, flags) DECL_DRIVER_API_N(setFrameCompletedCallback, backend::SwapChainHandle, sch, diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 0269c049c10..80b3047d870 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -19,6 +19,8 @@ #include "CommandStreamDispatcher.h" #include "metal/MetalDriver.h" +#include + #include "MetalBlitter.h" #include "MetalBufferPool.h" #include "MetalContext.h" @@ -240,10 +242,14 @@ } } -void MetalDriver::setFrameScheduledCallback( - Handle sch, CallbackHandler* handler, FrameScheduledCallback&& callback) { +void MetalDriver::setFrameScheduledCallback(Handle sch, CallbackHandler* handler, + FrameScheduledCallback&& callback, uint64_t flags) { + // Turn off the CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER flag if a custom handler is provided. + if (handler) { + flags &= ~SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER; + } auto* swapChain = handle_cast(sch); - swapChain->setFrameScheduledCallback(handler, std::move(callback)); + swapChain->setFrameScheduledCallback(handler, std::move(callback), flags); } void MetalDriver::setFrameCompletedCallback( diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 4fba4724eac..b57fcf4c7a3 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -73,7 +73,8 @@ class MetalSwapChain : public HwSwapChain { void releaseDrawable(); - void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback); + void setFrameScheduledCallback( + CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags); void setFrameCompletedCallback( CallbackHandler* handler, utils::Invocable&& callback); @@ -121,6 +122,7 @@ class MetalSwapChain : public HwSwapChain { struct { CallbackHandler* handler = nullptr; std::shared_ptr callback = nullptr; + uint64_t flags = 0; } frameScheduled; struct { diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 938d4569d36..c9779f5fc3a 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -233,9 +233,10 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { } void MetalSwapChain::setFrameScheduledCallback( - CallbackHandler* handler, FrameScheduledCallback&& callback) { + CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) { frameScheduled.handler = handler; frameScheduled.callback = std::make_shared(std::move(callback)); + frameScheduled.flags = flags; } void MetalSwapChain::setFrameCompletedCallback( @@ -264,10 +265,10 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { PresentDrawableData& operator=(const PresentDrawableData&) = delete; static PresentDrawableData* create(id drawable, - std::shared_ptr drawableMutex, MetalDriver* driver) { + std::shared_ptr drawableMutex, MetalDriver* driver, uint64_t flags) { assert_invariant(drawableMutex); assert_invariant(driver); - return new PresentDrawableData(drawable, drawableMutex, driver); + return new PresentDrawableData(drawable, drawableMutex, driver, flags); } static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) { @@ -275,16 +276,23 @@ static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPr [that->mDrawable present]; } - // mDrawable is acquired on the driver thread. Typically, we would release this object on - // the same thread, but after receiving consistent crash reports from within - // [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread. - dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); }); + if (that->mFlags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) { + cleanupAndDestroy(that); + } else { + // mDrawable is acquired on the driver thread. Typically, we would release this object + // on the same thread, but after receiving consistent crash reports from within + // [CAMetalDrawable dealloc], we suspect this object requires releasing on the main + // thread. + dispatch_async(dispatch_get_main_queue(), ^{ + cleanupAndDestroy(that); + }); + } } private: PresentDrawableData(id drawable, std::shared_ptr drawableMutex, - MetalDriver* driver) - : mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {} + MetalDriver* driver, uint64_t flags) + : mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver), mFlags(flags) {} static void cleanupAndDestroy(PresentDrawableData *that) { if (that->mDrawable) { @@ -299,6 +307,7 @@ static void cleanupAndDestroy(PresentDrawableData *that) { id mDrawable; std::shared_ptr mDrawableMutex; MetalDriver* mDriver = nullptr; + uint64_t mFlags = 0; }; void presentDrawable(bool presentFrame, void* user) { @@ -315,8 +324,8 @@ void presentDrawable(bool presentFrame, void* user) { struct Callback { Callback(std::shared_ptr callback, id drawable, - std::shared_ptr drawableMutex, MetalDriver* driver) - : f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {} + std::shared_ptr drawableMutex, MetalDriver* driver, uint64_t flags) + : f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver, flags)) {} std::shared_ptr f; // PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later. std::unique_ptr data; @@ -331,14 +340,19 @@ static void func(void* user) { // This callback pointer will be captured by the block. Even if the scheduled handler is never // called, the unique_ptr will still ensure we don't leak memory. + uint64_t const flags = frameScheduled.flags; __block auto callback = std::make_unique( - frameScheduled.callback, drawable, layerDrawableMutex, context.driver); + frameScheduled.callback, drawable, layerDrawableMutex, context.driver, flags); backend::CallbackHandler* handler = frameScheduled.handler; MetalDriver* driver = context.driver; [getPendingCommandBuffer(&context) addScheduledHandler:^(id cb) { Callback* user = callback.release(); - driver->scheduleCallback(handler, user, &Callback::func); + if (flags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) { + Callback::func(user); + } else { + driver->scheduleCallback(handler, user, &Callback::func); + } }]; } diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index cd3029d64bc..5309c691430 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -54,7 +54,7 @@ void NoopDriver::beginFrame(int64_t monotonic_clock_ns, } void NoopDriver::setFrameScheduledCallback(Handle sch, - CallbackHandler* handler, FrameScheduledCallback&& callback) { + CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) { } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index c753125d399..75c79b0fd1e 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -3528,8 +3528,8 @@ void OpenGLDriver::beginFrame( } } -void OpenGLDriver::setFrameScheduledCallback(Handle sch, - CallbackHandler* handler, FrameScheduledCallback&& callback) { +void OpenGLDriver::setFrameScheduledCallback(Handle sch, CallbackHandler* handler, + FrameScheduledCallback&& callback, uint64_t flags) { DEBUG_MARKER() } diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 587e4c823bc..27980bb329c 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -403,8 +403,8 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, FVK_SYSTRACE_END(); } -void VulkanDriver::setFrameScheduledCallback(Handle sch, - CallbackHandler* handler, FrameScheduledCallback&& callback) { +void VulkanDriver::setFrameScheduledCallback(Handle sch, CallbackHandler* handler, + FrameScheduledCallback&& callback, uint64_t flags) { } void VulkanDriver::setFrameCompletedCallback(Handle sch, diff --git a/filament/backend/test/test_Callbacks.cpp b/filament/backend/test/test_Callbacks.cpp index 0f02f1b2141..78f6fedf033 100644 --- a/filament/backend/test/test_Callbacks.cpp +++ b/filament/backend/test/test_Callbacks.cpp @@ -35,7 +35,7 @@ TEST_F(BackendTest, FrameScheduledCallback) { api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) { callable(); callbackCountA++; - }); + }, 0); // Render the first frame. api.makeCurrent(swapChain, swapChain); @@ -58,7 +58,7 @@ TEST_F(BackendTest, FrameScheduledCallback) { api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) { callable(); callbackCountB++; - }); + }, 0); // Render one final frame. api.makeCurrent(swapChain, swapChain); diff --git a/filament/include/filament/SwapChain.h b/filament/include/filament/SwapChain.h index 6917507a5d6..9b7e9cfa18c 100644 --- a/filament/include/filament/SwapChain.h +++ b/filament/include/filament/SwapChain.h @@ -254,6 +254,19 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { void* UTILS_NULLABLE getNativeWindow() const noexcept; + /** + * If this flag is passed to setFrameScheduledCallback, then the behavior of the default + * CallbackHandler (when nullptr is passed as the handler argument) is altered to call the + * callback on the Metal completion handler thread (as opposed to the main Filament thread). + * This flag also instructs the Metal backend to release the associated CAMetalDrawable on the + * completion handler thread. + * + * This flag has no effect if a custom CallbackHandler is passed. + * + * @see setFrameScheduledCallback + */ + static constexpr uint64_t CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER = 1; + /** * FrameScheduledCallback is a callback function that notifies an application when Filament has * completed processing a frame and that frame is ready to be scheduled for presentation. @@ -298,7 +311,7 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * @see PresentCallable */ void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr, - FrameScheduledCallback&& callback = {}); + FrameScheduledCallback&& callback = {}, uint64_t flags = 0); /** * Returns whether or not this SwapChain currently has a FrameScheduledCallback set. diff --git a/filament/src/SwapChain.cpp b/filament/src/SwapChain.cpp index dd7db7bd011..66502931e79 100644 --- a/filament/src/SwapChain.cpp +++ b/filament/src/SwapChain.cpp @@ -29,8 +29,8 @@ void* SwapChain::getNativeWindow() const noexcept { } void SwapChain::setFrameScheduledCallback( - backend::CallbackHandler* handler, FrameScheduledCallback&& callback) { - downcast(this)->setFrameScheduledCallback(handler, std::move(callback)); + backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) { + downcast(this)->setFrameScheduledCallback(handler, std::move(callback), flags); } bool SwapChain::isFrameScheduledCallbackSet() const noexcept { diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp index 20589c38f14..90d0920991b 100644 --- a/filament/src/details/SwapChain.cpp +++ b/filament/src/details/SwapChain.cpp @@ -70,9 +70,10 @@ void FSwapChain::terminate(FEngine& engine) noexcept { } void FSwapChain::setFrameScheduledCallback( - backend::CallbackHandler* handler, FrameScheduledCallback&& callback) { + backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags) { mFrameScheduledCallbackIsSet = bool(callback); - mEngine.getDriverApi().setFrameScheduledCallback(mHwSwapChain, handler, std::move(callback)); + mEngine.getDriverApi().setFrameScheduledCallback( + mHwSwapChain, handler, std::move(callback), flags); } bool FSwapChain::isFrameScheduledCallbackSet() const noexcept { diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index efe7483563e..e31bf670a36 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -79,7 +79,7 @@ class FSwapChain : public SwapChain { } void setFrameScheduledCallback( - backend::CallbackHandler* handler, FrameScheduledCallback&& callback); + backend::CallbackHandler* handler, FrameScheduledCallback&& callback, uint64_t flags); bool isFrameScheduledCallbackSet() const noexcept; From 7da09a7f4501a009dbee628906189034b104f8c7 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 25 Sep 2024 15:03:53 -0700 Subject: [PATCH 02/36] gl: add PlatformOSMesa as an offscreen context For GL+Linux, PlatformGLX will try to open an X11 window regardless of whether we are doing headless/offscreen rendering or not. Here we add an OSMesa platform, which will allow us to avoid opening any window on Linux. This is particularly useful for situation where a display is not available, like for CI. One important detail is that even though we are displaying through a window, we keep the SDL2 dependency in tact for gltf_viewer. This is due to the fact that gltf_viewer is built upon FilamentApp, which is heavily integrated with SDL2. This is mostly ok since we won't be hitting any path for opening a window due to gltf_viewer's existing support for headless mode. --- CMakeLists.txt | 16 +++ build.sh | 10 +- filament/backend/CMakeLists.txt | 13 +++ .../backend/platforms/PlatformOSMesa.h | 63 +++++++++++ filament/backend/src/PlatformFactory.cpp | 6 ++ .../src/opengl/platforms/PlatformOSMesa.cpp | 100 ++++++++++++++++++ third_party/libsdl2/include/SDL_config.h | 4 + third_party/libsdl2/tnt/CMakeLists.txt | 2 +- 8 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 filament/backend/include/backend/platforms/PlatformOSMesa.h create mode 100644 filament/backend/src/opengl/platforms/PlatformOSMesa.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 283778b327b..649577b396c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON) option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF) +option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF) + set(FILAMENT_NDK_VERSION "" CACHE STRING "Android NDK version or version prefix to be used when building for Android." ) @@ -73,6 +75,10 @@ set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING "A debug flag meant for enabling/disabling backend debugging paths" ) +set(FILAMENT_OSMESA_PATH "" CACHE STRING + "Path to the OSMesa header and lib" +) + # Enable exceptions by default in spirv-cross. set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF) @@ -132,12 +138,22 @@ else() endif() if (LINUX) + if (NOT FILAMENT_OSMESA_PATH STREQUAL "") + if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/) + message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}") + endif() + set(FILAMENT_SUPPORTS_OSMESA TRUE) + endif() + if (FILAMENT_SUPPORTS_WAYLAND) add_definitions(-DFILAMENT_SUPPORTS_WAYLAND) set(FILAMENT_SUPPORTS_X11 FALSE) elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX) add_definitions(-DFILAMENT_SUPPORTS_EGL_ON_LINUX) set(FILAMENT_SUPPORTS_X11 FALSE) + elseif (FILAMENT_SUPPORTS_OSMESA) + set(FILAMENT_SUPPORTS_X11 FALSE) + add_definitions(-DFILAMENT_SUPPORTS_OSMESA) else () if (FILAMENT_SUPPORTS_XCB) add_definitions(-DFILAMENT_SUPPORTS_XCB) diff --git a/build.sh b/build.sh index ab6440f45de..2c1a59b7159 100755 --- a/build.sh +++ b/build.sh @@ -64,6 +64,9 @@ function print_help { echo " enabling debug paths in the backend from the build script. For example, make a" echo " systrace-enabled build without directly changing #defines. Remember to add -f when" echo " changing this option." + echo " -X osmesa_path" + echo " Indicates a path to the path of a completed OSMesa build. OSMesa is used to create an" + echo " offscreen context for software rasterization" echo " -S type" echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only" echo " meant for building the samples." @@ -180,6 +183,8 @@ BACKEND_DEBUG_FLAG_OPTION="" STEREOSCOPIC_OPTION="" +OSMESA_OPTION="" + IOS_BUILD_SIMULATOR=false BUILD_UNIVERSAL_LIBRARIES=false @@ -240,6 +245,7 @@ function build_desktop_target { ${ASAN_UBSAN_OPTION} \ ${BACKEND_DEBUG_FLAG_OPTION} \ ${STEREOSCOPIC_OPTION} \ + ${OSMESA_OPTION} \ ${architectures} \ ../.. ln -sf "out/cmake-${lc_target}/compile_commands.json" \ @@ -796,7 +802,7 @@ function check_debug_release_build { pushd "$(dirname "$0")" > /dev/null -while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do +while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do case ${opt} in h) print_help @@ -950,6 +956,8 @@ while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do exit 1 esac ;; + X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}" + ;; \?) echo "Invalid option: -${OPTARG}" >&2 echo "" diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 6a7f66cc2fd..bbe4ea53880 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -114,6 +114,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3) list(APPEND SRCS src/opengl/platforms/PlatformGLX.cpp) elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX) list(APPEND SRCS src/opengl/platforms/PlatformEGLHeadless.cpp) + elseif (FILAMENT_SUPPORTS_OSMESA) + list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp) endif() elseif (WIN32) list(APPEND SRCS src/opengl/platforms/PlatformWGL.cpp) @@ -361,6 +363,15 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS -Wl,--exclude-libs,bluegl ) +if (LINUX AND FILAMENT_SUPPORTS_OSMESA) + set(OSMESA_COMPILE_FLAGS + -I${FILAMENT_OSMESA_PATH}/include/GL) + set(OSMESA_LINKER_FLAGS + -Wl,-L${FILAMENT_OSMESA_PATH}/lib/x86_64-linux-gnu/ + -lOSMesa + ) +endif() + if (MSVC) set(FILAMENT_WARNINGS /W3) else() @@ -381,6 +392,7 @@ endif() target_compile_options(${TARGET} PRIVATE ${FILAMENT_WARNINGS} + ${OSMESA_COMPILE_FLAGS} $<$:${OPTIMIZATION_FLAGS}> $<$,$>:${DARWIN_OPTIMIZATION_FLAGS}> ) @@ -390,6 +402,7 @@ if (FILAMENT_SUPPORTS_METAL) endif() target_link_libraries(${TARGET} PRIVATE + ${OSMESA_LINKER_FLAGS} $<$,$>:${LINUX_LINKER_OPTIMIZATION_FLAGS}> ) diff --git a/filament/backend/include/backend/platforms/PlatformOSMesa.h b/filament/backend/include/backend/platforms/PlatformOSMesa.h new file mode 100644 index 00000000000..bcb82195a4c --- /dev/null +++ b/filament/backend/include/backend/platforms/PlatformOSMesa.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H +#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H + +#include + +#include "bluegl/BlueGL.h" + +#include "osmesa.h" + +#include +#include + +namespace filament::backend { + +/** + * A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen + * context that can be used in conjunction with Mesa for software rasterization. + * See https://docs.mesa3d.org/osmesa.html for more information. + */ +class PlatformOSMesa : public OpenGLPlatform { +protected: + // -------------------------------------------------------------------------------------------- + // Platform Interface + + Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) noexcept override; + + int getOSVersion() const noexcept final override { return 0; } + + // -------------------------------------------------------------------------------------------- + // OpenGLPlatform Interface + + void terminate() noexcept override; + + SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; + void destroySwapChain(SwapChain* swapChain) noexcept override; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept override; + void commit(SwapChain* swapChain) noexcept override; + +private: + OSMesaContext mContext; +}; + +} // namespace filament::backend + +#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H diff --git a/filament/backend/src/PlatformFactory.cpp b/filament/backend/src/PlatformFactory.cpp index a97eec8f4f5..86be6d231e0 100644 --- a/filament/backend/src/PlatformFactory.cpp +++ b/filament/backend/src/PlatformFactory.cpp @@ -41,6 +41,10 @@ #if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) #include "backend/platforms/PlatformEGLHeadless.h" #endif + #elif defined(FILAMENT_SUPPORTS_OSMESA) + #if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) + #include "backend/platforms/PlatformOSMesa.h" + #endif #endif #elif defined(WIN32) #if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) @@ -124,6 +128,8 @@ Platform* PlatformFactory::create(Backend* backend) noexcept { return new PlatformGLX(); #elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX) return new PlatformEGLHeadless(); + #elif defined(FILAMENT_SUPPORTS_OSMESA) + return new PlatformOSMesa(); #else return nullptr; #endif diff --git a/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp b/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp new file mode 100644 index 00000000000..1e62543c11d --- /dev/null +++ b/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include + +namespace filament::backend { + +using namespace backend; + +namespace { + +using BackingType = GLfloat; +#define BACKING_GL_TYPE GL_FLOAT + +struct OSMesaSwapchain { + OSMesaSwapchain(uint32_t width, uint32_t height) + : width(width), + height(height), + buffer(new uint8_t[width * height * 4 * sizeof(BACKING_GL_TYPE)]) {} + + uint32_t width = 0; + uint32_t height = 0; + std::unique_ptr buffer; +}; + +} // anonymous namespace + +Driver* PlatformOSMesa::createDriver(void* const sharedGLContext, + const DriverConfig& driverConfig) noexcept { + FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr) + << "shared GL context is not supported with PlatformOSMesa"; + mContext = OSMesaCreateContext(GL_RGBA, NULL); + + // We need to do a no-op makecurrent here so that the context will be in a correct state before + // any GL calls. + auto chain = createSwapChain(1, 1, 0); + makeCurrent(ContextType::UNPROTECTED, chain, nullptr); + destroySwapChain(chain); + + int result = bluegl::bind(); + FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points."; + + return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig); +} + +void PlatformOSMesa::terminate() noexcept { + OSMesaDestroyContext(mContext); + bluegl::unbind(); +} + +Platform::SwapChain* PlatformOSMesa::createSwapChain(void* nativeWindow, uint64_t flags) noexcept { + FILAMENT_CHECK_POSTCONDITION(false) << "Cannot create non-headless swapchain"; + return (SwapChain*) nativeWindow; +} + +Platform::SwapChain* PlatformOSMesa::createSwapChain(uint32_t width, uint32_t height, + uint64_t flags) noexcept { + OSMesaSwapchain* swapchain = new OSMesaSwapchain(width, height); + return (SwapChain*) swapchain; +} + +void PlatformOSMesa::destroySwapChain(Platform::SwapChain* swapChain) noexcept { + OSMesaSwapchain* impl = (OSMesaSwapchain*) swapChain; + delete impl; +} + +bool PlatformOSMesa::makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { + OSMesaSwapchain* impl = (OSMesaSwapchain*) drawSwapChain; + + auto result = OSMesaMakeCurrent(mContext, (BackingType*) impl->buffer.get(), BACKING_GL_TYPE, + impl->width, impl->height); + FILAMENT_CHECK_POSTCONDITION(result) << "OSMesaMakeCurrent failed!"; + + return true; +} + +void PlatformOSMesa::commit(Platform::SwapChain* swapChain) noexcept { + // No-op since we are not scanning out to a display. +} + +} // namespace filament::backend diff --git a/third_party/libsdl2/include/SDL_config.h b/third_party/libsdl2/include/SDL_config.h index 4070905ea05..637f32353fc 100644 --- a/third_party/libsdl2/include/SDL_config.h +++ b/third_party/libsdl2/include/SDL_config.h @@ -46,6 +46,10 @@ #include "SDL_config_linux_wayland.h" #elif defined(FILAMENT_SUPPORTS_X11) #include "SDL_config_linux_x11.h" +#elif defined(FILAMENT_SUPPORTS_OSMESA) +// We still want the same defines and libs as x11 but we won't actually +// open any windows. +#include "SDL_config_linux_x11.h" #endif #else /* This is a minimal configuration just to get SDL running on new platforms */ diff --git a/third_party/libsdl2/tnt/CMakeLists.txt b/third_party/libsdl2/tnt/CMakeLists.txt index 73f56a5077e..5f6c35536a8 100755 --- a/third_party/libsdl2/tnt/CMakeLists.txt +++ b/third_party/libsdl2/tnt/CMakeLists.txt @@ -103,7 +103,7 @@ if (LINUX) if (FILAMENT_SUPPORTS_WAYLAND) file(GLOB SRCS_LINUX_WAYLAND ${SRC_DIR}/video/wayland/*.c) list(APPEND SRCS ${SRCS_LINUX_WAYLAND}) - elseif(FILAMENT_SUPPORTS_X11) + elseif(FILAMENT_SUPPORTS_X11 OR FILAMENT_SUPPORTS_OSMESA) file(GLOB SRCS_LINUX_X11 ${SRC_DIR}/video/x11/*.c) list(APPEND SRCS ${SRCS_LINUX_X11}) endif() From 7dc17980a3218bde194047f7826830d10f58d4d5 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 25 Sep 2024 20:48:52 -0700 Subject: [PATCH 03/36] addressed comment --- build.sh | 4 ++-- filament/backend/src/opengl/platforms/PlatformOSMesa.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index 2c1a59b7159..8c5d94a96db 100755 --- a/build.sh +++ b/build.sh @@ -65,8 +65,8 @@ function print_help { echo " systrace-enabled build without directly changing #defines. Remember to add -f when" echo " changing this option." echo " -X osmesa_path" - echo " Indicates a path to the path of a completed OSMesa build. OSMesa is used to create an" - echo " offscreen context for software rasterization" + echo " Indicates a path to a completed OSMesa build. OSMesa is used to create an offscreen GL" + echo " context for software rasterization" echo " -S type" echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only" echo " meant for building the samples." diff --git a/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp b/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp index 1e62543c11d..9a34577dc1c 100644 --- a/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp +++ b/filament/backend/src/opengl/platforms/PlatformOSMesa.cpp @@ -34,7 +34,7 @@ struct OSMesaSwapchain { OSMesaSwapchain(uint32_t width, uint32_t height) : width(width), height(height), - buffer(new uint8_t[width * height * 4 * sizeof(BACKING_GL_TYPE)]) {} + buffer(new uint8_t[width * height * 4 * sizeof(BackingType)]) {} uint32_t width = 0; uint32_t height = 0; From 3d4fc7852bd7eea40294bdcca5f79a0306089823 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 26 Sep 2024 16:06:40 -0700 Subject: [PATCH 04/36] github: add software rasterizer job for GL to presubmit (#8158) We use Mesa's gallium swrast to render as the driver with Filament's backend set to GL. We provide a few scripts to parse the tests (as jsons) and run gltf_viewer to produce the rendering. --- .github/actions/ubuntu-apt-add-src/action.yml | 9 ++ .github/workflows/presubmit.yml | 15 ++ test/renderdiff/README.md | 17 +++ test/renderdiff/parse_test_json.py | 142 ++++++++++++++++++ test/renderdiff/run.py | 54 +++++++ test/renderdiff/tests/presubmit.json | 34 +++++ test/renderdiff/tests/sample.json | 34 +++++ test/renderdiff/utils.py | 68 +++++++++ test/renderdiff_tests.sh | 45 ++++++ test/utils/get_mesa.sh | 38 +++++ 10 files changed, 456 insertions(+) create mode 100644 .github/actions/ubuntu-apt-add-src/action.yml create mode 100644 test/renderdiff/README.md create mode 100644 test/renderdiff/parse_test_json.py create mode 100644 test/renderdiff/run.py create mode 100644 test/renderdiff/tests/presubmit.json create mode 100644 test/renderdiff/tests/sample.json create mode 100644 test/renderdiff/utils.py create mode 100755 test/renderdiff_tests.sh create mode 100755 test/utils/get_mesa.sh diff --git a/.github/actions/ubuntu-apt-add-src/action.yml b/.github/actions/ubuntu-apt-add-src/action.yml new file mode 100644 index 00000000000..c104574a632 --- /dev/null +++ b/.github/actions/ubuntu-apt-add-src/action.yml @@ -0,0 +1,9 @@ +name: 'ubuntu apt add deb-src' +runs: + using: "composite" + steps: + - name: "ubuntu apt add deb-src" + run: | + echo "deb-src http://archive.ubuntu.com/ubuntu jammy main restricted universe" | sudo tee /etc/apt/sources.list.d/my.list + sudo apt-get update + shell: bash diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml index 8e0d9ccc08f..151dd375657 100644 --- a/.github/workflows/presubmit.yml +++ b/.github/workflows/presubmit.yml @@ -76,3 +76,18 @@ jobs: - name: Run build script run: | cd build/web && printf "y" | ./build.sh presubmit + + test-renderdiff: + name: test-renderdiff + runs-on: ubuntu-22.04-32core + + steps: + - uses: actions/checkout@v4.1.6 + - uses: ./.github/actions/ubuntu-apt-add-src + - name: Run script + run: | + source ./build/linux/ci-common.sh && bash test/renderdiff_tests.sh + - uses: actions/upload-artifact@v4 + with: + name: presubmit-renderdiff-result + path: ./out/renderdiff_tests diff --git a/test/renderdiff/README.md b/test/renderdiff/README.md new file mode 100644 index 00000000000..f90f2970636 --- /dev/null +++ b/test/renderdiff/README.md @@ -0,0 +1,17 @@ +# Rendering Difference Test + +We created a few scripts to run `gltf_viewer` and produce headless renderings. + +This is mainly useful for continuous integration where GPUs are generally not available on cloud +machines. To perform software rasterization, these scripts are centered around [Mesa]'s software +rasterizers, but nothing bars us from using another rasterizer like [SwiftShader]. Additionally, +we should be able to use GPUs where available (though this is more of a future work). + +The script `run.py` contains the core logic for taking input parameters (such as the test +description file) and then running gltf_viewer to produce the results. + +In the `test` directory is a list of test descriptions that are specified in json. Please see +`sample.json` to parse the structure. + +[Mesa]: https://docs.mesa3d.org +[SwiftShader]: https://github.com/google/swiftshader \ No newline at end of file diff --git a/test/renderdiff/parse_test_json.py b/test/renderdiff/parse_test_json.py new file mode 100644 index 00000000000..2cc260ffec3 --- /dev/null +++ b/test/renderdiff/parse_test_json.py @@ -0,0 +1,142 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from utils import execute, ArgParseImpl + +import glob +from itertools import chain +import json +import sys +import os +from os import path + +def _is_list_of_strings(field): + return isinstance(field, list) and\ + all(isinstance(item, str) for item in field) + +def _is_string(s): + return isinstance(s, str) + +def _is_dict(s): + return isinstance(s, dict) + +class RenderingConfig(): + def __init__(self, data): + assert 'name' in data + assert _is_string(data['name']) + self.name = data['name'] + + assert 'rendering' in data + assert _is_dict(data['rendering']) + self.rendering = data['rendering'] + +class PresetConfig(RenderingConfig): + def __init__(self, data, existing_models): + RenderingConfig.__init__(self, data) + models = data.get('models') + if models: + assert _is_list_of_strings(models) + assert all(m in existing_models for m in models) + self.models = models + +class TestConfig(RenderingConfig): + def __init__(self, data, existing_models, presets): + RenderingConfig.__init__(self, data) + description = data.get('description') + if description: + assert _is_string(description) + self.description = description + + apply_presets = data.get('apply_presets') + rendering = {} + preset_models = [] + if apply_presets: + given_presets = {p.name: p for p in presets} + assert all((name in given_presets) for name in apply_presets) + for preset in apply_presets: + rendering.update(given_presets[preset].rendering) + preset_models += given_presets[preset].models + + assert 'rendering' in data + rendering.update(data['rendering']) + self.rendering = rendering + + models = data.get('models') + self.models = preset_models + if models: + assert _is_list_of_strings(models) + assert all(m in existing_models for m in models) + self.models = set(models + self.models) + + def to_filament_format(self): + json_out = { + 'name': self.name, + 'base': self.rendering + } + return json.dumps(json_out) + +class RenderTestConfig(): + def __init__(self, data): + assert 'name' in data + name = data['name'] + assert _is_string(name) + self.name = name + + assert 'backends' in data + backends = data['backends'] + assert _is_list_of_strings(backends) + self.backends = backends + + assert 'model_search_paths' in data + model_search_paths = data.get('model_search_paths') + assert _is_list_of_strings(model_search_paths) + assert all(path.isdir(p) for p in model_search_paths) + + model_paths = list( + chain(*(glob.glob(f'{d}/**/*.glb', recursive=True) for d in model_search_paths))) + # This flatten the output for glob.glob + self.models = {path.splitext(path.basename(model))[0]: model for model in model_paths} + + preset_data = data.get('presets') + presets = [] + if preset_data: + presets = [PresetConfig(p, self.models) for p in preset_data] + + assert 'tests' in data + self.tests = [TestConfig(t, self.models, presets) for t in data['tests']] + test_names = list([t.name for t in self.tests]) + + # We cannot have duplicate test names + assert len(test_names) == len(set(test_names)) + +def _remove_comments_from_json_txt(json_txt): + res = [] + for line in json_txt.split('\n'): + if '//' in line: + line = line.split('//')[0] + res.append(line) + return '\n'.join(res) + +def parse_test_config_from_path(config_path): + with open(config_path, 'r') as f: + json_txt = json.loads(_remove_comments_from_json_txt(f.read())) + return RenderTestConfig(json_txt) + + +if __name__ == "__main__": + parser = ArgParseImpl() + parser.add_argument('--test', help='Configuration of the test', required=True) + + args, _ = parser.parse_known_args(sys.argv[1:]) + test = parse_test_config_from_path(args.test) diff --git a/test/renderdiff/run.py b/test/renderdiff/run.py new file mode 100644 index 00000000000..17a8a7acb47 --- /dev/null +++ b/test/renderdiff/run.py @@ -0,0 +1,54 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from utils import execute, ArgParseImpl + +from parse_test_json import parse_test_config_from_path +import sys +import os + +def run_test(gltf_viewer, pixel_test, output_dir, opengl_lib=None, vk_icd=None): + assert os.path.isdir(output_dir) + assert os.access(gltf_viewer, os.X_OK) + + for test in pixel_test.tests: + test_json_path = f'{output_dir}/{test.name}_simplified.json' + + with open(test_json_path, 'w') as f: + f.write(f'[{test.to_filament_format()}]') + + for backend in pixel_test.backends: + env = None + if backend == 'opengl' and opengl_lib and os.path.isdir(opengl_lib): + env = {'LD_LIBRARY_PATH': opengl_lib} + + for model in test.models: + model_path = pixel_test.models[model] + out_name = f'{test.name}_{model}_{backend}' + execute(f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless', + env=env, capture_output=False) + execute(f'mv -f {test.name}0.ppm {output_dir}/{out_name}.ppm', capture_output=False) + execute(f'mv -f {test.name}0.json {output_dir}/{test.name}.json', capture_output=False) + +if __name__ == "__main__": + parser = ArgParseImpl() + parser.add_argument('--test', help='Configuration of the test', required=True) + parser.add_argument('--gltf_viewer', help='Path to the gltf_viewer', required=True) + parser.add_argument('--output_dir', help='Output Directory', required=True) + parser.add_argument('--opengl_lib', help='Path to the folder containing OpenGL driver lib (for LD_LIBRARY_PATH)') + parser.add_argument('--vk_icd', help='Path to VK ICD file') + + args, _ = parser.parse_known_args(sys.argv[1:]) + test = parse_test_config_from_path(args.test) + run_test(args.gltf_viewer, test, args.output_dir, opengl_lib=args.opengl_lib, vk_icd=args.vk_icd) diff --git a/test/renderdiff/tests/presubmit.json b/test/renderdiff/tests/presubmit.json new file mode 100644 index 00000000000..78224d419ce --- /dev/null +++ b/test/renderdiff/tests/presubmit.json @@ -0,0 +1,34 @@ +{ + "name": "PresubmitPixelTests", + "backends": ["opengl"], + "model_search_paths": ["third_party/models"], + "presets": [ + { + "name": "Standard", + "models": ["lucy", "DamagedHelmet"], + "rendering": { + "viewer.cameraFocusDistance": 0, + "view.postProcessingEnabled": true + } + } + ], + "tests": [ + { + "name": "BloomFlare", + "description": "Testing bloom and flare", + "apply_presets": ["Standard"], + "rendering": { + "view.bloom.enabled": true, + "view.bloom.lensFlare": true + } + }, + { + "name": "MSAA", + "description": "Testing Multi-sample Anti-aliasing", + "apply_presets": ["Standard"], + "rendering": { + "view.msaa.enabled": true + } + } + ] +} diff --git a/test/renderdiff/tests/sample.json b/test/renderdiff/tests/sample.json new file mode 100644 index 00000000000..eb689d8102d --- /dev/null +++ b/test/renderdiff/tests/sample.json @@ -0,0 +1,34 @@ +{ + "name": "SampleTest" , // [required] + "backends": ["opengl"], // [required] Specifies the backend that will be used to render + // this test + "model_search_paths": ["third_party/models"], // [optional] Base iirectory from which we will glob for + // .glb files and try to match against names in the 'models' + // field. + "presets": [ // [optional] A list of preset configurations that tests can + // inherit from. + { + "name": "Standard", // [required] + "models": ["lucy", "DamagedHelmet"], // [optional] Base name for the gltf file used in the test. For + // example, source files are lucy.glb and DamagedHelmet.gltf + "rendering": { // [required] Rendering settings used in the test. The json format + "viewer.cameraFocusDistance": 0, // is taken from AutomationSpec in libs/viewer + "view.postProcessingEnabled": true + } + } + ], + "tests": [ // [required] List of test configurations + { + "name": "BloomFlare", // [required] + "description": "Testing bloom and flare", // [optional] + "apply_presets": ["Standard"], // [optional] Applies the preset in order. Item must be in + // 'presets' field in the top-level struct. + "model": [], // [optional] List of models used in this test. The list is + // *appended* onto the lists provided by the presets. + "rendering": { // [required] See description in 'presets' + "view.bloom.enabled": true, + "view.bloom.lensFlare": true + } + } + ] +} diff --git a/test/renderdiff/utils.py b/test/renderdiff/utils.py new file mode 100644 index 00000000000..f708d7a6596 --- /dev/null +++ b/test/renderdiff/utils.py @@ -0,0 +1,68 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess +import os +import argparse +import sys + +def execute(cmd, + cwd=None, + capture_output=True, + stdin=None, + env=None, + raise_errors=False): + in_env = os.environ + in_env.update(env if env else {}) + home = os.environ['HOME'] + if f'{home}/bin' not in in_env['PATH']: + in_env['PATH'] = in_env['PATH'] + f':{home}/bin' + + stdout = subprocess.PIPE if capture_output else sys.stdout + stderr = subprocess.PIPE if capture_output else sys.stdout + output = '' + err_output = '' + return_code = -1 + kwargs = { + 'cwd': cwd, + 'env': in_env, + 'stdout': stdout, + 'stderr': stderr, + 'stdin': stdin, + 'universal_newlines': True + } + if capture_output: + process = subprocess.Popen(cmd.split(' '), **kwargs) + output, err_output = process.communicate() + return_code = process.returncode + else: + return_code = subprocess.call(cmd.split(' '), **kwargs) + + if return_code: + # Error + if raise_errors: + raise subprocess.CalledProcessError(return_code, cmd) + if output: + if type(output) != str: + try: + output = output.decode('utf-8').strip() + except UnicodeDecodeError as e: + print('cannot decode ', output, file=sys.stderr) + return return_code, (output if return_code == 0 else err_output) + +class ArgParseImpl(argparse.ArgumentParser): + def error(self, message): + sys.stderr.write('error: %s\n' % message) + self.print_help() + sys.exit(1) diff --git a/test/renderdiff_tests.sh b/test/renderdiff_tests.sh new file mode 100755 index 00000000000..fc5b14fab04 --- /dev/null +++ b/test/renderdiff_tests.sh @@ -0,0 +1,45 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/usr/bin/bash + +OUTPUT_DIR="$(pwd)/out/renderdiff_tests" +RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff" +TEST_UTILS_DIR="$(pwd)/test/utils" +MESA_DIR="$(pwd)/mesa/out/" +MESA_LIB_DIR="${MESA_DIR}/lib/x86_64-linux-gnu" + +function prepare_mesa() { + if [ ! -d ${MESA_LIB_DIR} ]; then + rm -rf mesa + bash ${TEST_UTILS_DIR}/get_mesa.sh + fi +} + +# Following steps are taken: +# - Get and build mesa +# - Build gltf_viewer +# - Run the python script that runs the test +# - Zip up the result + +set -e && set -x && prepare_mesa && \ + mkdir -p ${OUTPUT_DIR} && \ + ./build.sh -X ${MESA_DIR} -p desktop debug gltf_viewer && \ + python3 ${RENDERDIFF_TEST_DIR}/run.py \ + --gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \ + --test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \ + --output_dir=${OUTPUT_DIR} \ + --opengl_lib=${MESA_LIB_DIR} + +unset MESA_LIB_DIR diff --git a/test/utils/get_mesa.sh b/test/utils/get_mesa.sh new file mode 100755 index 00000000000..575182846d0 --- /dev/null +++ b/test/utils/get_mesa.sh @@ -0,0 +1,38 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/usr/bin/bash + +set -e +set -x + +sudo apt-get -y build-dep mesa + +git clone https://gitlab.freedesktop.org/mesa/mesa.git + +pushd . + +cd mesa + +git checkout mesa-23.2.1 + +mkdir -p out + +# -Dosmesa=true => builds OSMesa, which is an offscreen GL context +# -Dgallium-drivers=swrast => builds GL software rasterizer +# -Dvulkan-drivers=swrast => builds VK software rasterizer +meson setup builddir/ -Dprefix="$(pwd)/out" -Dosmesa=true -Dglx=xlib -Dgallium-drivers=swrast -Dvulkan-drivers=swrast +meson install -C builddir/ + +popd From 2cade209b583b5c9fd8cff2726f65678fdf3376f Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Fri, 27 Sep 2024 16:25:12 -0700 Subject: [PATCH 05/36] Add Descriptor Sets to Filament (#8165) We add the concept of the descriptor set as a way to describe shader resources into Filament. This is a comprehensive change across Filament. Info on descriptor sets is available here: https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html Co-authored-by: Benjamin Doherty Co-authored-by: Mathias Agopian Co-authored-by: Sungun Park --- RELEASE_NOTES.md | 4 +- filament/CMakeLists.txt | 24 +- filament/backend/CMakeLists.txt | 38 +- .../backend/DescriptorSetOffsetArray.h | 101 ++ .../backend/include/backend/DriverEnums.h | 149 ++- filament/backend/include/backend/Handle.h | 30 +- .../backend/include/backend/PipelineState.h | 8 + filament/backend/include/backend/Program.h | 128 +- .../backend/include/private/backend/Driver.h | 1 + .../include/private/backend/DriverAPI.inc | 118 +- .../include/private/backend/DriverApi.h | 11 + .../include/private/backend/HandleAllocator.h | 2 +- filament/backend/src/DriverBase.h | 8 + filament/backend/src/Program.cpp | 46 +- filament/backend/src/metal/MetalBuffer.h | 9 +- filament/backend/src/metal/MetalBuffer.mm | 7 +- filament/backend/src/metal/MetalContext.h | 76 +- filament/backend/src/metal/MetalContext.mm | 9 +- filament/backend/src/metal/MetalDriver.h | 14 +- filament/backend/src/metal/MetalDriver.mm | 826 +++++------- .../backend/src/metal/MetalExternalImage.h | 105 +- .../backend/src/metal/MetalExternalImage.mm | 226 ++-- filament/backend/src/metal/MetalHandles.h | 189 ++- filament/backend/src/metal/MetalHandles.mm | 383 ++++-- filament/backend/src/metal/MetalState.h | 60 +- filament/backend/src/metal/MetalState.mm | 83 +- filament/backend/src/noop/NoopDriver.cpp | 51 +- filament/backend/src/opengl/BindingMap.h | 89 ++ .../backend/src/opengl/GLDescriptorSet.cpp | 361 +++++ filament/backend/src/opengl/GLDescriptorSet.h | 171 +++ .../src/opengl/GLDescriptorSetLayout.h | 52 + filament/backend/src/opengl/GLTexture.h | 28 +- filament/backend/src/opengl/OpenGLContext.h | 31 +- filament/backend/src/opengl/OpenGLDriver.cpp | 654 ++++----- filament/backend/src/opengl/OpenGLDriver.h | 36 +- filament/backend/src/opengl/OpenGLProgram.cpp | 187 ++- filament/backend/src/opengl/OpenGLProgram.h | 66 +- filament/backend/src/vulkan/VulkanBlitter.cpp | 31 +- .../backend/src/vulkan/VulkanCommands.cpp | 23 +- filament/backend/src/vulkan/VulkanCommands.h | 32 + filament/backend/src/vulkan/VulkanDriver.cpp | 456 +++---- filament/backend/src/vulkan/VulkanDriver.h | 32 +- .../backend/src/vulkan/VulkanFboCache.cpp | 5 +- filament/backend/src/vulkan/VulkanFboCache.h | 16 +- filament/backend/src/vulkan/VulkanHandles.cpp | 211 +-- filament/backend/src/vulkan/VulkanHandles.h | 151 +-- .../backend/src/vulkan/VulkanImageUtility.cpp | 9 +- .../backend/src/vulkan/VulkanImageUtility.h | 4 +- .../src/vulkan/VulkanPipelineCache.cpp | 6 +- .../backend/src/vulkan/VulkanReadPixels.cpp | 4 +- .../backend/src/vulkan/VulkanSwapChain.cpp | 16 +- filament/backend/src/vulkan/VulkanSwapChain.h | 5 +- filament/backend/src/vulkan/VulkanTexture.cpp | 492 ++++--- filament/backend/src/vulkan/VulkanTexture.h | 166 ++- filament/backend/src/vulkan/VulkanUtility.cpp | 2 +- filament/backend/src/vulkan/VulkanUtility.h | 141 +- .../caching/VulkanDescriptorSetManager.cpp | 1187 +++++------------ .../caching/VulkanDescriptorSetManager.h | 55 +- .../caching/VulkanPipelineLayoutCache.cpp | 9 +- .../caching/VulkanPipelineLayoutCache.h | 14 +- filament/backend/test/BackendTest.cpp | 18 +- filament/backend/test/BackendTest.h | 9 +- filament/backend/test/MetalTest.mm | 159 +++ filament/backend/test/ShaderGenerator.cpp | 23 +- filament/backend/test/ShaderGenerator.h | 5 +- filament/backend/test/mac_runner.mm | 3 +- filament/backend/test/test_Blit.cpp | 29 +- filament/backend/test/test_BufferUpdates.cpp | 138 +- filament/backend/test/test_ComputeBasic.cpp | 5 +- filament/backend/test/test_FeedbackLoops.cpp | 108 +- filament/backend/test/test_LoadImage.cpp | 180 ++- filament/backend/test/test_MipLevels.cpp | 67 +- .../backend/test/test_RenderExternalImage.cpp | 174 +-- filament/docs/Vulkan.md.html | 2 +- filament/include/filament/RenderableManager.h | 15 - filament/src/Bimap.h | 7 +- filament/src/HwDescriptorSetLayoutFactory.cpp | 103 ++ filament/src/HwDescriptorSetLayoutFactory.h | 119 ++ filament/src/HwRenderPrimitiveFactory.h | 7 +- filament/src/HwVertexBufferInfoFactory.h | 7 +- filament/src/MaterialParser.cpp | 235 ++-- filament/src/MaterialParser.h | 67 +- filament/src/PostProcessManager.cpp | 131 +- filament/src/PostProcessManager.h | 36 +- filament/src/RenderPass.cpp | 292 ++-- filament/src/RenderPass.h | 85 +- filament/src/RenderPrimitive.cpp | 2 +- filament/src/RenderPrimitive.h | 2 +- filament/src/RendererUtils.cpp | 8 +- filament/src/ResourceAllocator.cpp | 28 +- filament/src/ShadowMap.cpp | 18 +- filament/src/ShadowMap.h | 11 +- filament/src/ShadowMapManager.cpp | 10 +- filament/src/ShadowMapManager.h | 4 +- filament/src/TypedUniformBuffer.h | 13 +- filament/src/components/RenderableManager.cpp | 30 +- filament/src/components/RenderableManager.h | 39 +- filament/src/details/Engine.cpp | 61 +- filament/src/details/Engine.h | 26 + filament/src/details/IndirectLight.cpp | 6 +- filament/src/details/Material.cpp | 116 +- filament/src/details/Material.h | 59 +- filament/src/details/MaterialInstance.cpp | 177 ++- filament/src/details/MaterialInstance.h | 70 +- filament/src/details/MorphTargetBuffer.cpp | 15 +- filament/src/details/MorphTargetBuffer.h | 20 +- filament/src/details/RenderTarget.cpp | 4 +- filament/src/details/Renderer.cpp | 35 +- filament/src/details/Scene.h | 4 + filament/src/details/SkinningBuffer.cpp | 24 +- filament/src/details/SkinningBuffer.h | 26 +- filament/src/details/Texture.cpp | 191 ++- filament/src/details/Texture.h | 42 +- filament/src/details/View.cpp | 154 ++- filament/src/details/View.h | 52 +- .../ColorPassDescriptorSet.cpp} | 262 ++-- .../ColorPassDescriptorSet.h} | 58 +- filament/src/ds/DescriptorSet.cpp | 142 ++ filament/src/ds/DescriptorSet.h | 110 ++ filament/src/ds/DescriptorSetLayout.cpp | 58 + filament/src/ds/DescriptorSetLayout.h | 81 ++ filament/src/ds/PostProcessDescriptorSet.cpp | 67 + filament/src/ds/PostProcessDescriptorSet.h | 59 + .../ShadowMapDescriptorSet.cpp} | 39 +- .../ShadowMapDescriptorSet.h} | 21 +- filament/src/ds/SsrPassDescriptorSet.cpp | 109 ++ filament/src/ds/SsrPassDescriptorSet.h | 74 + filament/src/ds/TypedBuffer.h | 75 ++ filament/src/ds/TypedUniformBuffer.h | 89 ++ libs/filabridge/CMakeLists.txt | 3 +- .../include/filament/MaterialChunkType.h | 4 +- .../include/filament/MaterialEnums.h | 5 +- .../include/private/filament/DescriptorSets.h | 49 + .../include/private/filament/EngineEnums.h | 69 +- .../private/filament/SamplerBindingsInfo.h | 52 - .../private/filament/SamplerInterfaceBlock.h | 10 +- libs/filabridge/src/DescriptorSets.cpp | 197 +++ libs/filabridge/src/SamplerInterfaceBlock.cpp | 25 +- libs/filamat/CMakeLists.txt | 3 +- libs/filamat/src/GLSLPostProcessor.cpp | 424 ++++-- libs/filamat/src/GLSLPostProcessor.h | 19 +- libs/filamat/src/MaterialBuilder.cpp | 92 +- libs/filamat/src/MaterialVariants.cpp | 73 +- libs/filamat/src/MetalArgumentBuffer.cpp | 27 +- libs/filamat/src/MetalArgumentBuffer.h | 18 +- libs/filamat/src/SamplerBindingMap.cpp | 105 -- libs/filamat/src/SamplerBindingMap.h | 77 -- .../src/eiff/MaterialInterfaceBlockChunk.cpp | 177 ++- .../src/eiff/MaterialInterfaceBlockChunk.h | 60 +- libs/filamat/src/shaders/CodeGenerator.cpp | 76 +- libs/filamat/src/shaders/CodeGenerator.h | 42 +- libs/filamat/src/shaders/MaterialInfo.h | 3 - libs/filamat/src/shaders/ShaderGenerator.cpp | 173 ++- libs/filamat/src/shaders/ShaderGenerator.h | 20 +- libs/filamat/src/shaders/SibGenerator.cpp | 89 +- libs/filamat/src/shaders/SibGenerator.h | 5 +- libs/filamat/src/shaders/UibGenerator.cpp | 60 + libs/filamat/src/shaders/UibGenerator.h | 47 +- libs/filamat/tests/test_argBufferFixup.cpp | 44 + libs/iblprefilter/src/IBLPrefilterContext.cpp | 18 +- libs/matdbg/src/TextWriter.cpp | 6 + .../utils/include/utils/FixedCapacityVector.h | 2 +- libs/utils/src/EntityManagerImpl.h | 7 - shaders/src/ambient_occlusion.fs | 34 +- shaders/src/fog.fs | 2 +- shaders/src/getters.vs | 10 +- shaders/src/light_directional.fs | 2 +- shaders/src/light_indirect.fs | 22 +- shaders/src/light_punctual.fs | 2 +- shaders/src/light_reflections.fs | 6 +- shaders/src/main.fs | 2 +- shaders/src/shading_unlit.fs | 2 +- shaders/src/shadowing.fs | 2 +- 173 files changed, 8702 insertions(+), 5426 deletions(-) create mode 100644 filament/backend/include/backend/DescriptorSetOffsetArray.h create mode 100644 filament/backend/src/opengl/BindingMap.h create mode 100644 filament/backend/src/opengl/GLDescriptorSet.cpp create mode 100644 filament/backend/src/opengl/GLDescriptorSet.h create mode 100644 filament/backend/src/opengl/GLDescriptorSetLayout.h create mode 100644 filament/backend/test/MetalTest.mm create mode 100644 filament/src/HwDescriptorSetLayoutFactory.cpp create mode 100644 filament/src/HwDescriptorSetLayoutFactory.h rename filament/src/{PerViewUniforms.cpp => ds/ColorPassDescriptorSet.cpp} (65%) rename filament/src/{PerViewUniforms.h => ds/ColorPassDescriptorSet.h} (76%) create mode 100644 filament/src/ds/DescriptorSet.cpp create mode 100644 filament/src/ds/DescriptorSet.h create mode 100644 filament/src/ds/DescriptorSetLayout.cpp create mode 100644 filament/src/ds/DescriptorSetLayout.h create mode 100644 filament/src/ds/PostProcessDescriptorSet.cpp create mode 100644 filament/src/ds/PostProcessDescriptorSet.h rename filament/src/{PerShadowMapUniforms.cpp => ds/ShadowMapDescriptorSet.cpp} (72%) rename filament/src/{PerShadowMapUniforms.h => ds/ShadowMapDescriptorSet.h} (81%) create mode 100644 filament/src/ds/SsrPassDescriptorSet.cpp create mode 100644 filament/src/ds/SsrPassDescriptorSet.h create mode 100644 filament/src/ds/TypedBuffer.h create mode 100644 filament/src/ds/TypedUniformBuffer.h create mode 100644 libs/filabridge/include/private/filament/DescriptorSets.h create mode 100644 libs/filabridge/src/DescriptorSets.cpp diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6f01b7affb6..e6727d791a7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,8 +7,8 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). -## v1.54.6 - +## v1.55.0 +- Add descriptor sets to describe shader resources. [⚠️ **New Material Version**] ## v1.54.5 diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 18f402ad9e7..924ca1c52a0 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -66,6 +66,7 @@ set(SRCS src/FrameSkipper.cpp src/Froxelizer.cpp src/Frustum.cpp + src/HwDescriptorSetLayoutFactory.cpp src/HwRenderPrimitiveFactory.cpp src/HwVertexBufferInfoFactory.cpp src/IndexBuffer.cpp @@ -76,8 +77,6 @@ set(SRCS src/MaterialInstance.cpp src/MaterialParser.cpp src/MorphTargetBuffer.cpp - src/PerViewUniforms.cpp - src/PerShadowMapUniforms.cpp src/PostProcessManager.cpp src/RenderPass.cpp src/RenderPrimitive.cpp @@ -126,6 +125,12 @@ set(SRCS src/details/Texture.cpp src/details/VertexBuffer.cpp src/details/View.cpp + src/ds/ColorPassDescriptorSet.cpp + src/ds/DescriptorSet.cpp + src/ds/DescriptorSetLayout.cpp + src/ds/PostProcessDescriptorSet.cpp + src/ds/ShadowMapDescriptorSet.cpp + src/ds/SsrPassDescriptorSet.cpp src/fg/Blackboard.cpp src/fg/DependencyGraph.cpp src/fg/FrameGraph.cpp @@ -149,23 +154,21 @@ set(PRIVATE_HDRS src/FrameInfo.h src/FrameSkipper.h src/Froxelizer.h + src/HwDescriptorSetLayoutFactory.h src/HwRenderPrimitiveFactory.h src/HwVertexBufferInfoFactory.h src/Intersections.h src/MaterialParser.h - src/PerViewUniforms.h - src/PerShadowMapUniforms.h src/PIDController.h src/PostProcessManager.h - src/RendererUtils.h src/RenderPass.h src/RenderPrimitive.h + src/RendererUtils.h src/ResourceAllocator.h src/ResourceList.h src/ShadowMap.h src/ShadowMapManager.h src/SharedHandle.h - src/TypedUniformBuffer.h src/UniformBuffer.h src/components/CameraManager.h src/components/LightManager.h @@ -193,6 +196,14 @@ set(PRIVATE_HDRS src/details/Texture.h src/details/VertexBuffer.h src/details/View.h + src/downcast.h + src/ds/ColorPassDescriptorSet.h + src/ds/DescriptorSetLayout.h + src/ds/PostProcessDescriptorSet.h + src/ds/ShadowMapDescriptorSet.h + src/ds/SsrPassDescriptorSet.h + src/ds/TypedBuffer.h + src/ds/TypedUniformBuffer.h src/fg/Blackboard.h src/fg/FrameGraph.h src/fg/FrameGraphId.h @@ -210,7 +221,6 @@ set(PRIVATE_HDRS src/materials/fsr/ffx_a.h src/materials/fsr/ffx_fsr1.h src/materials/fsr/ffx_fsr1_mobile.fs - src/downcast.h ) set(MATERIAL_SRCS diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index bbe4ea53880..793486d8923 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -12,6 +12,7 @@ set(PUBLIC_HDRS include/backend/AcquiredImage.h include/backend/BufferDescriptor.h include/backend/CallbackHandler.h + include/backend/DescriptorSetOffsetArray.h include/backend/DriverApiForward.h include/backend/DriverEnums.h include/backend/Handle.h @@ -69,9 +70,13 @@ set(PRIVATE_HDRS if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3) list(APPEND SRCS include/backend/platforms/OpenGLPlatform.h + src/opengl/BindingMap.h src/opengl/gl_headers.cpp src/opengl/gl_headers.h src/opengl/GLBufferObject.h + src/opengl/GLDescriptorSet.cpp + src/opengl/GLDescriptorSet.h + src/opengl/GLDescriptorSetLayout.h src/opengl/GLTexture.h src/opengl/GLUtils.cpp src/opengl/GLUtils.h @@ -508,21 +513,38 @@ endif() # ================================================================================================== # Compute tests +# +#if (NOT IOS AND NOT WEBGL) +# +#add_executable(compute_test +# test/ComputeTest.cpp +# test/Arguments.cpp +# test/test_ComputeBasic.cpp +# ) +# +#target_link_libraries(compute_test PRIVATE +# backend +# getopt +# gtest +# ) +# +#set_target_properties(compute_test PROPERTIES FOLDER Tests) +# +#endif() -if (NOT IOS AND NOT WEBGL) +# ================================================================================================== +# Metal utils tests -add_executable(compute_test - test/ComputeTest.cpp - test/Arguments.cpp - test/test_ComputeBasic.cpp - ) +if (APPLE AND NOT IOS) + +add_executable(metal_utils_test test/MetalTest.mm) -target_link_libraries(compute_test PRIVATE +target_link_libraries(metal_utils_test PRIVATE backend getopt gtest ) -set_target_properties(compute_test PROPERTIES FOLDER Tests) +set_target_properties(metal_utils_test PROPERTIES FOLDER Tests) endif() diff --git a/filament/backend/include/backend/DescriptorSetOffsetArray.h b/filament/backend/include/backend/DescriptorSetOffsetArray.h new file mode 100644 index 00000000000..3c7b85664bc --- /dev/null +++ b/filament/backend/include/backend/DescriptorSetOffsetArray.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H +#define TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H + +#include + +#include +#include + +#include +#include + + +namespace filament::backend { + +void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept; + +class DescriptorSetOffsetArray { +public: + using value_type = uint32_t; + using reference = value_type&; + using const_reference = value_type const&; + using size_type = uint32_t; + using difference_type = int32_t; + using pointer = value_type*; + using const_pointer = value_type const*; + using iterator = pointer; + using const_iterator = const_pointer; + + DescriptorSetOffsetArray() noexcept = default; + + ~DescriptorSetOffsetArray() noexcept = default; + + DescriptorSetOffsetArray(size_type size, DriverApi& driver) noexcept { + mOffsets = (value_type *)allocateFromCommandStream(driver, + size * sizeof(value_type), alignof(value_type)); + std::uninitialized_fill_n(mOffsets, size, 0); + } + + DescriptorSetOffsetArray(std::initializer_list list, DriverApi& driver) noexcept { + mOffsets = (value_type *)allocateFromCommandStream(driver, + list.size() * sizeof(value_type), alignof(value_type)); + std::uninitialized_copy(list.begin(), list.end(), mOffsets); + } + + DescriptorSetOffsetArray(DescriptorSetOffsetArray const&) = delete; + DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray const&) = delete; + + DescriptorSetOffsetArray(DescriptorSetOffsetArray&& rhs) noexcept + : mOffsets(rhs.mOffsets) { + rhs.mOffsets = nullptr; + } + + DescriptorSetOffsetArray& operator=(DescriptorSetOffsetArray&& rhs) noexcept { + if (this != &rhs) { + mOffsets = rhs.mOffsets; + rhs.mOffsets = nullptr; + } + return *this; + } + + bool empty() const noexcept { return mOffsets == nullptr; } + + value_type* data() noexcept { return mOffsets; } + const value_type* data() const noexcept { return mOffsets; } + + + reference operator[](size_type n) noexcept { + return *(data() + n); + } + + const_reference operator[](size_type n) const noexcept { + return *(data() + n); + } + + void clear() noexcept { + mOffsets = nullptr; + } + +private: + value_type *mOffsets = nullptr; +}; + +} // namespace filament::backend + +#endif //TNT_FILAMENT_BACKEND_COMMANDSTREAMVECTOR_H diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 35cd89a57e7..51926ad2def 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -19,13 +19,16 @@ #ifndef TNT_FILAMENT_BACKEND_DRIVERENUMS_H #define TNT_FILAMENT_BACKEND_DRIVERENUMS_H -#include #include // Because we define ERROR in the FenceStatus enum. #include #include +#include +#include #include +#include +#include #include #include @@ -97,6 +100,8 @@ static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guarantee static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3. static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects. static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES. +static constexpr size_t MAX_DESCRIPTOR_SET_COUNT = 4; // This is guaranteed by Vulkan. +static constexpr size_t MAX_DESCRIPTOR_COUNT = 64; // per set static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte // of push constant (we assume 4-byte @@ -191,6 +196,70 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag } } +enum class ShaderStage : uint8_t { + VERTEX = 0, + FRAGMENT = 1, + COMPUTE = 2 +}; + +static constexpr size_t PIPELINE_STAGE_COUNT = 2; +enum class ShaderStageFlags : uint8_t { + NONE = 0, + VERTEX = 0x1, + FRAGMENT = 0x2, + COMPUTE = 0x4, + ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE +}; + +static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept { + switch (type) { + case ShaderStage::VERTEX: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX)); + case ShaderStage::FRAGMENT: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT)); + case ShaderStage::COMPUTE: + return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE)); + } +} + +enum class DescriptorType : uint8_t { + UNIFORM_BUFFER, + SHADER_STORAGE_BUFFER, + SAMPLER, + INPUT_ATTACHMENT, +}; + +enum class DescriptorFlags : uint8_t { + NONE = 0x00, + DYNAMIC_OFFSET = 0x01 +}; + +using descriptor_set_t = uint8_t; + +using descriptor_binding_t = uint8_t; + +struct DescriptorSetLayoutBinding { + DescriptorType type; + ShaderStageFlags stageFlags; + descriptor_binding_t binding; + DescriptorFlags flags = DescriptorFlags::NONE; + uint16_t count = 0; + + friend inline bool operator==( + DescriptorSetLayoutBinding const& lhs, + DescriptorSetLayoutBinding const& rhs) noexcept { + return lhs.type == rhs.type && + lhs.flags == rhs.flags && + lhs.count == rhs.count && + lhs.stageFlags == rhs.stageFlags; + } +}; + +struct DescriptorSetLayout { + utils::FixedCapacityVector bindings; +}; + + /** * Bitmask for selecting render buffers */ @@ -270,15 +339,6 @@ enum class FenceStatus : int8_t { TIMEOUT_EXPIRED = 1, //!< wait()'s timeout expired. The Fence condition is not satisfied. }; -/** - * Status codes for sync objects - */ -enum class SyncStatus : int8_t { - ERROR = -1, //!< An error occurred. The Sync is not signaled. - SIGNALED = 0, //!< The Sync is signaled. - NOT_SIGNALED = 1, //!< The Sync is not signaled yet -}; - static constexpr uint64_t FENCE_WAIT_FOR_EVER = uint64_t(-1); /** @@ -368,6 +428,18 @@ enum class SamplerType : uint8_t { SAMPLER_CUBEMAP_ARRAY, //!< Cube map array texture (feature level 2) }; +inline const char* stringify(SamplerType samplerType) { + switch (samplerType) { + case SamplerType::SAMPLER_2D: return "SAMPLER_2D"; + case SamplerType::SAMPLER_2D_ARRAY: return "SAMPLER_2D_ARRAY"; + case SamplerType::SAMPLER_CUBEMAP: return "SAMPLER_CUBEMAP"; + case SamplerType::SAMPLER_EXTERNAL: return "SAMPLER_EXTERNAL"; + case SamplerType::SAMPLER_3D: return "SAMPLER_3D"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: return "SAMPLER_CUBEMAP_ARRAY"; + } + return "UNKNOWN"; +} + //! Subpass type enum class SubpassType : uint8_t { SUBPASS_INPUT @@ -696,6 +768,23 @@ enum class TextureUsage : uint16_t { DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage }; +inline const char* stringify(TextureUsage usage) { + switch (usage) { + case TextureUsage::NONE: return "NONE"; + case TextureUsage::COLOR_ATTACHMENT: return "COLOR_ATTACHMENT"; + case TextureUsage::DEPTH_ATTACHMENT: return "DEPTH_ATTACHMENT"; + case TextureUsage::STENCIL_ATTACHMENT: return "STENCIL_ATTACHMENT"; + case TextureUsage::UPLOADABLE: return "UPLOADABLE"; + case TextureUsage::SAMPLEABLE: return "SAMPLEABLE"; + case TextureUsage::SUBPASS_INPUT: return "SUBPASS_INPUT"; + case TextureUsage::BLIT_SRC: return "BLIT_SRC"; + case TextureUsage::BLIT_DST: return "BLIT_DST"; + case TextureUsage::PROTECTED: return "PROTECTED"; + case TextureUsage::DEFAULT: return "DEFAULT"; + default: return "UNKNOWN"; + } +} + //! Texture swizzle enum class TextureSwizzle : uint8_t { SUBSTITUTE_ZERO, @@ -887,6 +976,9 @@ struct SamplerParams { // NOLINT struct EqualTo { bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept { + assert_invariant(lhs.padding0 == 0); + assert_invariant(lhs.padding1 == 0); + assert_invariant(lhs.padding2 == 0); auto* pLhs = reinterpret_cast(reinterpret_cast(&lhs)); auto* pRhs = reinterpret_cast(reinterpret_cast(&rhs)); return *pLhs == *pRhs; @@ -895,6 +987,9 @@ struct SamplerParams { // NOLINT struct LessThan { bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept { + assert_invariant(lhs.padding0 == 0); + assert_invariant(lhs.padding1 == 0); + assert_invariant(lhs.padding2 == 0); auto* pLhs = reinterpret_cast(reinterpret_cast(&lhs)); auto* pRhs = reinterpret_cast(reinterpret_cast(&rhs)); return *pLhs == *pRhs; @@ -902,6 +997,12 @@ struct SamplerParams { // NOLINT }; private: + friend inline bool operator == (SamplerParams lhs, SamplerParams rhs) noexcept { + return SamplerParams::EqualTo{}(lhs, rhs); + } + friend inline bool operator != (SamplerParams lhs, SamplerParams rhs) noexcept { + return !SamplerParams::EqualTo{}(lhs, rhs); + } friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept { return SamplerParams::LessThan{}(lhs, rhs); } @@ -1069,32 +1170,6 @@ struct RasterState { * \privatesection */ -enum class ShaderStage : uint8_t { - VERTEX = 0, - FRAGMENT = 1, - COMPUTE = 2 -}; - -static constexpr size_t PIPELINE_STAGE_COUNT = 2; -enum class ShaderStageFlags : uint8_t { - NONE = 0, - VERTEX = 0x1, - FRAGMENT = 0x2, - COMPUTE = 0x4, - ALL_SHADER_STAGE_FLAGS = VERTEX | FRAGMENT | COMPUTE -}; - -static inline constexpr bool hasShaderType(ShaderStageFlags flags, ShaderStage type) noexcept { - switch (type) { - case ShaderStage::VERTEX: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::VERTEX)); - case ShaderStage::FRAGMENT: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::FRAGMENT)); - case ShaderStage::COMPUTE: - return bool(uint8_t(flags) & uint8_t(ShaderStageFlags::COMPUTE)); - } -} - /** * Selects which buffers to clear at the beginning of the render pass, as well as which buffers * can be discarded at the beginning and end of the render pass. @@ -1259,6 +1334,8 @@ template<> struct utils::EnableBitMaskOperators struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableBitMaskOperators + : public std::true_type {}; template<> struct utils::EnableBitMaskOperators : public std::true_type {}; template<> struct utils::EnableBitMaskOperators diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index c54e9609cef..2cf52244149 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -41,6 +41,8 @@ struct HwTexture; struct HwTimerQuery; struct HwVertexBufferInfo; struct HwVertexBuffer; +struct HwDescriptorSetLayout; +struct HwDescriptorSet; /* * A handle to a backend resource. HandleBase is for internal use only. @@ -130,19 +132,21 @@ struct Handle : public HandleBase { // Types used by the command stream // (we use this renaming because the macro-system doesn't deal well with "<" and ">") -using BufferObjectHandle = Handle; -using FenceHandle = Handle; -using IndexBufferHandle = Handle; -using ProgramHandle = Handle; -using RenderPrimitiveHandle = Handle; -using RenderTargetHandle = Handle; -using SamplerGroupHandle = Handle; -using StreamHandle = Handle; -using SwapChainHandle = Handle; -using TextureHandle = Handle; -using TimerQueryHandle = Handle; -using VertexBufferHandle = Handle; -using VertexBufferInfoHandle = Handle; +using BufferObjectHandle = Handle; +using FenceHandle = Handle; +using IndexBufferHandle = Handle; +using ProgramHandle = Handle; +using RenderPrimitiveHandle = Handle; +using RenderTargetHandle = Handle; +using SamplerGroupHandle = Handle; +using StreamHandle = Handle; +using SwapChainHandle = Handle; +using TextureHandle = Handle; +using TimerQueryHandle = Handle; +using VertexBufferHandle = Handle; +using VertexBufferInfoHandle = Handle; +using DescriptorSetLayoutHandle = Handle; +using DescriptorSetHandle = Handle; } // namespace filament::backend diff --git a/filament/backend/include/backend/PipelineState.h b/filament/backend/include/backend/PipelineState.h index 220d04bbf26..051a381a521 100644 --- a/filament/backend/include/backend/PipelineState.h +++ b/filament/backend/include/backend/PipelineState.h @@ -22,15 +22,23 @@ #include +#include + #include namespace filament::backend { //! \privatesection +struct PipelineLayout { + using SetLayout = std::array, MAX_DESCRIPTOR_SET_COUNT>; + SetLayout setLayout; // 16 +}; + struct PipelineState { Handle program; // 4 Handle vertexBufferInfo; // 4 + PipelineLayout pipelineLayout; // 16 RasterState rasterState; // 4 StencilState stencilState; // 12 PolygonOffset polygonOffset; // 8 diff --git a/filament/backend/include/backend/Program.h b/filament/backend/include/backend/Program.h index 7cec72cd0c2..f8e9d50eb87 100644 --- a/filament/backend/include/backend/Program.h +++ b/filament/backend/include/backend/Program.h @@ -24,9 +24,11 @@ #include -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers -#include // FIXME: STL headers are not allowed in public headers +#include +#include +#include +#include +#include #include #include @@ -40,29 +42,36 @@ class Program { static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT; static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT; - struct Sampler { - utils::CString name = {}; // name of the sampler in the shader - uint32_t binding = 0; // binding point of the sampler in the shader + struct Descriptor { + utils::CString name; + backend::DescriptorType type; + backend::descriptor_binding_t binding; }; - struct SamplerGroupData { - utils::FixedCapacityVector samplers; - ShaderStageFlags stageFlags = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; + struct SpecializationConstant { + using Type = std::variant; + uint32_t id; // id set in glsl + Type value; // value and type }; - struct Uniform { + struct Uniform { // For ES2 support utils::CString name; // full qualified name of the uniform field uint16_t offset; // offset in 'uint32_t' into the uniform buffer uint8_t size; // >1 for arrays UniformType type; // uniform type }; - using UniformBlockInfo = std::array; - using UniformInfo = utils::FixedCapacityVector; - using SamplerGroupInfo = std::array; + using DescriptorBindingsInfo = utils::FixedCapacityVector; + using DescriptorSetInfo = std::array; + using SpecializationConstantsInfo = utils::FixedCapacityVector; using ShaderBlob = utils::FixedCapacityVector; using ShaderSource = std::array; + using AttributesInfo = utils::FixedCapacityVector>; + using UniformInfo = utils::FixedCapacityVector; + using BindingUniformsInfo = utils::FixedCapacityVector< + std::tuple>; + Program() noexcept; Program(const Program& rhs) = delete; @@ -79,43 +88,19 @@ class Program { Program& diagnostics(utils::CString const& name, utils::Invocable&& logger); - // sets one of the program's shader (e.g. vertex, fragment) + // Sets one of the program's shader (e.g. vertex, fragment) // string-based shaders are null terminated, consequently the size parameter must include the // null terminating character. Program& shader(ShaderStage shader, void const* data, size_t size); - // sets the language of the shader sources provided with shader() (defaults to ESSL3) + // Sets the language of the shader sources provided with shader() (defaults to ESSL3) Program& shaderLanguage(ShaderLanguage shaderLanguage); - // Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is - // not permitted in glsl. The backend needs a way to associate a uniform block - // to a binding point. - Program& uniformBlockBindings( - utils::FixedCapacityVector> const& uniformBlockBindings) noexcept; - - // Note: This is only needed for GLES2.0, this is used to emulate UBO. This function tells - // the program everything it needs to know about the uniforms at a given binding - Program& uniforms(uint32_t index, UniformInfo const& uniforms) noexcept; - - // Note: This is only needed for GLES2.0. - Program& attributes( - utils::FixedCapacityVector> attributes) noexcept; - - // sets the 'bindingPoint' sampler group descriptor for this program. - // 'samplers' can be destroyed after this call. - // This effectively associates a set of (BindingPoints, index) to a texture unit in the shader. - // Or more precisely, what layout(binding=) is set to in GLSL. - Program& setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags, - Sampler const* samplers, size_t count) noexcept; - - struct SpecializationConstant { - using Type = std::variant; - uint32_t id; // id set in glsl - Type value; // value and type - }; + // Descriptor binding (set, binding, type -> shader name) info + Program& descriptorBindings(backend::descriptor_set_t set, + DescriptorBindingsInfo descriptorBindings) noexcept; - Program& specializationConstants( - utils::FixedCapacityVector specConstants) noexcept; + Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept; struct PushConstant { utils::CString name; @@ -129,33 +114,40 @@ class Program { Program& multiview(bool multiview) noexcept; - ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } - ShaderSource& getShadersSource() noexcept { return mShadersSource; } - - UniformBlockInfo const& getUniformBlockBindings() const noexcept { return mUniformBlocks; } - UniformBlockInfo& getUniformBlockBindings() noexcept { return mUniformBlocks; } - - SamplerGroupInfo const& getSamplerGroupInfo() const { return mSamplerGroups; } - SamplerGroupInfo& getSamplerGroupInfo() { return mSamplerGroups; } + // For ES2 support only... + Program& uniforms(uint32_t index, utils::CString name, UniformInfo uniforms) noexcept; + Program& attributes(AttributesInfo attributes) noexcept; - auto const& getBindingUniformInfo() const { return mBindingUniformInfo; } - auto& getBindingUniformInfo() { return mBindingUniformInfo; } + // + // Getters for program construction... + // - auto const& getAttributes() const { return mAttributes; } - auto& getAttributes() { return mAttributes; } + ShaderSource const& getShadersSource() const noexcept { return mShadersSource; } + ShaderSource& getShadersSource() noexcept { return mShadersSource; } utils::CString const& getName() const noexcept { return mName; } utils::CString& getName() noexcept { return mName; } auto const& getShaderLanguage() const { return mShaderLanguage; } - utils::FixedCapacityVector const& getSpecializationConstants() const noexcept { + uint64_t getCacheId() const noexcept { return mCacheId; } + + bool isMultiview() const noexcept { return mMultiview; } + + CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } + + SpecializationConstantsInfo const& getSpecializationConstants() const noexcept { return mSpecializationConstants; } - utils::FixedCapacityVector& getSpecializationConstants() noexcept { + + SpecializationConstantsInfo& getSpecializationConstants() noexcept { return mSpecializationConstants; } + DescriptorSetInfo& getDescriptorBindings() noexcept { + return mDescriptorBindings; + } + utils::FixedCapacityVector const& getPushConstants( ShaderStage stage) const noexcept { return mPushConstants[static_cast(stage)]; @@ -165,27 +157,29 @@ class Program { return mPushConstants[static_cast(stage)]; } - uint64_t getCacheId() const noexcept { return mCacheId; } - - bool isMultiview() const noexcept { return mMultiview; } + auto const& getBindingUniformInfo() const { return mBindingUniformsInfo; } + auto& getBindingUniformInfo() { return mBindingUniformsInfo; } - CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; } + auto const& getAttributes() const { return mAttributes; } + auto& getAttributes() { return mAttributes; } private: friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder); - UniformBlockInfo mUniformBlocks = {}; - SamplerGroupInfo mSamplerGroups = {}; ShaderSource mShadersSource; ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3; utils::CString mName; uint64_t mCacheId{}; + CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; utils::Invocable mLogger; - utils::FixedCapacityVector mSpecializationConstants; + SpecializationConstantsInfo mSpecializationConstants; std::array, SHADER_TYPE_COUNT> mPushConstants; - utils::FixedCapacityVector> mAttributes; - std::array mBindingUniformInfo; - CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH; + DescriptorSetInfo mDescriptorBindings; + + // For ES2 support only + AttributesInfo mAttributes; + BindingUniformsInfo mBindingUniformsInfo; + // Indicates the current engine was initialized with multiview stereo, and the variant for this // program contains STE flag. This will be referred later for the OpenGL shader compiler to // determine whether shader code replacement for the num_views should be performed. diff --git a/filament/backend/include/private/backend/Driver.h b/filament/backend/include/private/backend/Driver.h index 527052378e6..1fe16446813 100644 --- a/filament/backend/include/private/backend/Driver.h +++ b/filament/backend/include/private/backend/Driver.h @@ -18,6 +18,7 @@ #define TNT_FILAMENT_BACKEND_PRIVATE_DRIVER_H #include +#include #include #include #include diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 0316339563a..66556b0f41f 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -201,20 +201,33 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture, uint32_t, depth, backend::TextureUsage, usage) -DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureSwizzled, - backend::SamplerType, target, - uint8_t, levels, - backend::TextureFormat, format, - uint8_t, samples, - uint32_t, width, - uint32_t, height, - uint32_t, depth, - backend::TextureUsage, usage, +DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureView, + backend::TextureHandle, texture, + uint8_t, baseLevel, + uint8_t, levelCount) + +DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureViewSwizzle, + backend::TextureHandle, texture, backend::TextureSwizzle, r, backend::TextureSwizzle, g, backend::TextureSwizzle, b, backend::TextureSwizzle, a) +DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImage, + backend::TextureFormat, format, + uint32_t, width, + uint32_t, height, + backend::TextureUsage, usage, + void*, image) + +DECL_DRIVER_API_R_N(backend::TextureHandle, createTextureExternalImagePlane, + backend::TextureFormat, format, + uint32_t, width, + uint32_t, height, + backend::TextureUsage, usage, + void*, image, + uint32_t, plane) + DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture, intptr_t, id, backend::SamplerType, target, @@ -226,9 +239,6 @@ DECL_DRIVER_API_R_N(backend::TextureHandle, importTexture, uint32_t, depth, backend::TextureUsage, usage) -DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup, - uint32_t, size, utils::FixedSizeString<32>, debugName) - DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive, backend::VertexBufferHandle, vbh, backend::IndexBufferHandle, ibh, @@ -262,25 +272,53 @@ DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChainHeadless, DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery) +DECL_DRIVER_API_R_N(backend::DescriptorSetLayoutHandle, createDescriptorSetLayout, + backend::DescriptorSetLayout&&, info) + +DECL_DRIVER_API_R_N(backend::DescriptorSetHandle, createDescriptorSet, + backend::DescriptorSetLayoutHandle, dslh) + +DECL_DRIVER_API_N(updateDescriptorSetBuffer, + backend::DescriptorSetHandle, dsh, + backend::descriptor_binding_t, binding, + backend::BufferObjectHandle, boh, + uint32_t, offset, + uint32_t, size +) + +DECL_DRIVER_API_N(updateDescriptorSetTexture, + backend::DescriptorSetHandle, dsh, + backend::descriptor_binding_t, binding, + backend::TextureHandle, th, + SamplerParams, params +) + +DECL_DRIVER_API_N(bindDescriptorSet, + backend::DescriptorSetHandle, dsh, + backend::descriptor_set_t, set, + backend::DescriptorSetOffsetArray&&, offsets +) + /* * Destroying driver objects * ------------------------- */ -DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh) -DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih) -DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh) -DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh) -DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph) -DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph) -DECL_DRIVER_API_N(destroySamplerGroup, backend::SamplerGroupHandle, sbh) -DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th) -DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth) -DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch) -DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh) -DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh) -DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh) +DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh) +DECL_DRIVER_API_N(destroyVertexBufferInfo, backend::VertexBufferInfoHandle, vbih) +DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh) +DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh) +DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph) +DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph) +DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th) +DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth) +DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch) +DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh) +DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh) +DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh) +DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh) +DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh) /* * Synchronous APIs @@ -347,15 +385,6 @@ DECL_DRIVER_API_N(updateBufferObjectUnsynchronized, DECL_DRIVER_API_N(resetBufferObject, backend::BufferObjectHandle, ibh) -DECL_DRIVER_API_N(updateSamplerGroup, - backend::SamplerGroupHandle, ubh, - backend::BufferDescriptor&&, data) - -DECL_DRIVER_API_N(setMinMaxLevels, - backend::TextureHandle, th, - uint32_t, minLevel, - uint32_t, maxLevel) - DECL_DRIVER_API_N(update3DImage, backend::TextureHandle, th, uint32_t, level, @@ -370,10 +399,12 @@ DECL_DRIVER_API_N(update3DImage, DECL_DRIVER_API_N(generateMipmaps, backend::TextureHandle, th) +// Deprecated DECL_DRIVER_API_N(setExternalImage, backend::TextureHandle, th, void*, image) +// Deprecated DECL_DRIVER_API_N(setExternalImagePlane, backend::TextureHandle, th, void*, image, @@ -420,25 +451,6 @@ DECL_DRIVER_API_N(commit, * ----------------------- */ -DECL_DRIVER_API_N(bindUniformBuffer, - uint32_t, index, - backend::BufferObjectHandle, ubh) - -DECL_DRIVER_API_N(bindBufferRange, - BufferObjectBinding, bindingType, - uint32_t, index, - backend::BufferObjectHandle, ubh, - uint32_t, offset, - uint32_t, size) - -DECL_DRIVER_API_N(unbindBuffer, - BufferObjectBinding, bindingType, - uint32_t, index) - -DECL_DRIVER_API_N(bindSamplers, - uint32_t, index, - backend::SamplerGroupHandle, sbh) - DECL_DRIVER_API_N(setPushConstant, backend::ShaderStage, stage, uint8_t, index, diff --git a/filament/backend/include/private/backend/DriverApi.h b/filament/backend/include/private/backend/DriverApi.h index 68d997a439d..dfd1a964d93 100644 --- a/filament/backend/include/private/backend/DriverApi.h +++ b/filament/backend/include/private/backend/DriverApi.h @@ -18,6 +18,17 @@ #define TNT_FILAMENT_BACKEND_PRIVATE_DRIVERAPI_H #include "backend/DriverApiForward.h" + #include "private/backend/CommandStream.h" +#include + +namespace filament::backend { + +inline void* allocateFromCommandStream(DriverApi& driver, size_t size, size_t alignment) noexcept { + return driver.allocate(size, alignment); +} + +} // namespace filament::backend + #endif // TNT_FILAMENT_BACKEND_PRIVATE_DRIVERAPI_H diff --git a/filament/backend/include/private/backend/HandleAllocator.h b/filament/backend/include/private/backend/HandleAllocator.h index 6a13b75e43b..8eaa9481d62 100644 --- a/filament/backend/include/private/backend/HandleAllocator.h +++ b/filament/backend/include/private/backend/HandleAllocator.h @@ -38,7 +38,7 @@ #include #include -#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB +#define HandleAllocatorGL HandleAllocator<32, 96, 136> // ~4520 / pool / MiB #define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB #define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 24acfe60e1b..c7c1a204155 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -101,6 +101,14 @@ struct HwProgram : public HwBase { HwProgram() noexcept = default; }; +struct HwDescriptorSetLayout : public HwBase { + HwDescriptorSetLayout() noexcept = default; +}; + +struct HwDescriptorSet : public HwBase { + HwDescriptorSet() noexcept = default; +}; + struct HwSamplerGroup : public HwBase { HwSamplerGroup() noexcept = default; }; diff --git a/filament/backend/src/Program.cpp b/filament/backend/src/Program.cpp index 53bfa5a7a15..bb4249f82a6 100644 --- a/filament/backend/src/Program.cpp +++ b/filament/backend/src/Program.cpp @@ -14,7 +14,18 @@ * limitations under the License. */ -#include "backend/Program.h" +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include namespace filament::backend { @@ -52,41 +63,24 @@ Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) { return *this; } -Program& Program::uniformBlockBindings( - FixedCapacityVector> const& uniformBlockBindings) noexcept { - for (auto const& item : uniformBlockBindings) { - assert_invariant(item.second < UNIFORM_BINDING_COUNT); - mUniformBlocks[item.second] = item.first; - } +Program& Program::descriptorBindings(backend::descriptor_set_t set, + DescriptorBindingsInfo descriptorBindings) noexcept { + mDescriptorBindings[set] = std::move(descriptorBindings); return *this; } -Program& Program::uniforms(uint32_t index, UniformInfo const& uniforms) noexcept { - assert_invariant(index < UNIFORM_BINDING_COUNT); - mBindingUniformInfo[index] = uniforms; +Program& Program::uniforms(uint32_t index, utils::CString name, UniformInfo uniforms) noexcept { + mBindingUniformsInfo.reserve(mBindingUniformsInfo.capacity() + 1); + mBindingUniformsInfo.emplace_back(index, std::move(name), std::move(uniforms)); return *this; } - -Program& Program::attributes( - utils::FixedCapacityVector> attributes) noexcept { +Program& Program::attributes(AttributesInfo attributes) noexcept { mAttributes = std::move(attributes); return *this; } -Program& Program::setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags, - const Program::Sampler* samplers, size_t count) noexcept { - auto& groupData = mSamplerGroups[bindingPoint]; - groupData.stageFlags = stageFlags; - auto& samplerList = groupData.samplers; - samplerList.reserve(count); - samplerList.resize(count); - std::copy_n(samplers, count, samplerList.data()); - return *this; -} - -Program& Program::specializationConstants( - FixedCapacityVector specConstants) noexcept { +Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept { mSpecializationConstants = std::move(specConstants); return *this; } diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 265c2896238..4fc725bb046 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -73,10 +73,11 @@ class TrackedMetalBuffer { enum class Type { NONE = 0, GENERIC = 1, - RING = 2, + RING = 2, // deprecated STAGING = 3, + DESCRIPTOR_SET = 4, }; - static constexpr size_t TypeCount = 3; + static constexpr size_t TypeCount = 4; static constexpr auto toIndex(Type t) { assert_invariant(t != Type::NONE); @@ -88,6 +89,8 @@ class TrackedMetalBuffer { return 1; case Type::STAGING: return 2; + case Type::DESCRIPTOR_SET: + return 3; } } @@ -182,7 +185,7 @@ class MetalBuffer { * is no device allocation. * */ - id getGpuBufferForDraw(id cmdBuffer) noexcept; + id getGpuBufferForDraw() noexcept; void* getCpuBuffer() const noexcept { return mCpuBuffer; } diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index a3eb80fa12d..d6f68c33af0 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -40,12 +40,15 @@ // If the buffer is less than 4K in size and is updated frequently, we don't use an explicit // buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:. // This won't work for SSBOs, since they are read/write. + + /* if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE && usage == BufferUsage::DYNAMIC && !forceGpuBuffer) { mBuffer = nil; mCpuBuffer = malloc(size); return; } + */ // Otherwise, we allocate a private GPU buffer. { @@ -94,7 +97,7 @@ copyIntoBuffer(src, size, byteOffset); } -id MetalBuffer::getGpuBufferForDraw(id cmdBuffer) noexcept { +id MetalBuffer::getGpuBufferForDraw() noexcept { // If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound // separately. if (mCpuBuffer) { @@ -137,7 +140,7 @@ } // getGpuBufferForDraw() might return nil, which means there isn't a device allocation for // this buffer. In this case, we'll bind the buffer below with the CPU-side memory. - id gpuBuffer = buffer->getGpuBufferForDraw(cmdBuffer); + id gpuBuffer = buffer->getGpuBufferForDraw(); if (!gpuBuffer) { continue; } diff --git a/filament/backend/src/metal/MetalContext.h b/filament/backend/src/metal/MetalContext.h index fc8cfc12b46..3f6cbaa1a95 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -21,6 +21,8 @@ #include "MetalShaderCompiler.h" #include "MetalState.h" +#include + #include #include #include @@ -46,13 +48,13 @@ class MetalBlitter; class MetalBufferPool; class MetalBumpAllocator; class MetalRenderTarget; -class MetalSamplerGroup; class MetalSwapChain; class MetalTexture; class MetalTimerQueryInterface; struct MetalUniformBuffer; struct MetalIndexBuffer; struct MetalVertexBuffer; +struct MetalDescriptorSet; constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples @@ -68,6 +70,53 @@ class MetalPushConstantBuffer { bool mDirty = false; }; +class MetalDynamicOffsets { +public: + void setOffsets(uint32_t set, const uint32_t* offsets, uint32_t count) { + assert(set < MAX_DESCRIPTOR_SET_COUNT); + + auto getStartIndexForSet = [&](uint32_t s) { + uint32_t startIndex = 0; + for (uint32_t i = 0; i < s; i++) { + startIndex += mCounts[i]; + } + return startIndex; + }; + + const bool resizeNecessary = mCounts[set] != count; + if (UTILS_UNLIKELY(resizeNecessary)) { + int delta = count - mCounts[set]; + + auto thisSetStart = mOffsets.begin() + getStartIndexForSet(set); + if (delta > 0) { + mOffsets.insert(thisSetStart, delta, 0); + } else { + mOffsets.erase(thisSetStart, thisSetStart - delta); + } + + mCounts[set] = count; + } + + if (resizeNecessary || + !std::equal( + offsets, offsets + count, mOffsets.begin() + getStartIndexForSet(set))) { + std::copy(offsets, offsets + count, mOffsets.begin() + getStartIndexForSet(set)); + mDirty = true; + } + } + bool isDirty() const { return mDirty; } + void setDirty(bool dirty) { mDirty = dirty; } + + std::pair getOffsets() const { + return { mOffsets.size(), mOffsets.data() }; + } + +private: + std::array mCounts = { 0 }; + std::vector mOffsets; + bool mDirty = false; +}; + struct MetalContext { explicit MetalContext(size_t metalFreedTextureListSize) : texturesToDestroy(metalFreedTextureListSize) {} @@ -76,8 +125,12 @@ struct MetalContext { id device = nullptr; id commandQueue = nullptr; - id pendingCommandBuffer = nullptr; - id currentRenderPassEncoder = nullptr; + // The ID of pendingCommandBuffer (or the next command buffer, if pendingCommandBuffer is nil). + uint64_t pendingCommandBufferId = 1; + // read from driver thread, set from completion handlers + std::atomic latestCompletedCommandBufferId = 0; + id pendingCommandBuffer = nil; + id currentRenderPassEncoder = nil; std::atomic memorylessLimitsReached = false; @@ -108,8 +161,6 @@ struct MetalContext { // State trackers. PipelineStateTracker pipelineState; DepthStencilStateTracker depthStencilState; - std::array uniformState; - std::array ssboState; CullModeStateTracker cullModeState; WindingStateTracker windingState; DepthClampStateTracker depthClampState; @@ -125,13 +176,15 @@ struct MetalContext { std::array currentPushConstants; - MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {}; - - // Keeps track of sampler groups we've finalized for the current render pass. - tsl::robin_set finalizedSamplerGroups; + // Keeps track of descriptor sets we've finalized for the current render pass. + tsl::robin_set finalizedDescriptorSets; + std::array currentDescriptorSets = {}; + MetalBufferBindings vertexDescriptorBindings; + MetalBufferBindings fragmentDescriptorBindings; + MetalBufferBindings computeDescriptorBindings; + MetalDynamicOffsets dynamicOffsets; - // Keeps track of all alive sampler groups, textures. - tsl::robin_set samplerGroups; + // Keeps track of all alive textures. tsl::robin_set textures; // This circular buffer implements delayed destruction for Metal texture handles. It keeps a @@ -154,6 +207,7 @@ struct MetalContext { // Empty texture used to prevent GPU errors when a sampler has been bound without a texture. id emptyTexture = nil; + id emptyBuffer = nil; MetalBlitter* blitter = nullptr; diff --git a/filament/backend/src/metal/MetalContext.mm b/filament/backend/src/metal/MetalContext.mm index 6eb7518d31c..465ce293a37 100644 --- a/filament/backend/src/metal/MetalContext.mm +++ b/filament/backend/src/metal/MetalContext.mm @@ -101,9 +101,14 @@ void initializeSupportedGpuFamilies(MetalContext* context) { context->pendingCommandBuffer = [context->commandQueue commandBuffer]; // It's safe for this block to capture the context variable. MetalDriver::terminate will ensure // all frames and their completion handlers finish before context is deallocated. + uint64_t thisCommandBufferId = context->pendingCommandBufferId; [context->pendingCommandBuffer addCompletedHandler:^(id buffer) { context->resourceTracker.clearResources((__bridge void*) buffer); - + + // Command buffers should complete in order, so latestCompletedCommandBufferId will only + // ever increase. + context->latestCompletedCommandBufferId = thisCommandBufferId; + auto errorCode = (MTLCommandBufferError)buffer.error.code; if (@available(macOS 11.0, *)) { if (errorCode == MTLCommandBufferErrorMemoryless) { @@ -125,6 +130,7 @@ void submitPendingCommands(MetalContext* context) { assert_invariant(context->pendingCommandBuffer.status != MTLCommandBufferStatusCommitted); [context->pendingCommandBuffer commit]; context->pendingCommandBuffer = nil; + context->pendingCommandBufferId++; } id getOrCreateEmptyTexture(MetalContext* context) { @@ -167,7 +173,6 @@ bool isInRenderPass(MetalContext* context) { void MetalPushConstantBuffer::setBytes(id encoder, ShaderStage stage) { constexpr size_t PUSH_CONSTANT_SIZE_BYTES = 4; - constexpr size_t PUSH_CONSTANT_BUFFER_INDEX = 26; static char buffer[MAX_PUSH_CONSTANT_COUNT * PUSH_CONSTANT_SIZE_BYTES]; assert_invariant(mPushConstants.size() <= MAX_PUSH_CONSTANT_COUNT); diff --git a/filament/backend/src/metal/MetalDriver.h b/filament/backend/src/metal/MetalDriver.h index 6b9eac013e9..7347a42a76d 100644 --- a/filament/backend/src/metal/MetalDriver.h +++ b/filament/backend/src/metal/MetalDriver.h @@ -32,6 +32,7 @@ #include #include #include +#include namespace filament { namespace backend { @@ -61,6 +62,7 @@ class MetalDriver final : public DriverBase { private: friend class MetalSwapChain; + friend struct MetalDescriptorSet; MetalPlatform& mPlatform; MetalContext* mContext; @@ -79,6 +81,17 @@ class MetalDriver final : public DriverBase { void executeTickOps() noexcept; std::vector> mTickOps; + // Tasks regularly executed on the driver thread after a command buffer has completed + struct DeferredTask { + DeferredTask(uint64_t commandBufferId, utils::Invocable&& fn) noexcept + : commandBufferId(commandBufferId), fn(std::move(fn)) {} + uint64_t commandBufferId; // after this command buffer completes + utils::Invocable fn; // execute this task + }; + void executeAfterCurrentCommandBufferCompletes(utils::Invocable&& fn) noexcept; + void executeDeferredOps() noexcept; + std::deque mDeferredTasks; + /* * Driver interface */ @@ -138,7 +151,6 @@ class MetalDriver final : public DriverBase { inline void setRenderPrimitiveBuffer(Handle rph, PrimitiveType pt, Handle vbh, Handle ibh); - void finalizeSamplerGroup(MetalSamplerGroup* sg); void enumerateBoundBuffers(BufferObjectBinding bindingType, const std::function& f); diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 80b3047d870..f10df34039b 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -43,6 +43,16 @@ #include +#ifndef FILAMENT_METAL_DEBUG_LOG +#define FILAMENT_METAL_DEBUG_LOG 0 +#endif + +#if FILAMENT_METAL_DEBUG_LOG == 1 +#define DEBUG_LOG(x, ...) printf("[METAL DEBUG] " x, ##__VA_ARGS__) +#else +#define DEBUG_LOG(...) +#endif + namespace filament { namespace backend { @@ -59,7 +69,6 @@ // MetalIndexBuffer : 56 moderate // MetalBufferObject : 64 many // -- less than or equal 64 bytes - // MetalSamplerGroup : 112 few // MetalProgram : 152 moderate // MetalTexture : 152 moderate // MetalSwapChain : 208 few @@ -73,7 +82,6 @@ << "\nMetalVertexBuffer: " << sizeof(MetalVertexBuffer) << "\nMetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo) << "\nMetalIndexBuffer: " << sizeof(MetalIndexBuffer) - << "\nMetalSamplerGroup: " << sizeof(MetalSamplerGroup) << "\nMetalRenderPrimitive: " << sizeof(MetalRenderPrimitive) << "\nMetalTexture: " << sizeof(MetalTexture) << "\nMetalTimerQuery: " << sizeof(MetalTimerQuery) @@ -114,6 +122,9 @@ mContext->device = mPlatform.createDevice(); assert_invariant(mContext->device); + mContext->emptyBuffer = [mContext->device newBufferWithLength:16 + options:MTLResourceStorageModePrivate]; + initializeSupportedGpuFamilies(mContext); utils::slog.v << "Supported GPU families: " << utils::io::endl; @@ -224,10 +235,13 @@ void MetalDriver::tick(int) { executeTickOps(); + executeDeferredOps(); } void MetalDriver::beginFrame(int64_t monotonic_clock_ns, int64_t refreshIntervalNs, uint32_t frameId) { + DEBUG_LOG("beginFrame(monotonic_clock_ns = %lld, refreshIntervalNs = %lld, frameId = %d)\n", + monotonic_clock_ns, refreshIntervalNs, frameId); #if defined(FILAMENT_METAL_PROFILING) os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId); #endif @@ -239,6 +253,8 @@ TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::RING)); mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging", TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::STAGING)); + mPlatform.debugUpdateStat("filament.metal.alive_buffers.descriptor_set", + TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::DESCRIPTOR_SET)); } } @@ -268,6 +284,7 @@ } void MetalDriver::endFrame(uint32_t frameId) { + DEBUG_LOG("endFrame(frameId = %d)\n", frameId); // If we haven't committed the command buffer (if the frame was canceled), do it now. There may // be commands in it (like fence signaling) that need to execute. submitPendingCommands(mContext); @@ -300,6 +317,69 @@ #endif } +void MetalDriver::updateDescriptorSetBuffer( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::BufferObjectHandle boh, + uint32_t offset, + uint32_t size) { + ASSERT_PRECONDITION(!isInRenderPass(mContext), + "updateDescriptorSetBuffer must be called outside of a render pass."); + DEBUG_LOG( + "updateDescriptorSetBuffer(dsh = %d, binding = %d, boh = %d, offset = %d, size = " + "%d)\n", + dsh.getId(), binding, boh.getId(), offset, size); + + auto* descriptorSet = handle_cast(dsh); + auto* bo = handle_cast(boh); + id mtlBuffer = bo->getBuffer()->getGpuBufferForDraw(); + descriptorSet->buffers[binding] = { mtlBuffer, offset, size }; + ShaderStageFlags stageFlags = descriptorSet->layout->getBindings()[binding].stageFlags; + if (any(stageFlags & ShaderStageFlags::VERTEX)) { + descriptorSet->vertexResources.push_back(mtlBuffer); + } + if (any(stageFlags & ShaderStageFlags::FRAGMENT)) { + descriptorSet->fragmentResources.push_back(mtlBuffer); + } +} + +void MetalDriver::updateDescriptorSetTexture( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::TextureHandle th, + SamplerParams params) { + ASSERT_PRECONDITION(!isInRenderPass(mContext), + "updateDescriptorSetTexture must be called outside of a render pass."); + DEBUG_LOG("updateDescriptorSetTexture(dsh = %d, binding = %d, th = %d, params = {...})\n", + dsh.getId(), binding, th.getId()); + + auto* descriptorSet = handle_cast(dsh); + auto* texture = handle_cast(th); + + id mtlTexture = texture->getMtlTextureForRead(); + if (texture->target == SamplerType::SAMPLER_EXTERNAL) { + auto externalImage = texture->getExternalImage(); + assert_invariant(externalImage != nil); + descriptorSet->externalImages.push_back(externalImage); + } + assert_invariant(mtlTexture != nil); + + descriptorSet->textures[binding] = MetalDescriptorSet::TextureBinding { mtlTexture, params }; + + auto const& bindings = descriptorSet->layout->getBindings(); + auto found = std::find_if(bindings.begin(), bindings.end(), + [binding](const auto& b) { return b.binding == binding; }); + assert_invariant(found != bindings.end()); + + ShaderStageFlags stageFlags = found->stageFlags; + if (any(stageFlags & ShaderStageFlags::VERTEX)) { + descriptorSet->vertexResources.push_back(mtlTexture); + } + if (any(stageFlags & ShaderStageFlags::FRAGMENT)) { + descriptorSet->fragmentResources.push_back(mtlTexture); + } +} + void MetalDriver::flush(int) { FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext)) << "flush must be called outside of a render pass."; @@ -311,9 +391,13 @@ << "finish must be called outside of a render pass."; // Wait for all frames to finish by submitting and waiting on a dummy command buffer. submitPendingCommands(mContext); - id oneOffBuffer = [mContext->commandQueue commandBuffer]; - [oneOffBuffer commit]; + + id oneOffBuffer = getPendingCommandBuffer(mContext); + submitPendingCommands(mContext); [oneOffBuffer waitUntilCompleted]; + + executeTickOps(); + executeDeferredOps(); } void MetalDriver::createVertexBufferInfoR(Handle vbih, uint8_t bufferCount, @@ -369,22 +453,46 @@ auto& sc = mContext->sampleCountLookup; samples = sc[std::min(MAX_SAMPLE_COUNT, samples)]; - mContext->textures.insert(construct_handle(th, *mContext, - target, levels, format, samples, width, height, depth, usage, - TextureSwizzle::CHANNEL_0, TextureSwizzle::CHANNEL_1, - TextureSwizzle::CHANNEL_2, TextureSwizzle::CHANNEL_3)); + mContext->textures.insert(construct_handle( + th, *mContext, target, levels, format, samples, width, height, depth, usage)); + + DEBUG_LOG( + "createTextureR(th = %d, target = %s, levels = %d, format = ?, samples = %d, width = " + "%d, height = %d, depth = %d, usage = %s)\n", + th.getId(), stringify(target), levels, samples, width, height, depth, stringify(usage)); } -void MetalDriver::createTextureSwizzledR(Handle th, SamplerType target, uint8_t levels, - TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, - uint32_t depth, TextureUsage usage, - TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) { - // Clamp sample count to what the device supports. - auto& sc = mContext->sampleCountLookup; - samples = sc[std::min(MAX_SAMPLE_COUNT, samples)]; +void MetalDriver::createTextureViewR( + Handle th, Handle srch, uint8_t baseLevel, uint8_t levelCount) { + MetalTexture const* src = handle_cast(srch); + mContext->textures.insert( + construct_handle(th, *mContext, src, baseLevel, levelCount)); +} - mContext->textures.insert(construct_handle(th, *mContext, - target, levels, format, samples, width, height, depth, usage, r, g, b, a)); +void MetalDriver::createTextureViewSwizzleR(Handle th, Handle srch, + backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, + backend::TextureSwizzle a) { + MetalTexture const* src = handle_cast(srch); + mContext->textures.insert(construct_handle(th, *mContext, src, r, g, b, a)); +} + +void MetalDriver::createTextureExternalImageR(Handle th, backend::TextureFormat format, + uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) { + mContext->textures.insert(construct_handle( + th, *mContext, format, width, height, usage, (CVPixelBufferRef)image)); + // This release matches the retain call in setupExternalImage. The MetalTexture will have + // retained the buffer by now. + CVPixelBufferRelease((CVPixelBufferRef)image); +} + +void MetalDriver::createTextureExternalImagePlaneR(Handle th, + backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, + void* image, uint32_t plane) { + mContext->textures.insert(construct_handle( + th, *mContext, format, width, height, usage, (CVPixelBufferRef)image, plane)); + // This release matches the retain call in setupExternalImage. The MetalTexture will have + // retained the buffer by now. + CVPixelBufferRelease((CVPixelBufferRef)image); } void MetalDriver::importTextureR(Handle th, intptr_t i, @@ -409,11 +517,6 @@ target, levels, format, samples, width, height, depth, usage, metalTexture)); } -void MetalDriver::createSamplerGroupR( - Handle sbh, uint32_t size, utils::FixedSizeString<32> debugName) { - mContext->samplerGroups.insert(construct_handle(sbh, size, debugName)); -} - void MetalDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, PrimitiveType pt) { @@ -422,6 +525,11 @@ } void MetalDriver::createProgramR(Handle rph, Program&& program) { +#if FILAMENT_METAL_DEBUG_LOG + auto handleId = rph.getId(); + DEBUG_LOG("createProgramR(rph = %d, program = ", handleId); + utils::slog.d << program << utils::io::endl; +#endif construct_handle(rph, *mContext, std::move(program)); } @@ -450,7 +558,6 @@ auto colorTexture = handle_cast(buffer.handle); FILAMENT_CHECK_PRECONDITION(colorTexture->getMtlTextureForWrite()) << "Color texture passed to render target has no texture allocation"; - colorTexture->extendLodRangeTo(buffer.level); colorAttachments[i] = { colorTexture, color[i].level, color[i].layer }; } @@ -461,7 +568,6 @@ auto depthTexture = handle_cast(depth.handle); FILAMENT_CHECK_PRECONDITION(depthTexture->getMtlTextureForWrite()) << "Depth texture passed to render target has no texture allocation."; - depthTexture->extendLodRangeTo(depth.level); depthAttachment = { depthTexture, depth.level, depth.layer }; } @@ -472,7 +578,6 @@ auto stencilTexture = handle_cast(stencil.handle); FILAMENT_CHECK_PRECONDITION(stencilTexture->getMtlTextureForWrite()) << "Stencil texture passed to render target has no texture allocation."; - stencilTexture->extendLodRangeTo(stencil.level); stencilAttachment = { stencilTexture, stencil.level, stencil.layer }; } @@ -489,6 +594,9 @@ if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) { CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow; construct_handle(sch, *mContext, pixelBuffer, flags); + // This release matches the retain call in setupExternalImage. The MetalSwapchain will have + // retained the buffer by now. + CVPixelBufferRelease((CVPixelBufferRef)pixelBuffer); } else { auto* metalLayer = (__bridge CAMetalLayer*) nativeWindow; construct_handle(sch, *mContext, metalLayer, flags); @@ -504,6 +612,72 @@ // nothing to do, timer query was constructed in createTimerQueryS } +const char* toString(DescriptorType type) { + switch (type) { + case DescriptorType::UNIFORM_BUFFER: + return "UNIFORM_BUFFER"; + case DescriptorType::SHADER_STORAGE_BUFFER: + return "SHADER_STORAGE_BUFFER"; + case DescriptorType::SAMPLER: + return "SAMPLER"; + case DescriptorType::INPUT_ATTACHMENT: + return "INPUT_ATTACHMENT"; + } +} + +const char* toString(ShaderStageFlags flags) { + std::vector stages; + if (any(flags & ShaderStageFlags::VERTEX)) { + stages.push_back("VERTEX"); + } + if (any(flags & ShaderStageFlags::FRAGMENT)) { + stages.push_back("FRAGMENT"); + } + if (any(flags & ShaderStageFlags::COMPUTE)) { + stages.push_back("COMPUTE"); + } + if (stages.empty()) { + return "NONE"; + } + static char buffer[64]; + buffer[0] = '\0'; + for (size_t i = 0; i < stages.size(); i++) { + if (i > 0) { + strcat(buffer, " | "); + } + strcat(buffer, stages[i]); + } + return buffer; +} + +const char* toString(DescriptorFlags flags) { + if (flags == DescriptorFlags::DYNAMIC_OFFSET) { + return "DYNAMIC_OFFSET"; + } + return "NONE"; +} + +void MetalDriver::createDescriptorSetLayoutR( + Handle dslh, DescriptorSetLayout&& info) { + std::sort(info.bindings.begin(), info.bindings.end(), + [](const auto& a, const auto& b) { return a.binding < b.binding; }); + DEBUG_LOG("createDescriptorSetLayoutR(dslh = %d, info = {\n", dslh.getId()); + for (size_t i = 0; i < info.bindings.size(); i++) { + DEBUG_LOG(" {binding = %d, type = %s, count = %d, stage = %s, flags = %s},\n", + info.bindings[i].binding, toString(info.bindings[i].type), info.bindings[i].count, + toString(info.bindings[i].stageFlags), toString(info.bindings[i].flags)); + } + DEBUG_LOG("})\n"); + construct_handle(dslh, std::move(info)); +} + +void MetalDriver::createDescriptorSetR( + Handle dsh, Handle dslh) { + DEBUG_LOG("createDescriptorSetR(dsh = %d, dslh = %d)\n", dsh.getId(), dslh.getId()); + MetalDescriptorSetLayout* layout = handle_cast(dslh); + construct_handle(dsh, layout); +} + Handle MetalDriver::createVertexBufferInfoS() noexcept { return alloc_handle(); } @@ -524,16 +698,24 @@ return alloc_handle(); } -Handle MetalDriver::createTextureSwizzledS() noexcept { +Handle MetalDriver::createTextureViewS() noexcept { return alloc_handle(); } -Handle MetalDriver::importTextureS() noexcept { +Handle MetalDriver::createTextureViewSwizzleS() noexcept { + return alloc_handle(); +} + +Handle MetalDriver::createTextureExternalImageS() noexcept { return alloc_handle(); } -Handle MetalDriver::createSamplerGroupS() noexcept { - return alloc_handle(); +Handle MetalDriver::createTextureExternalImagePlaneS() noexcept { + return alloc_handle(); +} + +Handle MetalDriver::importTextureS() noexcept { + return alloc_handle(); } Handle MetalDriver::createRenderPrimitiveS() noexcept { @@ -572,6 +754,14 @@ return alloc_and_construct_handle(); } +Handle MetalDriver::createDescriptorSetLayoutS() noexcept { + return alloc_handle(); +} + +Handle MetalDriver::createDescriptorSetS() noexcept { + return alloc_handle(); +} + void MetalDriver::destroyVertexBufferInfo(Handle vbih) { if (vbih) { destruct_handle(vbih); @@ -594,22 +784,6 @@ if (UTILS_UNLIKELY(!boh)) { return; } - auto* bo = handle_cast(boh); - // Unbind this buffer object from any uniform / SSBO slots it's still bound to. - bo->boundUniformBuffers.forEachSetBit([this](size_t index) { - mContext->uniformState[index] = BufferState { - .buffer = nullptr, - .offset = 0, - .bound = false - }; - }); - bo->boundSsbos.forEachSetBit([this](size_t index) { - mContext->ssboState[index] = BufferState { - .buffer = nullptr, - .offset = 0, - .bound = false - }; - }); destruct_handle(boh); } @@ -625,22 +799,8 @@ } } -void MetalDriver::destroySamplerGroup(Handle sbh) { - if (!sbh) { - return; - } - // Unbind this sampler group from our internal state. - auto* metalSampler = handle_cast(sbh); - for (auto& samplerBinding : mContext->samplerBindings) { - if (samplerBinding == metalSampler) { - samplerBinding = {}; - } - } - mContext->samplerGroups.erase(metalSampler); - destruct_handle(sbh); -} - void MetalDriver::destroyTexture(Handle th) { + DEBUG_LOG("destroyTexture(th = %d)\n", th.getId()); if (!th) { return; } @@ -667,7 +827,16 @@ void MetalDriver::destroySwapChain(Handle sch) { if (sch) { - destruct_handle(sch); + auto* swapChain = handle_cast(sch); + // If the SwapChain is a pixel buffer, we need to wait for the current command buffer to + // complete before destroying it. This is because pixel buffer SwapChains hold a + // MetalExternalImage that could still being rendered into. + if (UTILS_UNLIKELY(swapChain->isPixelBuffer())) { + executeAfterCurrentCommandBufferCompletes( + [this, sch]() mutable { destruct_handle(sch); }); + } else { + destruct_handle(sch); + } } } @@ -681,6 +850,21 @@ } } +void MetalDriver::destroyDescriptorSetLayout(Handle dslh) { + DEBUG_LOG("destroyDescriptorSetLayout(dslh = %d)\n", dslh.getId()); + if (dslh) { + destruct_handle(dslh); + } +} + +void MetalDriver::destroyDescriptorSet(Handle dsh) { + DEBUG_LOG("destroyDescriptorSet(dsh = %d)\n", dsh.getId()); + if (dsh) { + executeAfterCurrentCommandBufferCompletes( + [this, dsh]() mutable { destruct_handle(dsh); }); + } +} + void MetalDriver::terminate() { // Terminate any outstanding MetalTextures. while (!mContext->texturesToDestroy.empty()) { @@ -692,8 +876,6 @@ // This must be done before calling bufferPool->reset() to ensure no buffers are in flight. finish(); - executeTickOps(); - mContext->bufferPool->reset(); mContext->commandQueue = nil; @@ -951,11 +1133,6 @@ vertexBuffer->buffers[index] = bufferObject->getBuffer(); } -void MetalDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { - auto tex = handle_cast(th); - tex->setLodRange(minLevel, maxLevel); -} - void MetalDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, @@ -965,30 +1142,26 @@ auto tex = handle_cast(th); tex->loadImage(level, MTLRegionMake3D(xoffset, yoffset, zoffset, width, height, depth), data); scheduleDestroy(std::move(data)); + + DEBUG_LOG( + "update3DImage(th = %d, level = %d, xoffset = %d, yoffset = %d, zoffset = %d, width = " + "%d, height = %d, depth = %d, data = ?)\n", + th.getId(), level, xoffset, yoffset, zoffset, width, height, depth); } void MetalDriver::setupExternalImage(void* image) { - // This is called when passing in a CVPixelBuffer as either an external image or swap chain. - // Here we take ownership of the passed in buffer. It will be released the next time - // setExternalImage is called, when the texture is destroyed, or when the swap chain is - // destroyed. + // setupExternalImage is called on the Filament thread when creating a Texture or SwapChain from + // a CVPixelBuffer external image. Here we take ownership of the passed in buffer by calling + // CVPixelBufferRetain. This keeps the buffer alive until the driver thread processes it (inside + // createTextureExternalImage or createSwapChain), allowing the application to free their + // reference to the buffer. CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) image; CVPixelBufferRetain(pixelBuffer); } -void MetalDriver::setExternalImage(Handle th, void* image) { - FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext)) - << "setExternalImage must be called outside of a render pass."; - auto texture = handle_cast(th); - texture->externalImage.set((CVPixelBufferRef) image); -} +void MetalDriver::setExternalImage(Handle th, void* image) {} -void MetalDriver::setExternalImagePlane(Handle th, void* image, uint32_t plane) { - FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext)) - << "setExternalImagePlane must be called outside of a render pass."; - auto texture = handle_cast(th); - texture->externalImage.set((CVPixelBufferRef) image, plane); -} +void MetalDriver::setExternalImagePlane(Handle th, void* image, uint32_t plane) {} void MetalDriver::setExternalStream(Handle th, Handle sh) { } @@ -1004,96 +1177,8 @@ << "generateMipmaps must be called outside of a render pass."; auto tex = handle_cast(th); tex->generateMipmaps(); -} - -void MetalDriver::updateSamplerGroup(Handle sbh, BufferDescriptor&& data) { - FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext)) - << "updateSamplerGroup must be called outside of a render pass."; - - auto sb = handle_cast(sbh); - assert_invariant(sb->size == data.size / sizeof(SamplerDescriptor)); - auto const* const samplers = (SamplerDescriptor const*) data.buffer; - - // Verify that all the textures in the sampler group are still alive. - // These bugs lead to memory corruption and can be difficult to track down. - for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) { - if (!samplers[s].t) { - continue; - } - // The difference between this check and the one below is that in release, we do this for - // only a set number of recently freed textures, while the debug check is exhaustive. - auto* metalTexture = handle_cast(samplers[s].t); - metalTexture->checkUseAfterFree(sb->debugName.c_str(), s); -#ifndef NDEBUG - auto iter = mContext->textures.find(handle_cast(samplers[s].t)); - if (iter == mContext->textures.end()) { - utils::slog.e << "updateSamplerGroup: texture #" - << (int) s << " is dead, texture handle = " - << samplers[s].t << utils::io::endl; - } - assert_invariant(iter != mContext->textures.end()); -#endif - } - - // Create a MTLArgumentEncoder for these textures. - // Ideally, we would create this encoder at createSamplerGroup time, but we need to know the - // texture type of each texture. It's also not guaranteed that the types won't change between - // calls to updateSamplerGroup. - utils::FixedCapacityVector textureTypes(sb->size); - std::transform(samplers, samplers + data.size / sizeof(SamplerDescriptor), textureTypes.begin(), - [this](const SamplerDescriptor& sampler) { - if (!sampler.t) { - // Use Type2D for non-bound textures. - return MTLTextureType2D; - } - auto* t = handle_cast(sampler.t); - if (t->target == SamplerType::SAMPLER_EXTERNAL) { - // Use Type2D for external image textures. - return MTLTextureType2D; - } - id mtlTexture = t->getMtlTextureForRead(); - assert_invariant(mtlTexture); - return mtlTexture.textureType; - }); - auto& encoderCache = mContext->argumentEncoderCache; - id encoder = - encoderCache.getOrCreateState(ArgumentEncoderState(std::move(textureTypes))); - sb->reset(getPendingCommandBuffer(mContext), encoder, mContext->device); - - // In a perfect world, all the MTLTexture bindings would be known at updateSamplerGroup time. - // However, there are two special cases preventing this: - // 1. External images - // 2. LOD-clamped textures - // - // Both of these cases prevent us from knowing the final id that will be bound into - // the argument buffer representing the sampler group. So, we wait until draw call time to bind - // textures (done in finalizeSamplerGroup). - // The good news is that once a render pass has started, the texture bindings won't change. - // A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in - // a draw call. - // finalizeSamplerGroup has one additional responsibility: to call useResources for all the - // textures, which is required by Metal. - for (size_t s = 0; s < data.size / sizeof(SamplerDescriptor); s++) { - if (!samplers[s].t) { - // Assign a default sampler to empty slots. - // Metal requires all samplers referenced in shaders to be bound. - // An empty texture will be assigned inside finalizeSamplerGroup. - id sampler = mContext->samplerStateCache.getOrCreateState({}); - sb->setFinalizedSampler(s, sampler); - continue; - } - - // Bind the sampler state. We always know the full sampler state at updateSamplerGroup time. - SamplerState samplerState { - .samplerParams = samplers[s].s, - }; - id sampler = mContext->samplerStateCache.getOrCreateState(samplerState); - sb->setFinalizedSampler(s, sampler); - sb->setTextureHandle(s, samplers[s].t); - } - - scheduleDestroy(std::move(data)); + DEBUG_LOG("generateMipmaps(th = %d)\n", th.getId()); } void MetalDriver::compilePrograms(CompilerPriorityQueue priority, @@ -1105,6 +1190,7 @@ void MetalDriver::beginRenderPass(Handle rth, const RenderPassParams& params) { + DEBUG_LOG("beginRenderPass(rth = %d, params = {...})\n", rth.getId()); #if defined(FILAMENT_METAL_PROFILING) const char* renderPassName = "Unknown"; @@ -1146,7 +1232,27 @@ mContext->windingState.invalidate(); mContext->currentPolygonOffset = {0.0f, 0.0f}; - mContext->finalizedSamplerGroups.clear(); + mContext->finalizedDescriptorSets.clear(); + mContext->vertexDescriptorBindings.invalidate(); + mContext->fragmentDescriptorBindings.invalidate(); + mContext->computeDescriptorBindings.invalidate(); + mContext->dynamicOffsets.setDirty(true); + + // Finalize any descriptor sets that were bound before the render pass. + for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) { + auto* descriptorSet = mContext->currentDescriptorSets[i]; + if (!descriptorSet) { + continue; + } + descriptorSet->finalize(this); + mContext->finalizedDescriptorSets.insert(descriptorSet); + } + + // Bind descriptor sets. + mContext->vertexDescriptorBindings.bindBuffers( + mContext->currentRenderPassEncoder, DESCRIPTOR_SET_BINDING_START); + mContext->fragmentDescriptorBindings.bindBuffers( + mContext->currentRenderPassEncoder, DESCRIPTOR_SET_BINDING_START); for (auto& pc : mContext->currentPushConstants) { pc.clear(); @@ -1156,6 +1262,7 @@ void MetalDriver::nextSubpass(int dummy) {} void MetalDriver::endRenderPass(int dummy) { + DEBUG_LOG("endRenderPass()\n"); #if defined(FILAMENT_METAL_PROFILING) os_signpost_interval_end(mContext->log, OS_SIGNPOST_ID_EXCLUSIVE, "Render pass"); #endif @@ -1195,109 +1302,6 @@ swapChain->releaseDrawable(); } -void MetalDriver::bindUniformBuffer(uint32_t index, Handle boh) { - assert_invariant(index < Program::UNIFORM_BINDING_COUNT); - auto* bo = handle_cast(boh); - auto* currentBo = mContext->uniformState[index].buffer; - if (currentBo) { - currentBo->boundUniformBuffers.unset(index); - } - bo->boundUniformBuffers.set(index); - mContext->uniformState[index] = BufferState{ - .buffer = bo, - .offset = 0, - .bound = true - }; -} - -void MetalDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index, - Handle boh, uint32_t offset, uint32_t size) { - - assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE || - bindingType == BufferObjectBinding::UNIFORM); - - auto* bo = handle_cast(boh); - - switch (bindingType) { - default: - case BufferObjectBinding::UNIFORM: { - assert_invariant(index < Program::UNIFORM_BINDING_COUNT); - auto* currentBo = mContext->uniformState[index].buffer; - if (currentBo) { - currentBo->boundUniformBuffers.unset(index); - } - bo->boundUniformBuffers.set(index); - mContext->uniformState[index] = BufferState { - .buffer = bo, - .offset = offset, - .bound = true - }; - - break; - } - - case BufferObjectBinding::SHADER_STORAGE: { - assert_invariant(index < MAX_SSBO_COUNT); - auto* currentBo = mContext->ssboState[index].buffer; - if (currentBo) { - currentBo->boundSsbos.unset(index); - } - bo->boundSsbos.set(index); - mContext->ssboState[index] = BufferState { - .buffer = bo, - .offset = offset, - .bound = true - }; - - break; - } - } -} - -void MetalDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) { - - assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE || - bindingType == BufferObjectBinding::UNIFORM); - - switch (bindingType) { - default: - case BufferObjectBinding::UNIFORM: { - assert_invariant(index < Program::UNIFORM_BINDING_COUNT); - auto* currentBo = mContext->uniformState[index].buffer; - if (currentBo) { - currentBo->boundUniformBuffers.unset(index); - } - mContext->uniformState[index] = BufferState { - .buffer = nullptr, - .offset = 0, - .bound = false - }; - - break; - } - - case BufferObjectBinding::SHADER_STORAGE: { - assert_invariant(index < MAX_SSBO_COUNT); - auto* currentBo = mContext->ssboState[index].buffer; - if (currentBo) { - currentBo->boundSsbos.unset(index); - } - mContext->ssboState[index] = BufferState { - .buffer = nullptr, - .offset = 0, - .bound = false - }; - - break; - } - } -} - -void MetalDriver::bindSamplers(uint32_t index, Handle sbh) { - auto sb = handle_cast(sbh); - mContext->samplerBindings[index] = sb; -} - void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value) { FILAMENT_CHECK_PRECONDITION(isInRenderPass(mContext)) @@ -1541,7 +1545,11 @@ mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blit/resolve"); - dstTexture->extendLodRangeTo(dstLevel); + DEBUG_LOG( + "blit(dst = %d, srcLevel = %d, srcLayer = %d, dstOrigin = (%d, %d), src = %d, dstLevel " + "= %d, dstLayer = %d, srcOrigin = (%d, %d), size = (%d, %d))\n", + dst.getId(), srcLevel, srcLayer, dstOrigin.x, dstOrigin.y, src.getId(), dstLevel, + dstLayer, srcOrigin.x, srcOrigin.y, size.x, size.y); } void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers, @@ -1597,102 +1605,10 @@ } } -void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) { - // All the id objects have already been bound to the argument buffer. - // Here we bind all the textures. - - id cmdBuffer = getPendingCommandBuffer(mContext); - - // Verify that all the textures in the sampler group are still alive. - // These bugs lead to memory corruption and can be difficult to track down. - const auto& handles = samplerGroup->getTextureHandles(); - for (size_t s = 0; s < handles.size(); s++) { - if (!handles[s]) { - continue; - } - // The difference between this check and the one below is that in release, we do this for - // only a set number of recently freed textures, while the debug check is exhaustive. - auto* metalTexture = handle_cast(handles[s]); - metalTexture->checkUseAfterFree(samplerGroup->debugName.c_str(), s); -#ifndef NDEBUG - auto iter = mContext->textures.find(metalTexture); - if (iter == mContext->textures.end()) { - utils::slog.e << "finalizeSamplerGroup: texture #" - << (int) s << " is dead, texture handle = " - << handles[s] << utils::io::endl; - } - assert_invariant(iter != mContext->textures.end()); -#endif - } - - utils::FixedCapacityVector> newTextures(samplerGroup->size, nil); - for (size_t binding = 0; binding < samplerGroup->size; binding++) { - auto [th, _] = samplerGroup->getFinalizedTexture(binding); - - if (!th) { - // Bind an empty texture. - newTextures[binding] = getOrCreateEmptyTexture(mContext); - continue; - } - - assert_invariant(th); - auto* texture = handle_cast(th); - - // External images - if (texture->target == SamplerType::SAMPLER_EXTERNAL) { - if (texture->externalImage.isValid()) { - id mtlTexture = texture->externalImage.getMetalTextureForDraw(); - assert_invariant(mtlTexture); - newTextures[binding] = mtlTexture; - } else { - // Bind an empty texture. - newTextures[binding] = getOrCreateEmptyTexture(mContext); - } - continue; - } - - newTextures[binding] = texture->getMtlTextureForRead(); - } - - if (!std::equal(newTextures.begin(), newTextures.end(), samplerGroup->textures.begin())) { - // One or more of the ids has changed. - // First, determine if this SamplerGroup needs mutation. - // We can't just simply mutate the SamplerGroup, since it could currently be in use by the - // GPU from a prior render pass. - // If the SamplerGroup does need mutation, then there's two cases: - // 1. The SamplerGroup has not been finalized yet (which means it has not yet been used in a - // draw call). We're free to mutate it. - // 2. The SamplerGroup is finalized. We must call mutate(), which will create a new argument - // buffer that we can then mutate freely. - - if (samplerGroup->isFinalized()) { - samplerGroup->mutate(cmdBuffer); - } - - for (size_t binding = 0; binding < samplerGroup->size; binding++) { - samplerGroup->setFinalizedTexture(binding, newTextures[binding]); - } - - samplerGroup->finalize(); - } - - // At this point, all the id should be set to valid textures. Some of them will be - // the "empty" texture. Per Apple documentation, the useResource method must be called once per - // render pass. - samplerGroup->useResources(mContext->currentRenderPassEncoder); - - // useResources won't retain references to the textures, so we need to do so manually. - for (id texture : samplerGroup->textures) { - const void* retainedTexture = CFBridgingRetain(texture); - [cmdBuffer addCompletedHandler:^(id cb) { - CFBridgingRelease(retainedTexture); - }]; - } -} - void MetalDriver::bindPipeline(PipelineState const& ps) { FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr) << "bindPipeline() without a valid command encoder."; + DEBUG_LOG("bindPipeline(ps = { program = %d }))\n", ps.program.getId()); MetalVertexBufferInfo const* const vbi = handle_cast(ps.vertexBufferInfo); @@ -1837,37 +1753,6 @@ clamp:0.0]; mContext->currentPolygonOffset = ps.polygonOffset; } - - // Bind sampler groups (argument buffers). - for (size_t s = 0; s < Program::SAMPLER_BINDING_COUNT; s++) { - MetalSamplerGroup* const samplerGroup = mContext->samplerBindings[s]; - if (!samplerGroup) { - continue; - } - const auto& stageFlags = program->getSamplerGroupInfo()[s].stageFlags; - if (stageFlags == ShaderStageFlags::NONE) { - continue; - } - - auto iter = mContext->finalizedSamplerGroups.find(samplerGroup); - if (iter == mContext->finalizedSamplerGroups.end()) { - finalizeSamplerGroup(samplerGroup); - mContext->finalizedSamplerGroups.insert(samplerGroup); - } - - assert_invariant(samplerGroup->getArgumentBuffer()); - - if (uint8_t(stageFlags) & uint8_t(ShaderStageFlags::VERTEX)) { - [mContext->currentRenderPassEncoder setVertexBuffer:samplerGroup->getArgumentBuffer() - offset:samplerGroup->getArgumentBufferOffset() - atIndex:(SAMPLER_GROUP_BINDING_START + s)]; - } - if (uint8_t(stageFlags) & uint8_t(ShaderStageFlags::FRAGMENT)) { - [mContext->currentRenderPassEncoder setFragmentBuffer:samplerGroup->getArgumentBuffer() - offset:samplerGroup->getArgumentBufferOffset() - atIndex:(SAMPLER_GROUP_BINDING_START + s)]; - } - } } void MetalDriver::bindRenderPrimitive(Handle rph) { @@ -1907,23 +1792,65 @@ atIndex:ZERO_VERTEX_BUFFER_BINDING]; } +void MetalDriver::bindDescriptorSet( + backend::DescriptorSetHandle dsh, + backend::descriptor_set_t set, + backend::DescriptorSetOffsetArray&& offsets) { + auto descriptorSet = handle_cast(dsh); + const size_t dynamicBindings = descriptorSet->layout->getDynamicOffsetCount(); + utils::FixedCapacityVector offsetsVector(dynamicBindings, 0); +#if FILAMENT_METAL_DEBUG_LOG == 1 + printf("[METAL DEBUG] bindDescriptorSet(dsh = %d, set = %d, offsets = [", dsh.getId(), set); + for (size_t i = 0; i < dynamicBindings; i++) { + printf("%d", offsets[i]); + if (i < dynamicBindings - 1) { + printf(", "); + } + + offsetsVector[i] = offsets[i]; + } + printf("])\n"); +#endif + + // Bind the descriptor set. + mContext->currentDescriptorSets[set] = descriptorSet; + mContext->vertexDescriptorBindings.setBuffer( + descriptorSet->finalizeAndGetBuffer(this, ShaderStage::VERTEX), 0, set); + mContext->fragmentDescriptorBindings.setBuffer( + descriptorSet->finalizeAndGetBuffer(this, ShaderStage::FRAGMENT), 0, set); + mContext->dynamicOffsets.setOffsets(set, offsets.data(), dynamicBindings); + + // If we're inside a render pass, we should also finalize the descriptor set and update the + // argument buffers. Otherwise, we'll do this the next time we enter a render pass. + if (isInRenderPass(mContext)) { + auto found = mContext->finalizedDescriptorSets.find(descriptorSet); + if (found == mContext->finalizedDescriptorSets.end()) { + descriptorSet->finalize(this); + mContext->finalizedDescriptorSets.insert(descriptorSet); + } + mContext->vertexDescriptorBindings.bindBuffers( + mContext->currentRenderPassEncoder, DESCRIPTOR_SET_BINDING_START); + mContext->fragmentDescriptorBindings.bindBuffers( + mContext->currentRenderPassEncoder, DESCRIPTOR_SET_BINDING_START); + } +} + void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr) << "draw() without a valid command encoder."; + DEBUG_LOG("draw2(...)\n"); - // Bind uniform buffers. - MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil }; - NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 }; - - enumerateBoundBuffers(BufferObjectBinding::UNIFORM, - [&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer, - uint32_t index) { - uniformsToBind[index] = buffer; - offsets[index] = state.offset; - }); - MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder, - UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT, - uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT); + // Bind the offset data. + if (mContext->dynamicOffsets.isDirty()) { + const auto [size, data] = mContext->dynamicOffsets.getOffsets(); + [mContext->currentRenderPassEncoder setFragmentBytes:data + length:size * sizeof(uint32_t) + atIndex:DYNAMIC_OFFSET_BINDING]; + [mContext->currentRenderPassEncoder setVertexBytes:data + length:size * sizeof(uint32_t) + atIndex:DYNAMIC_OFFSET_BINDING]; + mContext->dynamicOffsets.setDirty(false); + } // Update push constants. for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) { @@ -1937,8 +1864,7 @@ MetalIndexBuffer* indexBuffer = primitive->indexBuffer; - id cmdBuffer = getPendingCommandBuffer(mContext); - id metalIndexBuffer = indexBuffer->buffer.getGpuBufferForDraw(cmdBuffer); + id metalIndexBuffer = indexBuffer->buffer.getGpuBufferForDraw(); [mContext->currentRenderPassEncoder drawIndexedPrimitives:getMetalPrimitiveType(primitive->type) indexCount:indexCount indexType:getIndexType(indexBuffer->elementSize) @@ -1990,31 +1916,6 @@ } assert_invariant(!error); - // Bind uniform buffers. - MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil }; - NSUInteger uniformOffsets[Program::UNIFORM_BINDING_COUNT] = { 0 }; - enumerateBoundBuffers(BufferObjectBinding::UNIFORM, - [&uniformsToBind, &uniformOffsets](const BufferState& state, MetalBuffer* buffer, - uint32_t index) { - uniformsToBind[index] = buffer; - uniformOffsets[index] = state.offset; - }); - MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), computeEncoder, - UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::COMPUTE, uniformsToBind, - uniformOffsets, Program::UNIFORM_BINDING_COUNT); - - // Bind SSBOs. - MetalBuffer* ssbosToBind[MAX_SSBO_COUNT] = { nil }; - NSUInteger ssboOffsets[MAX_SSBO_COUNT] = { 0 }; - enumerateBoundBuffers(BufferObjectBinding::SHADER_STORAGE, - [&ssbosToBind, &ssboOffsets](const BufferState& state, MetalBuffer* buffer, - uint32_t index) { - ssbosToBind[index] = buffer; - ssboOffsets[index] = state.offset; - }); - MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), computeEncoder, SSBO_BINDING_START, - MetalBuffer::Stage::COMPUTE, ssbosToBind, ssboOffsets, MAX_SSBO_COUNT); - [computeEncoder setComputePipelineState:computePipelineState]; MTLSize threadgroupsPerGrid = MTLSizeMake(workGroupCount.x, workGroupCount.y, workGroupCount.z); @@ -2072,32 +1973,6 @@ mContext->timerQueryImpl->endTimeElapsedQuery(tq); } -void MetalDriver::enumerateBoundBuffers(BufferObjectBinding bindingType, - const std::function& f) { - assert_invariant(bindingType == BufferObjectBinding::UNIFORM || - bindingType == BufferObjectBinding::SHADER_STORAGE); - - auto enumerate = [&](auto arrayType){ - for (auto i = 0u; i < arrayType.size(); i++) { - const auto& thisBuffer = arrayType[i]; - if (!thisBuffer.bound) { - continue; - } - f(thisBuffer, thisBuffer.buffer->getBuffer(), i); - } - }; - - switch (bindingType) { - default: - case (BufferObjectBinding::UNIFORM): - enumerate(mContext->uniformState); - break; - case (BufferObjectBinding::SHADER_STORAGE): - enumerate(mContext->ssboState); - break; - } -} - void MetalDriver::resetState(int) { } @@ -2117,6 +1992,21 @@ } } +void MetalDriver::executeAfterCurrentCommandBufferCompletes(utils::Invocable&& fn) noexcept { + mDeferredTasks.emplace_back(mContext->pendingCommandBufferId, std::move(fn)); +} + +void MetalDriver::executeDeferredOps() noexcept { + for (; !mDeferredTasks.empty(); mDeferredTasks.pop_front()) { + const auto& task = mDeferredTasks.front(); + if (task.commandBufferId <= mContext->latestCompletedCommandBufferId) { + task.fn(); + } else { + break; + } + } +} + // explicit instantiation of the Dispatcher template class ConcreteDispatcher; diff --git a/filament/backend/src/metal/MetalExternalImage.h b/filament/backend/src/metal/MetalExternalImage.h index ad8ef8630b6..c9dc1b91372 100644 --- a/filament/backend/src/metal/MetalExternalImage.h +++ b/filament/backend/src/metal/MetalExternalImage.h @@ -32,100 +32,75 @@ struct MetalContext; * texture. */ class MetalExternalImage { - public: + MetalExternalImage() = default; - MetalExternalImage(MetalContext& context, - TextureSwizzle r = TextureSwizzle::CHANNEL_0, - TextureSwizzle g = TextureSwizzle::CHANNEL_1, - TextureSwizzle b = TextureSwizzle::CHANNEL_2, - TextureSwizzle a = TextureSwizzle::CHANNEL_3) noexcept; + MetalExternalImage(MetalExternalImage&&); + MetalExternalImage& operator=(MetalExternalImage&&); + ~MetalExternalImage() noexcept; - /** - * @return true, if this MetalExternalImage is holding a live external image. Returns false - * until set has been called with a valid CVPixelBuffer. The image can be cleared via - * set(nullptr), and isValid will return false again. - */ - bool isValid() const noexcept; + MetalExternalImage(const MetalExternalImage&) = delete; + MetalExternalImage& operator=(const MetalExternalImage&) = delete; /** - * Set this external image to the passed-in CVPixelBuffer. Future calls to - * getMetalTextureForDraw will return a texture backed by this CVPixelBuffer. Previous - * CVPixelBuffers and related resources will be released when all GPU work using them has - * finished. - * - * Calling set with a YCbCr image will encode a compute pass to convert the image from YCbCr to - * RGB. + * While the texture is used for rendering, this MetalExternalImage must be kept alive. */ - void set(CVPixelBufferRef image) noexcept; + id getMtlTexture() const noexcept; - /** - * Set this external image to a specific plane of the passed-in CVPixelBuffer. Future calls to - * getMetalTextureForDraw will return a texture backed by a single plane of this CVPixelBuffer. - * Previous CVPixelBuffers and related resources will be released when all GPU work using them - * has finished. - */ - void set(CVPixelBufferRef image, size_t plane) noexcept; + bool isValid() const noexcept { + return mImage != nil || mRgbTexture != nullptr; + } - /** - * Returns the width of the external image, or 0 if one is not set. For YCbCr images, returns - * the width of the luminance plane. - */ - size_t getWidth() const noexcept { return mWidth; } + NSUInteger getWidth() const noexcept; + NSUInteger getHeight() const noexcept; /** - * Returns the height of the external image, or 0 if one is not set. For YCbCr images, returns - * the height of the luminance plane. + * Create an external image with the passed-in CVPixelBuffer. + * + * Ownership is taken of the CVPixelBuffer, which will be released when the returned + * MetalExternalImage is destroyed (or, in the case of a YCbCr image, after the conversion has + * completed). + * + * Calling set with a YCbCr image will encode a compute pass to convert the image from + * YCbCr to RGB. */ - size_t getHeight() const noexcept { return mHeight; } + static MetalExternalImage createFromImage(MetalContext& context, CVPixelBufferRef image); /** - * Get a Metal texture used to draw this image and denote that it is used for the current frame. - * For future frames that use this external image, getMetalTextureForDraw must be called again. + * Create an external image with a specific plane of the passed-in CVPixelBuffer. + * + * Ownership is taken of the CVPixelBuffer, which will be released when the returned + * MetalExternalImage is destroyed. */ - id getMetalTextureForDraw() const noexcept; + static MetalExternalImage createFromImagePlane( + MetalContext& context, CVPixelBufferRef image, uint32_t plane); + + static void assertWritableImage(CVPixelBufferRef image); /** * Free resources. Should be called at least once when no further calls to set will occur. */ static void shutdown(MetalContext& context) noexcept; - static void assertWritableImage(CVPixelBufferRef image); - private: + MetalExternalImage(CVPixelBufferRef image, CVMetalTextureRef texture) noexcept + : mImage(image), mTexture(texture) {} + explicit MetalExternalImage(id texture) noexcept : mRgbTexture(texture) {} - void unset(); - - CVMetalTextureRef createTextureFromImage(CVPixelBufferRef image, MTLPixelFormat format, - size_t plane); - id createRgbTexture(size_t width, size_t height); - id createSwizzledTextureView(id texture) const; - id createSwizzledTextureView(CVMetalTextureRef texture) const; - void ensureComputePipelineState(); - id encodeColorConversionPass(id inYPlane, id - inCbCrTexture, id outTexture); + static id createRgbTexture(id device, size_t width, size_t height); + static CVMetalTextureRef createTextureFromImage(CVMetalTextureCacheRef textureCache, + CVPixelBufferRef image, MTLPixelFormat format, size_t plane); + static void ensureComputePipelineState(MetalContext& context); + static id encodeColorConversionPass(MetalContext& context, + id inYPlane, id inCbCrTexture, id outTexture); static constexpr size_t Y_PLANE = 0; static constexpr size_t CBCR_PLANE = 1; - MetalContext& mContext; - - // If the external image has a single plane, mImage and mTexture hold references to the image - // and created Metal texture, respectively. - // mTextureView is a view of mTexture with any swizzling applied. + // TODO: this could probably be a union. CVPixelBufferRef mImage = nullptr; CVMetalTextureRef mTexture = nullptr; - id mTextureView = nullptr; - size_t mWidth = 0; - size_t mHeight = 0; - - // If the external image is in the YCbCr format, this holds the result of the converted RGB - // texture. id mRgbTexture = nil; - - struct { - TextureSwizzle r, g, b, a; - } mSwizzle; }; } // namespace backend diff --git a/filament/backend/src/metal/MetalExternalImage.mm b/filament/backend/src/metal/MetalExternalImage.mm index 518cbbe76df..9ffb587dd1c 100644 --- a/filament/backend/src/metal/MetalExternalImage.mm +++ b/filament/backend/src/metal/MetalExternalImage.mm @@ -34,10 +34,6 @@ namespace filament { namespace backend { -static const auto cvBufferDeleter = [](const void* buffer) { - CVBufferRelease((CVMetalTextureRef) buffer); -}; - static const char* kernel = R"( #include #include @@ -71,18 +67,30 @@ } )"; -MetalExternalImage::MetalExternalImage(MetalContext& context, TextureSwizzle r, TextureSwizzle g, - TextureSwizzle b, TextureSwizzle a) noexcept : mContext(context), mSwizzle{r, g, b, a} { } - -bool MetalExternalImage::isValid() const noexcept { - return mRgbTexture != nil || mImage != nullptr; +NSUInteger MetalExternalImage::getWidth() const noexcept { + if (mImage) { + return CVPixelBufferGetWidth(mImage); + } + if (mRgbTexture) { + return mRgbTexture.width; + } + return 0; } -void MetalExternalImage::set(CVPixelBufferRef image) noexcept { - unset(); +NSUInteger MetalExternalImage::getHeight() const noexcept { + if (mImage) { + return CVPixelBufferGetHeight(mImage); + } + if (mRgbTexture) { + return mRgbTexture.height; + } + return 0; +} +MetalExternalImage MetalExternalImage::createFromImage( + MetalContext& context, CVPixelBufferRef image) { if (!image) { - return; + return {}; } OSType formatType = CVPixelBufferGetPixelFormatType(image); @@ -96,30 +104,29 @@ << "."; if (planeCount == 0) { - mImage = image; - mTexture = createTextureFromImage(image, MTLPixelFormatBGRA8Unorm, 0); - mTextureView = createSwizzledTextureView(mTexture); - mWidth = CVPixelBufferGetWidth(image); - mHeight = CVPixelBufferGetHeight(image); + CVMetalTextureRef texture = + createTextureFromImage(context.textureCache, image, MTLPixelFormatBGRA8Unorm, 0); + return { CVPixelBufferRetain(image), texture }; } if (planeCount == 2) { - CVMetalTextureRef yPlane = createTextureFromImage(image, MTLPixelFormatR8Unorm, Y_PLANE); - CVMetalTextureRef cbcrPlane = createTextureFromImage(image, MTLPixelFormatRG8Unorm, - CBCR_PLANE); + CVPixelBufferRetain(image); + + CVMetalTextureRef yPlane = + createTextureFromImage(context.textureCache, image, MTLPixelFormatR8Unorm, Y_PLANE); + CVMetalTextureRef cbcrPlane = + createTextureFromImage(context.textureCache, image, MTLPixelFormatRG8Unorm, CBCR_PLANE); // Get the size of luminance plane. - mWidth = CVPixelBufferGetWidthOfPlane(image, Y_PLANE); - mHeight = CVPixelBufferGetHeightOfPlane(image, Y_PLANE); + NSUInteger width = CVPixelBufferGetWidthOfPlane(image, Y_PLANE); + NSUInteger height = CVPixelBufferGetHeightOfPlane(image, Y_PLANE); - id rgbTexture = createRgbTexture(mWidth, mHeight); - id commandBuffer = encodeColorConversionPass( + id rgbTexture = createRgbTexture(context.device, width, height); + id commandBuffer = encodeColorConversionPass(context, CVMetalTextureGetTexture(yPlane), CVMetalTextureGetTexture(cbcrPlane), rgbTexture); - mRgbTexture = createSwizzledTextureView(rgbTexture); - [commandBuffer addCompletedHandler:^(id o) { CVBufferRelease(yPlane); CVBufferRelease(cbcrPlane); @@ -127,70 +134,83 @@ }]; [commandBuffer commit]; + return MetalExternalImage { rgbTexture }; } -} -void MetalExternalImage::set(CVPixelBufferRef image, size_t plane) noexcept { - unset(); + return {}; +} +MetalExternalImage MetalExternalImage::createFromImagePlane( + MetalContext& context, CVPixelBufferRef image, uint32_t plane) { if (!image) { - return; + return {}; } const OSType formatType = CVPixelBufferGetPixelFormatType(image); FILAMENT_CHECK_POSTCONDITION(formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) << "Metal planar external images must be in the 420f format."; - - mImage = image; - - auto getPlaneFormat = [] (size_t plane) { - // Right now Metal only supports kCVPixelFormatType_420YpCbCr8BiPlanarFullRange planar - // external images, so we can make the following assumptions about the format of each plane. - if (plane == 0) { - return MTLPixelFormatR8Unorm; // luminance - } - if (plane == 1) { - // CbCr - return MTLPixelFormatRG8Unorm; // CbCr - } - return MTLPixelFormatInvalid; + FILAMENT_CHECK_POSTCONDITION(plane == 0 || plane == 1) + << "Metal planar external images must be created from planes 0 or 1."; + + auto getPlaneFormat = [](size_t plane) { + // Right now Metal only supports kCVPixelFormatType_420YpCbCr8BiPlanarFullRange planar + // external images, so we can make the following assumptions about the format of each plane. + if (plane == 0) { + return MTLPixelFormatR8Unorm; // luminance + } + if (plane == 1) { + return MTLPixelFormatRG8Unorm; // CbCr + } + return MTLPixelFormatInvalid; }; const MTLPixelFormat format = getPlaneFormat(plane); assert_invariant(format != MTLPixelFormatInvalid); - mTexture = createTextureFromImage(image, format, plane); - mTextureView = createSwizzledTextureView(mTexture); + CVMetalTextureRef mTexture = createTextureFromImage(context.textureCache, image, format, plane); + return { CVPixelBufferRetain(image), mTexture }; +} + +MetalExternalImage::MetalExternalImage(MetalExternalImage&& rhs) { + std::swap(mImage, rhs.mImage); + std::swap(mTexture, rhs.mTexture); + std::swap(mRgbTexture, rhs.mRgbTexture); +} + +MetalExternalImage& MetalExternalImage::operator=(MetalExternalImage&& rhs) { + CVPixelBufferRelease(mImage); + CVBufferRelease(mTexture); + mImage = nullptr; + mTexture = nullptr; + mRgbTexture = nullptr; + std::swap(mImage, rhs.mImage); + std::swap(mTexture, rhs.mTexture); + std::swap(mRgbTexture, rhs.mRgbTexture); + return *this; +} + +MetalExternalImage::~MetalExternalImage() noexcept { + CVPixelBufferRelease(mImage); + CVBufferRelease(mTexture); } -id MetalExternalImage::getMetalTextureForDraw() const noexcept { +id MetalExternalImage::getMtlTexture() const noexcept { if (mRgbTexture) { return mRgbTexture; } - - // Retain the image and Metal texture until the GPU has finished with this frame. This does - // not need to be done for the RGB texture, because it is an Objective-C object whose - // lifetime is automatically managed by Metal. - auto& tracker = mContext.resourceTracker; - auto commandBuffer = getPendingCommandBuffer(&mContext); - if (tracker.trackResource((__bridge void*) commandBuffer, mImage, cvBufferDeleter)) { - CVPixelBufferRetain(mImage); - } - if (tracker.trackResource((__bridge void*) commandBuffer, mTexture, cvBufferDeleter)) { - CVBufferRetain(mTexture); + if (mTexture) { + return CVMetalTextureGetTexture(mTexture); } - - assert_invariant(mTextureView); - return mTextureView; + return nil; } -CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVPixelBufferRef image, - MTLPixelFormat format, size_t plane) { +CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVMetalTextureCacheRef textureCache, + CVPixelBufferRef image, MTLPixelFormat format, size_t plane) { const size_t width = CVPixelBufferGetWidthOfPlane(image, plane); const size_t height = CVPixelBufferGetHeightOfPlane(image, plane); CVMetalTextureRef texture; - CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, - mContext.textureCache, image, nullptr, format, width, height, plane, &texture); + CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, textureCache, + image, nullptr, format, width, height, plane, &texture); FILAMENT_CHECK_POSTCONDITION(result == kCVReturnSuccess) << "Could not create a CVMetalTexture from CVPixelBuffer."; @@ -201,58 +221,19 @@ context.externalImageComputePipelineState = nil; } -void MetalExternalImage::assertWritableImage(CVPixelBufferRef image) { - OSType formatType = CVPixelBufferGetPixelFormatType(image); - FILAMENT_CHECK_PRECONDITION(formatType == kCVPixelFormatType_32BGRA) - << "Metal SwapChain images must be in the 32BGRA format."; -} - -void MetalExternalImage::unset() { - CVPixelBufferRelease(mImage); - CVBufferRelease(mTexture); - - mImage = nullptr; - mTexture = nullptr; - mTextureView = nil; - mRgbTexture = nil; - mWidth = 0; - mHeight = 0; -} - -id MetalExternalImage::createRgbTexture(size_t width, size_t height) { +id MetalExternalImage::createRgbTexture( + id device, size_t width, size_t height) { MTLTextureDescriptor *descriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:width height:height mipmapped:NO]; descriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead; - return [mContext.device newTextureWithDescriptor:descriptor]; -} - -id MetalExternalImage::createSwizzledTextureView(id texture) const { - const bool isDefaultSwizzle = - mSwizzle.r == TextureSwizzle::CHANNEL_0 && - mSwizzle.g == TextureSwizzle::CHANNEL_1 && - mSwizzle.b == TextureSwizzle::CHANNEL_2 && - mSwizzle.a == TextureSwizzle::CHANNEL_3; - if (!isDefaultSwizzle && mContext.supportsTextureSwizzling) { - // Even though we've already checked supportsTextureSwizzling, we still need to guard these - // calls with @availability, otherwise the API usage will generate compiler warnings. - if (@available(iOS 13, *)) { - texture = createTextureViewWithSwizzle(texture, - getSwizzleChannels(mSwizzle.r, mSwizzle.g, mSwizzle.b, mSwizzle.a)); - } - } - return texture; -} - -id MetalExternalImage::createSwizzledTextureView(CVMetalTextureRef ref) const { - id texture = CVMetalTextureGetTexture(ref); - return createSwizzledTextureView(texture); + return [device newTextureWithDescriptor:descriptor]; } -void MetalExternalImage::ensureComputePipelineState() { - if (mContext.externalImageComputePipelineState != nil) { +void MetalExternalImage::ensureComputePipelineState(MetalContext& context) { + if (context.externalImageComputePipelineState != nil) { return; } @@ -260,29 +241,28 @@ NSString* objcSource = [NSString stringWithCString:kernel encoding:NSUTF8StringEncoding]; - id library = [mContext.device newLibraryWithSource:objcSource - options:nil - error:&error]; + id library = [context.device newLibraryWithSource:objcSource + options:nil + error:&error]; NSERROR_CHECK("Unable to compile Metal shading library."); id kernelFunction = [library newFunctionWithName:@"ycbcrToRgb"]; - mContext.externalImageComputePipelineState = - [mContext.device newComputePipelineStateWithFunction:kernelFunction - error:&error]; + context.externalImageComputePipelineState = + [context.device newComputePipelineStateWithFunction:kernelFunction error:&error]; NSERROR_CHECK("Unable to create Metal compute pipeline state."); } -id MetalExternalImage::encodeColorConversionPass(id inYPlane, - id inCbCrTexture, id outTexture) { - ensureComputePipelineState(); +id MetalExternalImage::encodeColorConversionPass(MetalContext& context, + id inYPlane, id inCbCrTexture, id outTexture) { + ensureComputePipelineState(context); - id commandBuffer = [mContext.commandQueue commandBuffer]; + id commandBuffer = [context.commandQueue commandBuffer]; commandBuffer.label = @"YCbCr to RGB conversion"; id computeEncoder = [commandBuffer computeCommandEncoder]; - [computeEncoder setComputePipelineState:mContext.externalImageComputePipelineState]; + [computeEncoder setComputePipelineState:context.externalImageComputePipelineState]; [computeEncoder setTexture:inYPlane atIndex:0]; [computeEncoder setTexture:inCbCrTexture atIndex:1]; [computeEncoder setTexture:outTexture atIndex:2]; @@ -300,5 +280,11 @@ return commandBuffer; } +void MetalExternalImage::assertWritableImage(CVPixelBufferRef image) { + OSType formatType = CVPixelBufferGetPixelFormatType(image); + FILAMENT_CHECK_PRECONDITION(formatType == kCVPixelFormatType_32BGRA) + << "Metal SwapChain images must be in the 32BGRA format."; +} + } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index b57fcf4c7a3..4466f45cee7 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -44,6 +44,7 @@ #include #include #include +#include namespace filament { namespace backend { @@ -85,6 +86,8 @@ class MetalSwapChain : public HwSwapChain { NSUInteger getSurfaceWidth() const; NSUInteger getSurfaceHeight() const; + bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; } + private: enum class SwapChainType { @@ -94,7 +97,6 @@ class MetalSwapChain : public HwSwapChain { }; bool isCaMetalLayer() const { return type == SwapChainType::CAMETALLAYER; } bool isHeadless() const { return type == SwapChainType::HEADLESS; } - bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; } void scheduleFrameScheduledCallback(); void scheduleFrameCompletedCallback(); @@ -140,12 +142,6 @@ class MetalBufferObject : public HwBufferObject { void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset); MetalBuffer* getBuffer() { return &buffer; } - // Tracks which uniform/ssbo buffers this buffer object is bound into. - static_assert(Program::UNIFORM_BINDING_COUNT <= 32); - static_assert(MAX_SSBO_COUNT <= 32); - utils::bitset32 boundUniformBuffers; - utils::bitset32 boundSsbos; - private: MetalBuffer buffer; }; @@ -202,12 +198,10 @@ class MetalProgram : public HwProgram { MetalProgram(MetalContext& context, Program&& program) noexcept; const MetalShaderCompiler::MetalFunctionBundle& getFunctions(); - const Program::SamplerGroupInfo& getSamplerGroupInfo() { return samplerGroupInfo; } private: void initialize(); - Program::SamplerGroupInfo samplerGroupInfo; MetalContext& mContext; MetalShaderCompiler::MetalFunctionBundle mFunctionBundle; MetalShaderCompiler::program_token_t mToken; @@ -229,43 +223,42 @@ struct PixelBufferShape { class MetalTexture : public HwTexture { public: MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, - uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, - TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) - noexcept; + uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, + TextureUsage usage) noexcept; + + // constructors for creating texture views + MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel, + uint8_t levelCount) noexcept; + MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r, TextureSwizzle g, + TextureSwizzle b, TextureSwizzle a) noexcept; // Constructor for importing an id outside of Filament. MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, id texture) noexcept; - ~MetalTexture(); + // Constructors for importing external images. + MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, uint32_t height, + TextureUsage usage, CVPixelBufferRef image) noexcept; + MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, uint32_t height, + TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept; - // Returns an id suitable for reading in a shader, taking into account swizzle and - // LOD clamping. - id getMtlTextureForRead() noexcept; + // Returns an id suitable for reading in a shader, taking into account swizzle. + id getMtlTextureForRead() const noexcept; // Returns the id for attaching to a render pass. - id getMtlTextureForWrite() noexcept { + id getMtlTextureForWrite() const noexcept { return texture; } + std::shared_ptr getExternalImage() const noexcept { return externalImage; } + void loadImage(uint32_t level, MTLRegion region, PixelBufferDescriptor& p) noexcept; void generateMipmaps() noexcept; - // A texture starts out with none of its mip levels (also referred to as LODs) available for - // reading. 4 actions update the range of LODs available: - // - calling loadImage - // - calling generateMipmaps - // - using the texture as a render target attachment - // - calling setMinMaxLevels - // A texture's available mips are consistent throughout a render pass. - void setLodRange(uint16_t minLevel, uint16_t maxLevel); - void extendLodRangeTo(uint16_t level); - static MTLPixelFormat decidePixelFormat(MetalContext* context, TextureFormat format); MetalContext& context; - MetalExternalImage externalImage; // A "sidecar" texture used to implement automatic MSAA resolve. // This is created by MetalRenderTarget and stored here so it can be used with multiple @@ -304,97 +297,16 @@ class MetalTexture : public HwTexture { id texture = nil; + std::shared_ptr externalImage; + // If non-nil, a swizzled texture view to use instead of "texture". // Filament swizzling only affects texture reads, so this should not be used when the texture is // bound as a render target attachment. id swizzledTextureView = nil; - id lodTextureView = nil; - - uint16_t minLod = std::numeric_limits::max(); - uint16_t maxLod = 0; bool terminated = false; }; -class MetalSamplerGroup : public HwSamplerGroup { -public: - explicit MetalSamplerGroup(size_t size, utils::FixedSizeString<32> name) noexcept - : size(size), - debugName(name), - textureHandles(size, Handle()), - textures(size, nil), - samplers(size, nil) {} - - inline void setTextureHandle(size_t index, Handle th) { - assert_invariant(!finalized); - textureHandles[index] = th; - } - - // This method is only used for debugging, to ensure all texture handles are alive. - const auto& getTextureHandles() const { - return textureHandles; - } - - // Encode a MTLTexture into this SamplerGroup at the given index. - inline void setFinalizedTexture(size_t index, id t) { - assert_invariant(!finalized); - textures[index] = t; - } - - // Encode a MTLSamplerState into this SamplerGroup at the given index. - inline void setFinalizedSampler(size_t index, id s) { - assert_invariant(!finalized); - samplers[index] = s; - } - - // A SamplerGroup is "finalized" when all of its textures have been set and is ready for use in - // a draw call. - // Once a SamplerGroup is finalized, it must be reset or mutated to be written into again. - void finalize(); - bool isFinalized() const noexcept { return finalized; } - - // Both of these methods "unfinalize" a SamplerGroup, allowing it to be updated via calls to - // setFinalizedTexture or setFinalizedSampler. The difference is that when reset is called, all - // the samplers/textures must be rebound. The MTLArgumentEncoder must be specified, in case - // the texture types have changed. - // Mutate re-encodes the current set of samplers/textures into the new argument - // buffer. - void reset(id cmdBuffer, id e, id device); - void mutate(id cmdBuffer); - - id getArgumentBuffer() const { - assert_invariant(finalized); - return argBuffer->getCurrentAllocation().first; - } - - NSUInteger getArgumentBufferOffset() const { - return argBuffer->getCurrentAllocation().second; - } - - inline std::pair, id> getFinalizedTexture(size_t index) { - return {textureHandles[index], textures[index]}; - } - - // Calls the Metal useResource:usage:stages: method for all the textures in this SamplerGroup. - void useResources(id renderPassEncoder); - - size_t size; - utils::FixedSizeString<32> debugName; - -public: - - // These vectors are kept in sync with one another. - utils::FixedCapacityVector> textureHandles; - utils::FixedCapacityVector> textures; - utils::FixedCapacityVector> samplers; - - id encoder; - - std::unique_ptr argBuffer = nullptr; - - bool finalized = false; -}; - class MetalRenderTarget : public HwRenderTarget { public: @@ -549,6 +461,61 @@ struct MetalTimerQuery : public HwTimerQuery { std::shared_ptr status; }; +class MetalDescriptorSetLayout : public HwDescriptorSetLayout { +public: + MetalDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept; + + const auto& getBindings() const noexcept { return mLayout.bindings; } + + size_t getDynamicOffsetCount() const noexcept { return mDynamicOffsetCount; } + + /** + * Get an argument encoder for this descriptor set and shader stage. + * textureTypes should only include the textures present in the corresponding shader stage. + */ + id getArgumentEncoder(id device, ShaderStage stage, + utils::FixedCapacityVector const& textureTypes); + +private: + id getArgumentEncoderSlow(id device, ShaderStage stage, + utils::FixedCapacityVector const& textureTypes); + + DescriptorSetLayout mLayout; + size_t mDynamicOffsetCount = 0; + std::array, Program::SHADER_TYPE_COUNT> mCachedArgumentEncoder = { nil }; + std::array, Program::SHADER_TYPE_COUNT> + mCachedTextureTypes; +}; + +struct MetalDescriptorSet : public HwDescriptorSet { + MetalDescriptorSet(MetalDescriptorSetLayout* layout) noexcept; + + void finalize(MetalDriver* driver); + + id finalizeAndGetBuffer(MetalDriver* driver, ShaderStage stage); + + MetalDescriptorSetLayout* layout; + + struct BufferBinding { + id buffer; + uint32_t offset; + uint32_t size; + }; + struct TextureBinding { + id texture; + SamplerParams sampler; + }; + tsl::robin_map buffers; + tsl::robin_map textures; + + std::vector> vertexResources; + std::vector> fragmentResources; + + std::vector> externalImages; + + std::array cachedBuffer = { nil }; +}; + } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index c9779f5fc3a..c61504d3c7c 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -74,7 +74,6 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { depthStencilFormat(decideDepthStencilFormat(flags)), layer(nativeWindow), layerDrawableMutex(std::make_shared()), - externalImage(context), type(SwapChainType::CAMETALLAYER) { if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) { @@ -100,17 +99,15 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { depthStencilFormat(decideDepthStencilFormat(flags)), headlessWidth(width), headlessHeight(height), - externalImage(context), type(SwapChainType::HEADLESS) {} MetalSwapChain::MetalSwapChain(MetalContext& context, CVPixelBufferRef pixelBuffer, uint64_t flags) : context(context), depthStencilFormat(decideDepthStencilFormat(flags)), - externalImage(context), + externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)), type(SwapChainType::CVPIXELBUFFERREF) { assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER); MetalExternalImage::assertWritableImage(pixelBuffer); - externalImage.set(pixelBuffer); assert_invariant(externalImage.isValid()); } @@ -121,7 +118,6 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { } MetalSwapChain::~MetalSwapChain() { - externalImage.set(nullptr); } NSUInteger MetalSwapChain::getSurfaceWidth() const { @@ -171,7 +167,7 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) { } if (isPixelBuffer()) { - return externalImage.getMetalTextureForDraw(); + return externalImage.getMtlTexture(); } assert_invariant(isCaMetalLayer()); @@ -488,11 +484,6 @@ static void func(void* user) { MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept : HwProgram(program.getName()), mContext(context) { - - // Save this program's SamplerGroupInfo, it's used during draw calls to bind sampler groups to - // the appropriate stage(s). - samplerGroupInfo = program.getSamplerGroupInfo(); - mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program)); assert_invariant(mToken); } @@ -512,10 +503,9 @@ static void func(void* user) { MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, - TextureUsage usage, TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, - TextureSwizzle a) noexcept - : HwTexture(target, levels, samples, width, height, depth, format, usage), context(context), - externalImage(context, r, g, b, a) { + TextureUsage usage) noexcept + : HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) { + assert_invariant(target != SamplerType::SAMPLER_EXTERNAL); devicePixelFormat = decidePixelFormat(&context, format); FILAMENT_CHECK_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid) @@ -601,16 +591,28 @@ static void func(void* user) { << ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat) << ", width = " << width << ", height = " << height << ", depth = " << depth << "). Out of memory?"; +} + +MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel, + uint8_t levelCount) noexcept + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + context(context), + devicePixelFormat(src->devicePixelFormat), + externalImage(src->externalImage) { + texture = createTextureViewWithLodRange( + src->getMtlTextureForRead(), baseLevel, baseLevel + levelCount - 1); +} - // If swizzling is set, set up a swizzled texture view that we'll use when sampling this texture. - const bool isDefaultSwizzle = - r == TextureSwizzle::CHANNEL_0 && - g == TextureSwizzle::CHANNEL_1 && - b == TextureSwizzle::CHANNEL_2 && - a == TextureSwizzle::CHANNEL_3; - // If texture is nil, then it must be a SAMPLER_EXTERNAL texture. - // Swizzling for external textures is handled inside MetalExternalImage. - if (!isDefaultSwizzle && texture && context.supportsTextureSwizzling) { +MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r, + TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) noexcept + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + context(context), + devicePixelFormat(src->devicePixelFormat), + externalImage(src->externalImage) { + texture = src->getMtlTextureForRead(); + if (context.supportsTextureSwizzling) { // Even though we've already checked context.supportsTextureSwizzling, we still need to // guard these calls with @availability, otherwise the API usage will generate compiler // warnings. @@ -624,44 +626,38 @@ static void func(void* user) { MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, id metalTexture) noexcept - : HwTexture(target, levels, samples, width, height, depth, format, usage), context(context), - externalImage(context) { + : HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) { texture = metalTexture; - setLodRange(0, levels - 1); +} + +MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, + uint32_t height, TextureUsage usage, CVPixelBufferRef image) noexcept + : HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage), + context(context), + externalImage(std::make_shared( + MetalExternalImage::createFromImage(context, image))) { + texture = externalImage->getMtlTexture(); +} + +MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width, + uint32_t height, TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept + : HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage), + context(context), + externalImage(std::make_shared( + MetalExternalImage::createFromImagePlane(context, image, plane))) { + texture = externalImage->getMtlTexture(); } void MetalTexture::terminate() noexcept { texture = nil; swizzledTextureView = nil; - lodTextureView = nil; msaaSidecar = nil; - externalImage.set(nullptr); + externalImage = nullptr; terminated = true; } -MetalTexture::~MetalTexture() { - externalImage.set(nullptr); -} - -id MetalTexture::getMtlTextureForRead() noexcept { - if (lodTextureView) { - return lodTextureView; - } - // The texture's swizzle remains constant throughout its lifetime, however its LOD range can - // change. We'll cache the LOD view, and set lodTextureView to nil if minLod or maxLod is - // updated. - id t = swizzledTextureView ? swizzledTextureView : texture; - if (!t) { - return nil; - } - if (UTILS_UNLIKELY(minLod > maxLod)) { - // If the texture does not have any available LODs, provide a view of only level 0. - // Filament should prevent this from ever occurring. - lodTextureView = createTextureViewWithLodRange(t, 0, 0); - return lodTextureView; - } - lodTextureView = createTextureViewWithLodRange(t, minLod, maxLod); - return lodTextureView; +id MetalTexture::getMtlTextureForRead() const noexcept { + return swizzledTextureView ? swizzledTextureView : texture; } MTLPixelFormat MetalTexture::decidePixelFormat(MetalContext* context, TextureFormat format) { @@ -780,15 +776,12 @@ static void func(void* user) { assert_invariant(false); } } - - extendLodRangeTo(level); } void MetalTexture::generateMipmaps() noexcept { id blitEncoder = [getPendingCommandBuffer(&context) blitCommandEncoder]; [blitEncoder generateMipmapsForTexture:texture]; [blitEncoder endEncoding]; - setLodRange(0, texture.mipmapLevelCount - 1); } void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice, @@ -912,98 +905,6 @@ static void func(void* user) { context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit"); } -void MetalTexture::extendLodRangeTo(uint16_t level) { - assert_invariant(!isInRenderPass(&context)); - minLod = std::min(minLod, level); - maxLod = std::max(maxLod, level); - lodTextureView = nil; -} - -void MetalTexture::setLodRange(uint16_t min, uint16_t max) { - assert_invariant(!isInRenderPass(&context)); - assert_invariant(min <= max); - minLod = min; - maxLod = max; - lodTextureView = nil; -} - -void MetalSamplerGroup::finalize() { - assert_invariant(encoder); - // TODO: we should be able to encode textures and samplers inside setFinalizedTexture and - // setFinalizedSampler as they become available, but Metal doesn't seem to like this; the arg - // buffer gets encoded incorrectly. This warrants more investigation. - - auto [buffer, offset] = argBuffer->getCurrentAllocation(); - [encoder setArgumentBuffer:buffer offset:offset]; - - // Encode all textures and samplers. - for (size_t s = 0; s < size; s++) { - [encoder setTexture:textures[s] atIndex:(s * 2 + 0)]; - [encoder setSamplerState:samplers[s] atIndex:(s * 2 + 1)]; - } - - finalized = true; -} - -void MetalSamplerGroup::reset(id cmdBuffer, id e, - id device) { - encoder = e; - - // The number of slots in the ring buffer we use to manage argument buffer allocations. - // This number was chosen to avoid running out of slots and having to allocate a "fallback" - // buffer when SamplerGroups are updated multiple times a frame. This value can reduced after - // auditing Filament's calls to updateSamplerGroup, which should be as few times as possible. - // For example, the bloom downsample pass should be refactored to maintain two separate - // MaterialInstances instead of "ping ponging" between two texture bindings, which causes a - // single SamplerGroup to be updated many times a frame. - static constexpr auto METAL_ARGUMENT_BUFFER_SLOTS = 32; - - MTLSizeAndAlign argBufferLayout; - argBufferLayout.size = encoder.encodedLength; - argBufferLayout.align = encoder.alignment; - // Chances are, even though the MTLArgumentEncoder might change, the required size and alignment - // probably won't. So we can re-use the previous ring buffer. - if (UTILS_UNLIKELY(!argBuffer || !argBuffer->canAccomodateLayout(argBufferLayout))) { - argBuffer = std::make_unique(device, MTLResourceStorageModeShared, - argBufferLayout, METAL_ARGUMENT_BUFFER_SLOTS); - } else { - argBuffer->createNewAllocation(cmdBuffer); - } - - // Clear all textures and samplers. - assert_invariant(textureHandles.size() == textures.size()); - assert_invariant(textures.size() == samplers.size()); - for (size_t s = 0; s < textureHandles.size(); s++) { - textureHandles[s] = {}; - textures[s] = nil; - samplers[s] = nil; - } - - finalized = false; -} - -void MetalSamplerGroup::mutate(id cmdBuffer) { - assert_invariant(finalized); // only makes sense to mutate if this sampler group is finalized - assert_invariant(argBuffer); - argBuffer->createNewAllocation(cmdBuffer); - finalized = false; -} - -void MetalSamplerGroup::useResources(id renderPassEncoder) { - assert_invariant(finalized); - if (@available(iOS 13, *)) { - // TODO: pass only the appropriate stages to useResources. - [renderPassEncoder useResources:textures.data() - count:textures.size() - usage:MTLResourceUsageRead | MTLResourceUsageSample - stages:MTLRenderStageFragment | MTLRenderStageVertex]; - } else { - [renderPassEncoder useResources:textures.data() - count:textures.size() - usage:MTLResourceUsageRead | MTLResourceUsageSample]; - } -} - MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], Attachment depthAttachment, Attachment stencilAttachment) : @@ -1349,5 +1250,193 @@ static void func(void* user) { return FenceStatus::ERROR; } +MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept + : mLayout(std::move(l)) { + size_t dynamicBindings = 0; + for (const auto& binding : mLayout.bindings) { + if (any(binding.flags & DescriptorFlags::DYNAMIC_OFFSET)) { + dynamicBindings++; + } + } + mDynamicOffsetCount = dynamicBindings; +} + +id MetalDescriptorSetLayout::getArgumentEncoder(id device, ShaderStage stage, + utils::FixedCapacityVector const& textureTypes) { + auto const index = static_cast(stage); + assert_invariant(index < mCachedArgumentEncoder.size()); + if (mCachedArgumentEncoder[index] && + std::equal( + textureTypes.begin(), textureTypes.end(), mCachedTextureTypes[index].begin())) { + return mCachedArgumentEncoder[index]; + } + mCachedArgumentEncoder[index] = getArgumentEncoderSlow(device, stage, textureTypes); + mCachedTextureTypes[index] = textureTypes; + return mCachedArgumentEncoder[index]; +} + +id MetalDescriptorSetLayout::getArgumentEncoderSlow(id device, + ShaderStage stage, utils::FixedCapacityVector const& textureTypes) { + auto const& bindings = getBindings(); + NSMutableArray* arguments = [NSMutableArray new]; + // Important! The bindings must be sorted by binding number. This has already been done inside + // createDescriptorSetLayout. + size_t textureIndex = 0; + for (auto const& binding : bindings) { + if (!hasShaderType(binding.stageFlags, stage)) { + continue; + } + switch (binding.type) { + case DescriptorType::UNIFORM_BUFFER: + case DescriptorType::SHADER_STORAGE_BUFFER: { + MTLArgumentDescriptor* bufferArgument = [MTLArgumentDescriptor argumentDescriptor]; + bufferArgument.index = binding.binding * 2; + bufferArgument.dataType = MTLDataTypePointer; + bufferArgument.access = MTLArgumentAccessReadOnly; + [arguments addObject:bufferArgument]; + break; + } + case DescriptorType::SAMPLER: { + MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor]; + textureArgument.index = binding.binding * 2; + textureArgument.dataType = MTLDataTypeTexture; + MTLTextureType textureType = MTLTextureType2D; + if (textureIndex < textureTypes.size()) { + textureType = textureTypes[textureIndex++]; + } + textureArgument.textureType = textureType; + textureArgument.access = MTLArgumentAccessReadOnly; + [arguments addObject:textureArgument]; + + MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor]; + samplerArgument.index = binding.binding * 2 + 1; + samplerArgument.dataType = MTLDataTypeSampler; + textureArgument.access = MTLArgumentAccessReadOnly; + [arguments addObject:samplerArgument]; + break; + } + case DescriptorType::INPUT_ATTACHMENT: + // TODO: support INPUT_ATTACHMENT + assert_invariant(false); + break; + } + } + return [device newArgumentEncoderWithArguments:arguments]; +} + +MetalDescriptorSet::MetalDescriptorSet(MetalDescriptorSetLayout* layout) noexcept + : layout(layout) {} + +void MetalDescriptorSet::finalize(MetalDriver* driver) { + [driver->mContext->currentRenderPassEncoder useResource:driver->mContext->emptyBuffer + usage:MTLResourceUsageRead]; + [driver->mContext->currentRenderPassEncoder + useResource:getOrCreateEmptyTexture(driver->mContext) + usage:MTLResourceUsageRead]; + + if (@available(iOS 13.0, *)) { + [driver->mContext->currentRenderPassEncoder useResources:vertexResources.data() + count:vertexResources.size() + usage:MTLResourceUsageRead + stages:MTLRenderStageVertex]; + [driver->mContext->currentRenderPassEncoder useResources:fragmentResources.data() + count:fragmentResources.size() + usage:MTLResourceUsageRead + stages:MTLRenderStageFragment]; + } else { + [driver->mContext->currentRenderPassEncoder useResources:vertexResources.data() + count:vertexResources.size() + usage:MTLResourceUsageRead]; + [driver->mContext->currentRenderPassEncoder useResources:fragmentResources.data() + count:fragmentResources.size() + usage:MTLResourceUsageRead]; + } +} + +id MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, ShaderStage stage) { + auto const index = static_cast(stage); + assert_invariant(index < cachedBuffer.size()); + auto& buffer = cachedBuffer[index]; + + if (buffer) { + return buffer.get(); + } + + // Map all the texture bindings to their respective texture types. + auto const& bindings = layout->getBindings(); + auto textureTypes = utils::FixedCapacityVector::with_capacity(bindings.size()); + for (auto const& binding : bindings) { + if (!hasShaderType(binding.stageFlags, stage)) { + continue; + } + MTLTextureType textureType = MTLTextureType2D; + if (auto found = textures.find(binding.binding); found != textures.end()) { + auto const& textureBinding = textures[binding.binding]; + textureType = textureBinding.texture.textureType; + } + textureTypes.push_back(textureType); + } + + MetalContext const& context = *driver->mContext; + + id encoder = + layout->getArgumentEncoder(context.device, stage, textureTypes); + + { + ScopedAllocationTimer timer("descriptor_set"); + buffer = { [context.device newBufferWithLength:encoder.encodedLength + options:MTLResourceStorageModeShared], + TrackedMetalBuffer::Type::DESCRIPTOR_SET }; + } + [encoder setArgumentBuffer:buffer.get() offset:0]; + + for (auto const& binding : bindings) { + if (!hasShaderType(binding.stageFlags, stage)) { + continue; + } + switch (binding.type) { + case DescriptorType::UNIFORM_BUFFER: + case DescriptorType::SHADER_STORAGE_BUFFER: { + auto found = buffers.find(binding.binding); + if (found == buffers.end()) { + [encoder setBuffer:driver->mContext->emptyBuffer + offset:0 + atIndex:binding.binding * 2]; + continue; + } + + auto const& bufferBinding = buffers[binding.binding]; + [encoder setBuffer:bufferBinding.buffer + offset:bufferBinding.offset + atIndex:binding.binding * 2]; + break; + } + case DescriptorType::SAMPLER: { + auto found = textures.find(binding.binding); + if (found == textures.end()) { + [encoder setTexture:driver->mContext->emptyTexture atIndex:binding.binding * 2]; + id sampler = + driver->mContext->samplerStateCache.getOrCreateState({}); + [encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1]; + continue; + } + + auto const& textureBinding = textures[binding.binding]; + [encoder setTexture:textureBinding.texture atIndex:binding.binding * 2]; + SamplerState samplerState { .samplerParams = textureBinding.sampler }; + id sampler = + driver->mContext->samplerStateCache.getOrCreateState(samplerState); + [encoder setSamplerState:sampler atIndex:binding.binding * 2 + 1]; + break; + } + case DescriptorType::INPUT_ATTACHMENT: + assert_invariant(false); + break; + } + } + + return buffer.get(); +} + } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalState.h b/filament/backend/src/metal/MetalState.h index 1d541cc0065..1e14cddc258 100644 --- a/filament/backend/src/metal/MetalState.h +++ b/filament/backend/src/metal/MetalState.h @@ -33,32 +33,28 @@ namespace filament { namespace backend { -inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) { - return SamplerParams::EqualTo{}(lhs, rhs); -} - // Rasterization Bindings // ---------------------- // Bindings Buffer name Count // ------------------------------------------------------ // 0 Zero buffer (placeholder vertex buffer) 1 // 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT -// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT -// 26 Push constants 1 -// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT +// 20 Push constants 1 +// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT +// 25 Dynamic offset buffer 1 // -// Total 31 +// Total 23 // Compute Bindings // ---------------------- // Bindings Buffer name Count // ------------------------------------------------------ // 0-3 SSBO buffers 4 MAX_SSBO_COUNT -// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT -// 26 Push constants 1 -// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT +// 20 Push constants 1 +// 21-24 Descriptor sets (argument buffers) 4 limited by MAX_DESCRIPTOR_SET_COUNT +// 25 Dynamic offset buffer 1 // -// Total 18 +// Total 10 // The total number of vertex buffer "slots" that the Metal backend can bind. // + 1 to account for the zero buffer, a placeholder buffer used internally by the Metal backend. @@ -71,10 +67,11 @@ static constexpr uint32_t ZERO_VERTEX_BUFFER_BINDING = 0u; static constexpr uint32_t USER_VERTEX_BUFFER_BINDING_START = 1u; + // These constants must match the equivalent in CodeGenerator.h. -static constexpr uint32_t UNIFORM_BUFFER_BINDING_START = 17u; -static constexpr uint32_t SSBO_BINDING_START = 0u; -static constexpr uint32_t SAMPLER_GROUP_BINDING_START = 27u; +static constexpr uint32_t PUSH_CONSTANT_BUFFER_INDEX = 20u; +static constexpr uint32_t DESCRIPTOR_SET_BINDING_START = 21u; +static constexpr uint32_t DYNAMIC_OFFSET_BINDING = 25u; // Forward declarations necessary here, definitions at end of file. inline bool operator==(const MTLViewport& lhs, const MTLViewport& rhs); @@ -387,14 +384,17 @@ using DepthClampStateTracker = StateTracker; // Argument encoder struct ArgumentEncoderState { + NSUInteger bufferCount; utils::FixedCapacityVector textureTypes; - explicit ArgumentEncoderState(utils::FixedCapacityVector&& types) - : textureTypes(std::move(types)) {} + explicit ArgumentEncoderState( + NSUInteger bufferCount, utils::FixedCapacityVector&& types) + : bufferCount(bufferCount), textureTypes(std::move(types)) {} bool operator==(const ArgumentEncoderState& rhs) const noexcept { return std::equal(textureTypes.begin(), textureTypes.end(), rhs.textureTypes.begin(), - rhs.textureTypes.end()); + rhs.textureTypes.end()) && + bufferCount == rhs.bufferCount; } bool operator!=(const ArgumentEncoderState& rhs) const noexcept { @@ -416,6 +416,30 @@ struct ArgumentEncoderCreator { using ArgumentEncoderCache = StateCache, ArgumentEncoderCreator, ArgumentEncoderHasher>; +template +class MetalBufferBindings { +public: + MetalBufferBindings() { invalidate(); } + + void invalidate() { + mDirtyBuffers.reset(); + mDirtyOffsets.reset(); + for (int i = 0; i < int(N); i++) { + mDirtyBuffers.set(i, true); + mDirtyOffsets.set(i, true); + } + } + void setBuffer(const id buffer, NSUInteger offset, NSUInteger index); + void bindBuffers(id encoder, NSUInteger startIndex); + +private: + static_assert(N <= 8); + std::array<__unsafe_unretained id, N> mBuffers = { nil }; + std::array mOffsets = { 0 }; + utils::bitset8 mDirtyBuffers; + utils::bitset8 mDirtyOffsets; +}; + } // namespace backend } // namespace filament diff --git a/filament/backend/src/metal/MetalState.mm b/filament/backend/src/metal/MetalState.mm index 58be435a5bb..065e2b62cce 100644 --- a/filament/backend/src/metal/MetalState.mm +++ b/filament/backend/src/metal/MetalState.mm @@ -166,28 +166,40 @@ id ArgumentEncoderCreator::operator()(id device, const ArgumentEncoderState &state) noexcept { const auto& textureTypes = state.textureTypes; - const auto& count = textureTypes.size(); - assert_invariant(count > 0); + const auto& textureCount = textureTypes.size(); + const auto& bufferCount = state.bufferCount; + assert_invariant(textureCount > 0); // Metal has separate data types for textures versus samplers, so the argument buffer layout // alternates between texture and sampler, i.e.: + // buffer0 + // buffer1 // textureA // samplerA // textureB // samplerB // etc NSMutableArray* arguments = - [NSMutableArray arrayWithCapacity:(count * 2)]; - for (size_t i = 0; i < count; i++) { + [NSMutableArray arrayWithCapacity:(bufferCount + textureCount * 2)]; + size_t i = 0; + for (size_t j = 0; j < bufferCount; j++) { + MTLArgumentDescriptor* bufferArgument = [MTLArgumentDescriptor argumentDescriptor]; + bufferArgument.index = i++; + bufferArgument.dataType = MTLDataTypePointer; + bufferArgument.access = MTLArgumentAccessReadOnly; + [arguments addObject:bufferArgument]; + } + + for (size_t j = 0; j < textureCount; j++) { MTLArgumentDescriptor* textureArgument = [MTLArgumentDescriptor argumentDescriptor]; - textureArgument.index = i * 2 + 0; + textureArgument.index = i++; textureArgument.dataType = MTLDataTypeTexture; textureArgument.textureType = textureTypes[i]; textureArgument.access = MTLArgumentAccessReadOnly; [arguments addObject:textureArgument]; MTLArgumentDescriptor* samplerArgument = [MTLArgumentDescriptor argumentDescriptor]; - samplerArgument.index = i * 2 + 1; + samplerArgument.index = i++; samplerArgument.dataType = MTLDataTypeSampler; textureArgument.access = MTLArgumentAccessReadOnly; [arguments addObject:samplerArgument]; @@ -196,5 +208,64 @@ return [device newArgumentEncoderWithArguments:arguments]; } +template +void MetalBufferBindings::setBuffer(const id buffer, NSUInteger offset, NSUInteger index) { + assert_invariant(offset + 1 <= N); + + if (mBuffers[index] != buffer) { + mBuffers[index] = buffer; + mDirtyBuffers.set(index); + } + + if (mOffsets[index] != offset) { + mOffsets[index] = offset; + mDirtyOffsets.set(index); + } +} + +template +void MetalBufferBindings::bindBuffers( + id encoder, NSUInteger startIndex) { + if (mDirtyBuffers.none() && mDirtyOffsets.none()) { + return; + } + + utils::bitset8 onlyOffsetDirty = mDirtyOffsets & ~mDirtyBuffers; + onlyOffsetDirty.forEachSetBit([&](size_t i) { + if constexpr (stage == ShaderStage::VERTEX) { + [(id)encoder setVertexBufferOffset:mOffsets[i] + atIndex:i + startIndex]; + } else if constexpr (stage == ShaderStage::FRAGMENT) { + [(id)encoder setFragmentBufferOffset:mOffsets[i] + atIndex:i + startIndex]; + } else if constexpr (stage == ShaderStage::COMPUTE) { + [(id)encoder setBufferOffset:mOffsets[i] + atIndex:i + startIndex]; + } + }); + mDirtyOffsets.reset(); + + mDirtyBuffers.forEachSetBit([&](size_t i) { + if constexpr (stage == ShaderStage::VERTEX) { + [(id)encoder setVertexBuffer:mBuffers[i] + offset:mOffsets[i] + atIndex:i + startIndex]; + } else if constexpr (stage == ShaderStage::FRAGMENT) { + [(id)encoder setFragmentBuffer:mBuffers[i] + offset:mOffsets[i] + atIndex:i + startIndex]; + } else if constexpr (stage == ShaderStage::COMPUTE) { + [(id)encoder setBuffer:mBuffers[i] + offset:mOffsets[i] + atIndex:i + startIndex]; + } + }); + mDirtyBuffers.reset(); +} + +template class MetalBufferBindings; +template class MetalBufferBindings; +template class MetalBufferBindings; + } // namespace backend } // namespace filament diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 5309c691430..afc5ac808df 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -99,9 +99,6 @@ void NoopDriver::destroyProgram(Handle ph) { void NoopDriver::destroyRenderTarget(Handle rth) { } -void NoopDriver::destroySamplerGroup(Handle sbh) { -} - void NoopDriver::destroySwapChain(Handle sch) { } @@ -111,6 +108,12 @@ void NoopDriver::destroyStream(Handle sh) { void NoopDriver::destroyTimerQuery(Handle tqh) { } +void NoopDriver::destroyDescriptorSetLayout(Handle tqh) { +} + +void NoopDriver::destroyDescriptorSet(Handle tqh) { +} + Handle NoopDriver::createStreamNative(void* nativeStream) { return {}; } @@ -248,9 +251,6 @@ void NoopDriver::setVertexBufferObject(Handle vbh, uint32_t inde Handle boh) { } -void NoopDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { -} - void NoopDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, @@ -276,11 +276,6 @@ void NoopDriver::setExternalStream(Handle th, Handle sh) { void NoopDriver::generateMipmaps(Handle th) { } -void NoopDriver::updateSamplerGroup(Handle sbh, - BufferDescriptor&& data) { - scheduleDestroy(std::move(data)); -} - void NoopDriver::compilePrograms(CompilerPriorityQueue priority, CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { if (callback) { @@ -303,19 +298,6 @@ void NoopDriver::makeCurrent(Handle drawSch, Handle re void NoopDriver::commit(Handle sch) { } -void NoopDriver::bindUniformBuffer(uint32_t index, Handle ubh) { -} - -void NoopDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index, - Handle ubh, uint32_t offset, uint32_t size) { -} - -void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) { -} - -void NoopDriver::bindSamplers(uint32_t index, Handle sbh) { -} - void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value) { } @@ -392,6 +374,27 @@ void NoopDriver::endTimerQuery(Handle tqh) { void NoopDriver::resetState(int) { } +void NoopDriver::updateDescriptorSetBuffer( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::BufferObjectHandle boh, + uint32_t offset, + uint32_t size) { +} + +void NoopDriver::updateDescriptorSetTexture( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::TextureHandle th, + SamplerParams params) { +} + +void NoopDriver::bindDescriptorSet( + backend::DescriptorSetHandle dsh, + backend::descriptor_set_t set, + backend::DescriptorSetOffsetArray&& offsets) { +} + void NoopDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) { } diff --git a/filament/backend/src/opengl/BindingMap.h b/filament/backend/src/opengl/BindingMap.h new file mode 100644 index 00000000000..b4f53d1b5da --- /dev/null +++ b/filament/backend/src/opengl/BindingMap.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_BINDINGMAP_H +#define TNT_FILAMENT_BACKEND_OPENGL_BINDINGMAP_H + +#include + +#include "gl_headers.h" + +#include +#include + +#include + +#include +#include +#include + +namespace filament::backend { + +class BindingMap { + struct CompressedBinding { + // this is in fact a GLuint, but we only want 8-bits + uint8_t binding : 7; + uint8_t sampler : 1; + }; + + CompressedBinding (*mStorage)[MAX_DESCRIPTOR_COUNT]; + + utils::bitset64 mActiveDescriptors[MAX_DESCRIPTOR_SET_COUNT]; + +public: + BindingMap() noexcept + : mStorage(new (std::nothrow) CompressedBinding[MAX_DESCRIPTOR_SET_COUNT][MAX_DESCRIPTOR_COUNT]) { +#ifndef NDEBUG + memset(mStorage, 0xFF, sizeof(CompressedBinding[MAX_DESCRIPTOR_SET_COUNT][MAX_DESCRIPTOR_COUNT])); +#endif + } + + ~BindingMap() noexcept { + delete [] mStorage; + } + + BindingMap(BindingMap const&) noexcept = delete; + BindingMap(BindingMap&&) noexcept = delete; + BindingMap& operator=(BindingMap const&) noexcept = delete; + BindingMap& operator=(BindingMap&&) noexcept = delete; + + struct Binding { + GLuint binding; + DescriptorType type; + }; + + void insert(descriptor_set_t set, descriptor_binding_t binding, Binding entry) noexcept { + assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT); + assert_invariant(binding < MAX_DESCRIPTOR_COUNT); + assert_invariant(entry.binding < 128); // we reserve 1 bit for the type right now + mStorage[set][binding] = { (uint8_t)entry.binding, entry.type == DescriptorType::SAMPLER }; + mActiveDescriptors[set].set(binding); + } + + GLuint get(descriptor_set_t set, descriptor_binding_t binding) const noexcept { + assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT); + assert_invariant(binding < MAX_DESCRIPTOR_COUNT); + return mStorage[set][binding].binding; + } + + utils::bitset64 getActiveDescriptors(descriptor_set_t set) const noexcept { + return mActiveDescriptors[set]; + } +}; + +} // namespace filament::backend + +#endif //TNT_FILAMENT_BACKEND_OPENGL_BINDINGMAP_H diff --git a/filament/backend/src/opengl/GLDescriptorSet.cpp b/filament/backend/src/opengl/GLDescriptorSet.cpp new file mode 100644 index 00000000000..eb54fc90996 --- /dev/null +++ b/filament/backend/src/opengl/GLDescriptorSet.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GLDescriptorSet.h" + +#include "GLBufferObject.h" +#include "GLDescriptorSetLayout.h" +#include "GLTexture.h" +#include "GLUtils.h" +#include "OpenGLDriver.h" +#include "OpenGLContext.h" +#include "OpenGLProgram.h" + +#include "gl_headers.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace filament::backend { + +GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh, + GLDescriptorSetLayout const* layout) noexcept + : descriptors(layout->maxDescriptorBinding + 1), + dslh(std::move(dslh)) { + + // We have allocated enough storage for all descriptors. Now allocate the empty descriptor + // themselves. + for (auto const& entry : layout->bindings) { + size_t const index = entry.binding; + + // now we'll initialize the alternative for each way we can handle this descriptor. + auto& desc = descriptors[index].desc; + switch (entry.type) { + case DescriptorType::UNIFORM_BUFFER: { + // A uniform buffer can have dynamic offsets or not and have special handling for + // ES2 (where we need to emulate it). That's four alternatives. + bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET); + dynamicBuffers.set(index, dynamicOffset); + if (UTILS_UNLIKELY(gl.isES2())) { + dynamicBufferCount++; + desc.emplace(dynamicOffset); + } else { + auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::UNIFORM); + if (dynamicOffset) { + dynamicBufferCount++; + desc.emplace(type); + } else { + desc.emplace(type); + } + } + break; + } + case DescriptorType::SHADER_STORAGE_BUFFER: { + // shader storage buffers are not supported on ES2, So that's two alternatives. + bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET); + dynamicBuffers.set(index, dynamicOffset); + auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::SHADER_STORAGE); + if (dynamicOffset) { + dynamicBufferCount++; + desc.emplace(type); + } else { + desc.emplace(type); + } + break; + } + case DescriptorType::SAMPLER: + if (UTILS_UNLIKELY(gl.isES2())) { + desc.emplace(); + } else { + const bool anisotropyWorkaround = + gl.ext.EXT_texture_filter_anisotropic && + gl.bugs.texture_filter_anisotropic_broken_on_sampler; + if (anisotropyWorkaround) { + desc.emplace(); + } else { + desc.emplace(); + } + } + break; + case DescriptorType::INPUT_ATTACHMENT: + break; + } + } +} + +void GLDescriptorSet::update(OpenGLContext&, + descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept { + assert_invariant(binding < descriptors.size()); + std::visit([=](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v || std::is_same_v) { + assert_invariant(arg.target != 0); + arg.id = bo ? bo->gl.id : 0; + arg.offset = uint32_t(offset); + arg.size = uint32_t(size); + assert_invariant(arg.id || (!arg.size && !offset)); + } else if constexpr (std::is_same_v) { + arg.bo = bo; + arg.offset = uint32_t(offset); + } else { + // API usage error. User asked to update the wrong type of descriptor. + PANIC_PRECONDITION("descriptor %d is not a buffer", +binding); + } + }, descriptors[binding].desc); +} + +void GLDescriptorSet::update(OpenGLContext& gl, + descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept { + assert_invariant(binding < descriptors.size()); + std::visit([=, &gl](auto&& arg) mutable { + using T = std::decay_t; + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + if (UTILS_UNLIKELY(t && t->target == SamplerType::SAMPLER_EXTERNAL)) { + // From OES_EGL_image_external spec: + // "The default s and t wrap modes are CLAMP_TO_EDGE, and it is an INVALID_ENUM + // error to set the wrap mode to any other value." + params.wrapS = SamplerWrapMode::CLAMP_TO_EDGE; + params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE; + params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE; + } + // GLES3.x specification forbids depth textures to be filtered. + if (t && isDepthFormat(t->format) + && params.compareMode == SamplerCompareMode::NONE) { + params.filterMag = SamplerMagFilter::NEAREST; + switch (params.filterMin) { + case SamplerMinFilter::LINEAR: + params.filterMin = SamplerMinFilter::NEAREST; + break; + case SamplerMinFilter::LINEAR_MIPMAP_NEAREST: + case SamplerMinFilter::NEAREST_MIPMAP_LINEAR: + case SamplerMinFilter::LINEAR_MIPMAP_LINEAR: + params.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST; + break; + default: + break; + } + } + + arg.target = t ? t->gl.target : 0; + arg.id = t ? t->gl.id : 0; + if constexpr (std::is_same_v || + std::is_same_v) { + if constexpr (std::is_same_v) { + arg.anisotropy = float(1u << params.anisotropyLog2); + } + if (t) { + arg.ref = t->ref; + arg.baseLevel = t->gl.baseLevel; + arg.maxLevel = t->gl.maxLevel; + arg.swizzle = t->gl.swizzle; + } +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + arg.sampler = gl.getSampler(params); +#else + (void)gl; +#endif + } else { + arg.params = params; + } + } else { + // API usage error. User asked to update the wrong type of descriptor. + PANIC_PRECONDITION("descriptor %d is not a texture", +binding); + } + }, descriptors[binding].desc); +} + +template +void GLDescriptorSet::updateTextureView(OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept { + // The common case is that we don't have a ref handle (we only have one if + // the texture ever had a View on it). + assert_invariant(desc.ref); + GLTextureRef* const ref = handleAllocator.handle_cast(desc.ref); + if (UTILS_UNLIKELY((desc.baseLevel != ref->baseLevel || desc.maxLevel != ref->maxLevel))) { + // If we have views, then it's still uncommon that we'll switch often + // handle the case where we reset to the original texture + GLint baseLevel = GLint(desc.baseLevel); // NOLINT(*-signed-char-misuse) + GLint maxLevel = GLint(desc.maxLevel); // NOLINT(*-signed-char-misuse) + if (baseLevel > maxLevel) { + baseLevel = 0; + maxLevel = 1000; // per OpenGL spec + } + // that is very unfortunate that we have to call activeTexture here + gl.activeTexture(unit); + glTexParameteri(desc.target, GL_TEXTURE_BASE_LEVEL, baseLevel); + glTexParameteri(desc.target, GL_TEXTURE_MAX_LEVEL, maxLevel); + ref->baseLevel = desc.baseLevel; + ref->maxLevel = desc.maxLevel; + } + if (UTILS_UNLIKELY(desc.swizzle != ref->swizzle)) { + using namespace GLUtils; + gl.activeTexture(unit); +#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) + glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(desc.swizzle[0])); + glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(desc.swizzle[1])); + glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(desc.swizzle[2])); + glTexParameteri(desc.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(desc.swizzle[3])); +#endif + ref->swizzle = desc.swizzle; + } +} + +void GLDescriptorSet::bind( + OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, + OpenGLProgram const& p, + descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept { + // TODO: check that offsets is sized correctly + size_t dynamicOffsetIndex = 0; + + utils::bitset64 activeDescriptorBindings = p.getActiveDescriptors(set); + if (offsetsOnly) { + activeDescriptorBindings &= dynamicBuffers; + } + + // loop only over the active indices for this program + activeDescriptorBindings.forEachSetBit( + [this,&gl, &handleAllocator, &p, set, offsets, &dynamicOffsetIndex] + (size_t binding) { + + // This would fail here if we're trying to set a descriptor that doesn't exist in the + // program. In other words, a mismatch between the program's layout and this descriptor-set. + assert_invariant(binding < descriptors.size()); + + auto const& entry = descriptors[binding]; + std::visit( + [&gl, &handleAllocator, &p, &dynamicOffsetIndex, set, binding, offsets] + (auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + GLuint const bindingPoint = p.getBufferBinding(set, binding); + GLintptr const offset = arg.offset; + assert_invariant(arg.id || (!arg.size && !offset)); + gl.bindBufferRange(arg.target, bindingPoint, arg.id, offset, arg.size); + } else if constexpr (std::is_same_v) { + GLuint const bindingPoint = p.getBufferBinding(set, binding); + GLintptr const offset = arg.offset + offsets[dynamicOffsetIndex++]; + assert_invariant(arg.id || (!arg.size && !offset)); + gl.bindBufferRange(arg.target, bindingPoint, arg.id, offset, arg.size); + } else if constexpr (std::is_same_v) { + GLuint const bindingPoint = p.getBufferBinding(set, binding); + GLintptr offset = arg.offset; + if (arg.dynamicOffset) { + offset += offsets[dynamicOffsetIndex++]; + } + if (arg.bo) { + auto buffer = static_cast(arg.bo->gl.buffer) + offset; + p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age); + } + } else if constexpr (std::is_same_v) { + GLuint const unit = p.getTextureUnit(set, binding); + if (arg.target) { + gl.bindTexture(unit, arg.target, arg.id); + gl.bindSampler(unit, arg.sampler); + if (UTILS_UNLIKELY(arg.ref)) { + updateTextureView(gl, handleAllocator, unit, arg); + } + } else { + gl.unbindTextureUnit(unit); + } + } else if constexpr (std::is_same_v) { + GLuint const unit = p.getTextureUnit(set, binding); + if (arg.target) { + gl.bindTexture(unit, arg.target, arg.id); + gl.bindSampler(unit, arg.sampler); + if (UTILS_UNLIKELY(arg.ref)) { + updateTextureView(gl, handleAllocator, unit, arg); + } +#if defined(GL_EXT_texture_filter_anisotropic) + // Driver claims to support anisotropic filtering, but it fails when set on + // the sampler, we have to set it on the texture instead. + glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT, + std::min(gl.gets.max_anisotropy, float(arg.anisotropy))); +#endif + } else { + gl.unbindTextureUnit(unit); + } + } else if constexpr (std::is_same_v) { + // in ES2 the sampler parameters need to be set on the texture itself + GLuint const unit = p.getTextureUnit(set, binding); + if (arg.target) { + gl.bindTexture(unit, arg.target, arg.id); + SamplerParams const params = arg.params; + glTexParameteri(arg.target, GL_TEXTURE_MIN_FILTER, + (GLint)GLUtils::getTextureFilter(params.filterMin)); + glTexParameteri(arg.target, GL_TEXTURE_MAG_FILTER, + (GLint)GLUtils::getTextureFilter(params.filterMag)); + glTexParameteri(arg.target, GL_TEXTURE_WRAP_S, + (GLint)GLUtils::getWrapMode(params.wrapS)); + glTexParameteri(arg.target, GL_TEXTURE_WRAP_T, + (GLint)GLUtils::getWrapMode(params.wrapT)); +#if defined(GL_EXT_texture_filter_anisotropic) + glTexParameterf(arg.target, GL_TEXTURE_MAX_ANISOTROPY_EXT, + std::min(gl.gets.max_anisotropy, arg.anisotropy)); +#endif + } else { + gl.unbindTextureUnit(unit); + } + } + }, entry.desc); + }); + CHECK_GL_ERROR(utils::slog.e) +} + +void GLDescriptorSet::validate(HandleAllocatorGL& allocator, + DescriptorSetLayoutHandle pipelineLayout) const { + + if (UTILS_UNLIKELY(dslh != pipelineLayout)) { + auto* const dsl = allocator.handle_cast < GLDescriptorSetLayout const * > (dslh); + auto* const cur = allocator.handle_cast < GLDescriptorSetLayout const * > (pipelineLayout); + + UTILS_UNUSED_IN_RELEASE + bool const pipelineLayoutMatchesDescriptorSetLayout = std::equal( + dsl->bindings.begin(), dsl->bindings.end(), + cur->bindings.begin(), + [](DescriptorSetLayoutBinding const& lhs, + DescriptorSetLayoutBinding const& rhs) { + return lhs.type == rhs.type && + lhs.stageFlags == rhs.stageFlags && + lhs.binding == rhs.binding && + lhs.flags == rhs.flags && + lhs.count == rhs.count; + }); + + assert_invariant(pipelineLayoutMatchesDescriptorSetLayout); + } +} + +} // namespace filament::backend diff --git a/filament/backend/src/opengl/GLDescriptorSet.h b/filament/backend/src/opengl/GLDescriptorSet.h new file mode 100644 index 00000000000..26a783b740c --- /dev/null +++ b/filament/backend/src/opengl/GLDescriptorSet.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSET_H +#define TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSET_H + +#include "DriverBase.h" + +#include "gl_headers.h" + +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace filament::backend { + +struct GLBufferObject; +struct GLTexture; +struct GLTextureRef; +struct GLDescriptorSetLayout; +class OpenGLProgram; +class OpenGLContext; +class OpenGLDriver; + +struct GLDescriptorSet : public HwDescriptorSet { + + using HwDescriptorSet::HwDescriptorSet; + + GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh, + GLDescriptorSetLayout const* layout) noexcept; + + // update a buffer descriptor in the set + void update(OpenGLContext& gl, + descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept; + + // update a sampler descriptor in the set + void update(OpenGLContext& gl, + descriptor_binding_t binding, GLTexture* t, SamplerParams params) noexcept; + + // conceptually bind the set to the command buffer + void bind( + OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, + OpenGLProgram const& p, + descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept; + + uint32_t getDynamicBufferCount() const noexcept { + return dynamicBufferCount; + } + + void validate(HandleAllocatorGL& allocator, DescriptorSetLayoutHandle pipelineLayout) const; + +private: + // a Buffer Descriptor such as SSBO or UBO with static offset + struct Buffer { + Buffer() = default; + explicit Buffer(GLenum target) noexcept : target(target) { } + GLenum target; // 4 + GLuint id = 0; // 4 + uint32_t offset = 0; // 4 + uint32_t size = 0; // 4 + }; + + // a Buffer Descriptor such as SSBO or UBO with dynamic offset + struct DynamicBuffer { + DynamicBuffer() = default; + explicit DynamicBuffer(GLenum target) noexcept : target(target) { } + GLenum target; // 4 + GLuint id = 0; // 4 + uint32_t offset = 0; // 4 + uint32_t size = 0; // 4 + }; + + // a UBO descriptor for ES2 + struct BufferGLES2 { + BufferGLES2() = default; + explicit BufferGLES2(bool dynamicOffset) noexcept : dynamicOffset(dynamicOffset) { } + GLBufferObject const* bo = nullptr; // 8 + uint32_t offset = 0; // 4 + bool dynamicOffset = false; // 4 + }; + + // A sampler descriptor + struct Sampler { + GLenum target = 0; // 4 + GLuint id = 0; // 4 + GLuint sampler = 0; // 4 + Handle ref; // 4 + int8_t baseLevel = 0x7f; // 1 + int8_t maxLevel = -1; // 1 + std::array swizzle{ // 4 + TextureSwizzle::CHANNEL_0, + TextureSwizzle::CHANNEL_1, + TextureSwizzle::CHANNEL_2, + TextureSwizzle::CHANNEL_3 + }; + }; + + struct SamplerWithAnisotropyWorkaround { + GLenum target = 0; // 4 + GLuint id = 0; // 4 + GLuint sampler = 0; // 4 + Handle ref; // 4 + math::half anisotropy = 1.0f; // 2 + int8_t baseLevel = 0x7f; // 1 + int8_t maxLevel = -1; // 1 + std::array swizzle{ // 4 + TextureSwizzle::CHANNEL_0, + TextureSwizzle::CHANNEL_1, + TextureSwizzle::CHANNEL_2, + TextureSwizzle::CHANNEL_3 + }; + }; + + // A sampler descriptor for ES2 + struct SamplerGLES2 { + GLenum target = 0; // 4 + GLuint id = 0; // 4 + SamplerParams params{}; // 4 + float anisotropy = 1.0f; // 4 + }; + struct Descriptor { + std::variant< + Buffer, + DynamicBuffer, + BufferGLES2, + Sampler, + SamplerWithAnisotropyWorkaround, + SamplerGLES2> desc; + }; + static_assert(sizeof(Descriptor) <= 32); + + template + static void updateTextureView(OpenGLContext& gl, + HandleAllocatorGL& handleAllocator, GLuint unit, T const& desc) noexcept; + + utils::FixedCapacityVector descriptors; // 16 + utils::bitset64 dynamicBuffers; // 8 + DescriptorSetLayoutHandle dslh; // 4 + uint8_t dynamicBufferCount = 0; // 1 +}; +static_assert(sizeof(GLDescriptorSet) <= 32); + +} // namespace filament::backend + +#endif //TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSET_H diff --git a/filament/backend/src/opengl/GLDescriptorSetLayout.h b/filament/backend/src/opengl/GLDescriptorSetLayout.h new file mode 100644 index 00000000000..bce3519fceb --- /dev/null +++ b/filament/backend/src/opengl/GLDescriptorSetLayout.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSETLAYOUT_H +#define TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSETLAYOUT_H + +#include "DriverBase.h" + +#include + +#include +#include + +#include + +namespace filament::backend { + +struct GLDescriptorSetLayout : public HwDescriptorSetLayout, public DescriptorSetLayout { + using HwDescriptorSetLayout::HwDescriptorSetLayout; + explicit GLDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept + : DescriptorSetLayout(std::move(layout)) { + + std::sort(bindings.begin(), bindings.end(), + [](auto&& lhs, auto&& rhs){ + return lhs.binding < rhs.binding; + }); + + auto p = std::max_element(bindings.cbegin(), bindings.cend(), + [](auto const& lhs, auto const& rhs) { + return lhs.binding < rhs.binding; + }); + maxDescriptorBinding = p->binding; + } + uint8_t maxDescriptorBinding = 0; +}; + +} // namespace filament::backend + +#endif //TNT_FILAMENT_BACKEND_OPENGL_GLDESCRIPTORSETLAYOUT_H diff --git a/filament/backend/src/opengl/GLTexture.h b/filament/backend/src/opengl/GLTexture.h index 5e9460e17a5..5d721d3c214 100644 --- a/filament/backend/src/opengl/GLTexture.h +++ b/filament/backend/src/opengl/GLTexture.h @@ -21,12 +21,32 @@ #include "gl_headers.h" +#include +#include #include +#include + #include namespace filament::backend { +struct GLTextureRef { + GLTextureRef() = default; + // view reference counter + uint16_t count = 1; + // current per-view values of the texture (in GL we can only have a single View active at + // a time, and this tracks that state). It's used to avoid unnecessarily change state. + int8_t baseLevel = 127; + int8_t maxLevel = -1; + std::array swizzle{ + TextureSwizzle::CHANNEL_0, + TextureSwizzle::CHANNEL_1, + TextureSwizzle::CHANNEL_2, + TextureSwizzle::CHANNEL_3 + }; +}; + struct GLTexture : public HwTexture { using HwTexture::HwTexture; struct GL { @@ -44,8 +64,14 @@ struct GLTexture : public HwTexture { bool imported : 1; uint8_t sidecarSamples : 4; uint8_t reserved1 : 3; + std::array swizzle{ + TextureSwizzle::CHANNEL_0, + TextureSwizzle::CHANNEL_1, + TextureSwizzle::CHANNEL_2, + TextureSwizzle::CHANNEL_3 + }; } gl; - + mutable Handle ref; OpenGLPlatform::ExternalTexture* externalTexture = nullptr; }; diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index 2972480e71f..4544ca104fc 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -60,10 +60,19 @@ class OpenGLContext final : public TimerQueryFactoryInterface { struct RenderPrimitive { static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16); - GLuint vao[2] = {}; // 4 + GLuint vao[2] = {}; // 8 GLuint elementArray = 0; // 4 + GLenum indicesType = 0; // 4 + + // The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced + // VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is + // immutable. + Handle vertexBufferWithObjects; // 4 + mutable utils::bitset vertexAttribArray; // 2 + uint8_t reserved[2] = {}; // 2 + // if this differs from vertexBufferWithObjects->bufferObjectsVersion, this VAO needs to // be updated (see OpenGLDriver::updateVertexArrayObject()) uint8_t vertexBufferVersion = 0; // 1 @@ -76,16 +85,11 @@ class OpenGLContext final : public TimerQueryFactoryInterface { // See OpenGLContext::bindVertexArray() uint8_t nameVersion = 0; // 1 - // Size in bytes of indices in the index buffer - uint8_t indicesSize = 0; // 1 - - // The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced - // VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is - // immutable. - Handle vertexBufferWithObjects; // 4 + // Size in bytes of indices in the index buffer (1 or 2) + uint8_t indicesShift = 0; // 1 GLenum getIndicesType() const noexcept { - return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; + return indicesType; } } gl; @@ -474,12 +478,6 @@ class OpenGLContext final : public TimerQueryFactoryInterface { void unbindEverything() noexcept; void synchronizeStateAndCache(size_t index) noexcept; - void setEs2UniformBinding(size_t index, GLuint id, void const* data, uint16_t age) noexcept { - mUniformBindings[index] = { id, data, age }; - } - auto getEs2UniformBinding(size_t index) const noexcept { - return mUniformBindings[index]; - } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 GLuint getSamplerSlow(SamplerParams sp) const noexcept; @@ -506,9 +504,6 @@ class OpenGLContext final : public TimerQueryFactoryInterface { std::vector> mDestroyWithNormalContext; RenderPrimitive mDefaultVAO; std::optional mDefaultFbo[2]; - std::array< - std::tuple, - CONFIG_UNIFORM_BINDING_COUNT> mUniformBindings = {}; mutable tsl::robin_map mSamplerMap; diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 75c79b0fd1e..bfa04089ebb 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -17,6 +17,7 @@ #include "OpenGLDriver.h" #include "CommandStreamDispatcher.h" +#include "GLTexture.h" #include "GLUtils.h" #include "OpenGLContext.h" #include "OpenGLDriverFactory.h" @@ -28,19 +29,21 @@ #include #include +#include #include #include #include #include #include #include -#include #include #include "private/backend/Dispatcher.h" #include "private/backend/DriverApi.h" +#include #include +#include #include #include #include @@ -59,7 +62,9 @@ #include #include #include +#include #include +#include #include #include @@ -132,17 +137,16 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform, // this is useful for development, but too verbose even for debug builds // For reference on a 64-bits machine in Release mode: // GLIndexBuffer : 8 moderate - // GLSamplerGroup : 16 few // GLSwapChain : 16 few // GLTimerQuery : 16 few // GLFence : 24 few // GLRenderPrimitive : 32 many // GLBufferObject : 32 many // -- less than or equal 32 bytes - // OpenGLProgram : 56 moderate // GLTexture : 64 moderate - // -- less than or equal 64 bytes // GLVertexBuffer : 76 moderate + // OpenGLProgram : 96 moderate + // -- less than or equal 96 bytes // GLStream : 104 few // GLRenderTarget : 112 few // GLVertexBufferInfo : 132 moderate @@ -154,7 +158,6 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform, << "\nGLVertexBuffer: " << sizeof(GLVertexBuffer) << "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo) << "\nGLIndexBuffer: " << sizeof(GLIndexBuffer) - << "\nGLSamplerGroup: " << sizeof(GLSamplerGroup) << "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive) << "\nGLTexture: " << sizeof(GLTexture) << "\nGLTimerQuery: " << sizeof(GLTimerQuery) @@ -249,8 +252,6 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi mDriverConfig(driverConfig), mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) { - std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr); - // set a reasonable default value for our stream array mTexturesWithStreamsAttached.reserve(8); mStreamsWithPendingAcquiredImage.reserve(8); @@ -381,18 +382,28 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept { } bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept { - // set-up textures and samplers in the proper TMUs (as specified in setSamplers) + if (UTILS_UNLIKELY(mBoundProgram == p)) { + // program didn't change, don't do anything. + return true; + } + + // compile/link the program if needed and call glUseProgram bool const success = p->use(this, mContext); assert_invariant(success == p->isValid()); + + if (success) { + // TODO: we could even improve this if the program could tell us which of the descriptors + // bindings actually changed. In practice, it is likely that set 0 or 1 might not + // change often. + decltype(mInvalidDescriptorSetBindings) changed; + changed.setValue((1 << MAX_DESCRIPTOR_SET_COUNT) - 1); + mInvalidDescriptorSetBindings |= changed; + + mBoundProgram = p; + } if (UTILS_UNLIKELY(mContext.isES2() && success)) { - for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) { - auto [id, buffer, age] = mContext.getEs2UniformBinding(i); - if (buffer) { - p->updateUniforms(i, id, buffer, age); - } - } - // Set the output colorspace for this program (linear or rec709). This in only relevant + // Set the output colorspace for this program (linear or rec709). This is only relevant // when mPlatform.isSRGBSwapChainSupported() is false (no need to check though). p->setRec709ColorSpace(mRec709OutputColorspace); } @@ -532,15 +543,23 @@ Handle OpenGLDriver::createProgramS() noexcept { return initHandle(); } -Handle OpenGLDriver::createSamplerGroupS() noexcept { - return initHandle(); +Handle OpenGLDriver::createTextureS() noexcept { + return initHandle(); } -Handle OpenGLDriver::createTextureS() noexcept { +Handle OpenGLDriver::createTextureViewS() noexcept { + return initHandle(); +} + +Handle OpenGLDriver::createTextureViewSwizzleS() noexcept { return initHandle(); } -Handle OpenGLDriver::createTextureSwizzledS() noexcept { +Handle OpenGLDriver::createTextureExternalImageS() noexcept { + return initHandle(); +} + +Handle OpenGLDriver::createTextureExternalImagePlaneS() noexcept { return initHandle(); } @@ -572,6 +591,14 @@ Handle OpenGLDriver::createTimerQueryS() noexcept { return initHandle(); } +Handle OpenGLDriver::createDescriptorSetLayoutS() noexcept { + return initHandle(); +} + +Handle OpenGLDriver::createDescriptorSetS() noexcept { + return initHandle(); +} + void OpenGLDriver::createVertexBufferInfoR( Handle vbih, uint8_t bufferCount, @@ -644,7 +671,8 @@ void OpenGLDriver::createRenderPrimitiveR(Handle rph, GLVertexBuffer* const vb = handle_cast(vbh); GLRenderPrimitive* const rp = handle_cast(rph); - rp->gl.indicesSize = (ib->elementSize == 4u) ? 4u : 2u; + rp->gl.indicesShift = (ib->elementSize == 4u) ? 2u : 1u; + rp->gl.indicesType = (ib->elementSize == 4u) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; rp->gl.vertexBufferWithObjects = vbh; rp->type = pt; rp->vbih = vb->vbih; @@ -698,13 +726,6 @@ void OpenGLDriver::createProgramR(Handle ph, Program&& program) { CHECK_GL_ERROR(utils::slog.e) } -void OpenGLDriver::createSamplerGroupR(Handle sbh, uint32_t size, - utils::FixedSizeString<32> debugName) { - DEBUG_MARKER() - - construct(sbh, size); -} - UTILS_NOINLINE void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t, uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept { @@ -900,32 +921,129 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint CHECK_GL_ERROR(utils::slog.e) } -void OpenGLDriver::createTextureSwizzledR(Handle th, - SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, - uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage, - TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) { +void OpenGLDriver::createTextureViewR(Handle th, + Handle srch, uint8_t baseLevel, uint8_t levelCount) { DEBUG_MARKER() + GLTexture const* const src = handle_cast(srch); - assert_invariant(uint8_t(usage) & uint8_t(TextureUsage::SAMPLEABLE)); + FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE)) + << "TextureView can only be created on a SAMPLEABLE texture"; - createTextureR(th, target, levels, format, samples, w, h, depth, usage); + FILAMENT_CHECK_PRECONDITION(!src->gl.imported) + << "TextureView can't be created on imported textures"; - // WebGL does not support swizzling. We assert for this in the Texture builder, - // so it is probably fine to silently ignore the swizzle state here. -#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) - if (!mContext.isES2()) { - // the texture is still bound and active from createTextureR - GLTexture* t = handle_cast(th); - glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_R, (GLint)getSwizzleChannel(r)); - glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_G, (GLint)getSwizzleChannel(g)); - glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_B, (GLint)getSwizzleChannel(b)); - glTexParameteri(t->gl.target, GL_TEXTURE_SWIZZLE_A, (GLint)getSwizzleChannel(a)); + if (!src->ref) { + // lazily create the ref handle, because most textures will never get a texture view + src->ref = initHandle(); } -#endif + + GLTexture* t = construct(th, + src->target, + src->levels, + src->samples, + src->width, src->height, src->depth, + src->format, + src->usage); + + t->gl = src->gl; + t->gl.sidecarRenderBufferMS = 0; + t->gl.sidecarSamples = 1; + + auto srcBaseLevel = src->gl.baseLevel; + auto srcMaxLevel = src->gl.maxLevel; + if (srcBaseLevel > srcMaxLevel) { + srcBaseLevel = 0; + srcMaxLevel = 127; + } + t->gl.baseLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel); + t->gl.maxLevel = (int8_t)std::min(127, srcBaseLevel + baseLevel + levelCount - 1); + + // increase reference count to this texture handle + t->ref = src->ref; + GLTextureRef* ref = handle_cast(t->ref); + assert_invariant(ref); + ref->count++; + + CHECK_GL_ERROR(utils::slog.e) +} + +void OpenGLDriver::createTextureViewSwizzleR(Handle th, Handle srch, + backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, + backend::TextureSwizzle a) { + + DEBUG_MARKER() + GLTexture const* const src = handle_cast(srch); + + FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE)) + << "TextureView can only be created on a SAMPLEABLE texture"; + + FILAMENT_CHECK_PRECONDITION(!src->gl.imported) + << "TextureView can't be created on imported textures"; + + if (!src->ref) { + // lazily create the ref handle, because most textures will never get a texture view + src->ref = initHandle(); + } + + GLTexture* t = construct(th, + src->target, + src->levels, + src->samples, + src->width, src->height, src->depth, + src->format, + src->usage); + + t->gl = src->gl; + t->gl.baseLevel = src->gl.baseLevel; + t->gl.maxLevel = src->gl.maxLevel; + t->gl.sidecarRenderBufferMS = 0; + t->gl.sidecarSamples = 1; + + auto getChannel = [&swizzle = src->gl.swizzle](TextureSwizzle ch) { + switch (ch) { + case TextureSwizzle::SUBSTITUTE_ZERO: + case TextureSwizzle::SUBSTITUTE_ONE: + return ch; + case TextureSwizzle::CHANNEL_0: + return swizzle[0]; + case TextureSwizzle::CHANNEL_1: + return swizzle[1]; + case TextureSwizzle::CHANNEL_2: + return swizzle[2]; + case TextureSwizzle::CHANNEL_3: + return swizzle[3]; + } + }; + + t->gl.swizzle = { + getChannel(r), + getChannel(g), + getChannel(b), + getChannel(a), + }; + + // increase reference count to this texture handle + t->ref = src->ref; + GLTextureRef* ref = handle_cast(t->ref); + assert_invariant(ref); + ref->count++; CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::createTextureExternalImageR(Handle th, backend::TextureFormat format, + uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) { + createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage); + setExternalImage(th, image); +} + +void OpenGLDriver::createTextureExternalImagePlaneR(Handle th, + backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, + void* image, uint32_t plane) { + createTextureR(th, SamplerType::SAMPLER_EXTERNAL, 1, format, 1, width, height, 1, usage); + setExternalImagePlane(th, image, plane); +} + void OpenGLDriver::importTextureR(Handle th, intptr_t id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) { @@ -1335,14 +1453,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, rt->gl.resolve |= resolveFlags; - if (any(t->usage & TextureUsage::SAMPLEABLE)) { - // In a sense, drawing to a texture level is similar to calling setTextureData on it; in - // both cases, we update the base/max LOD to give shaders access to levels as they become - // available. Note that this can only expand the LOD range (never shrink it), and that - // users can override this range by calling setMinMaxLevels(). - updateTextureLodRange(t, (int8_t)binfo.level); - } - CHECK_GL_ERROR(utils::slog.e) CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER) } @@ -1583,6 +1693,19 @@ void OpenGLDriver::createTimerQueryR(Handle tqh, int) { mContext.createTimerQuery(tq); } +void OpenGLDriver::createDescriptorSetLayoutR(Handle dslh, + DescriptorSetLayout&& info) { + DEBUG_MARKER() + construct(dslh, std::move(info)); +} + +void OpenGLDriver::createDescriptorSetR(Handle dsh, + Handle dslh) { + DEBUG_MARKER() + GLDescriptorSetLayout const* dsl = handle_cast(dslh); + construct(dsh, mContext, dslh, dsl); +} + // ------------------------------------------------------------------------------------------------ // Destroying driver objects // ------------------------------------------------------------------------------------------------ @@ -1660,35 +1783,41 @@ void OpenGLDriver::destroyProgram(Handle ph) { } } -void OpenGLDriver::destroySamplerGroup(Handle sbh) { - DEBUG_MARKER() - if (sbh) { - GLSamplerGroup* sb = handle_cast(sbh); - for (auto& binding : mSamplerBindings) { - if (binding == sb) { - binding = nullptr; - } - } - destruct(sbh, sb); - } -} - void OpenGLDriver::destroyTexture(Handle th) { DEBUG_MARKER() if (th) { auto& gl = mContext; GLTexture* t = handle_cast(th); + if (UTILS_LIKELY(!t->gl.imported)) { if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) { - gl.unbindTexture(t->gl.target, t->gl.id); - if (UTILS_UNLIKELY(t->hwStream)) { - detachStream(t); + // drop a reference + uint16_t count = 0; + if (UTILS_UNLIKELY(t->ref)) { + // the common case is that we don't have a ref handle + GLTextureRef* const ref = handle_cast(t->ref); + count = --(ref->count); + if (count == 0) { + destruct(t->ref, ref); + } } - if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) { - mPlatform.destroyExternalImage(t->externalTexture); + if (count == 0) { + // if this was the last reference, we destroy the refcount as well as + // the GL texture name itself. + gl.unbindTexture(t->gl.target, t->gl.id); + if (UTILS_UNLIKELY(t->hwStream)) { + detachStream(t); + } + if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) { + mPlatform.destroyExternalImage(t->externalTexture); + } else { + glDeleteTextures(1, &t->gl.id); + } } else { - glDeleteTextures(1, &t->gl.id); + // The Handle is always destroyed. For extra precaution we also + // check that the GLTexture has a trivial destructor. + static_assert(std::is_trivially_destructible_v); } } else { assert_invariant(t->gl.target == GL_RENDERBUFFER); @@ -1793,6 +1922,28 @@ void OpenGLDriver::destroyTimerQuery(Handle tqh) { } } +void OpenGLDriver::destroyDescriptorSetLayout(Handle dslh) { + DEBUG_MARKER() + if (dslh) { + GLDescriptorSetLayout* dsl = handle_cast(dslh); + destruct(dslh, dsl); + } +} + +void OpenGLDriver::destroyDescriptorSet(Handle dsh) { + DEBUG_MARKER() + if (dsh) { + // unbind the descriptor-set, to avoid use-after-free + for (auto& bound : mBoundDescriptorSets) { + if (bound.dsh == dsh) { + bound = {}; + } + } + GLDescriptorSet* ds = handle_cast(dsh); + destruct(dsh, ds); + } +} + // ------------------------------------------------------------------------------------------------ // Synchronous APIs // These are called on the application's thread @@ -2369,120 +2520,6 @@ void OpenGLDriver::resetBufferObject(Handle boh) { } } -void OpenGLDriver::updateSamplerGroup(Handle sbh, - BufferDescriptor&& data) { - DEBUG_MARKER() - - OpenGLContext const& context = getContext(); - -#if defined(GL_EXT_texture_filter_anisotropic) - const bool anisotropyWorkaround = - context.ext.EXT_texture_filter_anisotropic && - context.bugs.texture_filter_anisotropic_broken_on_sampler; -#endif - - GLSamplerGroup* const sb = handle_cast(sbh); - assert_invariant(sb->textureUnitEntries.size() == data.size / sizeof(SamplerDescriptor)); - - bool const es2 = context.isES2(); - - auto const* const pSamplers = (SamplerDescriptor const*)data.buffer; - for (size_t i = 0, c = sb->textureUnitEntries.size(); i < c; i++) { - GLuint samplerId = 0u; - Handle th = pSamplers[i].t; - if (UTILS_LIKELY(th)) { - GLTexture const* const t = handle_cast(th); - assert_invariant(t); - - if (UTILS_UNLIKELY(es2) -#if defined(GL_EXT_texture_filter_anisotropic) - || UTILS_UNLIKELY(anisotropyWorkaround) - #endif - ) { - // We must set texture parameters on the texture itself. - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); - } - - SamplerParams params = pSamplers[i].s; - if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) { - // From OES_EGL_image_external spec: - // "The default s and t wrap modes are CLAMP_TO_EDGE, and it is an INVALID_ENUM - // error to set the wrap mode to any other value." - params.wrapS = SamplerWrapMode::CLAMP_TO_EDGE; - params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE; - params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE; - } - // GLES3.x specification forbids depth textures to be filtered. - if (UTILS_UNLIKELY(isDepthFormat(t->format) - && params.compareMode == SamplerCompareMode::NONE - && params.filterMag != SamplerMagFilter::NEAREST - && params.filterMin != SamplerMinFilter::NEAREST - && params.filterMin != SamplerMinFilter::NEAREST_MIPMAP_NEAREST)) { - params.filterMag = SamplerMagFilter::NEAREST; - params.filterMin = SamplerMinFilter::NEAREST; -#ifndef NDEBUG - slog.w << "HwSamplerGroup specifies a filtered depth texture, which is not allowed." - << io::endl; -#endif - } -#if defined(GL_EXT_texture_filter_anisotropic) - if (UTILS_UNLIKELY(anisotropyWorkaround)) { - // Driver claims to support anisotropic filtering, but it fails when set on - // the sampler, we have to set it on the texture instead. - // The texture is already bound here. - GLfloat const anisotropy = float(1u << params.anisotropyLog2); - glTexParameterf(t->gl.target, GL_TEXTURE_MAX_ANISOTROPY_EXT, - std::min(context.gets.max_anisotropy, anisotropy)); - } -#endif -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (UTILS_LIKELY(!es2)) { - samplerId = mContext.getSampler(params); - } else -#endif - { - // in ES2 the sampler parameters need to be set on the texture itself - glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER, - (GLint)getTextureFilter(params.filterMin)); - glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER, - (GLint)getTextureFilter(params.filterMag)); - glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_S, - (GLint)getWrapMode(params.wrapS)); - glTexParameteri(t->gl.target, GL_TEXTURE_WRAP_T, - (GLint)getWrapMode(params.wrapT)); - } - } else { - // this happens if the program doesn't use all samplers of a sampler group, - // which is not an error. - } - - sb->textureUnitEntries[i] = { th, samplerId }; - } - scheduleDestroy(std::move(data)); -} - -void OpenGLDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { - DEBUG_MARKER() - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - auto& gl = mContext; - if (!gl.isES2()) { - GLTexture* t = handle_cast(th); - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); - gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); - - // Must fit within int8_t. - assert_invariant(minLevel <= 0x7f && maxLevel <= 0x7f); - - t->gl.baseLevel = (int8_t)minLevel; - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - - t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000 - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } -#endif -} - void OpenGLDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, @@ -2512,16 +2549,6 @@ void OpenGLDriver::generateMipmaps(Handle th) { bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); - t->gl.baseLevel = 0; - t->gl.maxLevel = static_cast(t->levels - 1); - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!gl.isES2()) { - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } -#endif - glGenerateMipmap(t->gl.target); CHECK_GL_ERROR(utils::slog.e) @@ -2631,21 +2658,6 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level, } } -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!gl.isES2()) { - // Update the base/max LOD, so we don't access undefined LOD. this allows the app to - // specify levels as they become available. - if (int8_t(level) < t->gl.baseLevel) { - t->gl.baseLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - } - if (int8_t(level) > t->gl.maxLevel) { - t->gl.maxLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } - } -#endif - scheduleDestroy(std::move(p)); CHECK_GL_ERROR(utils::slog.e) @@ -2732,21 +2744,6 @@ void OpenGLDriver::setCompressedTextureData(GLTexture* t, uint32_t level, } } -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!gl.isES2()) { - // Update the base/max LOD, so we don't access undefined LOD. this allows the app to - // specify levels as they become available. - if (int8_t(level) < t->gl.baseLevel) { - t->gl.baseLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel); - } - if (int8_t(level) > t->gl.maxLevel) { - t->gl.maxLevel = int8_t(level); - glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel); - } - } -#endif - scheduleDestroy(std::move(p)); CHECK_GL_ERROR(utils::slog.e) @@ -3141,64 +3138,6 @@ void OpenGLDriver::setScissor(Viewport const& scissor) noexcept { // Setting rendering state // ------------------------------------------------------------------------------------------------ -void OpenGLDriver::bindUniformBuffer(uint32_t index, Handle ubh) { - DEBUG_MARKER() - GLBufferObject* ub = handle_cast(ubh); - assert_invariant(ub->bindingType == BufferObjectBinding::UNIFORM); - bindBufferRange(BufferObjectBinding::UNIFORM, index, ubh, 0, ub->byteCount); -} - -void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index, - Handle ubh, uint32_t offset, uint32_t size) { - DEBUG_MARKER() - auto& gl = mContext; - - assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE || - bindingType == BufferObjectBinding::UNIFORM); - - GLBufferObject* ub = handle_cast(ubh); - - assert_invariant(offset + size <= ub->byteCount); - - if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { - gl.setEs2UniformBinding(index, - ub->gl.id, - static_cast(ub->gl.buffer) + offset, - ub->age); - } else { - GLenum const target = GLUtils::getBufferBindingType(bindingType); - - assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE || - ub->gl.binding == target); - - gl.bindBufferRange(target, GLuint(index), ub->gl.id, offset, size); - } - - CHECK_GL_ERROR(utils::slog.e) -} - -void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) { - DEBUG_MARKER() - auto& gl = mContext; - - if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { - gl.setEs2UniformBinding(index, 0, nullptr, 0); - return; - } - - GLenum const target = GLUtils::getBufferBindingType(bindingType); - gl.bindBufferRange(target, GLuint(index), 0, 0, 0); - CHECK_GL_ERROR(utils::slog.e) -} - -void OpenGLDriver::bindSamplers(uint32_t index, Handle sbh) { - DEBUG_MARKER() - assert_invariant(index < Program::SAMPLER_BINDING_COUNT); - GLSamplerGroup* sb = handle_cast(sbh); - mSamplerBindings[index] = sb; - CHECK_GL_ERROR(utils::slog.e) -} - void OpenGLDriver::insertEventMarker(char const* string) { #ifndef __EMSCRIPTEN__ #ifdef GL_EXT_debug_marker @@ -3564,6 +3503,26 @@ void OpenGLDriver::endFrame(UTILS_UNUSED uint32_t frameId) { insertEventMarker("endFrame"); } +void OpenGLDriver::updateDescriptorSetBuffer( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::BufferObjectHandle boh, + uint32_t offset, uint32_t size) { + GLDescriptorSet* ds = handle_cast(dsh); + GLBufferObject* bo = boh ? handle_cast(boh) : nullptr; + ds->update(mContext, binding, bo, offset, size); +} + +void OpenGLDriver::updateDescriptorSetTexture( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::TextureHandle th, + SamplerParams params) { + GLDescriptorSet* ds = handle_cast(dsh); + GLTexture* t = th ? handle_cast(th) : nullptr; + ds->update(mContext, binding, t, params); +} + void OpenGLDriver::flush(int) { DEBUG_MARKER() auto& gl = mContext; @@ -3808,15 +3767,6 @@ void OpenGLDriver::blit( gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER); gl.unbindFramebuffer(GL_READ_FRAMEBUFFER); glDeleteFramebuffers(2, fbo); - - if (any(d->usage & TextureUsage::SAMPLEABLE)) { - // In a sense, blitting to a texture level is similar to calling setTextureData on it; in - // both cases, we update the base/max LOD to give shaders access to levels as they become - // available. Note that this can only expand the LOD range (never shrink it), and that - // users can override this range by calling setMinMaxLevels(). - updateTextureLodRange(d, int8_t(dstLevel)); - } - #endif } @@ -3887,29 +3837,6 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers, #endif } -void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept { -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - auto& gl = mContext; - if (!gl.isES2()) { - if (texture && any(texture->usage & TextureUsage::SAMPLEABLE)) { - if (targetLevel < texture->gl.baseLevel || targetLevel > texture->gl.maxLevel) { - bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, texture); - gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); - if (targetLevel < texture->gl.baseLevel) { - texture->gl.baseLevel = targetLevel; - glTexParameteri(texture->gl.target, GL_TEXTURE_BASE_LEVEL, targetLevel); - } - if (targetLevel > texture->gl.maxLevel) { - texture->gl.maxLevel = targetLevel; - glTexParameteri(texture->gl.target, GL_TEXTURE_MAX_LEVEL, targetLevel); - } - } - CHECK_GL_ERROR(utils::slog.e) - } - } -#endif -} - void OpenGLDriver::bindPipeline(PipelineState const& state) { DEBUG_MARKER() auto& gl = mContext; @@ -3919,6 +3846,8 @@ void OpenGLDriver::bindPipeline(PipelineState const& state) { OpenGLProgram* const p = handle_cast(state.program); mValidProgram = useProgram(p); (*mCurrentPushConstants) = p->getPushConstants(); + mCurrentSetLayout = state.pipelineLayout.setLayout; + // TODO: we should validate that the pipeline layout matches the program's } void OpenGLDriver::bindRenderPrimitive(Handle rph) { @@ -3942,18 +3871,82 @@ void OpenGLDriver::bindRenderPrimitive(Handle rph) { mBoundRenderPrimitive = rp; } +void OpenGLDriver::bindDescriptorSet( + backend::DescriptorSetHandle dsh, + backend::descriptor_set_t set, + backend::DescriptorSetOffsetArray&& offsets) { + // handle_cast<> here also serves to validate the handle (it actually cannot return nullptr) + GLDescriptorSet const* const ds = handle_cast(dsh); + if (ds) { + assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT); + if (mBoundDescriptorSets[set].dsh != dsh) { + // if the descriptor itself changed, we mark this descriptor binding + // invalid -- it will be re-bound at the next draw. + mInvalidDescriptorSetBindings.set(set, true); + } else if (!offsets.empty()) { + // if we reset offsets, we mark the offsets invalid so these descriptors only can + // be re-bound at the next draw. + mInvalidDescriptorSetBindingOffsets.set(set, true); + } + + // `offsets` data's lifetime will end when this function returns. We have to make a copy. + // (the data is allocated inside the CommandStream) + mBoundDescriptorSets[set].dsh = dsh; + std::copy_n(offsets.data(), ds->getDynamicBufferCount(), + mBoundDescriptorSets[set].offsets.data()); + } +} + +void OpenGLDriver::updateDescriptors(utils::bitset8 invalidDescriptorSets) noexcept { + assert_invariant(mBoundProgram); + auto const offsetOnly = mInvalidDescriptorSetBindingOffsets & ~mInvalidDescriptorSetBindings; + invalidDescriptorSets.forEachSetBit([this, offsetOnly, + &boundDescriptorSets = mBoundDescriptorSets, + &context = mContext, + &boundProgram = *mBoundProgram](size_t set) { + assert_invariant(set < MAX_DESCRIPTOR_SET_COUNT); + auto const& entry = boundDescriptorSets[set]; + if (entry.dsh) { + GLDescriptorSet* const ds = handle_cast(entry.dsh); +#ifndef NDEBUG + if (UTILS_UNLIKELY(!offsetOnly[set])) { + // validate that this descriptor-set layout matches the layout set in the pipeline + // we don't need to do the check if only the offset is changing + ds->validate(mHandleAllocator, mCurrentSetLayout[set]); + } +#endif + ds->bind(context, mHandleAllocator, boundProgram, + set, entry.offsets.data(), offsetOnly[set]); + } + }); + mInvalidDescriptorSetBindings.clear(); + mInvalidDescriptorSetBindingOffsets.clear(); +} + void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { DEBUG_MARKER() - GLRenderPrimitive const* const rp = mBoundRenderPrimitive; - if (UTILS_UNLIKELY(!rp || !mValidProgram)) { + assert_invariant(!mContext.isES2()); + assert_invariant(mBoundRenderPrimitive); +#if FILAMENT_ENABLE_MATDBG + if (UTILS_UNLIKELY(!mValidProgram)) { return; } +#endif + assert_invariant(mBoundProgram); + assert_invariant(mValidProgram); + + // When the program changes, we might have to rebind all or some descriptors + auto const invalidDescriptorSets = + mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets; + if (UTILS_UNLIKELY(invalidDescriptorSets.any())) { + updateDescriptors(invalidDescriptorSets); + } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - assert_invariant(!mContext.isES2()); + GLRenderPrimitive const* const rp = mBoundRenderPrimitive; glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(), - reinterpret_cast(indexOffset * rp->gl.indicesSize), + reinterpret_cast(indexOffset << rp->gl.indicesShift), (GLsizei)instanceCount); #endif @@ -3964,19 +3957,30 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins #endif } +// This is the ES2 version of draw2(). void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { DEBUG_MARKER() - GLRenderPrimitive const* const rp = mBoundRenderPrimitive; - if (UTILS_UNLIKELY(!rp || !mValidProgram)) { + assert_invariant(mContext.isES2()); + assert_invariant(mBoundRenderPrimitive); +#if FILAMENT_ENABLE_MATDBG + if (UTILS_UNLIKELY(!mValidProgram)) { return; } +#endif + assert_invariant(mBoundProgram); + assert_invariant(mValidProgram); - assert_invariant(mContext.isES2()); - assert_invariant(instanceCount == 1); + // When the program changes, we might have to rebind all or some descriptors + auto const invalidDescriptorSets = + mInvalidDescriptorSetBindings | mInvalidDescriptorSetBindingOffsets; + if (UTILS_UNLIKELY(invalidDescriptorSets.any())) { + updateDescriptors(invalidDescriptorSets); + } + GLRenderPrimitive const* const rp = mBoundRenderPrimitive; + assert_invariant(instanceCount == 1); glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(), - reinterpret_cast(indexOffset * rp->gl.indicesSize)); - + reinterpret_cast(indexOffset << rp->gl.indicesShift)); #if FILAMENT_ENABLE_MATDBG CHECK_GL_ERROR_NON_FATAL(utils::slog.e) diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index 7cc63c6cda4..29ea32c776d 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -21,6 +21,8 @@ #include "OpenGLContext.h" #include "OpenGLTimerQuery.h" #include "GLBufferObject.h" +#include "GLDescriptorSet.h" +#include "GLDescriptorSetLayout.h" #include "GLTexture.h" #include "ShaderCompilerService.h" @@ -36,6 +38,7 @@ #include "private/backend/Driver.h" #include "private/backend/HandleAllocator.h" +#include #include #include #include @@ -52,6 +55,7 @@ #include #include #include +#include #include #include @@ -123,16 +127,6 @@ class OpenGLDriver final : public DriverBase { } gl; }; - struct GLSamplerGroup : public HwSamplerGroup { - using HwSamplerGroup::HwSamplerGroup; - struct Entry { - Handle th; - GLuint sampler = 0u; - }; - utils::FixedCapacityVector textureUnitEntries; - explicit GLSamplerGroup(size_t size) noexcept : textureUnitEntries(size) { } - }; - struct GLRenderPrimitive : public HwRenderPrimitive { using HwRenderPrimitive::HwRenderPrimitive; OpenGLContext::RenderPrimitive gl; @@ -145,6 +139,10 @@ class OpenGLDriver final : public DriverBase { using GLTimerQuery = filament::backend::GLTimerQuery; + using GLDescriptorSetLayout = filament::backend::GLDescriptorSetLayout; + + using GLDescriptorSet = filament::backend::GLDescriptorSet; + struct GLStream : public HwStream { using HwStream::HwStream; struct Info { @@ -317,10 +315,6 @@ class OpenGLDriver final : public DriverBase { void resolvePass(ResolveAction action, GLRenderTarget const* rt, TargetBufferFlags discardFlags) noexcept; - const std::array& getSamplerBindings() const { - return mSamplerBindings; - } - using AttachmentArray = std::array; static GLsizei getAttachments(AttachmentArray& attachments, TargetBufferFlags buffers, bool isDefaultFramebuffer) noexcept; @@ -333,8 +327,16 @@ class OpenGLDriver final : public DriverBase { GLboolean mRenderPassStencilWrite{}; GLRenderPrimitive const* mBoundRenderPrimitive = nullptr; + OpenGLProgram* mBoundProgram = nullptr; bool mValidProgram = false; + utils::bitset8 mInvalidDescriptorSetBindings; + utils::bitset8 mInvalidDescriptorSetBindingOffsets; + void updateDescriptors(utils::bitset8 invalidDescriptorSets) noexcept; + struct { + backend::DescriptorSetHandle dsh; + std::array offsets; + } mBoundDescriptorSets[MAX_DESCRIPTOR_SET_COUNT]; void clearWithRasterPipe(TargetBufferFlags clearFlags, math::float4 const& linearColor, GLfloat depth, GLint stencil) noexcept; @@ -346,9 +348,6 @@ class OpenGLDriver final : public DriverBase { // ES2 only. Uniform buffer emulation binding points GLuint mLastAssignedEmulatedUboId = 0; - // sampler buffer binding points (nullptr if not used) - std::array mSamplerBindings = {}; // 4 pointers - // this must be accessed from the driver thread only std::vector mTexturesWithStreamsAttached; @@ -359,8 +358,6 @@ class OpenGLDriver final : public DriverBase { void detachStream(GLTexture* t) noexcept; void replaceStream(GLTexture* t, GLStream* stream) noexcept; - void updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept; - #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 // tasks executed on the main thread after the fence signaled void whenGpuCommandsComplete(const std::function& fn) noexcept; @@ -384,6 +381,7 @@ class OpenGLDriver final : public DriverBase { bool mRec709OutputColorspace = false; PushConstantBundle* mCurrentPushConstants = nullptr; + PipelineLayout::SetLayout mCurrentSetLayout; }; // ------------------------------------------------------------------------------------------------ diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index 7a85e3c63cd..46f515c0a27 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -17,6 +17,7 @@ #include "OpenGLProgram.h" #include "GLUtils.h" +#include "GLTexture.h" #include "OpenGLDriver.h" #include "ShaderCompilerService.h" @@ -24,6 +25,7 @@ #include #include +#include #include #include #include @@ -32,9 +34,10 @@ #include #include +#include +#include #include #include -#include #include #include @@ -46,9 +49,8 @@ using namespace utils; using namespace backend; struct OpenGLProgram::LazyInitializationData { - Program::UniformBlockInfo uniformBlockInfo; - Program::SamplerGroupInfo samplerGroupInfo; - std::array bindingUniformInfo; + Program::DescriptorSetInfo descriptorBindings; + Program::BindingUniformsInfo bindingUniformInfo; utils::FixedCapacityVector vertexPushConstants; utils::FixedCapacityVector fragmentPushConstants; }; @@ -57,16 +59,14 @@ struct OpenGLProgram::LazyInitializationData { OpenGLProgram::OpenGLProgram() noexcept = default; OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept - : HwProgram(std::move(program.getName())) { + : HwProgram(std::move(program.getName())), mRec709Location(-1) { auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData(); - lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo()); if (UTILS_UNLIKELY(gld.getContext().isES2())) { lazyInitializationData->bindingUniformInfo = std::move(program.getBindingUniformInfo()); - } else { - lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings()); } lazyInitializationData->vertexPushConstants = std::move(program.getPushConstants(ShaderStage::VERTEX)); lazyInitializationData->fragmentPushConstants = std::move(program.getPushConstants(ShaderStage::FRAGMENT)); + lazyInitializationData->descriptorBindings = std::move(program.getDescriptorBindings()); ShaderCompilerService& compiler = gld.getShaderCompilerService(); mToken = compiler.createProgram(name, std::move(program)); @@ -124,36 +124,86 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra SYSTRACE_CALL(); + // from the pipeline layout we compute a mapping from {set, binding} to {binding} + // for both buffers and textures + + for (auto&& entry: lazyInitializationData.descriptorBindings) { + std::sort(entry.begin(), entry.end(), + [](Program::Descriptor const& lhs, Program::Descriptor const& rhs) { + return lhs.binding < rhs.binding; + }); + } + + GLuint tmu = 0; + GLuint binding = 0; + + // needed for samplers + context.useProgram(program); + + UTILS_NOUNROLL + for (backend::descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) { + for (Program::Descriptor const& entry: lazyInitializationData.descriptorBindings[set]) { + switch (entry.type) { + case DescriptorType::UNIFORM_BUFFER: + case DescriptorType::SHADER_STORAGE_BUFFER: { + if (!entry.name.empty()) { #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (!context.isES2()) { - // Note: This is only needed, because the layout(binding=) syntax is not permitted in glsl - // (ES3.0 and GL4.1). The backend needs a way to associate a uniform block to a binding point. - UTILS_NOUNROLL - for (GLuint binding = 0, n = lazyInitializationData.uniformBlockInfo.size(); - binding < n; binding++) { - auto const& name = lazyInitializationData.uniformBlockInfo[binding]; - if (!name.empty()) { - GLuint const index = glGetUniformBlockIndex(program, name.c_str()); - if (index != GL_INVALID_INDEX) { - glUniformBlockBinding(program, index, binding); + if (UTILS_LIKELY(!context.isES2())) { + GLuint const index = glGetUniformBlockIndex(program, + entry.name.c_str()); + if (index != GL_INVALID_INDEX) { + // this can fail if the program doesn't use this descriptor + glUniformBlockBinding(program, index, binding); + mBindingMap.insert(set, entry.binding, + { binding, entry.type }); + ++binding; + } + } else +#endif + { + auto pos = std::find_if(lazyInitializationData.bindingUniformInfo.begin(), + lazyInitializationData.bindingUniformInfo.end(), + [&name = entry.name](const auto& item) { + return std::get<1>(item) == name; + }); + if (pos != lazyInitializationData.bindingUniformInfo.end()) { + binding = std::get<0>(*pos); + mBindingMap.insert(set, entry.binding, { binding, entry.type }); + } + } + } + break; + } + case DescriptorType::SAMPLER: { + if (!entry.name.empty()) { + GLint const loc = glGetUniformLocation(program, entry.name.c_str()); + if (loc >= 0) { + // this can fail if the program doesn't use this descriptor + mBindingMap.insert(set, entry.binding, { tmu, entry.type }); + glUniform1i(loc, GLint(tmu)); + ++tmu; + } + } + break; } - CHECK_GL_ERROR(utils::slog.e) + case DescriptorType::INPUT_ATTACHMENT: + break; } + CHECK_GL_ERROR(utils::slog.e) } - } else -#endif - { + } + + if (context.isES2()) { // ES2 initialization of (fake) UBOs UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT]; UTILS_NOUNROLL - for (GLuint binding = 0, n = Program::UNIFORM_BINDING_COUNT; binding < n; binding++) { - Program::UniformInfo& uniforms = lazyInitializationData.bindingUniformInfo[binding]; - uniformsRecords[binding].locations.reserve(uniforms.size()); - uniformsRecords[binding].locations.resize(uniforms.size()); + for (auto&& [index, name, uniforms] : lazyInitializationData.bindingUniformInfo) { + uniformsRecords[index].locations.reserve(uniforms.size()); + uniformsRecords[index].locations.resize(uniforms.size()); for (size_t j = 0, c = uniforms.size(); j < c; j++) { GLint const loc = glGetUniformLocation(program, uniforms[j].name.c_str()); - uniformsRecords[binding].locations[j] = loc; - if (UTILS_UNLIKELY(binding == 0)) { + uniformsRecords[index].locations[j] = loc; + if (UTILS_UNLIKELY(index == 0)) { // This is a bit of a gross hack here, we stash the location of // "frameUniforms.rec709", which obviously the backend shouldn't know about, // which is used for emulating the "rec709" colorspace in the shader. @@ -165,51 +215,11 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra } } } - uniformsRecords[binding].uniforms = std::move(uniforms); + uniformsRecords[index].uniforms = std::move(uniforms); } mUniformsRecords = uniformsRecords; } - uint8_t usedBindingCount = 0; - uint8_t tmu = 0; - - UTILS_NOUNROLL - for (size_t i = 0, c = lazyInitializationData.samplerGroupInfo.size(); i < c; i++) { - auto const& samplers = lazyInitializationData.samplerGroupInfo[i].samplers; - if (samplers.empty()) { - // this binding point doesn't have any samplers, skip it. - continue; - } - - // keep this in the loop, so we skip it in the rare case a program doesn't have - // sampler. The context cache will prevent repeated calls to GL. - context.useProgram(program); - - bool atLeastOneSamplerUsed = false; - UTILS_NOUNROLL - for (const Program::Sampler& sampler: samplers) { - // find its location and associate a TMU to it - GLint const loc = glGetUniformLocation(program, sampler.name.c_str()); - if (loc >= 0) { - // this can fail if the program doesn't use this sampler - glUniform1i(loc, tmu); - atLeastOneSamplerUsed = true; - } - tmu++; - } - - // if this program doesn't use any sampler from this HwSamplerGroup, just cancel the - // whole group. - if (atLeastOneSamplerUsed) { - // Cache the sampler uniform locations for each interface block - mUsedSamplerBindingPoints[usedBindingCount] = i; - usedBindingCount++; - } else { - tmu -= samplers.size(); - } - } - mUsedBindingsCount = usedBindingCount; - auto& vertexConstants = lazyInitializationData.vertexPushConstants; auto& fragmentConstants = lazyInitializationData.fragmentPushConstants; @@ -226,41 +236,8 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra } } -void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept { - using GLTexture = OpenGLDriver::GLTexture; - -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - bool const es2 = gld->getContext().isES2(); -#endif - - // cache a few member variable locally, outside the loop - auto const& UTILS_RESTRICT samplerBindings = gld->getSamplerBindings(); - auto const& UTILS_RESTRICT usedBindingPoints = mUsedSamplerBindingPoints; - - for (uint8_t i = 0, tmu = 0, n = mUsedBindingsCount; i < n; i++) { - size_t const binding = usedBindingPoints[i]; - assert_invariant(binding < Program::SAMPLER_BINDING_COUNT); - auto const * const sb = samplerBindings[binding]; - assert_invariant(sb); - if (!sb) continue; // should never happen, this would be a user error. - for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here - Handle th = sb->textureUnitEntries[j].th; - if (th) { // program may not use all samplers of sampler group - GLTexture const* const t = gld->handle_cast(th); - gld->bindTexture(tmu, t); -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - if (UTILS_LIKELY(!es2)) { - GLuint const s = sb->textureUnitEntries[j].sampler; - gld->bindSampler(tmu, s); - } -#endif - } - } - } - CHECK_GL_ERROR(utils::slog.e) -} - -void OpenGLProgram::updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept { +void OpenGLProgram::updateUniforms( + uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept { assert_invariant(mUniformsRecords); assert_invariant(buffer); diff --git a/filament/backend/src/opengl/OpenGLProgram.h b/filament/backend/src/opengl/OpenGLProgram.h index 2cb43131ea1..a5d35122e9b 100644 --- a/filament/backend/src/opengl/OpenGLProgram.h +++ b/filament/backend/src/opengl/OpenGLProgram.h @@ -19,17 +19,20 @@ #include "DriverBase.h" +#include "BindingMap.h" #include "OpenGLContext.h" #include "ShaderCompilerService.h" #include + +#include #include +#include #include #include #include -#include #include #include @@ -69,32 +72,25 @@ class OpenGLProgram : public HwProgram { } context.useProgram(gl.program); - if (UTILS_UNLIKELY(mUsedBindingsCount)) { - // We rely on GL state tracking to avoid unnecessary glBindTexture / glBindSampler - // calls. + return true; + } - // we need to do this if: - // - the content of mSamplerBindings has changed - // - the content of any bound sampler buffer has changed - // ... since last time we used this program + GLuint getBufferBinding(descriptor_set_t set, descriptor_binding_t binding) const noexcept { + return mBindingMap.get(set, binding); + } - // Turns out the former might be relatively cheap to check, the latter requires - // a bit less. Compared to what updateSamplers() actually does, which is - // pretty little, I'm not sure if we'll get ahead. + GLuint getTextureUnit(descriptor_set_t set, descriptor_binding_t binding) const noexcept { + return mBindingMap.get(set, binding); + } - updateSamplers(gld); - } - return true; + utils::bitset64 getActiveDescriptors(descriptor_set_t set) const noexcept { + return mBindingMap.getActiveDescriptors(set); } // For ES2 only - void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept; + void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept; void setRec709ColorSpace(bool rec709) const noexcept; - struct { - GLuint program = 0; - } gl; // 4 bytes - PushConstantBundle getPushConstants() { auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset; return { @@ -112,22 +108,15 @@ class OpenGLProgram : public HwProgram { void initializeProgramState(OpenGLContext& context, GLuint program, LazyInitializationData& lazyInitializationData) noexcept; - void updateSamplers(OpenGLDriver* gld) const noexcept; - - // number of bindings actually used by this program - std::array mUsedSamplerBindingPoints; // 4 bytes + BindingMap mBindingMap; // 8 bytes + out-of-line 256 bytes ShaderCompilerService::program_token_t mToken{}; // 16 bytes - uint8_t mUsedBindingsCount = 0u; // 1 byte - UTILS_UNUSED uint8_t padding[2] = {}; // 2 byte - - // Push constant array offset for fragment stage constants. - uint8_t mPushConstantFragmentStageOffset = 0u; // 1 byte + // Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the + // size of the container to 9 bytes if there is a need in the future. + utils::FixedCapacityVector> mPushConstants;// 16 bytes // only needed for ES2 - GLint mRec709Location = -1; // 4 bytes - using LocationInfo = utils::FixedCapacityVector; struct UniformsRecord { Program::UniformInfo uniforms; @@ -135,15 +124,20 @@ class OpenGLProgram : public HwProgram { mutable GLuint id = 0; mutable uint16_t age = std::numeric_limits::max(); }; - UniformsRecord const* mUniformsRecords = nullptr; // 8 bytes + UniformsRecord const* mUniformsRecords = nullptr; + GLint mRec709Location : 24; // 4 bytes - // Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the - // size of the container to 9 bytes if there is a need in the future. - utils::FixedCapacityVector> mPushConstants;// 16 bytes + // Push constant array offset for fragment stage constants. + GLint mPushConstantFragmentStageOffset : 8; // 1 byte + +public: + struct { + GLuint program = 0; + } gl; // 4 bytes }; -// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket. -static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes +// if OpenGLProgram is larger than 96 bytes, it'll fall in a larger Handle bucket. +static_assert(sizeof(OpenGLProgram) <= 96); // currently 96 bytes } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index 0decae3769e..7100b026352 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -15,6 +15,7 @@ */ #include "VulkanBlitter.h" +#include "VulkanCommands.h" #include "VulkanContext.h" #include "VulkanFboCache.h" #include "VulkanHandles.h" @@ -33,9 +34,10 @@ namespace filament::backend { namespace { -inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter, +inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, VkFilter filter, VulkanAttachment src, VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) { + VkCommandBuffer const cmdbuf = commands->buffer(); if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) { FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level << " layout=" << src.getLayout() @@ -49,8 +51,8 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VulkanLayout oldSrcLayout = src.getLayout(); VulkanLayout oldDstLayout = dst.getLayout(); - src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC); - dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST); + src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC); + dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST); const VkImageBlit blitRegions[1] = {{ .srcSubresource = { aspect, src.level, src.layer, 1 }, @@ -58,7 +60,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, .dstSubresource = { aspect, dst.level, dst.layer, 1 }, .dstOffsets = { dstRect[0], dstRect[1] }, }}; - vkCmdBlitImage(cmdbuffer, + vkCmdBlitImage(cmdbuf, src.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC), dst.getImage(), imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, blitRegions, filter); @@ -69,12 +71,13 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, if (oldDstLayout == VulkanLayout::UNDEFINED) { oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage); } - src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout); - dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout); + src.texture->transitionLayout(commands, srcRange, oldSrcLayout); + dst.texture->transitionLayout(commands, dstRange, oldDstLayout); } -inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, +inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, VulkanAttachment src, VulkanAttachment dst) { + VkCommandBuffer const cmdbuffer = commands->buffer(); if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) { FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level << " layout=" << src.getLayout() @@ -88,8 +91,8 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe VulkanLayout oldSrcLayout = src.getLayout(); VulkanLayout oldDstLayout = dst.getLayout(); - src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC); - dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST); + src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC); + dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST); assert_invariant( aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported."); @@ -111,8 +114,8 @@ inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspe if (oldDstLayout == VulkanLayout::UNDEFINED) { oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage); } - src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout); - dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout); + src.texture->transitionLayout(commands, srcRange, oldSrcLayout); + dst.texture->transitionLayout(commands, dstRange, oldDstLayout); } struct BlitterUniforms { @@ -149,10 +152,9 @@ void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) { #endif VulkanCommandBuffer& commands = mCommands->get(); - VkCommandBuffer const cmdbuffer = commands.buffer(); commands.acquire(src.texture); commands.acquire(dst.texture); - resolveFast(cmdbuffer, aspect, src, dst); + resolveFast(&commands, aspect, src, dst); } void VulkanBlitter::blit(VkFilter filter, @@ -175,10 +177,9 @@ void VulkanBlitter::blit(VkFilter filter, // src and dst should have the same aspect here VkImageAspectFlags const aspect = src.texture->getImageAspect(); VulkanCommandBuffer& commands = mCommands->get(); - VkCommandBuffer const cmdbuffer = commands.buffer(); commands.acquire(src.texture); commands.acquire(dst.texture); - blitFast(cmdbuffer, aspect, filter, src, dst, srcRectPair, dstRectPair); + blitFast(&commands, aspect, filter, src, dst, srcRectPair, dstRectPair); } void VulkanBlitter::terminate() noexcept { diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index 473ca8f1c16..ee8ffe24971 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -297,11 +297,11 @@ bool VulkanCommands::flush() { #endif auto& cmdfence = currentbuf->fence; - std::unique_lock lock(cmdfence->mutex); - cmdfence->status.store(VK_NOT_READY); - UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->fence); - cmdfence->condition.notify_all(); - lock.unlock(); + UTILS_UNUSED_IN_RELEASE VkResult result = VK_SUCCESS; + { + auto scope = cmdfence->setValue(VK_NOT_READY); + result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->getFence()); + } #if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER) if (result != VK_SUCCESS) { @@ -340,7 +340,7 @@ void VulkanCommands::wait() { auto wrapper = mStorage[i].get(); if (wrapper->buffer() != VK_NULL_HANDLE && mCurrentCommandBufferIndex != static_cast(i)) { - fences[count++] = wrapper->fence->fence; + fences[count++] = wrapper->fence->getFence(); } } if (count > 0) { @@ -361,12 +361,13 @@ void VulkanCommands::gc() { if (wrapper->buffer() == VK_NULL_HANDLE) { continue; } - VkResult const result = vkGetFenceStatus(mDevice, wrapper->fence->fence); + auto const vkfence = wrapper->fence->getFence(); + VkResult const result = vkGetFenceStatus(mDevice, vkfence); if (result != VK_SUCCESS) { continue; } - fences[count++] = wrapper->fence->fence; - wrapper->fence->status.store(VK_SUCCESS); + fences[count++] = vkfence; + wrapper->fence->setValue(VK_SUCCESS); wrapper->reset(); mAvailableBufferCount++; } @@ -383,9 +384,9 @@ void VulkanCommands::updateFences() { if (wrapper->buffer() != VK_NULL_HANDLE) { VulkanCmdFence* fence = wrapper->fence.get(); if (fence) { - VkResult status = vkGetFenceStatus(mDevice, fence->fence); + VkResult status = vkGetFenceStatus(mDevice, fence->getFence()); // This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST. - fence->status.store(status, std::memory_order_relaxed); + fence->setValue(status); } } } diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index e3c7a92f190..a946fc4314a 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -61,8 +61,40 @@ class VulkanGroupMarkers { // Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences. struct VulkanCmdFence { + struct SetValueScope { + public: + ~SetValueScope() { + mHolder->mutex.unlock(); + mHolder->condition.notify_all(); + } + + private: + SetValueScope(VulkanCmdFence* fenceHolder, VkResult result) : + mHolder(fenceHolder) { + mHolder->mutex.lock(); + mHolder->status.store(result); + } + VulkanCmdFence* mHolder; + friend struct VulkanCmdFence; + }; + VulkanCmdFence(VkFence ifence); ~VulkanCmdFence() = default; + + SetValueScope setValue(VkResult value) { + return {this, value}; + } + + VkFence& getFence() { + return fence; + } + + VkResult getStatus() { + std::unique_lock lock(mutex); + return status.load(std::memory_order_acquire); + } + +private: VkFence fence; utils::Condition condition; utils::Mutex mutex; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 27980bb329c..97c6102e74d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -90,9 +90,9 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, - VulkanStagePool& stagePool) { + VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) { VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator, - commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1, + commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT, stagePool, true /* heap allocated */); uint32_t black = 0; @@ -149,6 +149,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla } #endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS) + }// anonymous namespace #if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) @@ -256,15 +257,11 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mTimestamps = std::make_unique(mPlatform->getDevice()); mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), - mContext, mAllocator, &mCommands, mStagePool); + mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool); mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands); mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture, mEmptyBufferObject); - - mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts, VulkanProgram* program) { - return mPipelineLayoutCache.getLayout(layouts, program); - }; } VulkanDriver::~VulkanDriver() noexcept = default; @@ -387,7 +384,6 @@ void VulkanDriver::collectGarbage() { mStagePool.gc(); mFramebufferCache.gc(); mPipelineCache.gc(); - mDescriptorSetManager.gc(); #if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK) mResourceAllocator.print(); @@ -422,6 +418,36 @@ void VulkanDriver::endFrame(uint32_t frameId) { FVK_SYSTRACE_END(); } +void VulkanDriver::updateDescriptorSetBuffer( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::BufferObjectHandle boh, + uint32_t offset, + uint32_t size) { + VulkanDescriptorSet* set = mResourceAllocator.handle_cast(dsh); + VulkanBufferObject* obj = mResourceAllocator.handle_cast(boh); + mDescriptorSetManager.updateBuffer(set, binding, obj, offset, size); +} + +void VulkanDriver::updateDescriptorSetTexture( + backend::DescriptorSetHandle dsh, + backend::descriptor_binding_t binding, + backend::TextureHandle th, + SamplerParams params) { + VulkanDescriptorSet* set = mResourceAllocator.handle_cast(dsh); + VulkanTexture* texture = mResourceAllocator.handle_cast(th); + + // We need to make sure the initial layout transition has been completed before we can write + // the sampler descriptor. We flush and wait until the transition has been completed. + if (!texture->transitionReady()) { + mCommands.flush(); + mCommands.wait(); + } + + VkSampler const vksampler = mSamplerCache.getSampler(params); + mDescriptorSetManager.updateSampler(set, binding, texture, vksampler); +} + void VulkanDriver::flush(int) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("flush"); @@ -440,12 +466,6 @@ void VulkanDriver::finish(int dummy) { FVK_SYSTRACE_END(); } -void VulkanDriver::createSamplerGroupR(Handle sbh, uint32_t count, - utils::FixedSizeString<32> debugName) { - auto sg = mResourceAllocator.construct(sbh, count); - mResourceManager.acquire(sg); -} - void VulkanDriver::createRenderPrimitiveR(Handle rph, Handle vbh, Handle ibh, PrimitiveType pt) { @@ -528,23 +548,56 @@ void VulkanDriver::createTextureR(Handle th, SamplerType target, uint TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) { auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels, + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, + target, levels, format, samples, w, h, depth, usage, mStagePool); mResourceManager.acquire(vktexture); } -void VulkanDriver::createTextureSwizzledR(Handle th, SamplerType target, uint8_t levels, - TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, - TextureUsage usage, - TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) { - TextureSwizzle swizzleArray[] = {r, g, b, a}; - const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray); +//void VulkanDriver::createTextureSwizzledR(Handle th, SamplerType target, uint8_t levels, +// TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, +// TextureUsage usage, +// TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) { +// TextureSwizzle swizzleArray[] = {r, g, b, a}; +// const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray); +// auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), +// mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, +// target, levels, format, samples, w, h, depth, usage, mStagePool, +// false /*heap allocated */, swizzleMap); +// mResourceManager.acquire(vktexture); +//} + +void VulkanDriver::createTextureViewR(Handle th, Handle srch, + uint8_t baseLevel, uint8_t levelCount) { + VulkanTexture const* src = mResourceAllocator.handle_cast(srch); + auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, + src, baseLevel, levelCount); + mResourceManager.acquire(vktexture); +} + +void VulkanDriver::createTextureViewSwizzleR(Handle th, Handle srch, + backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, + backend::TextureSwizzle a) { + TextureSwizzle const swizzleArray[] = {r, g, b, a}; + VkComponentMapping const swizzle = getSwizzleMap(swizzleArray); + + VulkanTexture const* src = mResourceAllocator.handle_cast(srch); auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels, - format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap); + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, &mResourceAllocator, + src, swizzle); mResourceManager.acquire(vktexture); } +void VulkanDriver::createTextureExternalImageR(Handle th, backend::TextureFormat format, + uint32_t width, uint32_t height, backend::TextureUsage usage, void* image) { +} + +void VulkanDriver::createTextureExternalImagePlaneR(Handle th, + backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, + void* image, uint32_t plane) { +} + void VulkanDriver::importTextureR(Handle th, intptr_t id, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, @@ -571,7 +624,6 @@ void VulkanDriver::destroyProgram(Handle ph) { return; } auto vkprogram = mResourceAllocator.handle_cast(ph); - mDescriptorSetManager.clearProgram(vkprogram); mResourceManager.release(vkprogram); } @@ -638,9 +690,10 @@ void VulkanDriver::createRenderTargetR(Handle rth, assert_invariant(tmin == tmax); assert_invariant(tmin.x >= width && tmin.y >= height); - auto renderTarget = mResourceAllocator.construct(rth, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height, - samples, colorTargets, depthStencil, mStagePool, layerCount); + auto renderTarget = mResourceAllocator.construct(rth, + mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator, + &mCommands, &mResourceAllocator, width, height, samples, colorTargets, depthStencil, + mStagePool, layerCount); mResourceManager.acquire(renderTarget); } @@ -668,7 +721,7 @@ void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE); } auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, - mAllocator, &mCommands, mStagePool, nativeWindow, flags); + mAllocator, &mCommands, &mResourceAllocator, mStagePool, nativeWindow, flags); mResourceManager.acquire(swapChain); } @@ -681,7 +734,8 @@ void VulkanDriver::createSwapChainHeadlessR(Handle sch, uint32_t wi } assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions."); auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, - mAllocator, &mCommands, mStagePool, nullptr, flags, VkExtent2D{width, height}); + mAllocator, &mCommands, &mResourceAllocator, mStagePool, + nullptr, flags, VkExtent2D{width, height}); mResourceManager.acquire(swapChain); } @@ -689,6 +743,25 @@ void VulkanDriver::createTimerQueryR(Handle tqh, int) { // nothing to do, timer query was constructed in createTimerQueryS } +void VulkanDriver::createDescriptorSetLayoutR(Handle dslh, + backend::DescriptorSetLayout&& info) { + VulkanDescriptorSetLayout* layout = mResourceAllocator.construct( + dslh, info); + + // This will create a VkDescriptorSetLayout (which is cached) for this object. + mDescriptorSetManager.initVkLayout(layout); + mResourceManager.acquire(layout); +} + +void VulkanDriver::createDescriptorSetR(Handle dsh, + Handle dslh) { + auto layout = mResourceAllocator.handle_cast(dslh); + mDescriptorSetManager.createSet(dsh, layout); + + auto set = mResourceAllocator.handle_cast(dsh); + mResourceManager.acquire(set); +} + Handle VulkanDriver::createVertexBufferInfoS() noexcept { return mResourceAllocator.allocHandle(); } @@ -709,16 +782,24 @@ Handle VulkanDriver::createTextureS() noexcept { return mResourceAllocator.allocHandle(); } -Handle VulkanDriver::createTextureSwizzledS() noexcept { +Handle VulkanDriver::createTextureViewS() noexcept { return mResourceAllocator.allocHandle(); } -Handle VulkanDriver::importTextureS() noexcept { +Handle VulkanDriver::createTextureViewSwizzleS() noexcept { return mResourceAllocator.allocHandle(); } -Handle VulkanDriver::createSamplerGroupS() noexcept { - return mResourceAllocator.allocHandle(); +Handle VulkanDriver::createTextureExternalImageS() noexcept { + return mResourceAllocator.allocHandle(); +} + +Handle VulkanDriver::createTextureExternalImagePlaneS() noexcept { + return mResourceAllocator.allocHandle(); +} + +Handle VulkanDriver::importTextureS() noexcept { + return mResourceAllocator.allocHandle(); } Handle VulkanDriver::createRenderPrimitiveS() noexcept { @@ -759,21 +840,12 @@ Handle VulkanDriver::createTimerQueryS() noexcept { return tqh; } -void VulkanDriver::destroySamplerGroup(Handle sbh) { - if (!sbh) { - return; - } - // Unlike most of the other "Hw" handles, the sampler buffer is an abstract concept and does - // not map to any Vulkan objects. To handle destruction, the only thing we need to do is - // ensure that the next draw call doesn't try to access a zombie sampler buffer. Therefore, - // simply replace all weak references with null. - auto* hwsb = mResourceAllocator.handle_cast(sbh); - for (auto& binding : mSamplerBindings) { - if (binding == hwsb) { - binding = nullptr; - } - } - mResourceManager.release(hwsb); +Handle VulkanDriver::createDescriptorSetLayoutS() noexcept { + return mResourceAllocator.allocHandle(); +} + +Handle VulkanDriver::createDescriptorSetS() noexcept { + return mResourceAllocator.allocHandle(); } void VulkanDriver::destroySwapChain(Handle sch) { @@ -798,6 +870,17 @@ void VulkanDriver::destroyTimerQuery(Handle tqh) { mThreadSafeResourceManager.release(vtq); } +void VulkanDriver::destroyDescriptorSetLayout(Handle dslh) { + VulkanDescriptorSetLayout* layout = mResourceAllocator.handle_cast(dslh); + mResourceManager.release(layout); +} + +void VulkanDriver::destroyDescriptorSet(Handle dsh) { + mDescriptorSetManager.destroySet(dsh); + VulkanDescriptorSet* set = mResourceAllocator.handle_cast(dsh); + mResourceManager.release(set); +} + Handle VulkanDriver::createStreamNative(void* nativeStream) { return {}; } @@ -835,8 +918,7 @@ FenceStatus VulkanDriver::getFenceStatus(Handle fh) { // Internally we use the VK_INCOMPLETE status to mean "not yet submitted". // When this fence gets submitted, its status changes to VK_NOT_READY. - std::unique_lock lock(cmdfence->mutex); - if (cmdfence->status.load() == VK_SUCCESS) { + if (cmdfence->getStatus() == VK_SUCCESS) { return FenceStatus::CONDITION_SATISFIED; } @@ -887,7 +969,9 @@ bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) { } bool VulkanDriver::isFrameBufferFetchSupported() { - return true; + // TODO: we must fix this before landing descriptor set change. Otherwise, the scuba tests will fail. + //return true; + return false; } bool VulkanDriver::isFrameBufferFetchMultiSampleSupported() { @@ -1075,10 +1159,6 @@ void VulkanDriver::resetBufferObject(Handle boh) { // This is only useful if updateBufferObjectUnsynchronized() is implemented unsynchronizedly. } -void VulkanDriver::setMinMaxLevels(Handle th, uint32_t minLevel, uint32_t maxLevel) { - mResourceAllocator.handle_cast(th)->setPrimaryRange(minLevel, maxLevel); -} - void VulkanDriver::update3DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth, PixelBufferDescriptor&& data) { @@ -1164,23 +1244,6 @@ void VulkanDriver::generateMipmaps(Handle th) { srcw = dstw; srch = dsth; } while ((srcw > 1 || srch > 1) && level < t->levels); - t->setPrimaryRange(0, t->levels - 1); -} - -void VulkanDriver::updateSamplerGroup(Handle sbh, - BufferDescriptor&& data) { - auto* sb = mResourceAllocator.handle_cast(sbh); - - // FIXME: we shouldn't be using SamplerGroup here, instead the backend should create - // a descriptor or any internal data-structure that represents the textures/samplers. - // It's preferable to do as much work as possible here. - // Here, we emulate the older backend API by re-creating a SamplerGroup from the - // passed data. - SamplerGroup samplerGroup(data.size / sizeof(SamplerDescriptor)); - memcpy(samplerGroup.data(), data.buffer, data.size); - *sb->sb = std::move(samplerGroup); - - scheduleDestroy(std::move(data)); } void VulkanDriver::compilePrograms(CompilerPriorityQueue priority, @@ -1195,7 +1258,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP FVK_SYSTRACE_START("beginRenderPass"); VulkanRenderTarget* const rt = mResourceAllocator.handle_cast(rth); - const VkExtent2D extent = rt->getExtent(); + VkExtent2D const extent = rt->getExtent(); assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0); // Filament has the expectation that the contents of the swap chain are not preserved on the @@ -1231,35 +1294,6 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP VkRect2D const scissor{ .offset = { 0, 0 }, .extent = extent }; vkCmdSetScissor(cmdbuffer, 0, 1, &scissor); - UTILS_NOUNROLL - for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT; - samplerGroupIdx++) { - VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx]; - if (!vksb) { - continue; - } - SamplerGroup* sb = vksb->sb.get(); - for (size_t i = 0; i < sb->getSize(); i++) { - SamplerDescriptor const* boundSampler = sb->data() + i; - if (UTILS_LIKELY(boundSampler->t)) { - VulkanTexture* texture - = mResourceAllocator.handle_cast(boundSampler->t); - if (!any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)) { - continue; - } - if (texture->getPrimaryImageLayout() == VulkanLayout::DEPTH_SAMPLER) { - continue; - } - commands.acquire(texture); - - // Transition the primary view, which is the sampler's view into the right layout. - texture->transitionLayout(cmdbuffer, texture->getPrimaryViewRange(), - VulkanLayout::DEPTH_SAMPLER); - break; - } - } - } - VulkanLayout currentDepthLayout = depth.getLayout(); TargetBufferFlags clearVal = params.flags.clear; @@ -1272,18 +1306,15 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP // If the depth attachment texture was previously sampled, then we need to manually // transition it to an attachment. This is necessary to also set up a barrier between the // previous read and the potentially coming write. - if (currentDepthLayout == VulkanLayout::DEPTH_SAMPLER) { - depth.texture->transitionLayout(cmdbuffer, depth.getSubresourceRange(), - VulkanLayout::DEPTH_ATTACHMENT); - currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT; - } + depth.texture->transitionLayout(&commands, depth.getSubresourceRange(), + VulkanLayout::DEPTH_ATTACHMENT); + currentDepthLayout = VulkanLayout::DEPTH_ATTACHMENT; } uint8_t const renderTargetLayerCount = rt->getLayerCount(); // Create the VkRenderPass or fetch it from cache. VulkanFboCache::RenderPassKey rpkey = { - .initialColorLayoutMask = 0, .initialDepthLayout = currentDepthLayout, .depthFormat = depth.getFormat(), .clear = clearVal, @@ -1294,19 +1325,13 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP .viewCount = renderTargetLayerCount, }; for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { - const VulkanAttachment& info = rt->getColor(i); + VulkanAttachment const& info = rt->getColor(i); if (info.texture) { assert_invariant(info.layerCount == renderTargetLayerCount); - rpkey.initialColorLayoutMask |= 1 << i; rpkey.colorFormat[i] = info.getFormat(); if (rpkey.samples > 1 && info.texture->samples == 1) { rpkey.needsResolveMask |= (1 << i); } - if (info.texture->getPrimaryImageLayout() != VulkanLayout::COLOR_ATTACHMENT) { - ((VulkanTexture*) info.texture) - ->transitionLayout(cmdbuffer, info.getSubresourceRange(), - VulkanLayout::COLOR_ATTACHMENT); - } } else { rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED; } @@ -1325,28 +1350,45 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP }; auto& renderPassAttachments = mRenderPassFboInfo.attachments; for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { - if (!rt->getColor(i).texture) { + VulkanAttachment& attachment = rt->getColor(i); + if (!attachment.texture) { fbkey.color[i] = VK_NULL_HANDLE; fbkey.resolve[i] = VK_NULL_HANDLE; - } else if (fbkey.samples == 1) { - auto& colorAttachment = rt->getColor(i); - renderPassAttachments.insert(colorAttachment); - fbkey.color[i] = colorAttachment.getImageView(); + continue; + } + + if (fbkey.samples == 1) { + auto const& range = attachment.getSubresourceRange(); + auto tex = attachment.texture; + if (tex->getLayout(range.baseMipLevel, range.baseArrayLayer) != + VulkanLayout::COLOR_ATTACHMENT && + !tex->transitionLayout(&commands, range, VulkanLayout::COLOR_ATTACHMENT)) { + // If the layout transition did not emit a barrier, we do it manually here. + tex->samplerToAttachmentBarrier(&commands, range); + } + renderPassAttachments.insert(attachment); + + fbkey.color[i] = attachment.getImageView(); fbkey.resolve[i] = VK_NULL_HANDLE; assert_invariant(fbkey.color[i]); } else { auto& msaaColorAttachment = rt->getMsaaColor(i); + auto const& msaaRange = attachment.getSubresourceRange(); + msaaColorAttachment.texture->transitionLayout(&commands, + msaaRange, VulkanLayout::COLOR_ATTACHMENT); renderPassAttachments.insert(msaaColorAttachment); - auto& colorAttachment = rt->getColor(i); fbkey.color[i] = msaaColorAttachment.getImageView(); - VulkanTexture* texture = colorAttachment.texture; + VulkanTexture* texture = attachment.texture; if (texture->samples == 1) { mRenderPassFboInfo.hasColorResolve = true; - renderPassAttachments.insert(colorAttachment); - fbkey.resolve[i] = colorAttachment.getImageView(); + auto const& range = attachment.getSubresourceRange(); + attachment.texture->transitionLayout(&commands, + range, VulkanLayout::COLOR_ATTACHMENT); + renderPassAttachments.insert(attachment); + fbkey.resolve[i] = attachment.getImageView(); assert_invariant(fbkey.resolve[i]); } assert_invariant(fbkey.color[i]); @@ -1464,57 +1506,27 @@ void VulkanDriver::endRenderPass(int) { assert_invariant(rt); // Since we might soon be sampling from the render target that we just wrote to, we need a - // pipeline barrier between framebuffer writes and shader reads. This is a memory barrier rather - // than an image barrier. If we were to use image barriers here, we would potentially need to - // issue several of them when considering MRT. This would be very complex to set up and would - // require more state tracking, so we've chosen to use a memory barrier for simplicity and - // correctness. + // pipeline barrier between framebuffer writes and shader reads. if (!rt->isSwapChain()) { - for (auto const& attachment: mRenderPassFboInfo.attachments) { + for (auto& attachment: mRenderPassFboInfo.attachments) { + auto const& range = attachment.getSubresourceRange(); bool const isDepth = attachment.isDepth(); - VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - - // This is a workaround around a validation issue (might not be an actual driver issue). - if (mRenderPassFboInfo.hasColorResolve && !isDepth) { - srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; - } - - VkPipelineStageFlags dstStageMask = - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - VkAccessFlags srcAccess = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - VkAccessFlags dstAccess = VK_ACCESS_SHADER_READ_BIT; - VulkanLayout layout = VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT; + auto texture = attachment.texture; if (isDepth) { - srcAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - dstAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; - srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - layout = VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT; + texture->setLayout(range, VulkanFboCache::FINAL_DEPTH_ATTACHMENT_LAYOUT); + if (!texture->transitionLayout(&commands, range, VulkanLayout::DEPTH_SAMPLER)) { + texture->attachmentToSamplerBarrier(&commands, range); + } + } else { + texture->setLayout(range, VulkanFboCache::FINAL_COLOR_ATTACHMENT_LAYOUT); + if (!texture->transitionLayout(&commands, range, VulkanLayout::READ_WRITE)) { + texture->attachmentToSamplerBarrier(&commands, range); + } } - - auto const vkLayout = imgutil::getVkLayout(layout); - auto const& range = attachment.getSubresourceRange(); - VkImageMemoryBarrier barrier = { - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = srcAccess, - .dstAccessMask = dstAccess, - .oldLayout = vkLayout, - .newLayout = vkLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = attachment.getImage(), - .subresourceRange = range, - }; - - attachment.texture->setLayout(range, layout); - vkCmdPipelineBarrier(cmdbuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, - 1, &barrier); } } - mRenderPassFboInfo.clear(); - mDescriptorSetManager.clearState(); + mRenderPassFboInfo = {}; mCurrentRenderPass.renderTarget = nullptr; mCurrentRenderPass.renderPass = VK_NULL_HANDLE; FVK_SYSTRACE_END(); @@ -1573,33 +1585,6 @@ void VulkanDriver::commit(Handle sch) { FVK_SYSTRACE_END(); } -void VulkanDriver::bindUniformBuffer(uint32_t index, Handle boh) { - auto* bo = mResourceAllocator.handle_cast(boh); - VkDeviceSize const offset = 0; - VkDeviceSize const size = VK_WHOLE_SIZE; - mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size); -} - -void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index, - Handle boh, uint32_t offset, uint32_t size) { - - assert_invariant(bindingType == BufferObjectBinding::UNIFORM); - - // TODO: implement BufferObjectBinding::SHADER_STORAGE case - - auto* bo = mResourceAllocator.handle_cast(boh); - mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size); -} - -void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) { - mDescriptorSetManager.clearBuffer((uint32_t) index); -} - -void VulkanDriver::bindSamplers(uint32_t index, Handle sbh) { - auto* hwsb = mResourceAllocator.handle_cast(sbh); - mSamplerBindings[index] = hwsb; -} - void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant value) { assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants"); @@ -1800,8 +1785,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) { *mResourceAllocator.handle_cast(pipelineState.vertexBufferInfo); Handle programHandle = pipelineState.program; - RasterState rasterState = pipelineState.rasterState; - PolygonOffset depthOffset = pipelineState.polygonOffset; + RasterState const& rasterState = pipelineState.rasterState; + PolygonOffset const& depthOffset = pipelineState.polygonOffset; auto* program = mResourceAllocator.handle_cast(programHandle); commands->acquire(program); @@ -1845,59 +1830,26 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) { mPipelineCache.bindPrimitiveTopology(topology); mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi.getAttributeCount()); - // Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding), - // where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract - // Filament concept used to form groups of samplers. - - auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex(); -#if FVK_ENABLED_DEBUG_SAMPLER_NAME - auto const& bindingToName = program->getBindingToName(); -#endif - - for (auto binding: program->getBindings()) { - uint16_t const indexPair = bindingToSamplerIndex[binding]; - if (indexPair == 0xffff) { - continue; - } - - uint16_t const samplerGroupInd = (indexPair >> 8) & 0xff; - uint16_t const samplerInd = (indexPair & 0xff); - - VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd]; - if (!vksb) { - continue; - } - SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd; - - if (UTILS_UNLIKELY(!boundSampler->t)) { - continue; - } - VulkanTexture* texture = mResourceAllocator.handle_cast(boundSampler->t); - - // TODO: can this uninitialized check be checked in a higher layer? - // This fallback path is very flaky because the dummy texture might not have - // matching characteristics. (e.g. if the missing texture is a 3D texture) - if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) { -#if FVK_ENABLED(FVK_DEBUG_TEXTURE) && FVK_ENABLED_DEBUG_SAMPLER_NAME - FVK_LOGW << "Uninitialized texture bound to '" << bindingToName[binding] << "'"; - FVK_LOGW << " in material '" << program->name.c_str() << "'"; - FVK_LOGW << " at binding point " << +binding << utils::io::endl; -#endif - texture = mEmptyTexture; - } + auto& setLayouts = pipelineState.pipelineLayout.setLayout; + VulkanDescriptorSetLayout::DescriptorSetLayoutArray layoutList; + uint8_t layoutCount = 0; + std::transform(setLayouts.begin(), setLayouts.end(), layoutList.begin(), + [&](Handle handle) -> VkDescriptorSetLayout { + if (!handle) { + return VK_NULL_HANDLE; + } + auto layout = mResourceAllocator.handle_cast(handle); + layoutCount++; + return layout->getVkLayout(); + }); + auto pipelineLayout = mPipelineLayoutCache.getLayout(layoutList, program); - VkSampler const vksampler = mSamplerCache.getSampler(boundSampler->s); -#if FVK_ENABLED_DEBUG_SAMPLER_NAME - VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER, - reinterpret_cast(vksampler), bindingToName[binding].c_str()); -#endif - mDescriptorSetManager.updateSampler({}, binding, texture, vksampler); - } + constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF}; - auto const pipelineLayout = mDescriptorSetManager.bind(commands, program, mGetPipelineFunction); mBoundPipeline = { .program = program, .pipelineLayout = pipelineLayout, + .descriptorSetMask = DescriptorSetMask(descriptorSetMaskTable[layoutCount]), }; mPipelineCache.bindLayout(pipelineLayout); @@ -1935,6 +1887,14 @@ void VulkanDriver::bindRenderPrimitive(Handle rph) { FVK_SYSTRACE_END(); } +void VulkanDriver::bindDescriptorSet( + backend::DescriptorSetHandle dsh, + backend::descriptor_set_t setIndex, + backend::DescriptorSetOffsetArray&& offsets) { + VulkanDescriptorSet* set = mResourceAllocator.handle_cast(dsh); + mDescriptorSetManager.bind(setIndex, set, std::move(offsets)); +} + void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("draw2"); @@ -1942,8 +1902,8 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins VulkanCommandBuffer& commands = mCommands.get(); VkCommandBuffer cmdbuffer = commands.buffer(); - // Bind "dynamic" UBOs if they need to change. - mDescriptorSetManager.dynamicBind(&commands, {}); + mDescriptorSetManager.commit(&commands, mBoundPipeline.pipelineLayout, + mBoundPipeline.descriptorSetMask); // Finally, make the actual draw call. TODO: support subranges const uint32_t firstIndex = indexOffset; @@ -1994,7 +1954,7 @@ void VulkanDriver::scissor(Viewport scissorBox) { .extent = { uint32_t(r - l), uint32_t(t - b) } }; - const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget; + VulkanRenderTarget const* rt = mCurrentRenderPass.renderTarget; rt->transformClientRectToPlatform(&scissor); vkCmdSetScissor(cmdbuffer, 0, 1, &scissor); } diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index abdbc6304f6..d811bfebb2a 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -47,21 +47,6 @@ struct VulkanSamplerGroup; constexpr uint8_t MAX_RENDERTARGET_ATTACHMENT_TEXTURES = MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT * 2 + 1; -// We need to store information about a render pass to enable better barriers at the end of a -// renderpass. -struct RenderPassFboBundle { - using AttachmentArray = - CappedArray; - - AttachmentArray attachments; - bool hasColorResolve = false; - - void clear() { - attachments.clear(); - hasColorResolve = false; - } -}; - class VulkanDriver final : public DriverBase { public: static Driver* create(VulkanPlatform* platform, VulkanContext const& context, @@ -159,15 +144,20 @@ class VulkanDriver final : public DriverBase { VulkanReadPixels mReadPixels; VulkanDescriptorSetManager mDescriptorSetManager; - VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction; - // This is necessary for us to write to push constants after binding a pipeline. - struct BoundPipeline { + struct { VulkanProgram* program; VkPipelineLayout pipelineLayout; - }; - BoundPipeline mBoundPipeline = {}; - RenderPassFboBundle mRenderPassFboInfo; + DescriptorSetMask descriptorSetMask; + } mBoundPipeline = {}; + + // We need to store information about a render pass to enable better barriers at the end of a + // renderpass. + struct { + using AttachmentArray = CappedArray; + AttachmentArray attachments; + bool hasColorResolve = false; + } mRenderPassFboInfo = {}; bool const mIsSRGBSwapChainSupported; backend::StereoscopicType const mStereoscopicType; diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 40e3db2ee3c..927dd4bcedf 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -31,7 +31,6 @@ namespace filament::backend { bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1, const RenderPassKey& k2) const { - if (k1.initialColorLayoutMask != k2.initialColorLayoutMask) return false; if (k1.initialDepthLayout != k2.initialDepthLayout) return false; for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (k1.colorFormat[i] != k2.colorFormat[i]) return false; @@ -258,9 +257,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { .storeOp = kEnableStore, .stencilLoadOp = kDontCare, .stencilStoreOp = kDisableStore, - .initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear) - ? imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT) - : imgutil::getVkLayout(VulkanLayout::UNDEFINED), + .initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT), .finalLayout = imgutil::getVkLayout(FINAL_COLOR_ATTACHMENT_LAYOUT), }; } diff --git a/filament/backend/src/vulkan/VulkanFboCache.h b/filament/backend/src/vulkan/VulkanFboCache.h index 6af444a2032..3335db7c1c9 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.h +++ b/filament/backend/src/vulkan/VulkanFboCache.h @@ -42,19 +42,8 @@ class VulkanFboCache { // RenderPassKey is a small POD representing the immutable state that is used to construct // a VkRenderPass. It is hashed and used as a lookup key. struct alignas(8) RenderPassKey { - // For each target, we need to know three image layouts: the layout BEFORE the pass, the - // layout DURING the pass, and the layout AFTER the pass. Here are the rules: - // - For depth, we explicitly specify all three layouts. - // - Color targets have their initial image layout specified with a bitmask. - // - For each color target, the pre-existing layout is either UNDEFINED (0) or GENERAL (1). - // - The render pass and final images layout for color buffers is always - // VulkanLayout::COLOR_ATTACHMENT. - uint8_t initialColorLayoutMask; - - // Note that if VulkanLayout grows beyond 16, we'd need to up this. - VulkanLayout initialDepthLayout : 8; - uint8_t padding0; - uint8_t padding1; + VulkanLayout initialDepthLayout; + uint8_t padding[3] = {}; VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes VkFormat depthFormat; // 4 bytes @@ -71,7 +60,6 @@ class VulkanFboCache { uint32_t timestamp; }; static_assert(0 == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT % 8); - static_assert(sizeof(RenderPassKey::initialColorLayoutMask) == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT / 8); static_assert(sizeof(TargetBufferFlags) == 4, "TargetBufferFlags has unexpected size."); static_assert(sizeof(VkFormat) == 4, "VkFormat has unexpected size."); static_assert(sizeof(RenderPassKey) == 56, "RenderPassKey has unexpected size."); diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index ae445de79a2..e2adea0f560 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -17,11 +17,14 @@ #include "VulkanHandles.h" #include "VulkanConstants.h" + +// TODO: remove this by moving DebugUtils out of VulkanDriver #include "VulkanDriver.h" + #include "VulkanMemory.h" +#include "VulkanResourceAllocator.h" #include "VulkanUtility.h" #include "spirv/VulkanSpirvUtils.h" -#include "utils/Log.h" #include @@ -53,64 +56,16 @@ void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) { } template -static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) { - Bitmask ret = 0; - if (flags & ShaderStageFlags2::VERTEX) { - ret |= (getVertexStage() << binding); - } - if (flags & ShaderStageFlags2::FRAGMENT) { - ret |= (getFragmentStage() << binding); +inline void fromStageFlags(backend::ShaderStageFlags stage, descriptor_binding_t binding, + Bitmask& mask) { + if ((bool) (stage & ShaderStageFlags::VERTEX)) { + mask.set(binding + getVertexStageShift()); } - return ret; -} - -constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES = - VulkanProgram::MAX_SHADER_MODULES; - -using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList; - -template -void addDescriptors(Bitmask mask, - utils::FixedCapacityVector& outputList) { - constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES; - for (uint8_t i = 0; i < MODULE_OFFSET; ++i) { - bool const hasVertex = (mask & (1ULL << i)) != 0; - bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0; - if (!hasVertex && !hasFragment) { - continue; - } - - DescriptorSetLayoutBinding binding{ - .binding = i, - .flags = DescriptorFlags::NONE, - .count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API - // instead. - }; - if (hasVertex) { - binding.stageFlags = ShaderStageFlags2::VERTEX; - } - if (hasFragment) { - binding.stageFlags = static_cast( - binding.stageFlags | ShaderStageFlags2::FRAGMENT); - } - if constexpr (std::is_same_v) { - binding.type = DescriptorType::UNIFORM_BUFFER; - } else if constexpr (std::is_same_v) { - binding.type = DescriptorType::SAMPLER; - } else if constexpr (std::is_same_v) { - binding.type = DescriptorType::INPUT_ATTACHMENT; - } - outputList.push_back(binding); + if ((bool) (stage & ShaderStageFlags::FRAGMENT)) { + mask.set(binding + getFragmentStageShift()); } } -inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device, - VkDescriptorSetLayoutCreateInfo const& info) { - VkDescriptorSetLayout layout; - vkCreateDescriptorSetLayout(device, &info, VKALLOC, &layout); - return layout; -} - inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) { switch(stage) { case backend::ShaderStage::VERTEX: @@ -122,20 +77,49 @@ inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) { } } -} // anonymous namespace +using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask; +BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) { + BitmaskGroup mask; + for (auto const& binding: layout.bindings) { + switch (binding.type) { + case DescriptorType::UNIFORM_BUFFER: { + if ((binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE) { + fromStageFlags(binding.stageFlags, binding.binding, mask.dynamicUbo); + } else { + fromStageFlags(binding.stageFlags, binding.binding, mask.ubo); + } + break; + } + case DescriptorType::SAMPLER: { + fromStageFlags(binding.stageFlags, binding.binding, mask.sampler); + break; + } + case DescriptorType::INPUT_ATTACHMENT: { + fromStageFlags(binding.stageFlags, binding.binding, mask.inputAttachment); + break; + } + case DescriptorType::SHADER_STORAGE_BUFFER: + PANIC_POSTCONDITION("Shader storage is not supported"); + break; + } + } + return mask; +} +} // anonymous namespace -VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device, - VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask) +VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout) : VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT), - mDevice(device), - vklayout(createDescriptorSetLayout(device, info)), - bitmask(bitmask), - bindings(getBindings(bitmask)), + bitmask(fromBackendLayout(layout)), count(Count::fromLayoutBitmask(bitmask)) {} -VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() { - vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC); +void VulkanDescriptorSet::acquire(VulkanTexture* texture) { + mResources.acquire(texture); + mTextures[mTextureCount++] = texture; +} + +void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) { + mResources.acquire(bufferObject); } PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept { @@ -189,24 +173,11 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept mInfo(new(std::nothrow) PipelineInfo(builder)), mDevice(device) { - constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES; - constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES; - constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET = - (sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES; - Program::ShaderSource const& blobs = builder.getShadersSource(); auto& modules = mInfo->shaders; - auto const& specializationConstants = builder.getSpecializationConstants(); - std::vector shader; - // TODO: this will be moved out of the shader as the descriptor set layout will be provided by - // Filament instead of parsed from the shaders. See [GDSR] in VulkanDescriptorSetManager.h - UniformBufferBitmask uboMask = 0; - SamplerBitmask samplerMask = 0; - InputAttachmentBitmask inputAttachmentMask = 0; - static_assert(static_cast(0) == ShaderStage::VERTEX && static_cast(1) == ShaderStage::FRAGMENT && MAX_SHADER_MODULES == 2); @@ -223,12 +194,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept dataSize = shader.size() * 4; } - auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob); - uboMask |= (static_cast(ubo) << (UBO_MODULE_OFFSET * i)); - samplerMask |= (static_cast(sampler) << (SAMPLER_MODULE_OFFSET * i)); - inputAttachmentMask |= (static_cast(inputAttachment) - << (INPUT_ATTACHMENT_MODULE_OFFSET * i)); - VkShaderModule& module = modules[i]; VkShaderModuleCreateInfo moduleInfo = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, @@ -256,40 +221,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept #endif } - LayoutDescriptionList& layouts = mInfo->layouts; - layouts[0].bindings = utils::FixedCapacityVector::with_capacity( - countBits(collapseStages(uboMask))); - layouts[1].bindings = utils::FixedCapacityVector::with_capacity( - countBits(collapseStages(samplerMask))); - layouts[2].bindings = utils::FixedCapacityVector::with_capacity( - countBits(collapseStages(inputAttachmentMask))); - - addDescriptors(uboMask, layouts[0].bindings); - addDescriptors(samplerMask, layouts[1].bindings); - addDescriptors(inputAttachmentMask, layouts[2].bindings); - -#if FVK_ENABLED_DEBUG_SAMPLER_NAME - auto& bindingToName = mInfo->bindingToName; -#endif - - auto& groupInfo = builder.getSamplerGroupInfo(); - auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex; - auto& bindings = mInfo->bindings; - for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) { - auto const& group = groupInfo[groupInd]; - auto const& samplers = group.samplers; - for (size_t i = 0; i < samplers.size(); ++i) { - uint32_t const binding = samplers[i].binding; - bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i); - assert_invariant(bindings.find(binding) == bindings.end()); - bindings.insert(binding); - -#if FVK_ENABLED_DEBUG_SAMPLER_NAME - bindingToName[binding] = samplers[i].name.c_str(); -#endif - } - } - #if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE) FVK_LOGD << "Created VulkanProgram " << builder << ", shaders = (" << modules[0] << ", " << modules[1] << ")" << utils::io::endl; @@ -320,6 +251,7 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) { VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, uint32_t width, uint32_t height, uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount) @@ -353,6 +285,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica if (UTILS_UNLIKELY(!msTexture)) { // TODO: This should be allocated with the ResourceAllocator. msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands, + handleAllocator, texture->target, ((VulkanTexture const*) texture)->levels, texture->format, samples, texture->width, texture->height, texture->depth, texture->usage, stagePool, true /* heap allocated */); @@ -382,7 +315,8 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica VulkanTexture* msTexture = depthTexture->getSidecar(); if (UTILS_UNLIKELY(!msTexture)) { msTexture = new VulkanTexture(device, physicalDevice, context, allocator, - commands, depthTexture->target, msLevel, depthTexture->format, samples, + commands, handleAllocator, + depthTexture->target, msLevel, depthTexture->format, samples, depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage, stagePool, true /* heap allocated */); depthTexture->setSidecar(msTexture); @@ -534,15 +468,7 @@ bool VulkanTimerQuery::isCompleted() noexcept { // timestamp has at least been written into a processed command buffer. // This fence indicates that the corresponding buffer has been completed. - if (!mFence) { - return false; - } - VkResult status = mFence->status.load(std::memory_order_relaxed); - if (status != VK_SUCCESS) { - return false; - } - - return true; + return mFence && mFence->getStatus() == VK_SUCCESS; } VulkanTimerQuery::~VulkanTimerQuery() = default; @@ -558,35 +484,4 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAl mResources.acquire(indexBuffer); } -using Bitmask = VulkanDescriptorSetLayout::Bitmask; - -Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) { - Bitmask mask; - for (auto const& binding: layout.bindings) { - switch (binding.type) { - case descset::DescriptorType::UNIFORM_BUFFER: { - if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) { - mask.dynamicUbo |= fromStageFlags(binding.stageFlags, - binding.binding); - } else { - mask.ubo |= fromStageFlags(binding.stageFlags, - binding.binding); - } - break; - } - case descset::DescriptorType::SAMPLER: { - mask.sampler |= fromStageFlags(binding.stageFlags, binding.binding); - break; - } - case descset::DescriptorType::INPUT_ATTACHMENT: { - mask.inputAttachment |= - fromStageFlags(binding.stageFlags, binding.binding); - break; - } - } - } - return mask; -} - - } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index ee6ffed346a..16f9c888f2e 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -29,39 +29,52 @@ #include #include +#include #include #include #include namespace filament::backend { -using namespace descset; +namespace { +// Counts the total number of descriptors for both vertex and fragment stages. +template +inline uint8_t collapsedCount(Bitmask const& mask) { + static_assert(sizeof(mask) <= 64); + constexpr uint64_t VERTEX_MASK = (1ULL << getFragmentStageShift()) - 1ULL; + constexpr uint64_t FRAGMENT_MASK = (VERTEX_MASK << getFragmentStageShift()); + uint64_t val = mask.getValue(); + val = ((val & VERTEX_MASK) >> getVertexStageShift()) | + ((val & FRAGMENT_MASK) >> getFragmentStageShift()); + return (uint8_t) Bitmask(val).count(); +} + +} // anonymous namespace class VulkanTimestamps; +struct VulkanBufferObject; -struct VulkanDescriptorSetLayout : public VulkanResource { - static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3; +struct VulkanDescriptorSetLayout : public VulkanResource, HwDescriptorSetLayout { + static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 4; + static constexpr uint8_t MAX_BINDINGS = 25; + + using DescriptorSetLayoutArray = std::array; // The bitmask representation of a set layout. struct Bitmask { - UniformBufferBitmask ubo = 0; // 4 bytes - UniformBufferBitmask dynamicUbo = 0; // 4 bytes - SamplerBitmask sampler = 0; // 8 bytes - InputAttachmentBitmask inputAttachment = 0; // 1 bytes - - // Because we're using this struct as hash key, must make it's 8-bytes aligned, with no - // unaccounted bytes. - uint8_t padding0 = 0; // 1 bytes - uint16_t padding1 = 0;// 2 bytes - uint32_t padding2 = 0;// 4 bytes + // TODO: better utiltize the space below and use bitset instead. + UniformBufferBitmask ubo; // 8 bytes + UniformBufferBitmask dynamicUbo; // 8 bytes + SamplerBitmask sampler; // 8 bytes + InputAttachmentBitmask inputAttachment; // 8 bytes bool operator==(Bitmask const& right) const { return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && inputAttachment == right.inputAttachment; } - - static Bitmask fromBackendLayout(descset::DescriptorSetLayout const& layout); }; + static_assert(sizeof(Bitmask) == 32); // This is a convenience struct to quickly check layout compatibility in terms of descriptor set // pools. @@ -71,6 +84,10 @@ struct VulkanDescriptorSetLayout : public VulkanResource { uint32_t sampler = 0; uint32_t inputAttachment = 0; + inline uint32_t total() const { + return ubo + dynamicUbo + sampler + inputAttachment; + } + bool operator==(Count const& right) const noexcept { return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler && inputAttachment == right.inputAttachment; @@ -78,10 +95,10 @@ struct VulkanDescriptorSetLayout : public VulkanResource { static inline Count fromLayoutBitmask(Bitmask const& mask) { return { - .ubo = countBits(collapseStages(mask.ubo)), - .dynamicUbo = countBits(collapseStages(mask.dynamicUbo)), - .sampler = countBits(collapseStages(mask.sampler)), - .inputAttachment = countBits(collapseStages(mask.inputAttachment)), + .ubo = collapsedCount(mask.ubo), + .dynamicUbo = collapsedCount(mask.dynamicUbo), + .sampler = collapsedCount(mask.sampler), + .inputAttachment = collapsedCount(mask.inputAttachment), }; } @@ -97,89 +114,58 @@ struct VulkanDescriptorSetLayout : public VulkanResource { } }; - static_assert(sizeof(Bitmask) % 8 == 0); + VulkanDescriptorSetLayout(DescriptorSetLayout const& layout); - explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info, - Bitmask const& bitmask); + ~VulkanDescriptorSetLayout() = default; - ~VulkanDescriptorSetLayout(); + VkDescriptorSetLayout getVkLayout() const { return mVkLayout; } + void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; } - VkDevice const mDevice; - VkDescriptorSetLayout const vklayout; Bitmask const bitmask; - - // This is a convenience struct so that we don't have to iterate through all the bits of the - // bitmask (which correspondings to binding indices). - struct _Bindings { - utils::FixedCapacityVector const ubo; - utils::FixedCapacityVector const dynamicUbo; - utils::FixedCapacityVector const sampler; - utils::FixedCapacityVector const inputAttachment; - } bindings; - Count const count; private: - - template - utils::FixedCapacityVector bits(MaskType mask) { - utils::FixedCapacityVector ret = - utils::FixedCapacityVector::with_capacity(countBits(mask)); - for (uint8_t i = 0; i < sizeof(mask) * 4; ++i) { - if (mask & (1 << i)) { - ret.push_back(i); - } - } - return ret; - } - - _Bindings getBindings(Bitmask const& bitmask) { - auto const uboCollapsed = collapseStages(bitmask.ubo); - auto const dynamicUboCollapsed = collapseStages(bitmask.dynamicUbo); - auto const samplerCollapsed = collapseStages(bitmask.sampler); - auto const inputAttachmentCollapsed = collapseStages(bitmask.inputAttachment); - return { - bits(uboCollapsed), - bits(dynamicUboCollapsed), - bits(samplerCollapsed), - bits(inputAttachmentCollapsed), - }; - } + VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE; }; -using VulkanDescriptorSetLayoutList = std::array, - VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; - -struct VulkanDescriptorSet : public VulkanResource { +struct VulkanDescriptorSet : public VulkanResource, HwDescriptorSet { public: // Because we need to recycle descriptor sets not used, we allow for a callback that the "Pool" // can use to repackage the vk handle. - using OnRecycle = std::function; + using OnRecycle = std::function; - VulkanDescriptorSet(VulkanResourceAllocator* allocator, - VkDescriptorSet rawSet, OnRecycle&& onRecycleFn) + VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, + OnRecycle&& onRecycleFn) : VulkanResource(VulkanResourceType::DESCRIPTOR_SET), - resources(allocator), vkSet(rawSet), + mResources(allocator), mOnRecycleFn(std::move(onRecycleFn)) {} ~VulkanDescriptorSet() { if (mOnRecycleFn) { - mOnRecycleFn(); + mOnRecycleFn(this); } } + void acquire(VulkanTexture* texture); + + void acquire(VulkanBufferObject* texture); + + bool hasTexture(VulkanTexture* texture) { + return std::any_of(mTextures.begin(), mTextures.end(), + [texture](auto t) { return t == texture; }); + } + // TODO: maybe change to fixed size for performance. - VulkanAcquireOnlyResourceManager resources; VkDescriptorSet const vkSet; private: + std::array mTextures = { nullptr }; + uint8_t mTextureCount = 0; + VulkanAcquireOnlyResourceManager mResources; OnRecycle mOnRecycleFn; }; -using VulkanDescriptorSetList = std::array, - VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>; - using PushConstantNameArray = utils::FixedCapacityVector; using PushConstantNameByStage = std::array; @@ -224,16 +210,6 @@ struct VulkanProgram : public HwProgram, VulkanResource { // samplers. inline BindingList const& getBindings() const { return mInfo->bindings; } - // TODO: this is currently not used. This will replace getLayoutDescriptionList below. - // inline descset::DescriptorSetLayout const& getLayoutDescription() const { - // return mInfo->layout; - // } - // In the usual case, we would have just one layout per program. But in the current setup, we - // have a set/layout for each descriptor type. This will be changed in the future. - using LayoutDescriptionList = std::array; - inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; } - inline uint32_t getPushConstantRangeCount() const { return mInfo->pushConstantDescription.getVkRangeCount(); } @@ -273,10 +249,6 @@ struct VulkanProgram : public HwProgram, VulkanResource { utils::FixedCapacityVector bindingToSamplerIndex; VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE }; - // TODO: Use this instead of `layouts` after Filament-side Descriptor Set API is in place. - // descset::DescriptorSetLayout layout; - LayoutDescriptionList layouts; - PushConstantDescription pushConstantDescription; #if FVK_ENABLED_DEBUG_SAMPLER_NAME @@ -302,7 +274,9 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource { // Creates an offscreen render target. VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, - VulkanCommands* commands, uint32_t width, uint32_t height, + VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + uint32_t width, uint32_t height, uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount); @@ -484,6 +458,7 @@ struct VulkanTimerQuery : public HwTimerQuery, VulkanThreadSafeResource { utils::Mutex mFenceMutex; }; + inline constexpr VkBufferUsageFlagBits getBufferObjectUsage( BufferObjectBinding bindingType) noexcept { switch(bindingType) { diff --git a/filament/backend/src/vulkan/VulkanImageUtility.cpp b/filament/backend/src/vulkan/VulkanImageUtility.cpp index 6c038e429f0..415eb305cab 100644 --- a/filament/backend/src/vulkan/VulkanImageUtility.cpp +++ b/filament/backend/src/vulkan/VulkanImageUtility.cpp @@ -131,14 +131,18 @@ getVkTransition(const VulkanLayoutTransition& transition) { }// anonymous namespace -void transitionLayout(VkCommandBuffer cmdbuffer, +bool transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition) { if (transition.oldLayout == transition.newLayout) { - return; + return false; } auto [srcAccessMask, dstAccessMask, srcStage, dstStage, oldLayout, newLayout] = getVkTransition(transition); + if (oldLayout == newLayout) { + return false; + } + assert_invariant(transition.image != VK_NULL_HANDLE && "No image for transition"); VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -152,6 +156,7 @@ void transitionLayout(VkCommandBuffer cmdbuffer, .subresourceRange = transition.subresources, }; vkCmdPipelineBarrier(cmdbuffer, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier); + return true; } }// namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanImageUtility.h b/filament/backend/src/vulkan/VulkanImageUtility.h index 92aaac96ea4..82ff436c163 100644 --- a/filament/backend/src/vulkan/VulkanImageUtility.h +++ b/filament/backend/src/vulkan/VulkanImageUtility.h @@ -135,7 +135,9 @@ constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) { } } -void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition); +// Returns true if a transition has been added to the command buffer, false otherwis (where there is +// no transition necessary). +bool transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition); } // namespace imgutil diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index b3d09da86ed..4c440a00857 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -98,7 +98,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n VkPipelineColorBlendStateCreateInfo colorBlendState; colorBlendState = VkPipelineColorBlendStateCreateInfo{}; colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - colorBlendState.attachmentCount = 1; + colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount; colorBlendState.pAttachments = colorBlendAttachments; // If we reach this point, we need to create and stash a brand new pipeline object. @@ -210,8 +210,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n pipelineCreateInfo.pDynamicState = &dynamicState; // Filament assumes consistent blend state across all color attachments. - colorBlendState.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount; - for (auto& target : colorBlendAttachments) { + for (uint8_t i = 0; i < colorBlendState.attachmentCount; ++i) { + auto& target = colorBlendAttachments[i]; target.blendEnable = mPipelineRequirements.rasterState.blendEnable; target.srcColorBlendFactor = mPipelineRequirements.rasterState.srcColorBlendFactor; target.dstColorBlendFactor = mPipelineRequirements.rasterState.dstColorBlendFactor; diff --git a/filament/backend/src/vulkan/VulkanReadPixels.cpp b/filament/backend/src/vulkan/VulkanReadPixels.cpp index be43e2ae607..bef08811c6d 100644 --- a/filament/backend/src/vulkan/VulkanReadPixels.cpp +++ b/filament/backend/src/vulkan/VulkanReadPixels.cpp @@ -232,7 +232,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint VulkanAttachment const srcAttachment = srcTarget->getColor(0); VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange(); - srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC); + srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::TRANSFER_SRC); VkImageCopy const imageCopyRegion = { .srcSubresource = { @@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion); // Restore the source image layout. - srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::COLOR_ATTACHMENT); + srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT); vkEndCommandBuffer(cmdbuffer); diff --git a/filament/backend/src/vulkan/VulkanSwapChain.cpp b/filament/backend/src/vulkan/VulkanSwapChain.cpp index 8c4a345caec..4989b925a38 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.cpp +++ b/filament/backend/src/vulkan/VulkanSwapChain.cpp @@ -26,12 +26,14 @@ using namespace utils; namespace filament::backend { VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, - VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool, + VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, + VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags, VkExtent2D extent) : VulkanResource(VulkanResourceType::SWAP_CHAIN), mPlatform(platform), mCommands(commands), mAllocator(allocator), + mHandleAllocator(handleAllocator), mStagePool(stagePool), mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow), mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize), @@ -62,12 +64,12 @@ void VulkanSwapChain::update() { VkDevice const device = mPlatform->getDevice(); for (auto const color: bundle.colors) { - mColors.push_back(std::make_unique(device, mAllocator, mCommands, color, - bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, + mColors.push_back(std::make_unique(device, mAllocator, mCommands, mHandleAllocator, + color, bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */)); } - mDepth = std::make_unique(device, mAllocator, mCommands, bundle.depth, - bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, + mDepth = std::make_unique(device, mAllocator, mCommands, mHandleAllocator, + bundle.depth, bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */); mExtent = bundle.extent; @@ -75,7 +77,7 @@ void VulkanSwapChain::update() { void VulkanSwapChain::present() { if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) { - VkCommandBuffer const cmdbuf = mCommands->get().buffer(); + VulkanCommandBuffer& commands = mCommands->get(); VkImageSubresourceRange const subresources{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, @@ -83,7 +85,7 @@ void VulkanSwapChain::present() { .baseArrayLayer = 0, .layerCount = 1, }; - mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT); + mColors[mCurrentSwapIndex]->transitionLayout(&commands, subresources, VulkanLayout::PRESENT); } mCommands->flush(); diff --git a/filament/backend/src/vulkan/VulkanSwapChain.h b/filament/backend/src/vulkan/VulkanSwapChain.h index 80886f8b1b7..b3e49c044e5 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.h +++ b/filament/backend/src/vulkan/VulkanSwapChain.h @@ -36,11 +36,13 @@ namespace filament::backend { struct VulkanHeadlessSwapChain; struct VulkanSurfaceSwapChain; +class VulkanResourceAllocator; // A wrapper around the platform implementation of swapchain. struct VulkanSwapChain : public HwSwapChain, VulkanResource { VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator, - VulkanCommands* commands, VulkanStagePool& stagePool, + VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, + VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0}); ~VulkanSwapChain(); @@ -80,6 +82,7 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource { VulkanPlatform* mPlatform; VulkanCommands* mCommands; VmaAllocator mAllocator; + VulkanResourceAllocator* const mHandleAllocator; VulkanStagePool& mStagePool; bool const mHeadless; bool const mFlushAndWaitOnResize; diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index b154eec6532..2181ee59053 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -15,6 +15,7 @@ */ #include "VulkanMemory.h" +#include "VulkanResourceAllocator.h" #include "VulkanTexture.h" #include "VulkanUtility.h" @@ -28,50 +29,154 @@ using namespace bluevk; namespace filament::backend { -VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, +namespace { + +inline uint8_t getLayerCount(SamplerType const target, uint32_t const depth) { + switch (target) { + case SamplerType::SAMPLER_2D: + case SamplerType::SAMPLER_3D: + case SamplerType::SAMPLER_EXTERNAL: + return 1; + case SamplerType::SAMPLER_CUBEMAP: + return 6; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return depth * 6; + case SamplerType::SAMPLER_2D_ARRAY: + return depth; + } +} + +VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMapping const& next) { + static constexpr VkComponentSwizzle IDENTITY[] = { + VK_COMPONENT_SWIZZLE_R, + VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A, + }; + + auto const compose = [](VkComponentSwizzle out, VkComponentMapping const& prev, + uint8_t channelIndex) { + // We need to first change all identities to its equivalent channel. + if (out == VK_COMPONENT_SWIZZLE_IDENTITY) { + out = IDENTITY[channelIndex]; + } + switch (out) { + case VK_COMPONENT_SWIZZLE_R: + out = prev.r; + break; + case VK_COMPONENT_SWIZZLE_G: + out = prev.g; + break; + case VK_COMPONENT_SWIZZLE_B: + out = prev.b; + break; + case VK_COMPONENT_SWIZZLE_A: + out = prev.a; + break; + case VK_COMPONENT_SWIZZLE_IDENTITY: + case VK_COMPONENT_SWIZZLE_ZERO: + case VK_COMPONENT_SWIZZLE_ONE: + return out; + // Below is not exposed in Vulkan's API, but needs to be there for compilation. + case VK_COMPONENT_SWIZZLE_MAX_ENUM: + break; + } + // If the result correctly corresponds to the identity, just return identity. + if (IDENTITY[channelIndex] == out) { + return VK_COMPONENT_SWIZZLE_IDENTITY; + } + return out; + }; + + auto const identityToChannel = [](VkComponentSwizzle val, uint8_t channelIndex) { + if (val != VK_COMPONENT_SWIZZLE_IDENTITY) { + return val; + } + return IDENTITY[channelIndex]; + }; + + // We make sure all all identities are mapped into respective channels so that actual channel + // mapping will be passed onto the output. + VkComponentMapping const prevExplicit = { + identityToChannel(prev.r, 0), + identityToChannel(prev.g, 1), + identityToChannel(prev.b, 2), + identityToChannel(prev.a, 3), + }; + + // Note that the channel index corresponds to the VkComponentMapping struct layout. + return { + compose(next.r, prevExplicit, 0), + compose(next.g, prevExplicit, 1), + compose(next.b, prevExplicit, 2), + compose(next.a, prevExplicit, 3), + }; +} + +} // anonymous namespace + +VulkanTextureState::VulkanTextureState( + VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanStagePool& stagePool, + VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount) + : VulkanResource(VulkanResourceType::HEAP_ALLOCATED), + mVkFormat(format), + mViewType(viewType), + mFullViewRange { + filament::backend::getImageAspect(format), 0, levels, 0, layerCount + }, + mStagePool(stagePool), + mDevice(device), + mAllocator(allocator), + mCommands(commands) { +} + +VulkanTextureState* VulkanTexture::getSharedState() { + VulkanTextureState* state = mAllocator->handle_cast(mState); + return state; +} + +VulkanTextureState const* VulkanTexture::getSharedState() const { + VulkanTextureState const* state = mAllocator->handle_cast(mState); + return state; +} + +VulkanTexture::VulkanTexture( + VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated) - : HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED, - tusage), - VulkanResource( - heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), - mVkFormat(format), - mViewType(imgutil::getViewType(target)), - mSwizzle({}), - mTextureImage(image), - mFullViewRange{ - .aspectMask = getImageAspect(), - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - mPrimaryViewRange(mFullViewRange), - mStagePool(stagePool), - mDevice(device), - mAllocator(allocator), - mCommands(commands) {} + : HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED, + tusage), + VulkanResource( + heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), + mAllocator(handleAllocator), + mState(handleAllocator->initHandle( + device, allocator, commands, stagePool, + format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) { + auto* const state = getSharedState(); + state->mTextureImage = image; + mPrimaryViewRange = state->mFullViewRange; +} VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, - SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, - uint32_t h, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool, - bool heapAllocated, VkComponentMapping swizzle) + VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels, + TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, + TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated) : HwTexture(target, levels, samples, w, h, depth, tformat, tusage), VulkanResource( heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE), - mVkFormat(backend::getVkFormat(tformat)), - mViewType(imgutil::getViewType(target)), - mSwizzle(swizzle), - mStagePool(stagePool), - mDevice(device), - mAllocator(allocator), - mCommands(commands) { + mAllocator(handleAllocator), + mState(handleAllocator->initHandle(device, allocator, commands, stagePool, + backend::getVkFormat(tformat), imgutil::getViewType(target), levels, + getLayerCount(target, depth))) { + auto* const state = getSharedState(); // Create an appropriately-sized device-only VkImage, but do not fill it yet. VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D, - .format = mVkFormat, + .format = state->mVkFormat, .extent = {w, h, depth}, .mipLevels = levels, .arrayLayers = 1, @@ -109,9 +214,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, #if FVK_ENABLED(FVK_DEBUG_TEXTURE) // Validate that the format is actually sampleable. VkFormatProperties props; - vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props); + vkGetPhysicalDeviceFormatProperties(physicalDevice, state->mVkFormat, &props); if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) { - FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not " + FVK_LOGW << "Texture usage is SAMPLEABLE but format " << state->mVkFormat << " is not " "sampleable with optimal tiling." << utils::io::endl; } #endif @@ -144,7 +249,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, // any kind of attachment (color or depth). const auto& limits = context.getPhysicalDeviceLimits(); if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) { - samples = reduceSampleCount(samples, isVkDepthFormat(mVkFormat) + samples = reduceSampleCount(samples, isVkDepthFormat(state->mVkFormat) ? limits.sampledImageDepthSampleCounts : limits.sampledImageColorSampleCounts); } @@ -158,12 +263,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, this->samples = samples; imageInfo.samples = (VkSampleCountFlagBits) samples; - VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage); + VkResult error = vkCreateImage(state->mDevice, &imageInfo, VKALLOC, &state->mTextureImage); if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) { FVK_LOGD << "vkCreateImage: " - << "image = " << mTextureImage << ", " + << "image = " << state->mTextureImage << ", " << "result = " << error << ", " - << "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", " + << "handle = " << utils::io::hex << state->mTextureImage << utils::io::dec << ", " << "extent = " << w << "x" << h << "x"<< depth << ", " << "mipLevels = " << int(levels) << ", " << "TextureUsage = " << static_cast(usage) << ", " @@ -172,13 +277,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, << "type = " << imageInfo.imageType << ", " << "flags = " << imageInfo.flags << ", " << "target = " << static_cast(target) <<", " - << "format = " << mVkFormat << utils::io::endl; + << "format = " << state->mVkFormat << utils::io::endl; } FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image."; // Allocate memory for the VkImage and bind it. VkMemoryRequirements memReqs = {}; - vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs); + vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs); uint32_t memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); @@ -191,58 +296,67 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, .allocationSize = memReqs.size, .memoryTypeIndex = memoryTypeIndex, }; - error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory); + error = vkAllocateMemory(state->mDevice, &allocInfo, nullptr, &state->mTextureImageMemory); FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory."; - error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0); + error = vkBindImageMemory(state->mDevice, state->mTextureImage, state->mTextureImageMemory, 0); FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image."; - uint32_t layerCount = 0; - if (target == SamplerType::SAMPLER_CUBEMAP) { - layerCount = 6; - } else if (target == SamplerType::SAMPLER_CUBEMAP_ARRAY) { - layerCount = depth * 6; - } else if (target == SamplerType::SAMPLER_2D_ARRAY) { - layerCount = depth; - } else if (target == SamplerType::SAMPLER_3D) { - layerCount = 1; - } else { - layerCount = 1; - } - - mFullViewRange = { - .aspectMask = getImageAspect(), - .baseMipLevel = 0, - .levelCount = levels, - .baseArrayLayer = 0, - .layerCount = layerCount, - }; - // Spec out the "primary" VkImageView that shaders use to sample from the image. - mPrimaryViewRange = mFullViewRange; + mPrimaryViewRange = state->mFullViewRange; // Go ahead and create the primary image view. - getImageView(mPrimaryViewRange, mViewType, mSwizzle); - - // Transition the layout of each image slice that might be used as a render target. - // We do not transition images that are merely SAMPLEABLE, this is deferred until upload time - // because we do not know how many layers and levels will actually be used. - if (imageInfo.usage - & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT - | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) { - VulkanCommandBuffer& commands = mCommands->get(); - VkCommandBuffer const cmdbuf = commands.buffer(); - commands.acquire(this); - transitionLayout(cmdbuf, mFullViewRange, imgutil::getDefaultLayout(imageInfo.usage)); - } + getImageView(mPrimaryViewRange, state->mViewType, mSwizzle); + + VulkanCommandBuffer& commandsBuf = state->mCommands->get(); + commandsBuf.acquire(this); + transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage)); +} + +VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, + VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount) + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + VulkanResource(VulkanResourceType::TEXTURE), + mAllocator(handleAllocator) +{ + mState = src->mState; + auto* state = getSharedState(); + + state->refs++; + mPrimaryViewRange = src->mPrimaryViewRange; + mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel; + mPrimaryViewRange.levelCount = levelCount; +} + +VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, + VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VulkanTexture const* src, VkComponentMapping swizzle) + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + VulkanResource(VulkanResourceType::TEXTURE), + mAllocator(handleAllocator) { + mState = src->mState; + auto* state = getSharedState(); + state->refs++; + mPrimaryViewRange = src->mPrimaryViewRange; + mSwizzle = composeSwizzle(src->mSwizzle, swizzle); } VulkanTexture::~VulkanTexture() { - if (mTextureImageMemory != VK_NULL_HANDLE) { - vkDestroyImage(mDevice, mTextureImage, VKALLOC); - vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC); - } - for (auto entry : mCachedImageViews) { - vkDestroyImageView(mDevice, entry.second, VKALLOC); + auto* const state = getSharedState(); + state->refs--; + if (state->refs == 0) { + if (state->mTextureImageMemory != VK_NULL_HANDLE) { + vkDestroyImage(state->mDevice, state->mTextureImage, VKALLOC); + vkFreeMemory(state->mDevice, state->mTextureImageMemory, VKALLOC); + } + for (auto entry: state->mCachedImageViews) { + vkDestroyImageView(state->mDevice, entry.second, VKALLOC); + } + mAllocator->destruct(mState); } } @@ -251,7 +365,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt assert_invariant(width <= this->width && height <= this->height); assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP || target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1)); - + auto* const state = getSharedState(); const PixelBufferDescriptor* hostData = &data; PixelBufferDescriptor reshapedData; @@ -264,7 +378,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt // If format conversion is both required and supported, use vkCmdBlitImage. const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type); - const VkFormat deviceFormat = getVkFormatLinear(mVkFormat); + const VkFormat deviceFormat = getVkFormatLinear(state->mVkFormat); if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) { assert_invariant(xoffset == 0 && yoffset == 0 && zoffset == 0 && "Offsets not yet supported when format conversion is required."); @@ -276,14 +390,14 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt // Otherwise, use vkCmdCopyBufferToImage. void* mapped = nullptr; - VulkanStage const* stage = mStagePool.acquireStage(hostData->size); + VulkanStage const* stage = state->mStagePool.acquireStage(hostData->size); assert_invariant(stage->memory); - vmaMapMemory(mAllocator, stage->memory, &mapped); + vmaMapMemory(state->mAllocator, stage->memory, &mapped); memcpy(mapped, hostData->buffer, hostData->size); - vmaUnmapMemory(mAllocator, stage->memory); - vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size); + vmaUnmapMemory(state->mAllocator, stage->memory); + vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData->size); - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = state->mCommands->get(); VkCommandBuffer const cmdbuf = commands.buffer(); commands.acquire(this); @@ -329,24 +443,25 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt nextLayout = imgutil::getDefaultLayout(this->usage); } - transitionLayout(cmdbuf, transitionRange, newLayout); + transitionLayout(&commands, transitionRange, newLayout); - vkCmdCopyBufferToImage(cmdbuf, stage->buffer, mTextureImage, newVkLayout, 1, ©Region); + vkCmdCopyBufferToImage(cmdbuf, stage->buffer, state->mTextureImage, newVkLayout, 1, ©Region); - transitionLayout(cmdbuf, transitionRange, nextLayout); + transitionLayout(&commands, transitionRange, nextLayout); } void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height, uint32_t depth, uint32_t miplevel) { + auto* const state = getSharedState(); void* mapped = nullptr; VulkanStageImage const* stage - = mStagePool.acquireImage(hostData.format, hostData.type, width, height); - vmaMapMemory(mAllocator, stage->memory, &mapped); + = state->mStagePool.acquireImage(hostData.format, hostData.type, width, height); + vmaMapMemory(state->mAllocator, stage->memory, &mapped); memcpy(mapped, hostData.buffer, hostData.size); - vmaUnmapMemory(mAllocator, stage->memory); - vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size); + vmaUnmapMemory(state->mAllocator, stage->memory); + vmaFlushAllocation(state->mAllocator, stage->memory, 0, hostData.size); - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = state->mCommands->get(); VkCommandBuffer const cmdbuf = commands.buffer(); commands.acquire(this); @@ -368,19 +483,12 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST; VulkanLayout const oldLayout = getLayout(layer, miplevel); - transitionLayout(cmdbuf, range, newLayout); + transitionLayout(&commands, range, newLayout); vkCmdBlitImage(cmdbuf, stage->image, imgutil::getVkLayout(VulkanLayout::TRANSFER_SRC), - mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST); - - transitionLayout(cmdbuf, range, oldLayout); -} + state->mTextureImage, imgutil::getVkLayout(newLayout), 1, blitRegions, VK_FILTER_NEAREST); -void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel) { - maxMiplevel = filament::math::min(int(maxMiplevel), int(this->levels - 1)); - mPrimaryViewRange.baseMipLevel = minMiplevel; - mPrimaryViewRange.levelCount = maxMiplevel - minMiplevel + 1; - getImageView(mPrimaryViewRange, mViewType, mSwizzle); + transitionLayout(&commands, range, oldLayout); } VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) { @@ -399,35 +507,44 @@ VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) { - ImageViewKey const key {range, viewType, swizzle}; - auto iter = mCachedImageViews.find(key); - if (iter != mCachedImageViews.end()) { + auto* const state = getSharedState(); + VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle }; + auto iter = state->mCachedImageViews.find(key); + if (iter != state->mCachedImageViews.end()) { return iter->second; } VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, - .image = mTextureImage, + .image = state->mTextureImage, .viewType = viewType, - .format = mVkFormat, + .format = state->mVkFormat, .components = swizzle, .subresourceRange = range, }; VkImageView imageView; - vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView); - mCachedImageViews.emplace(key, imageView); + vkCreateImageView(state->mDevice, &viewInfo, VKALLOC, &imageView); + state->mCachedImageViews.emplace(key, imageView); return imageView; } VkImageAspectFlags VulkanTexture::getImageAspect() const { // Helper function in VulkanUtility - return filament::backend::getImageAspect(mVkFormat); + auto* const state = getSharedState(); + return filament::backend::getImageAspect(state->mVkFormat); } -void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubresourceRange& range, - VulkanLayout newLayout) { +bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands, + const VkImageSubresourceRange& range, VulkanLayout newLayout) { + return transitionLayout(commands->buffer(), commands->fence, range, newLayout); +} +bool VulkanTexture::transitionLayout( + VkCommandBuffer cmdbuf, std::shared_ptr fence, + const VkImageSubresourceRange& range, + VulkanLayout newLayout) { + auto* const state = getSharedState(); VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel); uint32_t const firstLayer = range.baseArrayLayer; @@ -438,7 +555,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres // If we are transitioning more than one layer/level (slice), we need to know whether they are // all of the same layer. If not, we need to transition slice-by-slice. Otherwise it would // trigger the validation layer saying that the `oldLayout` provided is incorrect. - // TODO: transition by multiple slices with more sophiscated range finding. + // TODO: transition by multiple slices with more sophisticated range finding. bool transitionSliceBySlice = false; for (uint32_t i = firstLayer; i < lastLayer; ++i) { for (uint32_t j = firstLevel; j < lastLevel; ++j) { @@ -449,50 +566,109 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres } } -#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) - FVK_LOGD << "transition texture=" << mTextureImage - << " (" << range.baseArrayLayer - << "," << range.baseMipLevel << ")" - << " count=(" << range.layerCount - << "," << range.levelCount << ")" - << " from=" << oldLayout << " to=" << newLayout - << " format=" << mVkFormat - << " depth=" << isVkDepthFormat(mVkFormat) - << " slice-by-slice=" << transitionSliceBySlice - << utils::io::endl; -#endif - + bool hasTransitions = false; if (transitionSliceBySlice) { for (uint32_t i = firstLayer; i < lastLayer; ++i) { for (uint32_t j = firstLevel; j < lastLevel; ++j) { VulkanLayout const layout = getLayout(i, j); - imgutil::transitionLayout(cmdbuf, { - .image = mTextureImage, - .oldLayout = layout, - .newLayout = newLayout, - .subresources = { - .aspectMask = range.aspectMask, - .baseMipLevel = j, - .levelCount = 1, - .baseArrayLayer = i, - .layerCount = 1, - }, - }); + if (layout == newLayout) { + continue; + } + hasTransitions = hasTransitions || imgutil::transitionLayout(cmdbuf, { + .image = state->mTextureImage, + .oldLayout = layout, + .newLayout = newLayout, + .subresources = { + .aspectMask = range.aspectMask, + .baseMipLevel = j, + .levelCount = 1, + .baseArrayLayer = i, + .layerCount = 1, + }, + }); } } - } else { - imgutil::transitionLayout(cmdbuf, { - .image = mTextureImage, + } else if (newLayout != oldLayout) { + hasTransitions = imgutil::transitionLayout(cmdbuf, { + .image = state->mTextureImage, .oldLayout = oldLayout, .newLayout = newLayout, .subresources = range, }); } + // Even if we didn't carry out the transition, we should assume that the new layout is defined + // through this call. setLayout(range, newLayout); + + if (hasTransitions) { + state->mTransitionFence = fence; + +#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) + FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer + << "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << "," + << range.levelCount << ")" << " from=" << oldLayout << " to=" << newLayout + << " format=" << state->mVkFormat << " depth=" << isVkDepthFormat(state->mVkFormat) + << " slice-by-slice=" << transitionSliceBySlice << utils::io::endl; +#endif + } else { +#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) + FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer + << "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << "," + << range.levelCount << ")" << " to=" << newLayout + << " is skipped because of no change in layout" << utils::io::endl; +#endif + } + return hasTransitions; +} + +void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands, + VkImageSubresourceRange const& range) { + VkCommandBuffer const cmdbuf = commands->buffer(); + auto* const state = getSharedState(); + VkImageLayout const layout = + imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel)); + VkImageMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = layout, + .newLayout = layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = state->mTextureImage, + .subresourceRange = range, + }; + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, 0, nullptr, 0, nullptr, 1, &barrier); +} + +void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands, + VkImageSubresourceRange const& range) { + VkCommandBuffer const cmdbuf = commands->buffer(); + auto* const state = getSharedState(); + VkImageLayout const layout + = imgutil::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel)); + VkImageMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = layout, + .newLayout = layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = state->mTextureImage, + .subresourceRange = range, + }; + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); } -void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout) { +void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout) { + auto* const state = getSharedState(); uint32_t const firstLayer = range.baseArrayLayer; uint32_t const lastLayer = firstLayer + range.layerCount; uint32_t const firstLevel = range.baseMipLevel; @@ -505,32 +681,34 @@ void VulkanTexture::setLayout(const VkImageSubresourceRange& range, VulkanLayout for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) { uint32_t const first = (layer << 16) | firstLevel; uint32_t const last = (layer << 16) | lastLevel; - mSubresourceLayouts.clear(first, last); + state->mSubresourceLayouts.clear(first, last); } } else { for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) { uint32_t const first = (layer << 16) | firstLevel; uint32_t const last = (layer << 16) | lastLevel; - mSubresourceLayouts.add(first, last, newLayout); + state->mSubresourceLayouts.add(first, last, newLayout); } } } VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const { assert_invariant(level <= 0xffff && layer <= 0xffff); + auto* const state = getSharedState(); const uint32_t key = (layer << 16) | level; - if (!mSubresourceLayouts.has(key)) { + if (!state->mSubresourceLayouts.has(key)) { return VulkanLayout::UNDEFINED; } - return mSubresourceLayouts.get(key); + return state->mSubresourceLayouts.get(key); } #if FVK_ENABLED(FVK_DEBUG_TEXTURE) void VulkanTexture::print() const { + auto* const state = getSharedState(); uint32_t const firstLayer = 0; - uint32_t const lastLayer = firstLayer + mFullViewRange.layerCount; + uint32_t const lastLayer = firstLayer + state->mFullViewRange.layerCount; uint32_t const firstLevel = 0; - uint32_t const lastLevel = firstLevel + mFullViewRange.levelCount; + uint32_t const lastLevel = firstLevel + state->mFullViewRange.levelCount; for (uint32_t layer = firstLayer; layer < lastLayer; ++layer) { for (uint32_t level = firstLevel; level < lastLevel; ++level) { @@ -539,16 +717,16 @@ void VulkanTexture::print() const { layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) && level >= mPrimaryViewRange.baseMipLevel && level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount); - FVK_LOGD << "[" << mTextureImage << "]: (" << layer << "," << level + FVK_LOGD << "[" << state->mTextureImage << "]: (" << layer << "," << level << ")=" << getLayout(layer, level) << " primary=" << primary << utils::io::endl; } } - for (auto view: mCachedImageViews) { + for (auto view: state->mCachedImageViews) { auto& range = view.first.range; - FVK_LOGD << "[" << mTextureImage << ", imageView=" << view.second << "]=>" + FVK_LOGD << "[" << state->mTextureImage << ", imageView=" << view.second << "]=>" << " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << "," << range.levelCount << ")" << " aspect=" << range.aspectMask << " viewType=" << view.first.type diff --git a/filament/backend/src/vulkan/VulkanTexture.h b/filament/backend/src/vulkan/VulkanTexture.h index df995fc7ed8..e3d1babbbd0 100644 --- a/filament/backend/src/vulkan/VulkanTexture.h +++ b/filament/backend/src/vulkan/VulkanTexture.h @@ -30,20 +30,87 @@ namespace filament::backend { +class VulkanResourceAllocator; + +struct VulkanTextureState : public VulkanResource { + VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanStagePool& stagePool, + VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount); + + struct ImageViewKey { + VkImageSubresourceRange range; // 4 * 5 bytes + VkImageViewType type; // 4 bytes + VkComponentMapping swizzle; // 4 * 4 bytes + + bool operator==(ImageViewKey const& k2) const { + auto const& k1 = *this; + return k1.range.aspectMask == k2.range.aspectMask + && k1.range.baseMipLevel == k2.range.baseMipLevel + && k1.range.levelCount == k2.range.levelCount + && k1.range.baseArrayLayer == k2.range.baseArrayLayer + && k1.range.layerCount == k2.range.layerCount && k1.type == k2.type + && k1.swizzle.r == k2.swizzle.r && k1.swizzle.g == k2.swizzle.g + && k1.swizzle.b == k2.swizzle.b && k1.swizzle.a == k2.swizzle.a; + } + }; + // No implicit padding allowed due to it being a hash key. + static_assert(sizeof(ImageViewKey) == 40); + + using ImageViewHash = utils::hash::MurmurHashFn; + + uint32_t refs = 1; + + // The texture with the sidecar owns the sidecar. + std::unique_ptr mSidecarMSAA; + VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE; + + VkFormat const mVkFormat; + VkImageViewType const mViewType; + VkImageSubresourceRange const mFullViewRange; + + VkImage mTextureImage = VK_NULL_HANDLE; + + // Track the image layout of each subresource using a sparse range map. + utils::RangeMap mSubresourceLayouts; + + std::unordered_map mCachedImageViews; + VulkanStagePool& mStagePool; + VkDevice mDevice; + VmaAllocator mAllocator; + VulkanCommands* mCommands; + std::shared_ptr mTransitionFence; +}; + + struct VulkanTexture : public HwTexture, VulkanResource { // Standard constructor for user-facing textures. VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, - VmaAllocator allocator, VulkanCommands* commands, SamplerType target, uint8_t levels, + VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, - TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false, - VkComponentMapping swizzle = {}); + TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false); // Specialized constructor for internally created textures (e.g. from a swap chain) // The texture will never destroy the given VkImage, but it does manages its subresources. - VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image, + VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false); + // Constructor for creating a texture view for wrt specific mip range + VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, + VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount); + + // Constructor for creating a texture view for swizzle. + VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, + VmaAllocator allocator, VulkanCommands* commands, + VulkanResourceAllocator* handleAllocator, + VulkanTexture const* src, VkComponentMapping swizzle); + ~VulkanTexture(); // Uploads data into a subregion of a 2D or 3D texture. @@ -52,18 +119,17 @@ struct VulkanTexture : public HwTexture, VulkanResource { // Returns the primary image view, which is used for shader sampling. VkImageView getPrimaryImageView() { - return getImageView(mPrimaryViewRange, mViewType, mSwizzle); + VulkanTextureState* state = getSharedState(); + return getImageView(mPrimaryViewRange, state->mViewType, mSwizzle); } - VkImageViewType getViewType() const { return mViewType; } - - // Sets the min/max range of miplevels in the primary image view. - void setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel); + VkImageViewType getViewType() const { + VulkanTextureState const* state = getSharedState(); + return state->mViewType; + } VkImageSubresourceRange getPrimaryViewRange() const { return mPrimaryViewRange; } - VkImageSubresourceRange getFullViewRange() const { return mFullViewRange; } - VulkanLayout getPrimaryImageLayout() const { return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel); } @@ -83,56 +149,61 @@ struct VulkanTexture : public HwTexture, VulkanResource { // view type. Swizzle option does not matter in this case. VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type); - VkFormat getVkFormat() const { return mVkFormat; } - VkImage getVkImage() const { return mTextureImage; } + VkFormat getVkFormat() const { + VulkanTextureState const* state = getSharedState(); + return state->mVkFormat; + } + VkImage getVkImage() const { + VulkanTextureState const* state = getSharedState(); + return state->mTextureImage; + } VulkanLayout getLayout(uint32_t layer, uint32_t level) const; void setSidecar(VulkanTexture* sidecar) { - mSidecarMSAA.reset(sidecar); + VulkanTextureState* state = getSharedState(); + state->mSidecarMSAA.reset(sidecar); } VulkanTexture* getSidecar() const { - return mSidecarMSAA.get(); + VulkanTextureState const* state = getSharedState(); + return state->mSidecarMSAA.get(); } - void transitionLayout(VkCommandBuffer commands, const VkImageSubresourceRange& range, + bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range, VulkanLayout newLayout); + bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr fence, + VkImageSubresourceRange const& range, VulkanLayout newLayout); + + void attachmentToSamplerBarrier(VulkanCommandBuffer* commands, + VkImageSubresourceRange const& range); + + void samplerToAttachmentBarrier(VulkanCommandBuffer* commands, + VkImageSubresourceRange const& range); + // Returns the preferred data plane of interest for all image views. // For now this always returns either DEPTH or COLOR. VkImageAspectFlags getImageAspect() const; // For implicit transition like the end of a render pass, we need to be able to set the layout // manually (outside of calls to transitionLayout). - void setLayout(const VkImageSubresourceRange& range, VulkanLayout newLayout); + void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout); + + bool transitionReady() { + VulkanTextureState* state = getSharedState(); + auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS; + state->mTransitionFence.reset(); + return res; + } #if FVK_ENABLED(FVK_DEBUG_TEXTURE) void print() const; #endif private: - - struct ImageViewKey { - VkImageSubresourceRange range; // 4 * 5 bytes - VkImageViewType type; // 4 bytes - VkComponentMapping swizzle; // 4 * 4 bytes - - bool operator==(ImageViewKey const& k2) const { - auto const& k1 = *this; - return k1.range.aspectMask == k2.range.aspectMask - && k1.range.baseMipLevel == k2.range.baseMipLevel - && k1.range.levelCount == k2.range.levelCount - && k1.range.baseArrayLayer == k2.range.baseArrayLayer - && k1.range.layerCount == k2.range.layerCount && k1.type == k2.type - && k1.swizzle.r == k2.swizzle.r && k1.swizzle.g == k2.swizzle.g - && k1.swizzle.b == k2.swizzle.b && k1.swizzle.a == k2.swizzle.a; - } - }; - // No implicit padding allowed due to it being a hash key. - static_assert(sizeof(ImageViewKey) == 40); - - using ImageViewHash = utils::hash::MurmurHashFn; + VulkanTextureState* getSharedState(); + VulkanTextureState const* getSharedState() const; // Gets or creates a cached VkImageView for a range of miplevels, array layers, viewType, and // swizzle (or not). @@ -142,28 +213,15 @@ struct VulkanTexture : public HwTexture, VulkanResource { void updateImageWithBlit(const PixelBufferDescriptor& hostData, uint32_t width, uint32_t height, uint32_t depth, uint32_t miplevel); - // The texture with the sidecar owns the sidecar. - std::unique_ptr mSidecarMSAA; - const VkFormat mVkFormat; - const VkImageViewType mViewType; - const VkComponentMapping mSwizzle; - VkImage mTextureImage = VK_NULL_HANDLE; - VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE; - - // Track the image layout of each subresource using a sparse range map. - utils::RangeMap mSubresourceLayouts; + VulkanResourceAllocator* const mAllocator; - VkImageSubresourceRange mFullViewRange; + Handle mState; // Track the range of subresources that define the "primary" image view, which is the special // image view that gets bound to an actual texture sampler. VkImageSubresourceRange mPrimaryViewRange; - std::unordered_map mCachedImageViews; - VulkanStagePool& mStagePool; - VkDevice mDevice; - VmaAllocator mAllocator; - VulkanCommands* mCommands; + VkComponentMapping mSwizzle {}; }; } // namespace filament::backend diff --git a/filament/backend/src/vulkan/VulkanUtility.cpp b/filament/backend/src/vulkan/VulkanUtility.cpp index 3b8e5f851a2..889e2bdd2f3 100644 --- a/filament/backend/src/vulkan/VulkanUtility.cpp +++ b/filament/backend/src/vulkan/VulkanUtility.cpp @@ -577,7 +577,7 @@ uint32_t getComponentCount(VkFormat format) { return {}; } -VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]) { +VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]) { VkComponentMapping map; VkComponentSwizzle* dst = &map.r; for (int i = 0; i < 4; ++i, ++dst) { diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index cb780cf8857..33be80d3fa8 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -19,6 +19,7 @@ #include +#include #include #include @@ -38,7 +39,7 @@ VkCullModeFlags getCullMode(CullingMode mode); VkFrontFace getFrontFace(bool inverseFrontFaces); PixelDataType getComponentType(VkFormat format); uint32_t getComponentCount(VkFormat format); -VkComponentMapping getSwizzleMap(TextureSwizzle swizzle[4]); +VkComponentMapping getSwizzleMap(TextureSwizzle const swizzle[4]); VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags); bool equivalent(const VkRect2D& a, const VkRect2D& b); @@ -405,12 +406,13 @@ constexpr VkFormat ALL_VK_FORMATS[] = { VK_FORMAT_R16G16_S10_5_NV, }; -// An Array that will be fixed capacity, but the "size" (as in user added elements) is variable. -// Note that this class is movable. +// An Array that will be statically fixed in capacity, but the "size" (as in user added elements) is +// variable. Note that this class is movable. template class CappedArray { private: using FixedSizeArray = std::array; + public: using const_iterator = typename FixedSizeArray::const_iterator; using iterator = typename FixedSizeArray::iterator; @@ -448,6 +450,20 @@ class CappedArray { return mArray.cend(); } + inline iterator begin() { + if (mInd == 0) { + return mArray.end(); + } + return mArray.begin(); + } + + inline iterator end() { + if (mInd > 0 && mInd < CAPACITY) { + return mArray.begin() + mInd; + } + return mArray.end(); + } + inline T back() { assert_invariant(mInd > 0); return *(mArray.begin() + mInd); @@ -512,125 +528,28 @@ class CappedArray { uint32_t mInd = 0; }; -// TODO: ok to remove once Filament-side API is complete -namespace descset { - -// Used to describe the descriptor binding in shader stages. We assume that the binding index does -// not exceed 31. We also assume that we have two shader stages - vertex and fragment. The below -// types and struct are used across VulkanDescriptorSet and VulkanProgram. -using UniformBufferBitmask = uint32_t; -using SamplerBitmask = uint64_t; +using UniformBufferBitmask = utils::bitset64; +using SamplerBitmask = utils::bitset64; // We only have at most one input attachment, so this bitmask exists only to make the code more // general. -using InputAttachmentBitmask = uint8_t; - -constexpr UniformBufferBitmask UBO_VERTEX_STAGE = 0x1; -constexpr UniformBufferBitmask UBO_FRAGMENT_STAGE = (0x1ULL << (sizeof(UniformBufferBitmask) * 4)); -constexpr SamplerBitmask SAMPLER_VERTEX_STAGE = 0x1; -constexpr SamplerBitmask SAMPLER_FRAGMENT_STAGE = (0x1ULL << (sizeof(SamplerBitmask) * 4)); -constexpr InputAttachmentBitmask INPUT_ATTACHMENT_VERTEX_STAGE = 0x1; -constexpr InputAttachmentBitmask INPUT_ATTACHMENT_FRAGMENT_STAGE = - (0x1ULL << (sizeof(InputAttachmentBitmask) * 4)); +using InputAttachmentBitmask = utils::bitset64; template -static constexpr Bitmask getVertexStage() noexcept { - if constexpr (std::is_same_v) { - return UBO_VERTEX_STAGE; - } - if constexpr (std::is_same_v) { - return SAMPLER_VERTEX_STAGE; - } - if constexpr (std::is_same_v) { - return INPUT_ATTACHMENT_VERTEX_STAGE; - } +static constexpr uint8_t getVertexStageShift() noexcept { + // We assume the bottom half of bits are for vertex stages. + return 0; } template -static constexpr Bitmask getFragmentStage() noexcept { - if constexpr (std::is_same_v) { - return UBO_FRAGMENT_STAGE; - } - if constexpr (std::is_same_v) { - return SAMPLER_FRAGMENT_STAGE; - } - if constexpr (std::is_same_v) { - return INPUT_ATTACHMENT_FRAGMENT_STAGE; - } +static constexpr uint8_t getFragmentStageShift() noexcept { + // We assume the top half of bits are for fragment stages. + return sizeof(Bitmask) * 4; } -typedef enum ShaderStageFlags2 : uint8_t { - NONE = 0, - VERTEX = 0x1, - FRAGMENT = 0x2, -} ShaderStageFlags2; - -enum class DescriptorType : uint8_t { - UNIFORM_BUFFER, - SAMPLER, - INPUT_ATTACHMENT, -}; - -enum class DescriptorFlags : uint8_t { - NONE = 0x00, - DYNAMIC_OFFSET = 0x01 -}; - -struct DescriptorSetLayoutBinding { - DescriptorType type; - ShaderStageFlags2 stageFlags; - uint8_t binding; - DescriptorFlags flags; - uint16_t count; -}; - -struct DescriptorSetLayout { - utils::FixedCapacityVector bindings; -}; - -} // namespace descset - -namespace { -// Use constexpr to statically generate a bit count table for 8-bit numbers. -struct _BitCountHelper { - constexpr _BitCountHelper() : data{} { - for (uint16_t i = 0; i < 256; ++i) { - data[i] = 0; - for (auto j = i; j > 0; j /= 2) { - if (j & 1) { - data[i]++; - } - } - } - } +// We have at most 4 descriptor sets. This is to indicate which ones are active. +using DescriptorSetMask = utils::bitset8; - template - constexpr uint8_t count(MaskType num) { - uint8_t count = 0; - for (uint8_t i = 0; i < sizeof(MaskType) * 8; i+=8) { - count += data[(num >> i) & 0xFF]; - } - return count; - } - -private: - uint8_t data[256]; -}; -} // namespace anonymous - -template -inline uint8_t countBits(MaskType num) { - static _BitCountHelper BitCounter = {}; - return BitCounter.count(num); -} - -// This is useful for counting the total number of descriptors for both vertex and fragment stages. -template -inline MaskType collapseStages(MaskType mask) { - constexpr uint8_t NBITS_DIV_2 = sizeof(MaskType) * 4; - // First zero out the top-half and then or the bottom-half against the original top-half. - return ((mask << NBITS_DIV_2) >> NBITS_DIV_2) | (mask >> NBITS_DIV_2); -} } // namespace filament::backend diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp index 5ae7477af02..eb72d1be908 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp @@ -26,6 +26,7 @@ #include +#include #include #include #include @@ -34,22 +35,12 @@ namespace filament::backend { namespace { -// This assumes we have at most 32-bound samplers, 10 UBOs and, 1 input attachment. -// TODO: Obsolete after [GDSR]. -constexpr uint8_t MAX_SAMPLER_BINDING = 32; -constexpr uint8_t MAX_UBO_BINDING = 10; -constexpr uint8_t MAX_INPUT_ATTACHMENT_BINDING = 1; -constexpr uint8_t MAX_BINDINGS = - MAX_SAMPLER_BINDING + MAX_UBO_BINDING + MAX_INPUT_ATTACHMENT_BINDING; - -using Bitmask = VulkanDescriptorSetLayout::Bitmask; +using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask; using DescriptorCount = VulkanDescriptorSetLayout::Count; -using UBOMap = std::array, MAX_UBO_BINDING>; -using SamplerMap = - std::array, MAX_SAMPLER_BINDING>; -using BitmaskHashFn = utils::hash::MurmurHashFn; -struct BitmaskEqual { - bool operator()(Bitmask const& k1, Bitmask const& k2) const { +using DescriptorSetLayoutArray = VulkanDescriptorSetManager::DescriptorSetLayoutArray; +using BitmaskGroupHashFn = utils::hash::MurmurHashFn; +struct BitmaskGroupEqual { + bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; } }; @@ -71,15 +62,12 @@ struct BitmaskEqual { // single pool without too much waste. class DescriptorPool { public: - DescriptorPool(VkDevice device, VulkanResourceAllocator* allocator, - DescriptorCount const& count, uint16_t capacity) + DescriptorPool(VkDevice device, DescriptorCount const& count, uint16_t capacity) : mDevice(device), - mAllocator(allocator), mCount(count), mCapacity(capacity), mSize(0), - mUnusedCount(0), - mDisableRecycling(false) { + mUnusedCount(0) { DescriptorCount const actual = mCount * capacity; VkDescriptorPoolSize sizes[4]; uint8_t npools = 0; @@ -122,45 +110,36 @@ class DescriptorPool { DescriptorPool& operator=(DescriptorPool const&) = delete; ~DescriptorPool() { - // Note that these have to manually destroyed because they were not explicitly ref-counted. - for (auto const& [mask, sets]: mUnused) { - for (auto set: sets) { - mAllocator->destruct(set); - } - } vkDestroyDescriptorPool(mDevice, mPool, VKALLOC); } - void disableRecycling() noexcept { - mDisableRecycling = true; - } - uint16_t const& capacity() { return mCapacity; } // A convenience method for checking if this pool can allocate sets for a given layout. - inline bool canAllocate(VulkanDescriptorSetLayout* layout) { - return layout->count == mCount; + inline bool canAllocate(DescriptorCount const& count) { + return count == mCount; } - Handle obtainSet(VulkanDescriptorSetLayout* layout) { - if (UnusedSetMap::iterator itr = mUnused.find(layout->bitmask); itr != mUnused.end()) { + VkDescriptorSet obtainSet(VkDescriptorSetLayout vklayout) { + auto itr = findSets(vklayout); + if (itr != mUnused.end()) { // If we don't have any unused, then just return an empty handle. if (itr->second.empty()) { - return {}; + return VK_NULL_HANDLE; } - std::vector>& sets = itr->second; + std::vector& sets = itr->second; auto set = sets.back(); sets.pop_back(); mUnusedCount--; return set; } if (mSize + 1 > mCapacity) { - return {}; + return VK_NULL_HANDLE; } // Creating a new set - VkDescriptorSetLayout layouts[1] = {layout->vklayout}; + VkDescriptorSetLayout layouts[1] = {vklayout}; VkDescriptorSetAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pNext = nullptr, @@ -174,31 +153,33 @@ class DescriptorPool { << "Failed to allocate descriptor set code=" << result << " size=" << mSize << " capacity=" << mCapacity << " count=" << mUnusedCount; mSize++; - return createSet(layout->bitmask, vkSet); + return vkSet; + } + + void recycle(VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) { + // We are recycling - release the set back into the pool. Note that the + // vk handle has not changed, but we need to change the backend handle to allow + // for proper refcounting of resources referenced in this set. + auto itr = findSets(vklayout); + if (itr != mUnused.end()) { + itr->second.push_back(vkSet); + } else { + mUnused.push_back(std::make_pair(vklayout, std::vector {vkSet})); + } + mUnusedCount++; } private: - Handle createSet(Bitmask const& layoutMask, VkDescriptorSet vkSet) { - return mAllocator->initHandle(mAllocator, vkSet, - [this, layoutMask, vkSet]() { - if (mDisableRecycling) { - return; - } - // We are recycling - release the set back into the pool. Note that the - // vk handle has not changed, but we need to change the backend handle to allow - // for proper refcounting of resources referenced in this set. - auto setHandle = createSet(layoutMask, vkSet); - if (auto itr = mUnused.find(layoutMask); itr != mUnused.end()) { - itr->second.push_back(setHandle); - } else { - mUnused[layoutMask].push_back(setHandle); - } - mUnusedCount++; - }); + using UnusedSets = std::pair>; + using UnusedSetMap = std::vector; + + inline UnusedSetMap::iterator findSets(VkDescriptorSetLayout vklayout) { + return std::find_if(mUnused.begin(), mUnused.end(), [vklayout](auto const& value) { + return value.first == vklayout; + }); } VkDevice mDevice; - VulkanResourceAllocator* mAllocator; VkDescriptorPool mPool; DescriptorCount const mCount; uint16_t const mCapacity; @@ -208,12 +189,8 @@ class DescriptorPool { // Tracks the number of in-use descriptor sets. uint16_t mUnusedCount; - // This maps a layout ot a list of descriptor sets allocated for that layout. - using UnusedSetMap = std::unordered_map>, - BitmaskHashFn, BitmaskEqual>; + // This maps a layout to a list of descriptor sets allocated for that layout. UnusedSetMap mUnused; - - bool mDisableRecycling; }; // This is an ever-expanding pool of sets where it @@ -225,17 +202,17 @@ class DescriptorInfinitePool { static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5; public: - DescriptorInfinitePool(VkDevice device, VulkanResourceAllocator* allocator) - : mDevice(device), - mAllocator(allocator) {} + DescriptorInfinitePool(VkDevice device) + : mDevice(device) {} - Handle obtainSet(VulkanDescriptorSetLayout* layout) { + VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) { + auto const vklayout = layout->getVkLayout(); DescriptorPool* sameTypePool = nullptr; for (auto& pool: mPools) { - if (!pool->canAllocate(layout)) { + if (!pool->canAllocate(layout->count)) { continue; } - if (auto set = pool->obtainSet(layout); set) { + if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) { return set; } if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) { @@ -250,121 +227,28 @@ class DescriptorInfinitePool { } // We need to increase the set of pools by one. - mPools.push_back(std::make_unique(mDevice, mAllocator, + mPools.push_back(std::make_unique(mDevice, DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity)); auto& pool = mPools.back(); - auto ret = pool->obtainSet(layout); - assert_invariant(ret && "failed to obtain a set?"); + auto ret = pool->obtainSet(vklayout); + assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?"); return ret; } - void disableRecycling() noexcept { + void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout, + VkDescriptorSet vkSet) { for (auto& pool: mPools) { - pool->disableRecycling(); - } - } - -private: - VkDevice mDevice; - VulkanResourceAllocator* mAllocator; - std::vector> mPools; -}; - -class LayoutCache { -private: - using Key = Bitmask; - - // Make sure the key is 8-bytes aligned. - static_assert(sizeof(Key) % 8 == 0); - - using LayoutMap = std::unordered_map, BitmaskHashFn, - BitmaskEqual>; - -public: - explicit LayoutCache(VkDevice device, VulkanResourceAllocator* allocator) - : mDevice(device), - mAllocator(allocator) {} - - ~LayoutCache() { - for (auto [key, layout]: mLayouts) { - mAllocator->destruct(layout); - } - mLayouts.clear(); - } - - void destroyLayout(Handle handle) { - for (auto [key, layout]: mLayouts) { - if (layout == handle) { - mLayouts.erase(key); - break; - } - } - mAllocator->destruct(handle); - } - - Handle getLayout(descset::DescriptorSetLayout const& layout) { - Key key = Bitmask::fromBackendLayout(layout); - if (auto iter = mLayouts.find(key); iter != mLayouts.end()) { - return iter->second; - } - - VkDescriptorSetLayoutBinding toBind[MAX_BINDINGS]; - uint32_t count = 0; - - for (auto const& binding: layout.bindings) { - VkShaderStageFlags stages = 0; - VkDescriptorType type; - - if (binding.stageFlags & descset::ShaderStageFlags2::VERTEX) { - stages |= VK_SHADER_STAGE_VERTEX_BIT; - } - if (binding.stageFlags & descset::ShaderStageFlags2::FRAGMENT) { - stages |= VK_SHADER_STAGE_FRAGMENT_BIT; - } - assert_invariant(stages != 0); - - switch (binding.type) { - case descset::DescriptorType::UNIFORM_BUFFER: { - type = binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET - ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC - : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - break; - } - case descset::DescriptorType::SAMPLER: { - type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - break; - } - case descset::DescriptorType::INPUT_ATTACHMENT: { - type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; - break; - } + if (!pool->canAllocate(count)) { + continue; } - toBind[count++] = { - .binding = binding.binding, - .descriptorType = type, - .descriptorCount = 1, - .stageFlags = stages, - }; - } - - if (count == 0) { - return {}; + pool->recycle(vklayout, vkSet); + break; } - - VkDescriptorSetLayoutCreateInfo dlinfo = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = nullptr, - .bindingCount = count, - .pBindings = toBind, - }; - return (mLayouts[key] = - mAllocator->initHandle(mDevice, dlinfo, key)); } private: VkDevice mDevice; - VulkanResourceAllocator* mAllocator; - LayoutMap mLayouts; + std::vector> mPools; }; template @@ -374,632 +258,271 @@ struct Equal { } }; -// TODO: Obsolete after [GDSR]. -// No need to cache afterwards. -struct UBOKey { - uint8_t count; - uint8_t padding[5]; - uint8_t bindings[MAX_UBO_BINDING]; - // Note that the number of bytes for above is 1 + 5 + 10 = 16, which is divisible by 8. - static_assert((sizeof(count) + sizeof(padding) + sizeof(bindings)) % 8 == 0); - - VkBuffer buffers[MAX_UBO_BINDING]; - VkDeviceSize offsets[MAX_UBO_BINDING]; - VkDeviceSize sizes[MAX_UBO_BINDING]; - - static inline UBOKey key(UBOMap const& uboMap, VulkanDescriptorSetLayout* layout) { - UBOKey ret{ - .count = (uint8_t) layout->count.ubo, - }; - uint8_t count = 0; - for (uint8_t binding: layout->bindings.ubo) { - auto const& [info, obj] = uboMap[binding]; - ret.bindings[count] = binding; - if (obj) { - ret.buffers[count] = info.buffer; - ret.offsets[count] = info.offset; - ret.sizes[count] = info.range; - }// else we keep them as VK_NULL_HANDLE and 0s. - count++; +template +uint32_t createBindings(VkDescriptorSetLayoutBinding* toBind, uint32_t count, VkDescriptorType type, + Bitmask const& mask) { + Bitmask alreadySeen; + mask.forEachSetBit([&](size_t index) { + VkShaderStageFlags stages = 0; + uint32_t binding = 0; + if (index < getFragmentStageShift()) { + binding = (uint32_t) index; + stages |= VK_SHADER_STAGE_VERTEX_BIT; + auto fragIndex = index + getFragmentStageShift(); + if (mask.test(fragIndex)) { + stages |= VK_SHADER_STAGE_FRAGMENT_BIT; + alreadySeen.set(fragIndex); + } + } else if (!alreadySeen.test(index)) { + // We are in fragment stage bits + binding = (uint32_t) (index - getFragmentStageShift()); + stages |= VK_SHADER_STAGE_FRAGMENT_BIT; } - return ret; - } - - using HashFn = utils::hash::MurmurHashFn; - using Equal = Equal; -}; -// TODO: Obsolete after [GDSR]. -// No need to cache afterwards. -struct SamplerKey { - uint8_t count; - uint8_t padding[7]; - uint8_t bindings[MAX_SAMPLER_BINDING]; - static_assert(sizeof(bindings) % 8 == 0); - VkSampler sampler[MAX_SAMPLER_BINDING]; - VkImageView imageView[MAX_SAMPLER_BINDING]; - VkImageLayout imageLayout[MAX_SAMPLER_BINDING]; - - static inline SamplerKey key(SamplerMap const& samplerMap, VulkanDescriptorSetLayout* layout) { - SamplerKey ret{ - .count = (uint8_t) layout->count.sampler, - }; - uint8_t count = 0; - for (uint8_t binding: layout->bindings.sampler) { - auto const& [info, obj] = samplerMap[binding]; - ret.bindings[count] = binding; - if (obj) { - ret.sampler[count] = info.sampler; - ret.imageView[count] = info.imageView; - ret.imageLayout[count] = info.imageLayout; - } // else keep them as VK_NULL_HANDLEs. - count++; + if (stages) { + toBind[count++] = { + .binding = binding, + .descriptorType = type, + .descriptorCount = 1, + .stageFlags = stages, + }; } - return ret; - } - - using HashFn = utils::hash::MurmurHashFn; - using Equal = Equal; -}; - -// TODO: Obsolete after [GDSR]. -// No need to cache afterwards. -struct InputAttachmentKey { - // This count should be fixed. - uint8_t count; - uint8_t padding[3]; - VkImageLayout imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; - VkImageView view = VK_NULL_HANDLE; - - static inline InputAttachmentKey key(VkDescriptorImageInfo const& info, - VulkanDescriptorSetLayout* layout) { - return { - .count = (uint8_t) layout->count.inputAttachment, - .imageLayout = info.imageLayout, - .view = info.imageView, - }; - } + }); + return count; +} - using HashFn = utils::hash::MurmurHashFn; - using Equal = Equal; -}; +inline VkDescriptorSetLayout createLayout(VkDevice device, BitmaskGroup const& bitmaskGroup) { + // Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not + // refer to stack memory. + VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS]; + uint32_t count = 0; + + count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, + bitmaskGroup.dynamicUbo); + count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmaskGroup.ubo); + count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + bitmaskGroup.sampler); + count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + bitmaskGroup.inputAttachment); + + assert_invariant(count != 0 && "Need at least one binding for descriptor set layout."); + VkDescriptorSetLayoutCreateInfo dlinfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .bindingCount = count, + .pBindings = toBind, + }; -// TODO: Obsolete after [GDSR]. -// No need to cache afterwards. -template -class LRUDescriptorSetCache { -private: - static constexpr size_t MINIMUM_DESCRIPTOR_SETS = 100; - static constexpr float LRU_REDUCTION_FACTOR = .8f; - static constexpr uint64_t N_FRAMES_AGO = 20; + VkDescriptorSetLayout layout; + vkCreateDescriptorSetLayout(device, &dlinfo, VKALLOC, &layout); + return layout; +} +class DescriptorSetLayoutManager { public: - using SetPtr = VulkanDescriptorSet*; - LRUDescriptorSetCache(VulkanResourceAllocator* allocator) - : mFrame(0), - mId(0), - mResources(allocator) {} - - void gc() { - mFrame++; - - // Never gc for the first N frames. - if (mFrame < N_FRAMES_AGO) { - return; - } - - uint64_t const nFramesAgo = (mFrame - N_FRAMES_AGO) << 32; - size_t const size = mCache.size(); - if (size < MINIMUM_DESCRIPTOR_SETS) { - return; - } + DescriptorSetLayoutManager(VkDevice device) + : mDevice(device) {} - auto const& popped = mLRU.pop((size_t) (size * LRU_REDUCTION_FACTOR), nFramesAgo); - for (auto p: popped) { - mCache.erase(p); - mResources.release(p); + VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout* layout) { + auto const& bitmasks = layout->bitmask; + if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) { + return itr->second; } + auto vklayout = createLayout(mDevice, layout->bitmask); + mVkLayouts[layout->bitmask] = vklayout; + return vklayout; } - inline SetPtr get(Key const& key) { - if (auto itr = mCache.find(key); itr != mCache.end()) { - auto const& ret = itr->second; - mLRU.update(ret, (mFrame << 32) | mId++); - return ret; + ~DescriptorSetLayoutManager() { + for (auto& itr: mVkLayouts) { + vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC); } - return nullptr; - } - - void put(Key const& key, SetPtr set) { - mLRU.update(set, (mFrame << 32) | mId++); - mCache.put(key, set); - mResources.acquire(set); - } - - void erase(SetPtr set) { - mCache.erase(set); - mLRU.erase(set); - mResources.release(set); - } - - inline size_t size() const { - return mCache.size(); } private: - struct BiMap { - using ForwardMap - = std::unordered_map; - - typename ForwardMap::const_iterator find(Key const& key) const { - return forward.find(key); - } - typename ForwardMap::const_iterator end() const { - return forward.end(); - } - - inline size_t size() const { - return forward.size(); - } - - void erase(Key const& key) { - if (auto itr = forward.find(key); itr != forward.end()) { - auto const& ptr = itr->second; - forward.erase(key); - backward.erase(ptr); - } - } - - void erase(SetPtr ptr) { - if (auto itr = backward.find(ptr); itr != backward.end()) { - auto const& key = itr->second; - forward.erase(key); - backward.erase(ptr); - } - } - - void put(Key const& key, SetPtr ptr) { - forward[key] = ptr; - backward[ptr] = key; - } + VkDevice mDevice; + tsl::robin_map + mVkLayouts; +}; - SetPtr const& get(Key const& key) { - return forward[key]; - } +} // anonymous namespace +class VulkanDescriptorSetManager::Impl { +private: + struct DescriptorSetHistory { private: - ForwardMap forward; - std::unordered_map backward; - }; - - struct PriorityQueue { - void update(SetPtr const& ptr, uint64_t priority) { - if (auto itr = backward.find(ptr); itr != backward.end()) { - auto const& priority = itr->second; - forward.erase(priority); - forward[priority] = ptr; - backward[ptr] = priority; - } else { - backward[ptr] = priority; - forward[priority] = ptr; + using TextureBundle = std::pair; + public: + DescriptorSetHistory() + : dynamicUboCount(0), + mResources(nullptr) {} + + DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount, + VulkanResourceAllocator* allocator, VulkanDescriptorSet* set) + : dynamicUboMask(dynamicUbo), + dynamicUboCount(uniqueDynamicUboCount), + mResources(allocator), + mSet(set), + mBound(false) { + assert_invariant(set); + // initial state is unbound. + mResources.acquire(mSet); + unbind(); + } + + ~DescriptorSetHistory() { + if (mSet) { + mResources.clear(); } } - void erase(SetPtr ptr) { - if (auto itr = backward.find(ptr); itr != backward.end()) { - auto const& priority = itr->second; - forward.erase(priority); - backward.erase(ptr); - } - } - - void erase(uint64_t priority) { - if (auto itr = forward.find(priority); itr != forward.end()) { - auto const& ptr = itr->second; - backward.erase(ptr); - forward.erase(itr); - } + void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept { + mOffsets = std::move(offsets); + mBound = false; } - // Pop the lowest `popCount` elements that are equal or less than `priority` - utils::FixedCapacityVector pop(size_t popCount, uint64_t priority) { - utils::FixedCapacityVector evictions - = utils::FixedCapacityVector::with_capacity(popCount); - for (auto itr = forward.begin(); itr != forward.end() && popCount > 0; - itr++, popCount--) { - auto const& [ipriority, ival] = *itr; - if (ipriority > priority) { - break; - } - evictions.push_back(ival); - } - for (auto p: evictions) { - erase(p); - } - return evictions; + void write(uint8_t binding) noexcept { + mBound = false; } - private: - std::map forward; - std::unordered_map backward; - }; + // Ownership will be transfered to the commandbuffer. + void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, uint8_t index) noexcept { + VkCommandBuffer const cmdbuffer = commands->buffer(); + vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, + index, 1, &mSet->vkSet, dynamicUboCount, mOffsets.data()); - uint64_t mFrame; - uint64_t mId; - - BiMap mCache; - PriorityQueue mLRU; - VulkanResourceManager mResources; -}; - -// TODO: Obsolete after [GDSR]. -// No need to cache afterwards. -// The purpose of this class is to ensure that each descriptor set is only written to once, and can -// be re-bound if necessary. Therefore, we'll cache a set based on its content and return a cached -// set if we find a content match. -// It also uses a LRU heuristic for caching. The implementation of the heuristic is in the above -// class LRUDescriptorSetCache. -class DescriptorSetCache { -public: - DescriptorSetCache(VkDevice device, VulkanResourceAllocator* allocator) - : mAllocator(allocator), - mDescriptorPool(std::make_unique(device, allocator)), - mUBOCache(std::make_unique>(allocator)), - mSamplerCache(std::make_unique>(allocator)), - mInputAttachmentCache( - std::make_unique>(allocator)) {} - - template - inline std::pair get(Key const& key, - VulkanDescriptorSetLayout* layout) { - if constexpr (std::is_same_v) { - return get(key, *mUBOCache, layout); - } else if constexpr (std::is_same_v) { - return get(key, *mSamplerCache, layout); - } else if constexpr (std::is_same_v) { - return get(key, *mInputAttachmentCache, layout); + commands->acquire(mSet); + mResources.clear(); + mBound = true; } - PANIC_POSTCONDITION("Unexpected key type"); - } - ~DescriptorSetCache() { - // This will prevent the descriptor sets recycling when we destroy descriptor set caches. - mDescriptorPool->disableRecycling(); - - mInputAttachmentCache.reset(); - mSamplerCache.reset(); - mUBOCache.reset(); - mDescriptorPool.reset(); - } - - // gc() should be called at the end of everyframe - void gc() { - mUBOCache->gc(); - mSamplerCache->gc(); - mInputAttachmentCache->gc(); - } - -private: - template - inline std::pair get(Key const& key, - LRUDescriptorSetCache& cache, VulkanDescriptorSetLayout* layout) { - if (auto set = cache.get(key); set) { - return {set, true}; + void unbind() noexcept { + mResources.acquire(mSet); + mBound = false; } - auto set = mAllocator->handle_cast( - mDescriptorPool->obtainSet(layout)); - cache.put(key, set); - return {set, false}; - } - VulkanResourceAllocator* mAllocator; + bool bound() const noexcept { return mBound; } - // We need to heap-allocate so that the destruction can be strictly ordered. - std::unique_ptr mDescriptorPool; - std::unique_ptr> mUBOCache; - std::unique_ptr> mSamplerCache; - std::unique_ptr> mInputAttachmentCache; -}; + UniformBufferBitmask const dynamicUboMask; + uint8_t const dynamicUboCount; -} // anonymous namespace + private: + FixedSizeVulkanResourceManager<1> mResources; + VulkanDescriptorSet* mSet = nullptr; -class VulkanDescriptorSetManager::Impl { -private: - using GetPipelineLayoutFunction = VulkanDescriptorSetManager::GetPipelineLayoutFunction; - using DescriptorSetVkHandles = utils::FixedCapacityVector; + backend::DescriptorSetOffsetArray mOffsets; + bool mBound = false; + }; - static inline DescriptorSetVkHandles initDescSetHandles() { - return DescriptorSetVkHandles::with_capacity( - VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT); - } + using DescriptorSetHistoryArray = + std::array; - struct BoundState { - BoundState() - : cmdbuf(VK_NULL_HANDLE), - pipelineLayout(VK_NULL_HANDLE), - vkSets(initDescSetHandles()) {} + struct BoundInfo { + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + DescriptorSetMask setMask; + DescriptorSetHistoryArray boundSets; - inline bool operator==(BoundState const& b) const { - if (cmdbuf != b.cmdbuf || pipelineLayout != b.pipelineLayout) { + bool operator==(BoundInfo const& info) const { + if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) { return false; } - for (size_t i = 0; i < vkSets.size(); ++i) { - if (vkSets[i] != b.vkSets[i]) { - return false; + bool equal = true; + setMask.forEachSetBit([&](size_t i) { + if (boundSets[i] != info.boundSets[i]) { + equal = false; } - } - return true; - } - - inline bool operator!=(BoundState const& b) const { - return !(*this == b); + }); + return equal; } - - inline bool valid() noexcept { - return cmdbuf != VK_NULL_HANDLE; - } - - VkCommandBuffer cmdbuf; - VkPipelineLayout pipelineLayout; - DescriptorSetVkHandles vkSets; - VulkanDescriptorSetLayoutList layouts; }; - static constexpr uint8_t UBO_SET_ID = 0; - static constexpr uint8_t SAMPLER_SET_ID = 1; - static constexpr uint8_t INPUT_ATTACHMENT_SET_ID = 2; - public: - Impl(VkDevice device, VulkanResourceAllocator* allocator) + Impl(VkDevice device, VulkanResourceAllocator* resourceAllocator) : mDevice(device), - mAllocator(allocator), - mLayoutCache(device, allocator), - mDescriptorSetCache(device, allocator), - mHaveDynamicUbos(false), - mResources(allocator) {} - - VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program, - GetPipelineLayoutFunction& getPipelineLayoutFn) { - FVK_SYSTRACE_CONTEXT(); - FVK_SYSTRACE_START("bind"); - - VulkanDescriptorSetLayoutList layouts; - if (auto itr = mLayoutStash.find(program); itr != mLayoutStash.end()) { - layouts = itr->second; - } else { - auto const& layoutDescriptions = program->getLayoutDescriptionList(); - uint8_t count = 0; - for (auto const& description: layoutDescriptions) { - layouts[count++] = createLayout(description); - } - mLayoutStash[program] = layouts; - } + mResourceAllocator(resourceAllocator), + mLayoutManager(device), + mDescriptorPool(device) {} - VulkanDescriptorSetLayoutList outLayouts = layouts; - DescriptorSetVkHandles vkDescSets = initDescSetHandles(); - VkWriteDescriptorSet descriptorWrites[MAX_BINDINGS]; - uint32_t nwrites = 0; - - // Use placeholders when necessary - for (uint8_t i = 0; i < VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; ++i) { - if (!layouts[i]) { - if (i == INPUT_ATTACHMENT_SET_ID || - (i == SAMPLER_SET_ID && !layouts[INPUT_ATTACHMENT_SET_ID])) { - continue; - } - outLayouts[i] = getPlaceHolderLayout(i); - } else { - outLayouts[i] = layouts[i]; - auto p = mAllocator->handle_cast(layouts[i]); - if (!((i == UBO_SET_ID && p->bitmask.ubo) - || (i == SAMPLER_SET_ID && p->bitmask.sampler) - || (i == INPUT_ATTACHMENT_SET_ID && p->bitmask.inputAttachment - && mInputAttachment.first.texture))) { - outLayouts[i] = getPlaceHolderLayout(i); - } - } - } + // bind() is not really binding the set but just stashing until we have all the info + // (pipelinelayout). + void bind(uint8_t setIndex, VulkanDescriptorSet* set, + backend::DescriptorSetOffsetArray&& offsets) { + auto& history = mHistory[set]; + history.setOffsets(std::move(offsets)); - for (uint8_t i = 0; i < VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; ++i) { - if (!outLayouts[i]) { - continue; - } - VulkanDescriptorSetLayout* layout - = mAllocator->handle_cast(outLayouts[i]); - bool const usePlaceholder = layouts[i] != outLayouts[i]; - - auto const& [set, cached] = getSet(i, layout); - VkDescriptorSet const vkSet = set->vkSet; - commands->acquire(set); - vkDescSets.push_back(vkSet); - - // Note that we still need to bind the set, but 'cached' means that we found a set with - // the exact same content already written, and we would just bind that one instead. - // We also don't need to write to the placeholder set. - if (cached || usePlaceholder) { - continue; - } - - switch (i) { - case UBO_SET_ID: { - for (uint8_t binding: layout->bindings.ubo) { - auto const& [info, ubo] = mUboMap[binding]; - descriptorWrites[nwrites++] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = vkSet, - .dstBinding = binding, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pBufferInfo = ubo ? &info : &mPlaceHolderBufferInfo, - }; - if (ubo) { - set->resources.acquire(ubo); - } - } - break; - } - case SAMPLER_SET_ID: { - for (uint8_t binding: layout->bindings.sampler) { - auto const& [info, texture] = mSamplerMap[binding]; - descriptorWrites[nwrites++] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = vkSet, - .dstBinding = binding, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = texture ? &info : &mPlaceHolderImageInfo, - }; - if (texture) { - set->resources.acquire(texture); - } - } - break; - } - case INPUT_ATTACHMENT_SET_ID: { - descriptorWrites[nwrites++] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = vkSet, - .dstBinding = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, - .pImageInfo = &mInputAttachment.second, - }; - set->resources.acquire(mInputAttachment.first.texture); - break; - } - default: - PANIC_POSTCONDITION("Invalid set id=%d", i); - } - } - - if (nwrites) { - vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr); + auto lastHistory = mStashedSets[setIndex]; + if (lastHistory) { + lastHistory->unbind(); } + mStashedSets[setIndex] = &history; + } - VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(outLayouts, program); - VkCommandBuffer const cmdbuffer = commands->buffer(); - - BoundState state{}; - state.cmdbuf = cmdbuffer; - state.pipelineLayout = pipelineLayout; - state.vkSets = vkDescSets; - state.layouts = layouts; + void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, + DescriptorSetMask const& setMask) { + DescriptorSetHistoryArray& updateSets = mStashedSets; - if (state != mBoundState) { - vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, - vkDescSets.size(), vkDescSets.data(), 0, nullptr); - mBoundState = state; - } + // setMask indicates the set of descriptor sets the driver wants to bind, curMask is the + // actual set of sets that *needs* to be bound. + DescriptorSetMask curMask = setMask; - // Once bound, the resources are now ref'd in the descriptor set and the some resources can - // be released and the descriptor set is ref'd by the command buffer. - for (uint8_t i = 0; i < mSamplerMap.size(); ++i) { - auto const& [info, texture] = mSamplerMap[i]; - if (texture) { - mResources.release(texture); + setMask.forEachSetBit([&](size_t index) { + if (!updateSets[index] || updateSets[index]->bound()) { + curMask.unset(index); } - mSamplerMap[i] = {{}, nullptr}; - } - mInputAttachment = {}; - mHaveDynamicUbos = false; - - FVK_SYSTRACE_END(); - return pipelineLayout; - } + }); - void dynamicBind(VulkanCommandBuffer* commands, Handle uboLayout) { - if (!mHaveDynamicUbos) { + BoundInfo nextInfo = { + pipelineLayout, + setMask, + updateSets, + }; + if (curMask.none() && mLastBoundInfo == nextInfo) { return; } - FVK_SYSTRACE_CONTEXT(); - FVK_SYSTRACE_START("dynamic-bind"); - - assert_invariant(mBoundState.valid()); - assert_invariant(commands->buffer() == mBoundState.cmdbuf); - - auto layout = mAllocator->handle_cast( - mBoundState.layouts[UBO_SET_ID]); - - // Note that this is costly, instead just use dynamic UBOs with dynamic offsets. - auto const& [set, cached] = getSet(UBO_SET_ID, layout); - VkDescriptorSet const vkSet = set->vkSet; - - if (!cached) { - VkWriteDescriptorSet descriptorWrites[MAX_UBO_BINDING]; - uint8_t nwrites = 0; - - for (uint8_t binding: layout->bindings.ubo) { - auto const& [info, ubo] = mUboMap[binding]; - descriptorWrites[nwrites++] = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = vkSet, - .dstBinding = binding, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pBufferInfo = ubo ? &info : &mPlaceHolderBufferInfo, - }; - if (ubo) { - set->resources.acquire(ubo); - } - } - if (nwrites > 0) { - vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr); - } - } - commands->acquire(set); - - if (mBoundState.vkSets[UBO_SET_ID] != vkSet) { - vkCmdBindDescriptorSets(mBoundState.cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, - mBoundState.pipelineLayout, 0, 1, &vkSet, 0, nullptr); - mBoundState.vkSets[UBO_SET_ID] = vkSet; - } - mHaveDynamicUbos = false; - FVK_SYSTRACE_END(); - } - - void clearProgram(VulkanProgram* program) noexcept { - mLayoutStash.erase(program); - } - Handle createLayout( - descset::DescriptorSetLayout const& description) { - return mLayoutCache.getLayout(description); + curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) { + updateSets[index]->bind(commands, pipelineLayout, index); + }); + mLastBoundInfo = nextInfo; } - void destroyLayout(Handle layout) { - mLayoutCache.destroyLayout(layout); - } - - // Note that before [GDSR] arrives, the "update" methods stash state within this class and is - // not actually working with respect to a descriptor set. - void updateBuffer(Handle, uint8_t binding, - VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { - VkDescriptorBufferInfo const info{ - .buffer = bufferObject->buffer.getGpuBuffer(), - .offset = offset, - .range = size, + void updateBuffer(VulkanDescriptorSet* set, uint8_t binding, VulkanBufferObject* bufferObject, + VkDeviceSize offset, VkDeviceSize size) noexcept { + VkDescriptorBufferInfo const info = { + .buffer = bufferObject->buffer.getGpuBuffer(), + .offset = offset, + .range = size, }; - mUboMap[binding] = {info, bufferObject}; - mResources.acquire(bufferObject); - if (!mHaveDynamicUbos && mBoundState.valid()) { - mHaveDynamicUbos = true; + VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + auto& history = mHistory[set]; + + if (history.dynamicUboMask.test(binding)) { + type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; } + VkWriteDescriptorSet const descriptorWrite = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set->vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = type, + .pBufferInfo = &info, + }; + vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); + set->acquire(bufferObject); + history.write(binding); } - void updateSampler(Handle, uint8_t binding, VulkanTexture* texture, + void updateSampler(VulkanDescriptorSet* set, uint8_t binding, VulkanTexture* texture, VkSampler sampler) noexcept { VkDescriptorImageInfo info{ - .sampler = sampler, + .sampler = sampler, }; VkImageSubresourceRange const range = texture->getPrimaryViewRange(); VkImageViewType const expectedType = texture->getViewType(); - if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) - && expectedType == VK_IMAGE_VIEW_TYPE_2D) { + if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && + expectedType == VK_IMAGE_VIEW_TYPE_2D) { // If the sampler is part of a mipmapped depth texture, where one of the level *can* be // an attachment, then the sampler for this texture has the same view properties as a // view for an attachment. Therefore, we can use getAttachmentView to get a @@ -1009,25 +532,22 @@ class VulkanDescriptorSetManager::Impl { info.imageView = texture->getViewForType(range, expectedType); } info.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()); - mSamplerMap[binding] = {info, texture}; - mResources.acquire(texture); - } - - void updateInputAttachment(Handle, VulkanAttachment attachment) noexcept { - VkDescriptorImageInfo info = { - .imageView = attachment.getImageView(), - .imageLayout = imgutil::getVkLayout(attachment.getLayout()), + VkWriteDescriptorSet const descriptorWrite = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set->vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &info, }; - mInputAttachment = {attachment, info}; - mResources.acquire(attachment.texture); + vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); + set->acquire(texture); + mHistory[set].write(binding); } - void clearBuffer(uint32_t binding) { - auto const& [info, ubo] = mUboMap[binding]; - if (ubo) { - mResources.release(ubo); - } - mUboMap[binding] = {{}, nullptr}; + void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept { + // TOOD: fill-in this region } void setPlaceHolders(VkSampler sampler, VulkanTexture* texture, @@ -1044,96 +564,56 @@ class VulkanDescriptorSetManager::Impl { }; } - void clearState() noexcept { - mHaveDynamicUbos = false; - if (mInputAttachment.first.texture) { - mResources.release(mInputAttachment.first.texture); - } - mInputAttachment = {}; - mBoundState = {}; + void createSet(Handle handle, VulkanDescriptorSetLayout* layout) { + auto const vkSet = mDescriptorPool.obtainSet(layout); + auto const& count = layout->count; + auto const vklayout = layout->getVkLayout(); + VulkanDescriptorSet* set = + mResourceAllocator->construct(handle, mResourceAllocator, + vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) { + eraseSetFromHistory(set); + mDescriptorPool.recycle(count, vklayout, vkSet); + }); + mHistory.emplace( + std::piecewise_construct, + std::forward_as_tuple(set), + std::forward_as_tuple(layout->bitmask.dynamicUbo, layout->count.dynamicUbo, + mResourceAllocator, set)); } - inline void gc() { - mDescriptorSetCache.gc(); + void destroySet(Handle handle) { + VulkanDescriptorSet* set = mResourceAllocator->handle_cast(handle); + eraseSetFromHistory(set); } -private: - inline std::pair getSet(uint8_t const setIndex, - VulkanDescriptorSetLayout* layout) { - switch (setIndex) { - case UBO_SET_ID: { - auto key = UBOKey::key(mUboMap, layout); - return mDescriptorSetCache.get(key, layout); - } - case SAMPLER_SET_ID: { - auto key = SamplerKey::key(mSamplerMap, layout); - return mDescriptorSetCache.get(key, layout); - } - case INPUT_ATTACHMENT_SET_ID: { - auto key = InputAttachmentKey::key(mInputAttachment.second, layout); - return mDescriptorSetCache.get(key, layout); - } - default: - PANIC_POSTCONDITION("Invalid set-id=%d", setIndex); - } + void initVkLayout(VulkanDescriptorSetLayout* layout) { + layout->setVkLayout(mLayoutManager.getVkLayout(layout)); } - inline Handle getPlaceHolderLayout(uint8_t setID) { - if (mPlaceholderLayout[setID]) { - return mPlaceholderLayout[setID]; - } - descset::DescriptorSetLayout inputLayout { - .bindings = {{}}, - }; - switch (setID) { - case UBO_SET_ID: - inputLayout.bindings[0] = { - .type = descset::DescriptorType::UNIFORM_BUFFER, - .stageFlags = descset::ShaderStageFlags2::VERTEX, - .binding = 0, - .flags = descset::DescriptorFlags::NONE, - .count = 0, - }; - break; - case SAMPLER_SET_ID: - inputLayout.bindings[0] = { - .type = descset::DescriptorType::SAMPLER, - .stageFlags = descset::ShaderStageFlags2::FRAGMENT, - .binding = 0, - .flags = descset::DescriptorFlags::NONE, - .count = 0, - }; - break; - case INPUT_ATTACHMENT_SET_ID: - inputLayout.bindings[0] = { - .type = descset::DescriptorType::INPUT_ATTACHMENT, - .stageFlags = descset::ShaderStageFlags2::FRAGMENT, - .binding = 0, - .flags = descset::DescriptorFlags::NONE, - .count = 0, - }; - break; - default: - PANIC_POSTCONDITION("Unexpected set id=%d", setID); +private: + inline void eraseSetFromHistory(VulkanDescriptorSet* set) { + DescriptorSetHistory* history = &mHistory[set]; + mHistory.erase(set); + + for (uint8_t i = 0; i < mStashedSets.size(); ++i) { + if (mStashedSets[i] == history) { + mStashedSets[i] = nullptr; + } } - mPlaceholderLayout[setID] = mLayoutCache.getLayout(inputLayout); - return mPlaceholderLayout[setID]; } VkDevice mDevice; - VulkanResourceAllocator* mAllocator; - LayoutCache mLayoutCache; - DescriptorSetCache mDescriptorSetCache; - bool mHaveDynamicUbos; - UBOMap mUboMap; - SamplerMap mSamplerMap; + VulkanResourceAllocator* mResourceAllocator; + DescriptorSetLayoutManager mLayoutManager; + DescriptorInfinitePool mDescriptorPool; std::pair mInputAttachment; - VulkanResourceManager mResources; + std::unordered_map mHistory; + DescriptorSetHistoryArray mStashedSets = {}; + + BoundInfo mLastBoundInfo; + VkDescriptorBufferInfo mPlaceHolderBufferInfo; VkDescriptorImageInfo mPlaceHolderImageInfo; - std::unordered_map mLayoutStash; - BoundState mBoundState; - VulkanDescriptorSetLayoutList mPlaceholderLayout = {}; }; VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device, @@ -1146,59 +626,48 @@ void VulkanDescriptorSetManager::terminate() noexcept { mImpl = nullptr; } -void VulkanDescriptorSetManager::gc() { - mImpl->gc(); -} - -VkPipelineLayout VulkanDescriptorSetManager::bind(VulkanCommandBuffer* commands, - VulkanProgram* program, - VulkanDescriptorSetManager::GetPipelineLayoutFunction& getPipelineLayoutFn) { - return mImpl->bind(commands, program, getPipelineLayoutFn); -} - -void VulkanDescriptorSetManager::dynamicBind(VulkanCommandBuffer* commands, - Handle uboLayout) { - mImpl->dynamicBind(commands, uboLayout); +void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding, + VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { + mImpl->updateBuffer(set, binding, bufferObject, offset, size); } -void VulkanDescriptorSetManager::clearProgram(VulkanProgram* program) noexcept { - mImpl->clearProgram(program); +void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding, + VulkanTexture* texture, VkSampler sampler) noexcept { + mImpl->updateSampler(set, binding, texture, sampler); } -Handle VulkanDescriptorSetManager::createLayout( - descset::DescriptorSetLayout const& layout) { - return mImpl->createLayout(layout); +void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set, + VulkanAttachment attachment) noexcept { + mImpl->updateInputAttachment(set, attachment); } -void VulkanDescriptorSetManager::destroyLayout(Handle layout) { - mImpl->destroyLayout(layout); +void VulkanDescriptorSetManager::setPlaceHolders(VkSampler sampler, VulkanTexture* texture, + VulkanBufferObject* bufferObject) noexcept { + mImpl->setPlaceHolders(sampler, texture, bufferObject); } -void VulkanDescriptorSetManager::updateBuffer(Handle set, - uint8_t binding, VulkanBufferObject* bufferObject, VkDeviceSize offset, - VkDeviceSize size) noexcept { - mImpl->updateBuffer(set, binding, bufferObject, offset, size); +void VulkanDescriptorSetManager::bind(uint8_t setIndex, VulkanDescriptorSet* set, + backend::DescriptorSetOffsetArray&& offsets) { + return mImpl->bind(setIndex, set, std::move(offsets)); } -void VulkanDescriptorSetManager::updateSampler(Handle set, - uint8_t binding, VulkanTexture* texture, VkSampler sampler) noexcept { - mImpl->updateSampler(set, binding, texture, sampler); +void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands, + VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) { + mImpl->commit(commands, pipelineLayout, setMask); } -void VulkanDescriptorSetManager::updateInputAttachment(Handle set, VulkanAttachment attachment) noexcept { - mImpl->updateInputAttachment(set, attachment); +void VulkanDescriptorSetManager::createSet(Handle handle, + VulkanDescriptorSetLayout* layout) { + mImpl->createSet(handle, layout); } -void VulkanDescriptorSetManager::clearBuffer(uint32_t bindingIndex) { - mImpl->clearBuffer(bindingIndex); +void VulkanDescriptorSetManager::destroySet(Handle handle) { + mImpl->destroySet(handle); } -void VulkanDescriptorSetManager::setPlaceHolders(VkSampler sampler, VulkanTexture* texture, - VulkanBufferObject* bufferObject) noexcept { - mImpl->setPlaceHolders(sampler, texture, bufferObject); +void VulkanDescriptorSetManager::initVkLayout(VulkanDescriptorSetLayout* layout) { + mImpl->initVkLayout(layout); } -void VulkanDescriptorSetManager::clearState() noexcept { mImpl->clearState(); } - }// namespace filament::backend diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h index dbb969080aa..6d78f31b244 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h @@ -30,12 +30,8 @@ #include #include -#include - namespace filament::backend { -using namespace descset; - // [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to // introduce descriptor set. This PR will arrive before that change is complete. As such, some of // the methods introduced here will be obsolete, and certain logic will be generalized. @@ -45,60 +41,34 @@ class VulkanDescriptorSetManager { public: static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT; - using GetPipelineLayoutFunction = std::function; + + using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray; VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator); void terminate() noexcept; - void gc(); - - // TODO: Obsolete after [GDSR]. - // This will write/update/bind all of the descriptor set. After [GDSR], the binding for - // descriptor sets will not depend on the layout described in the program. - VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program, - GetPipelineLayoutFunction& getPipelineLayoutFn); - - // TODO: Obsolete after [GDSR]. - // This is to "dynamically" bind UBOs that might have offsets changed between pipeline binding - // and the draw call. We do this because UBOs for primitives that are part of the same - // renderable can be stored within one buffer. This can be a no-op if there were no range - // changes between the pipeline bind and the draw call. We will re-use applicable states - // provided within the bind() call, including the UBO descriptor set layout. TODO: make it a - // proper dynamic binding when Filament-side descriptor changes are completed. - void dynamicBind(VulkanCommandBuffer* commands, Handle uboLayout); - - // TODO: Obsolete after [GDSR]. - // Since we use program pointer as cache key, we need to clear the cache when it's freed. - void clearProgram(VulkanProgram* program) noexcept; - - Handle createLayout(descset::DescriptorSetLayout const& layout); - - void destroyLayout(Handle layout); - - void updateBuffer(Handle set, uint8_t binding, + void updateBuffer(VulkanDescriptorSet* set, uint8_t binding, VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept; - void updateSampler(Handle set, uint8_t binding, + void updateSampler(VulkanDescriptorSet* set, uint8_t binding, VulkanTexture* texture, VkSampler sampler) noexcept; - void updateInputAttachment(Handle set, VulkanAttachment attachment) noexcept; + void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept; - void clearBuffer(uint32_t bindingIndex); + void bind(uint8_t setIndex, VulkanDescriptorSet* set, backend::DescriptorSetOffsetArray&& offsets); + + void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, + DescriptorSetMask const& setMask); void setPlaceHolders(VkSampler sampler, VulkanTexture* texture, VulkanBufferObject* bufferObject) noexcept; - void clearState() noexcept; + void createSet(Handle handle, VulkanDescriptorSetLayout* layout); - // TODO: To be completed after [GDSR] - Handle createSet(Handle layout) { - return Handle(); - } + void destroySet(Handle handle); - // TODO: To be completed after [GDSR] - void destroySet(Handle set) {} + void initVkLayout(VulkanDescriptorSetLayout* layout); private: class Impl; @@ -108,4 +78,3 @@ class VulkanDescriptorSetManager { }// namespace filament::backend #endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H - diff --git a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp index 79ae8ed72ab..09a84d33993 100644 --- a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp +++ b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.cpp @@ -21,14 +21,15 @@ namespace filament::backend { VkPipelineLayout VulkanPipelineLayoutCache::getLayout( - VulkanDescriptorSetLayoutList const& descriptorSetLayouts, VulkanProgram* program) { + DescriptorSetLayoutArray const& descriptorSetLayouts, VulkanProgram* program) { PipelineLayoutKey key = {}; uint8_t descSetLayoutCount = 0; + key.descSetLayouts = descriptorSetLayouts; for (auto layoutHandle: descriptorSetLayouts) { - if (layoutHandle) { - auto layout = mAllocator->handle_cast(layoutHandle); - key.descSetLayouts[descSetLayoutCount++] = layout->vklayout; + if (layoutHandle == VK_NULL_HANDLE) { + break; } + descSetLayoutCount++; } // build the push constant layout key diff --git a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h index 25c401b21e7..01d7122e201 100644 --- a/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h +++ b/filament/backend/src/vulkan/caching/VulkanPipelineLayoutCache.h @@ -28,9 +28,10 @@ namespace filament::backend { class VulkanPipelineLayoutCache { public: + using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray; + VulkanPipelineLayoutCache(VkDevice device, VulkanResourceAllocator* allocator) : mDevice(device), - mAllocator(allocator), mTimestamp(0) {} void terminate() noexcept; @@ -43,20 +44,18 @@ class VulkanPipelineLayoutCache { }; struct PipelineLayoutKey { - using DescriptorSetLayoutArray = std::array; - DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3 - PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3 + DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 4 + PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3 uint16_t padding = 0; }; - static_assert(sizeof(PipelineLayoutKey) == 32); + static_assert(sizeof(PipelineLayoutKey) == 40); VulkanPipelineLayoutCache(VulkanPipelineLayoutCache const&) = delete; VulkanPipelineLayoutCache& operator=(VulkanPipelineLayoutCache const&) = delete; // A pipeline layout depends on the descriptor set layout and the push constant ranges, which // are described in the program. - VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts, + VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts, VulkanProgram* program); private: @@ -77,7 +76,6 @@ class VulkanPipelineLayoutCache { PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>; VkDevice mDevice; - VulkanResourceAllocator* mAllocator; Timestamp mTimestamp; PipelineLayoutMap mPipelineLayouts; }; diff --git a/filament/backend/test/BackendTest.cpp b/filament/backend/test/BackendTest.cpp index 5bb694c974b..6eede04f00c 100644 --- a/filament/backend/test/BackendTest.cpp +++ b/filament/backend/test/BackendTest.cpp @@ -112,9 +112,10 @@ void BackendTest::fullViewport(Viewport& viewport) { } void BackendTest::renderTriangle( - filament::backend::Handle renderTarget, - filament::backend::Handle swapChain, - filament::backend::Handle program) { + PipelineLayout const& pipelineLayout, + Handle renderTarget, + Handle swapChain, + Handle program) { RenderPassParams params = {}; fullViewport(params); params.flags.clear = TargetBufferFlags::COLOR; @@ -123,11 +124,15 @@ void BackendTest::renderTriangle( params.flags.discardEnd = TargetBufferFlags::NONE; params.viewport.height = 512; params.viewport.width = 512; - renderTriangle(renderTarget, swapChain, program, params); + renderTriangle(pipelineLayout, renderTarget, swapChain, program, params); } -void BackendTest::renderTriangle(Handle renderTarget, - Handle swapChain, Handle program, const RenderPassParams& params) { +void BackendTest::renderTriangle( + PipelineLayout const& pipelineLayout, + Handle renderTarget, + Handle swapChain, + Handle program, + const RenderPassParams& params) { auto& api = getDriverApi(); TrianglePrimitive triangle(api); @@ -138,6 +143,7 @@ void BackendTest::renderTriangle(Handle renderTarget, PipelineState state; state.program = program; + state.pipelineLayout = pipelineLayout; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; diff --git a/filament/backend/test/BackendTest.h b/filament/backend/test/BackendTest.h index 777a96f0404..981b0eaa074 100644 --- a/filament/backend/test/BackendTest.h +++ b/filament/backend/test/BackendTest.h @@ -51,10 +51,15 @@ class BackendTest : public ::testing::Test { static void fullViewport(filament::backend::RenderPassParams& params); static void fullViewport(filament::backend::Viewport& viewport); - void renderTriangle(filament::backend::Handle renderTarget, + void renderTriangle( + filament::backend::PipelineLayout const& pipelineLayout, + filament::backend::Handle renderTarget, filament::backend::Handle swapChain, filament::backend::Handle program); - void renderTriangle(filament::backend::Handle renderTarget, + + void renderTriangle( + filament::backend::PipelineLayout const& pipelineLayout, + filament::backend::Handle renderTarget, filament::backend::Handle swapChain, filament::backend::Handle program, const filament::backend::RenderPassParams& params); diff --git a/filament/backend/test/MetalTest.mm b/filament/backend/test/MetalTest.mm new file mode 100644 index 00000000000..1fda63f0c12 --- /dev/null +++ b/filament/backend/test/MetalTest.mm @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "../src/metal/MetalContext.h" + +namespace test { + +TEST(MetalDynamicOffsets, none) { + filament::backend::MetalDynamicOffsets dynamicOffsets; + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 0u); +} + +TEST(MetalDynamicOffsets, basic) { + filament::backend::MetalDynamicOffsets dynamicOffsets; + { + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 0u); + } + + { + uint32_t o[2] = { 1, 2 }; + dynamicOffsets.setOffsets(0, o, 2); + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 2); + EXPECT_EQ(offsets[0], 1); + EXPECT_EQ(offsets[1], 2); + } + + { + uint32_t o[3] = { 3, 4, 5 }; + dynamicOffsets.setOffsets(1, o, 3); + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 5); + EXPECT_EQ(offsets[0], 1); + EXPECT_EQ(offsets[1], 2); + EXPECT_EQ(offsets[2], 3); + EXPECT_EQ(offsets[3], 4); + EXPECT_EQ(offsets[4], 5); + } + + // skip descriptor set index 2 + + { + uint32_t o[1] = { 6 }; + dynamicOffsets.setOffsets(3, o, 1); + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 6); + EXPECT_EQ(offsets[0], 1); + EXPECT_EQ(offsets[1], 2); + EXPECT_EQ(offsets[2], 3); + EXPECT_EQ(offsets[3], 4); + EXPECT_EQ(offsets[4], 5); + EXPECT_EQ(offsets[5], 6); + } +} + +TEST(MetalDynamicOffsets, outOfOrder) { + filament::backend::MetalDynamicOffsets dynamicOffsets; + uint32_t o1[2] = { 2, 3 }; + dynamicOffsets.setOffsets(1, o1, 2); + uint32_t o2[2] = { 0, 1 }; + dynamicOffsets.setOffsets(0, o2, 2); + uint32_t o3[2] = { 4, 5 }; + dynamicOffsets.setOffsets(2, o3, 2); + + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 6); + EXPECT_EQ(offsets[0], 0); + EXPECT_EQ(offsets[1], 1); + EXPECT_EQ(offsets[2], 2); + EXPECT_EQ(offsets[3], 3); + EXPECT_EQ(offsets[4], 4); + EXPECT_EQ(offsets[5], 5); +}; + +TEST(MetalDynamicOffsets, removal) { + filament::backend::MetalDynamicOffsets dynamicOffsets; + uint32_t o1[2] = { 2, 3 }; + dynamicOffsets.setOffsets(1, o1, 2); + uint32_t o2[2] = { 0, 1 }; + dynamicOffsets.setOffsets(0, o2, 2); + uint32_t o3[2] = { 4, 5 }; + dynamicOffsets.setOffsets(2, o3, 2); + dynamicOffsets.setOffsets(1, nullptr, 0); + + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 4); + EXPECT_EQ(offsets[0], 0); + EXPECT_EQ(offsets[1], 1); + EXPECT_EQ(offsets[2], 4); + EXPECT_EQ(offsets[3], 5); +}; + +TEST(MetalDynamicOffsets, resize) { + filament::backend::MetalDynamicOffsets dynamicOffsets; + uint32_t o1[2] = { 2, 3 }; + dynamicOffsets.setOffsets(1, o1, 2); + uint32_t o2[2] = { 0, 1 }; + dynamicOffsets.setOffsets(0, o2, 2); + uint32_t o3[2] = { 6, 7 }; + dynamicOffsets.setOffsets(2, o3, 2); + uint32_t o4[4] = { 2, 3, 4, 5 }; + dynamicOffsets.setOffsets(1, o4, 4); + + const auto [count, offsets] = dynamicOffsets.getOffsets(); + EXPECT_EQ(count, 8); + EXPECT_EQ(offsets[0], 0); + EXPECT_EQ(offsets[1], 1); + EXPECT_EQ(offsets[2], 2); + EXPECT_EQ(offsets[3], 3); + EXPECT_EQ(offsets[4], 4); + EXPECT_EQ(offsets[5], 5); + EXPECT_EQ(offsets[6], 6); + EXPECT_EQ(offsets[7], 7); +}; + +TEST(MetalDynamicOffsets, dirty) { + filament::backend::MetalDynamicOffsets dynamicOffsets; + EXPECT_FALSE(dynamicOffsets.isDirty()); + + uint32_t o1[2] = { 2, 3 }; + dynamicOffsets.setOffsets(1, o1, 2); + EXPECT_TRUE(dynamicOffsets.isDirty()); + + dynamicOffsets.setDirty(false); + EXPECT_FALSE(dynamicOffsets.isDirty()); + + // Setting the same offsets should not mark the offsets as dirty + dynamicOffsets.setOffsets(1, o1, 2); + EXPECT_FALSE(dynamicOffsets.isDirty()); + + // Resizing the offsets should mark the offsets as dirty + uint32_t o2[3] = { 4, 5, 6 }; + dynamicOffsets.setOffsets(1, o2, 3); + EXPECT_TRUE(dynamicOffsets.isDirty()); +}; + +}; + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/filament/backend/test/ShaderGenerator.cpp b/filament/backend/test/ShaderGenerator.cpp index 7d0ad716e1d..545a6f97089 100644 --- a/filament/backend/test/ShaderGenerator.cpp +++ b/filament/backend/test/ShaderGenerator.cpp @@ -73,12 +73,13 @@ void ShaderGenerator::shutdown() { FinalizeProcess(); } -ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment, - Backend backend, bool isMobile, const filament::SamplerInterfaceBlock* sib) noexcept - : mBackend(backend), - mVertexBlob(transpileShader(ShaderStage::VERTEX, std::move(vertex), backend, isMobile, sib)), - mFragmentBlob(transpileShader(ShaderStage::FRAGMENT, std::move(fragment), backend, - isMobile, sib)) { +ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment, Backend backend, + bool isMobile, filamat::DescriptorSets&& descriptorSets) noexcept + : mBackend(backend), + mVertexBlob(transpileShader( + ShaderStage::VERTEX, std::move(vertex), backend, isMobile, descriptorSets)), + mFragmentBlob(transpileShader( + ShaderStage::FRAGMENT, std::move(fragment), backend, isMobile, descriptorSets)) { switch (backend) { case Backend::OPENGL: mShaderLanguage = filament::backend::ShaderLanguage::ESSL3; @@ -95,9 +96,8 @@ ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment, } } -ShaderGenerator::Blob ShaderGenerator::transpileShader( - ShaderStage stage, std::string shader, Backend backend, bool isMobile, - const filament::SamplerInterfaceBlock* sib) noexcept { +ShaderGenerator::Blob ShaderGenerator::transpileShader(ShaderStage stage, std::string shader, + Backend backend, bool isMobile, const filamat::DescriptorSets& descriptorSets) noexcept { TProgram program; const EShLanguage language = stage == ShaderStage::VERTEX ? EShLangVertex : EShLangFragment; TShader tShader(language); @@ -161,9 +161,8 @@ ShaderGenerator::Blob ShaderGenerator::transpileShader( return { result.c_str(), result.c_str() + result.length() + 1 }; } else if (backend == Backend::METAL) { const auto sm = isMobile ? ShaderModel::MOBILE : ShaderModel::DESKTOP; - filamat::SibVector sibs = filamat::SibVector::with_capacity(1); - if (sib) { sibs.emplace_back(0, sib); } - filamat::GLSLPostProcessor::spirvToMsl(&spirv, &result, sm, false, sibs, nullptr); + filamat::GLSLPostProcessor::spirvToMsl( + &spirv, &result, stage, sm, false, descriptorSets, nullptr); return { result.c_str(), result.c_str() + result.length() + 1 }; } else if (backend == Backend::VULKAN) { return { (uint8_t*)spirv.data(), (uint8_t*)(spirv.data() + spirv.size()) }; diff --git a/filament/backend/test/ShaderGenerator.h b/filament/backend/test/ShaderGenerator.h index 39a87a7f208..9ce6d76b3d2 100644 --- a/filament/backend/test/ShaderGenerator.h +++ b/filament/backend/test/ShaderGenerator.h @@ -22,6 +22,7 @@ #include "private/filament/SamplerInterfaceBlock.h" #include "private/backend/DriverApi.h" #include "backend/Program.h" +#include "../src/GLSLPostProcessor.h" #include @@ -40,7 +41,7 @@ class ShaderGenerator { * @param fragment The fragment shader, written in GLSL 450 core. */ ShaderGenerator(std::string vertex, std::string fragment, Backend backend, bool isMobile, - const filament::SamplerInterfaceBlock* sib = nullptr) noexcept; + filamat::DescriptorSets&& descriptorSets = {}) noexcept; ShaderGenerator(const ShaderGenerator& rhs) = delete; ShaderGenerator& operator=(const ShaderGenerator& rhs) = delete; @@ -52,7 +53,7 @@ class ShaderGenerator { using Blob = std::vector; static Blob transpileShader(ShaderStage stage, std::string shader, Backend backend, - bool isMobile, const filament::SamplerInterfaceBlock* sib = nullptr) noexcept; + bool isMobile, const filamat::DescriptorSets& descriptorSets) noexcept; Backend mBackend; diff --git a/filament/backend/test/mac_runner.mm b/filament/backend/test/mac_runner.mm index 99205e2e7b6..1aab059fb11 100644 --- a/filament/backend/test/mac_runner.mm +++ b/filament/backend/test/mac_runner.mm @@ -53,8 +53,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { nativeView.width = static_cast(drawableSize.width); nativeView.height = static_cast(drawableSize.height); - test::runTests(); - // exit(runTests()); + exit(test::runTests()); } - (NSView*)createView { diff --git a/filament/backend/test/test_Blit.cpp b/filament/backend/test/test_Blit.cpp index 61c8b89e5e3..8ba0be44c94 100644 --- a/filament/backend/test/test_Blit.cpp +++ b/filament/backend/test/test_Blit.cpp @@ -38,7 +38,7 @@ using namespace utils; static const char* const triangleVs = R"(#version 450 core layout(location = 0) in vec4 mesh_position; -uniform Params { highp vec4 color; highp vec4 scale; } params; +layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params; void main() { gl_Position = vec4((mesh_position.xy + 0.5) * params.scale.xy, params.scale.z, 1.0); #if defined(TARGET_VULKAN_ENVIRONMENT) @@ -50,7 +50,7 @@ void main() { static const char* const triangleFs = R"(#version 450 core precision mediump int; precision highp float; layout(location = 0) out vec4 fragColor; -uniform Params { highp vec4 color; highp vec4 scale; } params; +layout(binding = 0, set = 1) uniform Params { highp vec4 color; highp vec4 scale; } params; void main() { fragColor = params.color; })"; @@ -348,12 +348,26 @@ TEST_F(BackendTest, ColorResolve) { // Create a program. ProgramHandle program; { - ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform); + filamat::DescriptorSets descriptors; + descriptors[1] = { { "Params", + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0 }, + {} } }; + ShaderGenerator shaderGen( + triangleVs, triangleFs, sBackend, sIsMobilePlatform, std::move(descriptors)); Program prog = shaderGen.getProgram(api); - prog.uniformBlockBindings({{"params", 1}}); + prog.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }}); program = api.createProgram(std::move(prog)); } + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::UNIFORM_BUFFER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + // Create a VertexBuffer, IndexBuffer, and RenderPrimitive. TrianglePrimitive const triangle(api); @@ -388,6 +402,7 @@ TEST_F(BackendTest, ColorResolve) { PipelineState state = {}; state.program = program; + state.pipelineLayout.setLayout[1] = { descriptorSetLayout }; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; @@ -401,10 +416,12 @@ TEST_F(BackendTest, ColorResolve) { .scale = float4(1, 1, 0.5, 0), }); + api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams)); + api.bindDescriptorSet(descriptorSet, 1, {}); + // FIXME: on Metal this triangle is not drawn. Can't understand why. api.beginFrame(0, 0, 0); api.beginRenderPass(srcRenderTarget, params); - api.bindUniformBuffer(0, ubuffer); api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); api.endFrame(0); @@ -428,6 +445,8 @@ TEST_F(BackendTest, ColorResolve) { EXPECT_TRUE(sparams.pixelHashResult == expected); // Cleanup. + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyBufferObject(ubuffer); api.destroyProgram(program); api.destroyTexture(srcColorTexture); diff --git a/filament/backend/test/test_BufferUpdates.cpp b/filament/backend/test/test_BufferUpdates.cpp index bde771448a1..bcaa2fc21a4 100644 --- a/filament/backend/test/test_BufferUpdates.cpp +++ b/filament/backend/test/test_BufferUpdates.cpp @@ -31,7 +31,7 @@ layout(location = 0) in vec4 mesh_position; layout(location = 0) out uvec4 indices; -uniform Params { +layout(binding = 0, set = 1) uniform Params { highp vec4 padding[4]; // offset of 64 bytes highp vec4 color; @@ -51,7 +51,7 @@ std::string fragment (R"(#version 450 core layout(location = 0) out vec4 fragColor; -uniform Params { +layout(binding = 0, set = 1) uniform Params { highp vec4 padding[4]; // offset of 64 bytes highp vec4 color; @@ -89,20 +89,36 @@ TEST_F(BackendTest, VertexBufferUpdate) { // The test is executed within this block scope to force destructors to run before // executeCommands(). { + auto& api = getDriverApi(); + // Create a platform-specific SwapChain and make it current. auto swapChain = createSwapChain(); - getDriverApi().makeCurrent(swapChain, swapChain); + api.makeCurrent(swapChain, swapChain); // Create a program. - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform); - Program p = shaderGen.getProgram(getDriverApi()); - auto program = getDriverApi().createProgram(std::move(p)); - - auto defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0); + filamat::DescriptorSets descriptors; + descriptors[1] = { { "Params", + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 0 }, {} } }; + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); + Program p = shaderGen.getProgram(api); + p.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }}); + auto program = api.createProgram(std::move(p)); + + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::UNIFORM_BUFFER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + + auto defaultRenderTarget = api.createDefaultRenderTarget(0); // To test large buffers (which exercise a different code path) create an extra large // buffer. Only the first 3 vertices will be used. - TrianglePrimitive triangle(getDriverApi(), largeBuffers); + TrianglePrimitive triangle(api, largeBuffers); RenderPassParams params = {}; fullViewport(params); @@ -113,6 +129,7 @@ TEST_F(BackendTest, VertexBufferUpdate) { PipelineState state; state.program = program; + state.pipelineLayout.setLayout[1] = { descriptorSetLayout }; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; @@ -121,11 +138,13 @@ TEST_F(BackendTest, VertexBufferUpdate) { // Create a uniform buffer. // We use STATIC here, even though the buffer is updated, to force the Metal backend to use a // GPU buffer, which is more interesting to test. - auto ubuffer = getDriverApi().createBufferObject(sizeof(MaterialParams) + 64, + auto ubuffer = api.createBufferObject(sizeof(MaterialParams) + 64, BufferObjectBinding::UNIFORM, BufferUsage::STATIC); - getDriverApi().bindUniformBuffer(0, ubuffer); - getDriverApi().startCapture(0); + api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64); + api.bindDescriptorSet(descriptorSet, 1, {}); + + api.startCapture(0); // Upload uniforms. { @@ -139,11 +158,11 @@ TEST_F(BackendTest, VertexBufferUpdate) { delete sp; }; BufferDescriptor bd(tmp, sizeof(MaterialParams), cb); - getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64); + api.updateBufferObject(ubuffer, std::move(bd), 64); } - getDriverApi().makeCurrent(swapChain, swapChain); - getDriverApi().beginFrame(0, 0, 0); + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); // Draw 10 triangles, updating the vertex buffer / index buffer each time. size_t triangleIndex = 0; @@ -171,22 +190,25 @@ TEST_F(BackendTest, VertexBufferUpdate) { params.flags.discardStart = TargetBufferFlags::NONE; } - getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); - getDriverApi().endRenderPass(); + api.beginRenderPass(defaultRenderTarget, params); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); + api.endRenderPass(); triangleIndex++; } - getDriverApi().flush(); - getDriverApi().commit(swapChain); - getDriverApi().endFrame(0); + api.flush(); + api.commit(swapChain); + api.endFrame(0); - getDriverApi().stopCapture(0); + api.stopCapture(0); - getDriverApi().destroyProgram(program); - getDriverApi().destroySwapChain(swapChain); - getDriverApi().destroyRenderTarget(defaultRenderTarget); + api.destroyProgram(program); + api.destroySwapChain(swapChain); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); + api.destroyBufferObject(ubuffer); + api.destroyRenderTarget(defaultRenderTarget); } executeCommands(); @@ -195,27 +217,45 @@ TEST_F(BackendTest, VertexBufferUpdate) { // This test renders two triangles in two separate draw calls. Between the draw calls, a uniform // buffer object is partially updated. TEST_F(BackendTest, BufferObjectUpdateWithOffset) { + auto& api = getDriverApi(); + // Create a platform-specific SwapChain and make it current. auto swapChain = createSwapChain(); - getDriverApi().makeCurrent(swapChain, swapChain); + api.makeCurrent(swapChain, swapChain); // Create a program. - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform); - Program p = shaderGen.getProgram(getDriverApi()); - p.uniformBlockBindings({{"params", 1}}); - auto program = getDriverApi().createProgram(std::move(p)); + filamat::DescriptorSets descriptors; + descriptors[1] = { { "Params", + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 0 }, {} } }; + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); + Program p = shaderGen.getProgram(api); + p.descriptorBindings(1, {{ "Params", DescriptorType::UNIFORM_BUFFER, 0 }}); + auto program = api.createProgram(std::move(p)); + + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::UNIFORM_BUFFER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + // Create a uniform buffer. // We use STATIC here, even though the buffer is updated, to force the Metal backend to use a // GPU buffer, which is more interesting to test. - auto ubuffer = getDriverApi().createBufferObject(sizeof(MaterialParams) + 64, + auto ubuffer = api.createBufferObject(sizeof(MaterialParams) + 64, BufferObjectBinding::UNIFORM, BufferUsage::STATIC); - getDriverApi().bindUniformBuffer(0, ubuffer); + + api.updateDescriptorSetBuffer(descriptorSet, 0, ubuffer, 0, sizeof(MaterialParams) + 64); + api.bindDescriptorSet(descriptorSet, 1, {}); // Create a render target. - auto colorTexture = getDriverApi().createTexture(SamplerType::SAMPLER_2D, 1, + auto colorTexture = api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT); - auto renderTarget = getDriverApi().createRenderTarget( + auto renderTarget = api.createRenderTarget( TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {}); // Upload uniforms for the first triangle. @@ -230,7 +270,7 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) { delete sp; }; BufferDescriptor bd(tmp, sizeof(MaterialParams), cb); - getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64); + api.updateBufferObject(ubuffer, std::move(bd), 64); } RenderPassParams params = {}; @@ -240,7 +280,8 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) { params.flags.discardEnd = TargetBufferFlags::NONE; params.viewport.height = 512; params.viewport.width = 512; - renderTriangle(renderTarget, swapChain, program, params); + renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }}, + renderTarget, swapChain, program, params); // Upload uniforms for the second triangle. To test partial buffer updates, we'll only update // color.b, color.a, offset.x, and offset.y. @@ -255,29 +296,32 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) { delete sp; }; BufferDescriptor bd((char*)tmp + offsetof(MaterialParams, color.b), sizeof(float) * 4, cb); - getDriverApi().updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b)); + api.updateBufferObject(ubuffer, std::move(bd), 64 + offsetof(MaterialParams, color.b)); } params.flags.clear = TargetBufferFlags::NONE; params.flags.discardStart = TargetBufferFlags::NONE; - renderTriangle(renderTarget, swapChain, program, params); + renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }}, + renderTarget, swapChain, program, params); static const uint32_t expectedHash = 91322442; readPixelsAndAssertHash( "BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true); - getDriverApi().flush(); - getDriverApi().commit(swapChain); - getDriverApi().endFrame(0); + api.flush(); + api.commit(swapChain); + api.endFrame(0); - getDriverApi().destroyProgram(program); - getDriverApi().destroySwapChain(swapChain); - getDriverApi().destroyBufferObject(ubuffer); - getDriverApi().destroyRenderTarget(renderTarget); - getDriverApi().destroyTexture(colorTexture); + api.destroyProgram(program); + api.destroySwapChain(swapChain); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); + api.destroyBufferObject(ubuffer); + api.destroyRenderTarget(renderTarget); + api.destroyTexture(colorTexture); // This ensures all driver commands have finished before exiting the test. - getDriverApi().finish(); + api.finish(); executeCommands(); diff --git a/filament/backend/test/test_ComputeBasic.cpp b/filament/backend/test/test_ComputeBasic.cpp index 70622595592..0e5febfa564 100644 --- a/filament/backend/test/test_ComputeBasic.cpp +++ b/filament/backend/test/test_ComputeBasic.cpp @@ -155,8 +155,9 @@ kernel void main0(device Output_data& output_data [[buffer(0)]], driver.dispatchCompute(ph, { groupCount, 1, 1 }); - driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 0); - driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 1); +// FIXME: we need a way to unbind the buffer in order to read from them +// driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 0); +// driver.unbindBuffer(BufferObjectBinding::SHADER_STORAGE, 1); float* const user = (float*)malloc(size); driver.readBufferSubData(output_data, 0, size, { user, size }); diff --git a/filament/backend/test/test_FeedbackLoops.cpp b/filament/backend/test/test_FeedbackLoops.cpp index 7babb407459..bb7a83416ae 100644 --- a/filament/backend/test/test_FeedbackLoops.cpp +++ b/filament/backend/test/test_FeedbackLoops.cpp @@ -19,12 +19,17 @@ #include "ShaderGenerator.h" #include "TrianglePrimitive.h" -#include "private/backend/SamplerGroup.h" +#include +#include #include #include #include +#include + +#include +#include #ifndef IOS #include @@ -37,27 +42,28 @@ using namespace image; // Shaders //////////////////////////////////////////////////////////////////////////////////////////////////// -static std::string fullscreenVs = R"(#version 450 core +static std::string const fullscreenVs = R"(#version 450 core layout(location = 0) in vec4 mesh_position; void main() { // Hack: move and scale triangle so that it covers entire viewport. gl_Position = vec4((mesh_position.xy + 0.5) * 5.0, 0.0, 1.0); })"; -static std::string fullscreenFs = R"(#version 450 core +static std::string const fullscreenFs = R"(#version 450 core precision mediump int; precision highp float; layout(location = 0) out vec4 fragColor; // Filament's Vulkan backend requires a descriptor set index of 1 for all samplers. // This parameter is ignored for other backends. -layout(location = 0, set = 1) uniform sampler2D test_tex; +layout(binding = 0, set = 1) uniform sampler2D test_tex; -uniform Params { +layout(binding = 1, set = 1) uniform Params { highp float fbWidth; highp float fbHeight; highp float sourceLevel; highp float unused; } params; + void main() { vec2 fbsize = vec2(params.fbWidth, params.fbHeight); vec2 uv = (gl_FragCoord.xy + 0.5) / fbsize; @@ -106,12 +112,12 @@ static void dumpScreenshot(DriverApi& dapi, Handle rt) { int w = kTexWidth, h = kTexHeight; const uint32_t* texels = (uint32_t*) buffer; sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0); - #ifndef IOS +#ifndef IOS LinearImage image(w, h, 4); image = toLinearWithAlpha(w, h, w * 4, (uint8_t*) buffer); std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc); ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png"); - #endif +#endif free(buffer); }; PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb); @@ -134,19 +140,37 @@ TEST_F(BackendTest, FeedbackLoops) { // Create a program. ProgramHandle program; { - SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); - ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, &sib); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { + { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo }, + { "Params", { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, 1 }, {} } + }; + ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, + std::move(descriptors)); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); - prog.uniformBlockBindings({{"params", 1}}); + prog.descriptorBindings(1, { + { "test_tex", DescriptorType::SAMPLER, 0 }, + { "Params", DescriptorType::UNIFORM_BUFFER, 1 } + }); program = api.createProgram(std::move(prog)); } + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }, + { + DescriptorType::UNIFORM_BUFFER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 1, + DescriptorFlags::NONE, 0 + }}}); + + TrianglePrimitive const triangle(getDriverApi()); // Create a texture. @@ -154,6 +178,10 @@ TEST_F(BackendTest, FeedbackLoops) { Handle const texture = api.createTexture( SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage); + // Create ubo + auto ubuffer = api.createBufferObject(sizeof(MaterialParams), + BufferObjectBinding::UNIFORM, BufferUsage::STATIC); + // Create a RenderTarget for each miplevel. Handle renderTargets[kNumLevels]; for (uint8_t level = 0; level < kNumLevels; level++) { @@ -189,20 +217,10 @@ TEST_F(BackendTest, FeedbackLoops) { state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; state.program = program; - SamplerGroup samplers(1); - SamplerParams sparams = {}; - sparams.filterMag = SamplerMagFilter::LINEAR; - sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; - samplers.setSampler(0, { texture, sparams }); - auto sgroup = - api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); - api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); - auto ubuffer = api.createBufferObject(sizeof(MaterialParams), - BufferObjectBinding::UNIFORM, BufferUsage::STATIC); + state.pipelineLayout.setLayout[1] = { descriptorSetLayout }; + api.makeCurrent(swapChain, swapChain); api.beginFrame(0, 0, 0); - api.bindSamplers(0, sgroup); - api.bindUniformBuffer(0, ubuffer); // Downsample passes params.flags.discardStart = TargetBufferFlags::ALL; @@ -211,7 +229,16 @@ TEST_F(BackendTest, FeedbackLoops) { const uint32_t sourceLevel = targetLevel - 1; params.viewport.width = kTexWidth >> targetLevel; params.viewport.height = kTexHeight >> targetLevel; - getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel); + + auto textureView = api.createTextureView(texture, sourceLevel, 1); + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + api.updateDescriptorSetTexture(descriptorSet, 0, textureView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); + api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams)); + api.bindDescriptorSet(descriptorSet, 1, {}); + uploadUniforms(getDriverApi(), ubuffer, { .fbWidth = float(params.viewport.width), .fbHeight = float(params.viewport.height), @@ -220,6 +247,8 @@ TEST_F(BackendTest, FeedbackLoops) { api.beginRenderPass(renderTargets[targetLevel], params); api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); + api.destroyTexture(textureView); + api.destroyDescriptorSet(descriptorSet); } // Upsample passes @@ -230,7 +259,16 @@ TEST_F(BackendTest, FeedbackLoops) { const uint32_t sourceLevel = targetLevel + 1; params.viewport.width = kTexWidth >> targetLevel; params.viewport.height = kTexHeight >> targetLevel; - getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel); + + auto textureView = api.createTextureView(texture, sourceLevel, 1); + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + api.updateDescriptorSetTexture(descriptorSet, 0, textureView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); + api.updateDescriptorSetBuffer(descriptorSet, 1, ubuffer, 0, sizeof(MaterialParams)); + api.bindDescriptorSet(descriptorSet, 1, {}); + uploadUniforms(getDriverApi(), ubuffer, { .fbWidth = float(params.viewport.width), .fbHeight = float(params.viewport.height), @@ -239,10 +277,10 @@ TEST_F(BackendTest, FeedbackLoops) { api.beginRenderPass(renderTargets[targetLevel], params); api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); api.endRenderPass(); + api.destroyTexture(textureView); + api.destroyDescriptorSet(descriptorSet); } - getDriverApi().setMinMaxLevels(texture, 0, 0x7f); - // Read back the render target corresponding to the base level. // // NOTE: Calling glReadPixels on any miplevel other than the base level @@ -259,10 +297,14 @@ TEST_F(BackendTest, FeedbackLoops) { getDriver().purge(); } + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyProgram(program); api.destroySwapChain(swapChain); api.destroyTexture(texture); - for (auto rt : renderTargets) api.destroyRenderTarget(rt); + api.destroyBufferObject(ubuffer); + for (auto rt : renderTargets) { + api.destroyRenderTarget(rt); + } } const uint32_t expected = 0x70695aa1; diff --git a/filament/backend/test/test_LoadImage.cpp b/filament/backend/test/test_LoadImage.cpp index 2af57eb7196..c5946cd2293 100644 --- a/filament/backend/test/test_LoadImage.cpp +++ b/filament/backend/test/test_LoadImage.cpp @@ -20,14 +20,18 @@ #include "TrianglePrimitive.h" #include "BackendTestUtils.h" +#include +#include + #include "private/filament/SamplerInterfaceBlock.h" -#include "private/backend/SamplerGroup.h" #include -#include #include +#include +#include + using namespace filament; using namespace filament::backend; @@ -297,20 +301,31 @@ TEST_F(BackendTest, UpdateImage2D) { auto defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a program. - SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH }} ) - .build(); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo } }; std::string const fragment = stringReplace("{samplerType}", getSamplerTypeName(t.textureFormat), fragmentTemplate); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); + Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); + prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}}); + ProgramHandle const program = api.createProgram(std::move(prog)); + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + // Create a Texture. auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE; Handle const texture = api.createTexture(SamplerType::SAMPLER_2D, 1, @@ -331,23 +346,22 @@ TEST_F(BackendTest, UpdateImage2D) { checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding)); } - SamplerGroup samplers(1); - samplers.setSampler(0, { texture, { + api.updateDescriptorSetTexture(descriptorSet, 0, texture, { .filterMag = SamplerMagFilter::NEAREST, - .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST } }); + .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); - auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); - api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); + api.bindDescriptorSet(descriptorSet, 1, {}); - api.bindSamplers(0, sgroup); - - renderTriangle(defaultRenderTarget, swapChain, program); + renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }}, + defaultRenderTarget, swapChain, program); readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash); api.commit(swapChain); api.endFrame(0); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyProgram(program); api.destroySwapChain(swapChain); api.destroyRenderTarget(defaultRenderTarget); @@ -374,18 +388,27 @@ TEST_F(BackendTest, UpdateImageSRGB) { auto defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a program. - SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo } }; + std::string const fragment = stringReplace("{samplerType}", getSamplerTypeName(textureFormat), fragmentTemplate); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); + prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}}); ProgramHandle const program = api.createProgram(std::move(prog)); + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); // Create a texture. Handle const texture = api.createTexture(SamplerType::SAMPLER_2D, 1, @@ -417,17 +440,15 @@ TEST_F(BackendTest, UpdateImageSRGB) { api.beginFrame(0, 0, 0); // Update samplers. - SamplerGroup samplers(1); - SamplerParams sparams = {}; - sparams.filterMag = SamplerMagFilter::LINEAR; - sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; - samplers.setSampler(0, { texture, sparams }); - auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); - api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); + api.updateDescriptorSetTexture(descriptorSet, 0, texture, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); - api.bindSamplers(0, sgroup); + api.bindDescriptorSet(descriptorSet, 1, {}); - renderTriangle(defaultRenderTarget, swapChain, program); + renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }}, + defaultRenderTarget, swapChain, program); static const uint32_t expectedHash = 359858623; readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash); @@ -436,7 +457,8 @@ TEST_F(BackendTest, UpdateImageSRGB) { api.commit(swapChain); api.endFrame(0); - api.destroySamplerGroup(sgroup); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyProgram(program); api.destroySwapChain(swapChain); api.destroyRenderTarget(defaultRenderTarget); @@ -462,18 +484,26 @@ TEST_F(BackendTest, UpdateImageMipLevel) { auto defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a program. - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo } }; std::string const fragment = stringReplace("{samplerType}", getSamplerTypeName(textureFormat), fragmentUpdateImageMip); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); + prog.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}}); ProgramHandle const program = api.createProgram(std::move(prog)); + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); // Create a texture with 3 mip levels. // Base level: 1024 @@ -489,17 +519,15 @@ TEST_F(BackendTest, UpdateImageMipLevel) { api.beginFrame(0, 0, 0); // Update samplers. - SamplerGroup samplers(1); - SamplerParams sparams = {}; - sparams.filterMag = SamplerMagFilter::LINEAR; - sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; - samplers.setSampler(0, { texture, sparams }); - auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); - api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); + api.updateDescriptorSetTexture(descriptorSet, 0, texture, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); - api.bindSamplers(0, sgroup); + api.bindDescriptorSet(descriptorSet, 1, {}); - renderTriangle(defaultRenderTarget, swapChain, program); + renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }}, + defaultRenderTarget, swapChain, program); static const uint32_t expectedHash = 3644679986; readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash); @@ -508,7 +536,8 @@ TEST_F(BackendTest, UpdateImageMipLevel) { api.commit(swapChain); api.endFrame(0); - api.destroySamplerGroup(sgroup); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyProgram(program); api.destroySwapChain(swapChain); api.destroyRenderTarget(defaultRenderTarget); @@ -536,18 +565,26 @@ TEST_F(BackendTest, UpdateImage3D) { auto defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a program. - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_2D_ARRAY, getSamplerFormat(textureFormat), Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo } }; std::string fragment = stringReplace("{samplerType}", getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); Program prog = shaderGen.getProgram(api); - Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 }; - prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0])); + prog.descriptorBindings(1, {{ "test_tex", DescriptorType::SAMPLER, 0 }}); ProgramHandle const program = api.createProgram(std::move(prog)); + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); // Create a texture. Handle texture = api.createTexture(samplerType, 1, @@ -573,17 +610,15 @@ TEST_F(BackendTest, UpdateImage3D) { api.beginFrame(0, 0, 0); // Update samplers. - SamplerGroup samplers(1); - SamplerParams sparams = {}; - sparams.filterMag = SamplerMagFilter::LINEAR; - sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST; - samplers.setSampler(0, { texture, sparams}); - auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test")); - api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api)); + api.updateDescriptorSetTexture(descriptorSet, 0, texture, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST + }); - api.bindSamplers(0, sgroup); + api.bindDescriptorSet(descriptorSet, 1, {}); - renderTriangle(defaultRenderTarget, swapChain, program); + renderTriangle({{ DescriptorSetLayoutHandle{}, descriptorSetLayout }}, + defaultRenderTarget, swapChain, program); static const uint32_t expectedHash = 3644679986; readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash); @@ -592,7 +627,8 @@ TEST_F(BackendTest, UpdateImage3D) { api.commit(swapChain); api.endFrame(0); - api.destroySamplerGroup(sgroup); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyProgram(program); api.destroySwapChain(swapChain); api.destroyRenderTarget(defaultRenderTarget); diff --git a/filament/backend/test/test_MipLevels.cpp b/filament/backend/test/test_MipLevels.cpp index 3c54839dad1..e0c41fb1725 100644 --- a/filament/backend/test/test_MipLevels.cpp +++ b/filament/backend/test/test_MipLevels.cpp @@ -21,7 +21,11 @@ #include "TrianglePrimitive.h" #include "BackendTestUtils.h" -#include "private/backend/SamplerGroup.h" +#include +#include + +#include +#include namespace { @@ -71,7 +75,7 @@ namespace test { using namespace filament; using namespace filament::backend; -TEST_F(BackendTest, SetMinMaxLevel) { +TEST_F(BackendTest, TextureViewLod) { auto& api = getDriverApi(); api.startCapture(0); @@ -87,26 +91,35 @@ TEST_F(BackendTest, SetMinMaxLevel) { { ShaderGenerator shaderGen(vertex, whiteFragment, sBackend, sIsMobilePlatform); Program p = shaderGen.getProgram(api); - Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0}; - p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1); + p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}}); whiteProgram = api.createProgram(std::move(p)); } // Create a program that samples a texture. Handle textureProgram; { - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("backend_test_sib") - .stageFlags(backend::ShaderStageFlags::FRAGMENT) - .add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "backend_test", "sib_tex", 0, + SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "backend_test_sib_tex", + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, samplerInfo } }; + ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); Program p = shaderGen.getProgram(api); - Program::Sampler sampler{utils::CString("backend_test_sib_tex"), 0}; - p.setSamplerGroup(0, ShaderStageFlags::FRAGMENT, &sampler, 1); + p.descriptorBindings(0, {{"backend_test_sib_tex", DescriptorType::SAMPLER, 0}}); textureProgram = api.createProgram(std::move(p)); } + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet[2]; + descriptorSet[0] = api.createDescriptorSet(descriptorSetLayout); + descriptorSet[1] = api.createDescriptorSet(descriptorSetLayout); + // Create a texture that has 4 mip levels. Each level is a different color. // Level 0: 128x128 (red) // Level 1: 64x64 (green) @@ -150,7 +163,7 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Level 1: 64x64 (green) <-- base // Level 2: 32x32 (blue) <--- white triangle rendered // Level 3: 16x16 (yellow) <-- max - api.setMinMaxLevels(texture, 1, 3); + auto texture13 = api.createTextureView(texture, 1, 3); // Render a white triangle into level 2. // We specify mip level 2, because minMaxLevels has no effect when rendering into a texture. @@ -183,20 +196,17 @@ TEST_F(BackendTest, SetMinMaxLevel) { PipelineState state; state.program = textureProgram; + state.pipelineLayout.setLayout = { descriptorSetLayout }; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; state.rasterState.depthFunc = SamplerCompareFunc::A; state.rasterState.culling = CullingMode::NONE; - SamplerGroup samplers(1); - SamplerParams samplerParams {}; - samplerParams.filterMag = SamplerMagFilter::NEAREST; - samplerParams.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST; - samplers.setSampler(0, { texture, samplerParams }); - backend::Handle samplerGroup = - api.createSamplerGroup(1, utils::FixedSizeString<32>("Test")); - api.updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(api)); - api.bindSamplers(0, samplerGroup); + api.updateDescriptorSetTexture(descriptorSet[0], 0, texture13, { + .filterMag = SamplerMagFilter::NEAREST, + .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); + + api.bindDescriptorSet(descriptorSet[0], 0, {}); // Render a triangle to the screen, sampling from mip level 1. // Because the min level is 1, the result color should be the white triangle drawn in the @@ -208,7 +218,13 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Adjust the base mip to 2. // Note that this is done without another call to updateSamplerGroup. - api.setMinMaxLevels(texture, 2, 3); + auto texture22 = api.createTextureView(texture, 2, 2); + + api.updateDescriptorSetTexture(descriptorSet[1], 0, texture22, { + .filterMag = SamplerMagFilter::NEAREST, + .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); + + api.bindDescriptorSet(descriptorSet[1], 0, {}); // Render a second, smaller, triangle, again sampling from mip level 1. // This triangle should be yellow striped. @@ -233,7 +249,12 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Cleanup. api.destroySwapChain(swapChain); api.destroyRenderTarget(renderTarget); + api.destroyDescriptorSet(descriptorSet[0]); + api.destroyDescriptorSet(descriptorSet[1]); + api.destroyDescriptorSetLayout(descriptorSetLayout); api.destroyTexture(texture); + api.destroyTexture(texture13); + api.destroyTexture(texture22); api.destroyProgram(whiteProgram); api.destroyProgram(textureProgram); } diff --git a/filament/backend/test/test_RenderExternalImage.cpp b/filament/backend/test/test_RenderExternalImage.cpp index b8261434b23..414e9c414d9 100644 --- a/filament/backend/test/test_RenderExternalImage.cpp +++ b/filament/backend/test/test_RenderExternalImage.cpp @@ -19,10 +19,14 @@ #include "ShaderGenerator.h" #include "TrianglePrimitive.h" -#include "private/backend/SamplerGroup.h" +#include +#include #include +#include +#include + namespace { //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -61,29 +65,39 @@ using namespace filament::backend; // Rendering an external image without setting any data should not crash. TEST_F(BackendTest, RenderExternalImageWithoutSet) { - TrianglePrimitive triangle(getDriverApi()); + auto& api = getDriverApi(); + + TrianglePrimitive triangle(api); auto swapChain = createSwapChain(); - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo } }; + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); // Create a program that samples a texture. - Program p = shaderGen.getProgram(getDriverApi()); - Program::Sampler sampler { utils::CString("test_tex"), 0 }; - p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1); - backend::Handle program = getDriverApi().createProgram(std::move(p)); + Program p = shaderGen.getProgram(api); + p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}}); + backend::Handle program = api.createProgram(std::move(p)); + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); - backend::Handle defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0); + backend::Handle defaultRenderTarget = api.createDefaultRenderTarget(0); // Create a texture that will be backed by an external image. auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE; const NativeView& view = getNativeView(); - backend::Handle texture = getDriverApi().createTexture( + backend::Handle texture = api.createTexture( SamplerType::SAMPLER_EXTERNAL, // target 1, // levels TextureFormat::RGBA8, // format @@ -102,63 +116,74 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) { PipelineState state; state.program = program; + state.pipelineLayout.setLayout[1] = { descriptorSetLayout }; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; state.rasterState.culling = CullingMode::NONE; - getDriverApi().startCapture(0); - getDriverApi().makeCurrent(swapChain, swapChain); - getDriverApi().beginFrame(0, 0, 0); + api.startCapture(0); + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); - SamplerGroup samplers(1); - samplers.setSampler(0, { texture, {} }); - backend::Handle samplerGroup = - getDriverApi().createSamplerGroup(1, utils::FixedSizeString<32>("Test")); - getDriverApi().updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(getDriverApi())); - getDriverApi().bindSamplers(0, samplerGroup); + api.updateDescriptorSetTexture(descriptorSet, 0, texture, {}); + api.bindDescriptorSet(descriptorSet, 1, {}); // Render a triangle. - getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); - getDriverApi().endRenderPass(); + api.beginRenderPass(defaultRenderTarget, params); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); + api.endRenderPass(); - getDriverApi().flush(); - getDriverApi().commit(swapChain); - getDriverApi().endFrame(0); + api.flush(); + api.commit(swapChain); + api.endFrame(0); - getDriverApi().stopCapture(0); + api.stopCapture(0); // Delete our resources. - getDriverApi().destroyTexture(texture); - getDriverApi().destroySamplerGroup(samplerGroup); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); + api.destroyTexture(texture); // Destroy frame resources. - getDriverApi().destroyProgram(program); - getDriverApi().destroyRenderTarget(defaultRenderTarget); + api.destroyProgram(program); + api.destroyRenderTarget(defaultRenderTarget); + + api.finish(); executeCommands(); } TEST_F(BackendTest, RenderExternalImage) { - TrianglePrimitive triangle(getDriverApi()); + auto& api = getDriverApi(); + + TrianglePrimitive triangle(api); auto swapChain = createSwapChain(); - SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder() - .name("Test") - .stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) - .add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} ) - .build(); - ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib); + filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0, + SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH, false }; + filamat::DescriptorSets descriptors; + descriptors[1] = { { "test_tex", { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, 0 }, + samplerInfo } }; + ShaderGenerator shaderGen( + vertex, fragment, sBackend, sIsMobilePlatform, std::move(descriptors)); // Create a program that samples a texture. - Program p = shaderGen.getProgram(getDriverApi()); - Program::Sampler sampler { utils::CString("test_tex"), 0 }; - p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1); - auto program = getDriverApi().createProgram(std::move(p)); + Program p = shaderGen.getProgram(api); + p.descriptorBindings(1, {{"test_tex", DescriptorType::SAMPLER, 0}}); + auto program = api.createProgram(std::move(p)); - backend::Handle defaultRenderTarget = getDriverApi().createDefaultRenderTarget(0); + DescriptorSetLayoutHandle descriptorSetLayout = api.createDescriptorSetLayout({ + {{ + DescriptorType::SAMPLER, + ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, 0, + DescriptorFlags::NONE, 0 + }}}); + + DescriptorSetHandle descriptorSet = api.createDescriptorSet(descriptorSetLayout); + + backend::Handle defaultRenderTarget = api.createDefaultRenderTarget(0); // require users to create two Filament textures and have two material parameters // add a "plane" parameter to setExternalImage @@ -166,15 +191,6 @@ TEST_F(BackendTest, RenderExternalImage) { // Create a texture that will be backed by an external image. auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE; const NativeView& view = getNativeView(); - backend::Handle texture = getDriverApi().createTexture( - SamplerType::SAMPLER_EXTERNAL, // target - 1, // levels - TextureFormat::RGBA8, // format - 1, // samples - view.width, // width - view.height, // height - 1, // depth - usage); // usage // Create an external image. CFStringRef keys[4]; @@ -209,8 +225,9 @@ TEST_F(BackendTest, RenderExternalImage) { } } - getDriverApi().setupExternalImage(pixBuffer); - getDriverApi().setExternalImage(texture, pixBuffer); + api.setupExternalImage(pixBuffer); + backend::Handle texture = + api.createTextureExternalImage(TextureFormat::RGBA8, 1024, 1024, usage, pixBuffer); // We're now free to release the buffer. CVBufferRelease(pixBuffer); @@ -224,40 +241,43 @@ TEST_F(BackendTest, RenderExternalImage) { PipelineState state; state.program = program; + state.pipelineLayout.setLayout[1] = { descriptorSetLayout }; state.rasterState.colorWrite = true; state.rasterState.depthWrite = false; state.rasterState.depthFunc = RasterState::DepthFunc::A; state.rasterState.culling = CullingMode::NONE; - getDriverApi().startCapture(0); - getDriverApi().makeCurrent(swapChain, swapChain); - getDriverApi().beginFrame(0, 0, 0); + api.startCapture(0); + api.makeCurrent(swapChain, swapChain); + api.beginFrame(0, 0, 0); + + api.updateDescriptorSetTexture(descriptorSet, 0, texture, {}); - SamplerGroup samplers(1); - samplers.setSampler(0, { texture, {} }); - backend::Handle samplerGroup = - getDriverApi().createSamplerGroup(1, utils::FixedSizeString<32>("Test")); - getDriverApi().updateSamplerGroup(samplerGroup, samplers.toBufferDescriptor(getDriverApi())); - getDriverApi().bindSamplers(0, samplerGroup); + api.bindDescriptorSet(descriptorSet, 1, {}); // Render a triangle. - getDriverApi().beginRenderPass(defaultRenderTarget, params); - getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1); - getDriverApi().endRenderPass(); + api.beginRenderPass(defaultRenderTarget, params); + api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1); + api.endRenderPass(); - getDriverApi().flush(); - getDriverApi().commit(swapChain); - getDriverApi().endFrame(0); + readPixelsAndAssertHash("RenderExternalImage", 512, 512, defaultRenderTarget, 267229901, true); - getDriverApi().stopCapture(0); + api.flush(); + api.commit(swapChain); + api.endFrame(0); + + api.stopCapture(0); // Delete our resources. - getDriverApi().destroyTexture(texture); - getDriverApi().destroySamplerGroup(samplerGroup); + api.destroyDescriptorSet(descriptorSet); + api.destroyDescriptorSetLayout(descriptorSetLayout); + api.destroyTexture(texture); // Destroy frame resources. - getDriverApi().destroyProgram(program); - getDriverApi().destroyRenderTarget(defaultRenderTarget); + api.destroyProgram(program); + api.destroyRenderTarget(defaultRenderTarget); + + api.finish(); executeCommands(); } diff --git a/filament/docs/Vulkan.md.html b/filament/docs/Vulkan.md.html index 80db49db33e..2a8902f3455 100644 --- a/filament/docs/Vulkan.md.html +++ b/filament/docs/Vulkan.md.html @@ -182,7 +182,7 @@ If a shader samples from a texture whose mipmaps are only partially loaded, we might see validation warnings about how some subresources are in an UNDEFINED layout. However if we are properly using -the `setMinMaxLevels` driver API, then the Vulkan backend will not bind those particular +the `createTextureView` driver API, then the Vulkan backend will not bind those particular subresources, so validation should not complain. (2) **Writeable Color Textures** diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 2c227e0c644..1ca238352b9 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -597,21 +597,6 @@ class UTILS_PUBLIC RenderableManager : public FilamentAPI { friend class FEngine; friend class FRenderPrimitive; friend class FRenderableManager; - struct Entry { - VertexBuffer* UTILS_NULLABLE vertices = nullptr; - IndexBuffer* UTILS_NULLABLE indices = nullptr; - size_t offset = 0; - size_t count = 0; - MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr; - PrimitiveType type = PrimitiveType::TRIANGLES; - uint16_t blendOrder = 0; - bool globalBlendOrderEnabled = false; - struct { - MorphTargetBuffer* UTILS_NULLABLE buffer = nullptr; - size_t offset = 0; - size_t count = 0; - } morphing; - }; }; /** diff --git a/filament/src/Bimap.h b/filament/src/Bimap.h index c765b542a0f..1f730df9a2f 100644 --- a/filament/src/Bimap.h +++ b/filament/src/Bimap.h @@ -56,7 +56,12 @@ class Bimap { } }; - using ForwardMap = tsl::robin_map; + using ForwardMap = tsl::robin_map< + KeyDelegate, Value, + KeyHasherDelegate, + std::equal_to, + std::allocator>, + true>; using BackwardMap = tsl::robin_map; Allocator mAllocator; diff --git a/filament/src/HwDescriptorSetLayoutFactory.cpp b/filament/src/HwDescriptorSetLayoutFactory.cpp new file mode 100644 index 00000000000..371ffde4b7e --- /dev/null +++ b/filament/src/HwDescriptorSetLayoutFactory.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HwDescriptorSetLayoutFactory.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace filament { + +using namespace utils; +using namespace backend; + +size_t HwDescriptorSetLayoutFactory::Parameters::hash() const noexcept { + return utils::hash::murmurSlow( + reinterpret_cast(dsl.bindings.data()), + dsl.bindings.size() * sizeof(backend::DescriptorSetLayoutBinding), + 42); +} + +bool operator==(HwDescriptorSetLayoutFactory::Parameters const& lhs, + HwDescriptorSetLayoutFactory::Parameters const& rhs) noexcept { + return (lhs.dsl.bindings.size() == rhs.dsl.bindings.size()) && + std::equal( + lhs.dsl.bindings.begin(), lhs.dsl.bindings.end(), + rhs.dsl.bindings.begin()); +} + +// ------------------------------------------------------------------------------------------------ + +HwDescriptorSetLayoutFactory::HwDescriptorSetLayoutFactory() + : mArena("HwDescriptorSetLayoutFactory::mArena", SET_ARENA_SIZE), + mBimap(mArena) { + mBimap.reserve(256); +} + +HwDescriptorSetLayoutFactory::~HwDescriptorSetLayoutFactory() noexcept = default; + +void HwDescriptorSetLayoutFactory::terminate(DriverApi&) noexcept { + assert_invariant(mBimap.empty()); +} + +auto HwDescriptorSetLayoutFactory::create(DriverApi& driver, + backend::DescriptorSetLayout dsl) noexcept -> Handle { + + std::sort(dsl.bindings.begin(), dsl.bindings.end(), + [](auto&& lhs, auto&& rhs) { + return lhs.binding < rhs.binding; + }); + + // see if we already have seen this RenderPrimitive + Key const key({ dsl }); + auto pos = mBimap.find(key); + + // the common case is that we've never seen it (i.e.: no reuse) + if (UTILS_LIKELY(pos == mBimap.end())) { + auto handle = driver.createDescriptorSetLayout(std::move(dsl)); + mBimap.insert(key, { handle }); + return handle; + } + + ++(pos->first.pKey->refs); + + return pos->second.handle; +} + +void HwDescriptorSetLayoutFactory::destroy(DriverApi& driver, Handle handle) noexcept { + // look for this handle in our map + auto pos = mBimap.find(Value{ handle }); + if (--pos->second.pKey->refs == 0) { + mBimap.erase(pos); + driver.destroyDescriptorSetLayout(handle); + } +} + +} // namespace filament diff --git a/filament/src/HwDescriptorSetLayoutFactory.h b/filament/src/HwDescriptorSetLayoutFactory.h new file mode 100644 index 00000000000..155f55a1c68 --- /dev/null +++ b/filament/src/HwDescriptorSetLayoutFactory.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H +#define TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H + +#include "Bimap.h" + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace filament { + +class FEngine; + +class HwDescriptorSetLayoutFactory { +public: + using Handle = backend::DescriptorSetLayoutHandle; + + HwDescriptorSetLayoutFactory(); + ~HwDescriptorSetLayoutFactory() noexcept; + + HwDescriptorSetLayoutFactory(HwDescriptorSetLayoutFactory const& rhs) = delete; + HwDescriptorSetLayoutFactory(HwDescriptorSetLayoutFactory&& rhs) noexcept = delete; + HwDescriptorSetLayoutFactory& operator=(HwDescriptorSetLayoutFactory const& rhs) = delete; + HwDescriptorSetLayoutFactory& operator=(HwDescriptorSetLayoutFactory&& rhs) noexcept = delete; + + void terminate(backend::DriverApi& driver) noexcept; + + struct Parameters { // 16 bytes + heap allocations + backend::DescriptorSetLayout dsl; + size_t hash() const noexcept; + }; + + friend bool operator==(Parameters const& lhs, Parameters const& rhs) noexcept; + + Handle create(backend::DriverApi& driver, backend::DescriptorSetLayout dsl) noexcept; + + void destroy(backend::DriverApi& driver, Handle handle) noexcept; + +private: + struct Key { // 24 bytes + // The key should not be copyable, unfortunately due to how the Bimap works we have + // to copy-construct it once. + Key(Key const&) = default; + Key& operator=(Key const&) = delete; + Key& operator=(Key&&) noexcept = delete; + explicit Key(Parameters const& params) : params(params), refs(1) { } + Parameters params; + mutable uint32_t refs; // 4 bytes + bool operator==(Key const& rhs) const noexcept { + return params == rhs.params; + } + }; + + struct KeyHasher { + size_t operator()(Key const& p) const noexcept { + return p.params.hash(); + } + }; + + struct Value { // 4 bytes + Handle handle; + }; + + struct ValueHasher { + size_t operator()(Value const v) const noexcept { + return std::hash()(v.handle.getId()); + } + }; + + friend bool operator==(Value const lhs, Value const rhs) noexcept { + return lhs.handle == rhs.handle; + } + + // Size of the arena used for the "set" part of the bimap + // about ~1K entries before fall back to heap + static constexpr size_t SET_ARENA_SIZE = 24 * 1024; + + // Arena for the set<>, using a pool allocator inside a heap area. + using PoolAllocatorArena = utils::Arena< + utils::PoolAllocatorWithFallback, + utils::LockingPolicy::NoLock, + utils::TrackingPolicy::Untracked, + utils::AreaPolicy::HeapArea>; + + + // Arena where the set memory is allocated + PoolAllocatorArena mArena; + + // The special Bimap + Bimap> mBimap; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_HWDESCRIPTORSETLAYOUTFACTORY_H diff --git a/filament/src/HwRenderPrimitiveFactory.h b/filament/src/HwRenderPrimitiveFactory.h index 6601a0c25ba..021e0d0ec91 100644 --- a/filament/src/HwRenderPrimitiveFactory.h +++ b/filament/src/HwRenderPrimitiveFactory.h @@ -48,7 +48,7 @@ class HwRenderPrimitiveFactory { void terminate(backend::DriverApi& driver) noexcept; - struct Parameters { // 20 bytes + struct Parameters { // 12 bytes backend::VertexBufferHandle vbh; // 4 backend::IndexBufferHandle ibh; // 4 backend::PrimitiveType type; // 4 @@ -65,7 +65,7 @@ class HwRenderPrimitiveFactory { void destroy(backend::DriverApi& driver, Handle handle) noexcept; private: - struct Key { + struct Key { // 16 bytes // The key should not be copyable, unfortunately due to how the Bimap works we have // to copy-construct it once. Key(Key const&) = default; @@ -100,7 +100,8 @@ class HwRenderPrimitiveFactory { } // Size of the arena used for the "set" part of the bimap - static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024; + // about ~65K entry before fall back to heap + static constexpr size_t SET_ARENA_SIZE = 1 * 1024 * 1024; // Arena for the set<>, using a pool allocator inside a heap area. using PoolAllocatorArena = utils::Arena< diff --git a/filament/src/HwVertexBufferInfoFactory.h b/filament/src/HwVertexBufferInfoFactory.h index c467105b1ad..75a89b9e09c 100644 --- a/filament/src/HwVertexBufferInfoFactory.h +++ b/filament/src/HwVertexBufferInfoFactory.h @@ -48,7 +48,7 @@ class HwVertexBufferInfoFactory { void terminate(backend::DriverApi& driver) noexcept; - struct Parameters { // 136 bytes + struct Parameters { // 132 bytes uint8_t bufferCount; uint8_t attributeCount; uint8_t padding[2] = {}; @@ -66,7 +66,7 @@ class HwVertexBufferInfoFactory { void destroy(backend::DriverApi& driver, Handle handle) noexcept; private: - struct Key { // 140 bytes + struct Key { // 136 bytes // The key should not be copyable, unfortunately due to how the Bimap works we have // to copy-construct it once. Key(Key const&) = default; @@ -101,7 +101,8 @@ class HwVertexBufferInfoFactory { } // Size of the arena used for the "set" part of the bimap - static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024; + // about ~15K entry before fall back to heap + static constexpr size_t SET_ARENA_SIZE = 2 * 1024 * 1024; // Arena for the set<>, using a pool allocator inside a heap area. using PoolAllocatorArena = utils::Arena< diff --git a/filament/src/MaterialParser.cpp b/filament/src/MaterialParser.cpp index 128288136aa..e2e62be5056 100644 --- a/filament/src/MaterialParser.cpp +++ b/filament/src/MaterialParser.cpp @@ -24,18 +24,28 @@ #include -#include #include #include #include #include #include #include +#include +#include +#include + +#include #include +#include -#include +#include #include +#include +#include + +#include +#include using namespace utils; using namespace filament::backend; @@ -62,12 +72,13 @@ constexpr std::pair shaderLanguageToTags(ShaderLanguage la // ------------------------------------------------------------------------------------------------ MaterialParser::MaterialParserDetails::MaterialParserDetails( - const utils::FixedCapacityVector& preferredLanguages, const void* data, + utils::FixedCapacityVector preferredLanguages, const void* data, size_t size) : mManagedBuffer(data, size), mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()), - mPreferredLanguages(preferredLanguages), - mMaterialChunk(mChunkContainer) {} + mPreferredLanguages(std::move(preferredLanguages)), + mMaterialChunk(mChunkContainer) { +} template UTILS_NOINLINE @@ -82,11 +93,29 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk( return false; } +MaterialParser::MaterialParserDetails::ManagedBuffer::ManagedBuffer(const void* start, size_t size) + : mStart(malloc(size)), mSize(size) { + memcpy(mStart, start, size); +} + +MaterialParser::MaterialParserDetails::ManagedBuffer::~ManagedBuffer() noexcept { + free(mStart); +} + // ------------------------------------------------------------------------------------------------ +template +bool MaterialParser::get(typename T::Container* container) const noexcept { + auto [start, end] = mImpl.mChunkContainer.getChunkRange(T::tag); + if (start == end) return false; + filaflat::Unflattener unflattener{ start, end }; + return T::unflatten(unflattener, container); +} + MaterialParser::MaterialParser(utils::FixedCapacityVector preferredLanguages, const void* data, size_t size) - : mImpl(preferredLanguages, data, size) {} + : mImpl(std::move(preferredLanguages), data, size) { +} ChunkContainer& MaterialParser::getChunkContainer() noexcept { return mImpl.mChunkContainer; @@ -158,25 +187,16 @@ bool MaterialParser::getCacheId(uint64_t* cacheId) const noexcept { return unflattener.read(cacheId); } -bool MaterialParser::getUIB(BufferInterfaceBlock* uib) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUib); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkUniformInterfaceBlock::unflatten(unflattener, uib); +bool MaterialParser::getUIB(BufferInterfaceBlock* container) const noexcept { + return get(container); } -bool MaterialParser::getSIB(SamplerInterfaceBlock* sib) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSib); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkSamplerInterfaceBlock::unflatten(unflattener, sib); +bool MaterialParser::getSIB(SamplerInterfaceBlock* container) const noexcept { + return get(container); } -bool MaterialParser::getSubpasses(SubpassInfo* subpass) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSubpass); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkSubpassInterfaceBlock::unflatten(unflattener, subpass); +bool MaterialParser::getSubpasses(SubpassInfo* container) const noexcept { + return get(container); } bool MaterialParser::getShaderModels(uint32_t* value) const noexcept { @@ -187,43 +207,24 @@ bool MaterialParser::getMaterialProperties(uint64_t* value) const noexcept { return mImpl.getFromSimpleChunk(ChunkType::MaterialProperties, value); } -bool MaterialParser::getUniformBlockBindings( - utils::FixedCapacityVector>* value) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialUniformBindings); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkUniformBlockBindings::unflatten(unflattener, value); -} - bool MaterialParser::getBindingUniformInfo(BindingUniformInfoContainer* container) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialBindingUniformInfo); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkBindingUniformInfo::unflatten(unflattener, container); + return get(container); } bool MaterialParser::getAttributeInfo(AttributeInfoContainer* container) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialAttributeInfo); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkAttributeInfo::unflatten(unflattener, container); + return get(container); } -bool MaterialParser::getSamplerBlockBindings( - SamplerGroupBindingInfoList* pSamplerGroupInfoList, - SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialSamplerBindings); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkSamplerBlockBindings::unflatten(unflattener, - pSamplerGroupInfoList, pSamplerBindingToNameMap); +bool MaterialParser::getDescriptorBindings(DescriptorBindingsContainer* container) const noexcept { + return get(container); } -bool MaterialParser::getConstants(utils::FixedCapacityVector* value) const noexcept { - auto [start, end] = mImpl.mChunkContainer.getChunkRange(filamat::MaterialConstants); - if (start == end) return false; - Unflattener unflattener(start, end); - return ChunkMaterialConstants::unflatten(unflattener, value); +bool MaterialParser::getDescriptorSetLayout(DescriptorSetLayoutContainer* container) const noexcept { + return get(container); +} + +bool MaterialParser::getConstants(utils::FixedCapacityVector* container) const noexcept { + return get(container); } bool MaterialParser::getPushConstants(utils::CString* structVarName, @@ -466,7 +467,9 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener, } for (uint64_t i = 0; i < numFields; i++) { + static_assert(sizeof(backend::descriptor_binding_t) == sizeof(uint8_t)); CString fieldName; + uint8_t fieldBinding = 0; uint8_t fieldType = 0; uint8_t fieldFormat = 0; uint8_t fieldPrecision = 0; @@ -476,6 +479,10 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener, return false; } + if (!unflattener.read(&fieldBinding)) { + return false; + } + if (!unflattener.read(&fieldType)) { return false; } @@ -492,7 +499,9 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener, return false; } - builder.add({ fieldName.data(), fieldName.size() }, SamplerInterfaceBlock::Type(fieldType), + builder.add({ fieldName.data(), fieldName.size() }, + SamplerInterfaceBlock::Binding(fieldBinding), + SamplerInterfaceBlock::Type(fieldType), SamplerInterfaceBlock::Format(fieldFormat), SamplerInterfaceBlock::Precision(fieldPrecision), fieldMultisample); @@ -557,28 +566,6 @@ bool ChunkSubpassInterfaceBlock::unflatten(Unflattener& unflattener, return true; } -bool ChunkUniformBlockBindings::unflatten(filaflat::Unflattener& unflattener, - utils::FixedCapacityVector>* uniformBlockBindings) { - uint8_t count; - if (!unflattener.read(&count)) { - return false; - } - uniformBlockBindings->reserve(count); - - for (uint8_t i = 0; i < count; i++) { - CString name; - uint8_t binding; - if (!unflattener.read(&name)) { - return false; - } - if (!unflattener.read(&binding)) { - return false; - } - uniformBlockBindings->emplace_back(std::move(name), binding); - } - return true; -} - bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener, MaterialParser::BindingUniformInfoContainer* bindingUniformInfo) { uint8_t bindingPointCount; @@ -591,6 +578,10 @@ bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener, if (!unflattener.read(&index)) { return false; } + utils::CString uboName; + if (!unflattener.read(&uboName)) { + return false; + } uint8_t uniformCount; if (!unflattener.read(&uniformCount)) { return false; @@ -616,7 +607,7 @@ bool ChunkBindingUniformInfo::unflatten(filaflat::Unflattener& unflattener, } uniforms.push_back({ name, offset, size, UniformType(type) }); } - bindingUniformInfo->emplace_back(UniformBindingPoints(index), std::move(uniforms)); + bindingUniformInfo->emplace_back(index, std::move(uboName), std::move(uniforms)); } return true; } @@ -646,49 +637,91 @@ bool ChunkAttributeInfo::unflatten(filaflat::Unflattener& unflattener, return true; } -bool ChunkSamplerBlockBindings::unflatten(Unflattener& unflattener, - SamplerGroupBindingInfoList* pSamplerGroupBindingInfoList, - SamplerBindingToNameMap* pSamplerBindingToNameMap) { - assert_invariant(pSamplerGroupBindingInfoList && pSamplerBindingToNameMap); - SamplerGroupBindingInfoList& samplerGroupBindingInfoList = *pSamplerGroupBindingInfoList; - SamplerBindingToNameMap& samplerBindingToNameMap = *pSamplerBindingToNameMap; +bool ChunkDescriptorBindingsInfo::unflatten(filaflat::Unflattener& unflattener, + MaterialParser::DescriptorBindingsContainer* container) { - uint8_t count; - if (!unflattener.read(&count)) { + uint8_t setCount; + if (!unflattener.read(&setCount)) { return false; } - assert_invariant(count == utils::Enum::count()); - UTILS_NOUNROLL - for (size_t i = 0; i < count; i++) { - if (!unflattener.read(&samplerGroupBindingInfoList[i].bindingOffset)) { + for (size_t j = 0; j < setCount; j++) { + static_assert(sizeof(DescriptorSetBindingPoints) == sizeof(uint8_t)); + + DescriptorSetBindingPoints set; + if (!unflattener.read(reinterpret_cast(&set))) { return false; } - if (!unflattener.read((uint8_t *)&samplerGroupBindingInfoList[i].shaderStageFlags)) { + + uint8_t descriptorCount; + if (!unflattener.read(&descriptorCount)) { return false; } - if (!unflattener.read(&samplerGroupBindingInfoList[i].count)) { - return false; + + auto& descriptors = (*container)[+set]; + descriptors.reserve(descriptorCount); + for (size_t i = 0; i < descriptorCount; i++) { + utils::CString name; + if (!unflattener.read(&name)) { + return false; + } + uint8_t type; + if (!unflattener.read(&type)) { + return false; + } + uint8_t binding; + if (!unflattener.read(&binding)) { + return false; + } + descriptors.push_back({ + std::move(name), + backend::DescriptorType(type), + backend::descriptor_binding_t(binding)}); } } - if (!unflattener.read(&count)) { - return false; - } + return true; +} - samplerBindingToNameMap.reserve(count); - samplerBindingToNameMap.resize(count); - for (size_t i = 0; i < count; i++) { - uint8_t binding; - if (!unflattener.read(&binding)) { +bool ChunkDescriptorSetLayoutInfo::unflatten(filaflat::Unflattener& unflattener, + MaterialParser::DescriptorSetLayoutContainer* container) { + for (size_t j = 0; j < 2; j++) { + uint8_t descriptorCount; + if (!unflattener.read(&descriptorCount)) { return false; } - assert_invariant(binding < backend::MAX_SAMPLER_COUNT); - if (!unflattener.read(&samplerBindingToNameMap[binding])) { - return false; + auto& descriptors = (*container)[j].bindings; + descriptors.reserve(descriptorCount); + for (size_t i = 0; i < descriptorCount; i++) { + uint8_t type; + if (!unflattener.read(&type)) { + return false; + } + uint8_t stageFlags; + if (!unflattener.read(&stageFlags)) { + return false; + } + uint8_t binding; + if (!unflattener.read(&binding)) { + return false; + } + uint8_t flags; + if (!unflattener.read(&flags)) { + return false; + } + uint16_t count; + if (!unflattener.read(&count)) { + return false; + } + descriptors.push_back({ + backend::DescriptorType(type), + backend::ShaderStageFlags(stageFlags), + backend::descriptor_binding_t(binding), + backend::DescriptorFlags(flags), + count, + }); } } - return true; } diff --git a/filament/src/MaterialParser.h b/filament/src/MaterialParser.h index a524b184109..4883a0dbba6 100644 --- a/filament/src/MaterialParser.h +++ b/filament/src/MaterialParser.h @@ -23,7 +23,6 @@ #include #include -#include "../../libs/filamat/src/SamplerBindingMap.h" #include #include @@ -31,11 +30,14 @@ #include #include -#include -#include +#include +#include #include +#include +#include + namespace filaflat { class ChunkContainer; class Unflattener; @@ -76,23 +78,24 @@ class MaterialParser { bool getSubpasses(SubpassInfo* subpass) const noexcept; bool getShaderModels(uint32_t* value) const noexcept; bool getMaterialProperties(uint64_t* value) const noexcept; - bool getUniformBlockBindings(utils::FixedCapacityVector>* value) const noexcept; - bool getSamplerBlockBindings(SamplerGroupBindingInfoList* pSamplerGroupInfoList, - SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept; bool getConstants(utils::FixedCapacityVector* value) const noexcept; bool getPushConstants(utils::CString* structVarName, utils::FixedCapacityVector* value) const noexcept; - using BindingUniformInfoContainer = utils::FixedCapacityVector< - std::pair>; - + using BindingUniformInfoContainer = utils::FixedCapacityVector>; bool getBindingUniformInfo(BindingUniformInfoContainer* container) const noexcept; using AttributeInfoContainer = utils::FixedCapacityVector< std::pair>; - bool getAttributeInfo(AttributeInfoContainer* container) const noexcept; + using DescriptorBindingsContainer = backend::Program::DescriptorSetInfo; + bool getDescriptorBindings(DescriptorBindingsContainer* container) const noexcept; + + using DescriptorSetLayoutContainer = std::array; + bool getDescriptorSetLayout(DescriptorSetLayoutContainer* container) const noexcept; + bool getDepthWriteSet(bool* value) const noexcept; bool getDepthWrite(bool* value) const noexcept; bool getDoubleSidedSet(bool* value) const noexcept; @@ -137,9 +140,13 @@ class MaterialParser { } private: + + template + bool get(typename T::Container* container) const noexcept; + struct MaterialParserDetails { MaterialParserDetails( - const utils::FixedCapacityVector& preferredLanguages, + utils::FixedCapacityVector preferredLanguages, const void* data, size_t size); template @@ -152,11 +159,8 @@ class MaterialParser { void* mStart = nullptr; size_t mSize = 0; public: - explicit ManagedBuffer(const void* start, size_t size) - : mStart(malloc(size)), mSize(size) { - memcpy(mStart, start, size); - } - ~ManagedBuffer() noexcept { free(mStart); } + explicit ManagedBuffer(const void* start, size_t size); + ~ManagedBuffer() noexcept; ManagedBuffer(ManagedBuffer const& rhs) = delete; ManagedBuffer& operator=(ManagedBuffer const& rhs) = delete; void* data() const noexcept { return mStart; } @@ -182,40 +186,55 @@ class MaterialParser { struct ChunkUniformInterfaceBlock { static bool unflatten(filaflat::Unflattener& unflattener, BufferInterfaceBlock* uib); + using Container = BufferInterfaceBlock; + static filamat::ChunkType const tag = filamat::MaterialUib; }; struct ChunkSamplerInterfaceBlock { static bool unflatten(filaflat::Unflattener& unflattener, SamplerInterfaceBlock* sib); + using Container = SamplerInterfaceBlock; + static filamat::ChunkType const tag = filamat::MaterialSib; }; struct ChunkSubpassInterfaceBlock { static bool unflatten(filaflat::Unflattener& unflattener, SubpassInfo* sib); -}; - -struct ChunkUniformBlockBindings { - static bool unflatten(filaflat::Unflattener& unflattener, - utils::FixedCapacityVector>* uniformBlockBindings); + using Container = SubpassInfo; + static filamat::ChunkType const tag = filamat::MaterialSubpass; }; struct ChunkBindingUniformInfo { static bool unflatten(filaflat::Unflattener& unflattener, MaterialParser::BindingUniformInfoContainer* bindingUniformInfo); + using Container = MaterialParser::BindingUniformInfoContainer; + static filamat::ChunkType const tag = filamat::MaterialBindingUniformInfo; }; struct ChunkAttributeInfo { static bool unflatten(filaflat::Unflattener& unflattener, MaterialParser::AttributeInfoContainer* attributeInfoContainer); + using Container = MaterialParser::AttributeInfoContainer; + static filamat::ChunkType const tag = filamat::MaterialAttributeInfo; +}; + +struct ChunkDescriptorBindingsInfo { + static bool unflatten(filaflat::Unflattener& unflattener, + MaterialParser::DescriptorBindingsContainer* container); + using Container = MaterialParser::DescriptorBindingsContainer; + static filamat::ChunkType const tag = filamat::MaterialDescriptorBindingsInfo; }; -struct ChunkSamplerBlockBindings { +struct ChunkDescriptorSetLayoutInfo { static bool unflatten(filaflat::Unflattener& unflattener, - SamplerGroupBindingInfoList* pSamplerGroupBindingInfoList, - SamplerBindingToNameMap* pSamplerBindingToNameMap); + MaterialParser::DescriptorSetLayoutContainer* container); + using Container = MaterialParser::DescriptorSetLayoutContainer; + static filamat::ChunkType const tag = filamat::MaterialDescriptorSetLayoutInfo; }; struct ChunkMaterialConstants { static bool unflatten(filaflat::Unflattener& unflattener, utils::FixedCapacityVector* materialConstants); + using Container = utils::FixedCapacityVector; + static filamat::ChunkType const tag = filamat::MaterialConstants; }; struct ChunkMaterialPushConstants { diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index f0ee30c682b..98150556f6b 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -26,6 +26,8 @@ #include "details/Engine.h" +#include "ds/SsrPassDescriptorSet.h" + #include "fg/FrameGraph.h" #include "fg/FrameGraphId.h" #include "fg/FrameGraphResources.h" @@ -33,7 +35,6 @@ #include "fsr.h" #include "FrameHistory.h" -#include "PerViewUniforms.h" #include "RenderPass.h" #include "details/Camera.h" @@ -187,12 +188,18 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine) UTILS_NOINLINE std::pair PostProcessManager::PostProcessMaterial::getPipelineState( - FEngine& engine, Variant::type_t variantKey) const noexcept { + FEngine& engine, + Variant::type_t variantKey) const noexcept { FMaterial* const material = getMaterial(engine); material->prepareProgram(Variant{ variantKey }); return {{ .program = material->getProgram(Variant{ variantKey }), .vertexBufferInfo = engine.getFullScreenVertexBuffer()->getVertexBufferInfoHandle(), + .pipelineLayout = { .setLayout = { + material->getPerViewDescriptorSetLayout().getHandle(), + engine.getPerRenderableDescriptorSetLayout().getHandle(), + material->getDescriptorSetLayout().getHandle() + } }, .rasterState = material->getRasterState() }, material->getDefaultInstance()->getScissor() }; } @@ -238,10 +245,21 @@ PostProcessManager::PostProcessManager(FEngine& engine) noexcept : mEngine(engine), mWorkaroundSplitEasu(false), mWorkaroundAllowReadOnlyAncillaryFeedbackLoop(false) { + // don't use Engine here, it's not fully initialized yet } PostProcessManager::~PostProcessManager() noexcept = default; +void PostProcessManager::setFrameUniforms(backend::DriverApi& driver, + TypedUniformBuffer& uniforms) noexcept { + mPostProcessDescriptorSet.setFrameUniforms(driver, uniforms); + mSsrPassDescriptorSet.setFrameUniforms(uniforms); +} + +void PostProcessManager::bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept { + mPostProcessDescriptorSet.bind(driver); +} + UTILS_NOINLINE void PostProcessManager::registerPostProcessMaterial(std::string_view name, MaterialInfo const& info) { @@ -324,6 +342,9 @@ void PostProcessManager::init() noexcept { //debugRegistry.registerProperty("d.ssao.kernelSize", &engine.debug.ssao.kernelSize); //debugRegistry.registerProperty("d.ssao.stddev", &engine.debug.ssao.stddev); + mSsrPassDescriptorSet.init(engine); + mPostProcessDescriptorSet.init(engine); + mWorkaroundSplitEasu = driver.isWorkaroundNeeded(Workaround::SPLIT_EASU); mWorkaroundAllowReadOnlyAncillaryFeedbackLoop = @@ -362,12 +383,16 @@ void PostProcessManager::init() noexcept { void PostProcessManager::terminate(DriverApi& driver) noexcept { FEngine& engine = mEngine; driver.destroyTexture(mStarburstTexture); + auto first = mMaterialRegistry.begin(); auto last = mMaterialRegistry.end(); while (first != last) { first.value().terminate(engine); ++first; } + + mPostProcessDescriptorSet.terminate(engine.getDescriptorSetLayoutFactory(), driver); + mSsrPassDescriptorSet.terminate(driver); } backend::Handle PostProcessManager::getOneTexture() const { @@ -391,6 +416,8 @@ void PostProcessManager::render(FrameGraphResources::RenderPassInfo const& out, backend::PipelineState const& pipeline, backend::Viewport const& scissor, DriverApi& driver) const noexcept { + bindPostProcessDescriptorSet(driver); + assert_invariant( ((out.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) && !pipeline.rasterState.depthWrite) @@ -472,12 +499,14 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph }); }, [=, passBuilder = passBuilder](FrameGraphResources const& resources, - auto const&, DriverApi&) mutable { + auto const&, DriverApi& driver) mutable { Variant structureVariant(Variant::DEPTH_VARIANT); structureVariant.setPicking(config.picking); auto out = resources.getRenderPassInfo(); + bindPostProcessDescriptorSet(driver); + passBuilder.renderFlags(structureRenderFlags); passBuilder.variant(structureVariant); passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SSAO); @@ -513,14 +542,14 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph auto in = resources.getTexture(data.depth); auto& material = getPostProcessMaterial("mipmapDepth"); FMaterialInstance* const mi = material.getMaterialInstance(mEngine); - mi->setParameter("depth", in, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); // The first mip already exists, so we process n-1 lods for (size_t level = 0; level < levelCount - 1; level++) { auto out = resources.getRenderPassInfo(level); - driver.setMinMaxLevels(in, level, level); + auto th = driver.createTextureView(in, level, 1); + mi->setParameter("depth", th, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); commitAndRender(out, material, driver); + driver.destroyTexture(th); } - driver.setMinMaxLevels(in, 0, levelCount - 1); }); return { depth, structurePass->picking }; @@ -532,7 +561,6 @@ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, RenderPassBuilder const& passBuilder, FrameHistory const& frameHistory, CameraInfo const& cameraInfo, - PerViewUniforms& uniforms, FrameGraphId structure, ScreenSpaceReflectionsOptions const& options, FrameGraphTexture::Descriptor const& desc) noexcept { @@ -592,10 +620,10 @@ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, }, [this, projection = cameraInfo.projection, userViewMatrix = cameraInfo.getUserViewMatrix(), uvFromClipMatrix, historyProjection, - options, &uniforms, passBuilder = passBuilder] + options, passBuilder = passBuilder] (FrameGraphResources const& resources, auto const& data, DriverApi& driver) mutable { // set structure sampler - uniforms.prepareStructure(data.structure ? + mSsrPassDescriptorSet.prepareStructure(data.structure ? resources.getTexture(data.structure) : getOneTexture()); // set screen-space reflections and screen-space refractions @@ -606,9 +634,11 @@ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, // the history sampler is a regular texture2D TextureHandle const history = data.history ? resources.getTexture(data.history) : getZeroTexture(); - uniforms.prepareHistorySSR(history, reprojection, uvFromViewMatrix, options); + mSsrPassDescriptorSet.prepareHistorySSR(history, reprojection, uvFromViewMatrix, options); + + mSsrPassDescriptorSet.commit(mEngine); - uniforms.commit(driver); + mSsrPassDescriptorSet.bind(driver); auto out = resources.getRenderPassInfo(); @@ -1170,7 +1200,7 @@ FrameGraphId PostProcessManager::gaussianBlurPass(FrameGraph& mi->setParameter("layer", 0.0f); mi->setParameter("axis", float2{ 0, 1.0f / tempDesc.height }); mi->commit(driver); - // we don't need to call use() here, since it's the same material + mi->use(driver); render(hwOutRT, separableGaussianBlur.getPipelineState(mEngine), driver); }); @@ -1619,26 +1649,25 @@ FrameGraphId PostProcessManager::dof(FrameGraph& fg, auto const& material = getPostProcessMaterial("dofMipmap"); FMaterialInstance* const mi = material.getMaterialInstance(mEngine); - mi->setParameter("color", inOutColor, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); - mi->setParameter("coc", inOutCoc, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); - mi->use(driver); auto const pipeline = material.getPipelineState(mEngine, variant); for (size_t level = 0 ; level < mipmapCount - 1u ; level++) { const float w = FTexture::valueForLevel(level, desc.width); const float h = FTexture::valueForLevel(level, desc.height); - auto const& out = resources.getRenderPassInfo(data.rp[level]); - driver.setMinMaxLevels(inOutColor, level, level); - driver.setMinMaxLevels(inOutCoc, level, level); + auto inColor = driver.createTextureView(inOutColor, level, 1); + auto inCoc = driver.createTextureView(inOutCoc, level, 1); + mi->setParameter("color", inColor, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); + mi->setParameter("coc", inCoc, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST }); mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres? mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h }); mi->commit(driver); + mi->use(driver); render(out, pipeline, driver); + driver.destroyTexture(inColor); + driver.destroyTexture(inCoc); } - driver.setMinMaxLevels(inOutColor, 0, mipmapCount - 1u); - driver.setMinMaxLevels(inOutCoc, 0, mipmapCount - 1u); }); /* @@ -2060,17 +2089,6 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, auto* mi9 = material9.getMaterialInstance(mEngine); auto* mi13 = material13.getMaterialInstance(mEngine); - mi9->setParameter("source", hwOut, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - - mi13->setParameter("source", hwOut, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); - - mi9->commit(driver); - mi13->commit(driver); - // PipelineState for both materials should be the same auto const pipeline = material9.getPipelineState(mEngine); @@ -2083,11 +2101,15 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, // 9 samples filter. auto vp = resources.getRenderPassInfo(data.outRT[i-1]).params.viewport; auto* const mi = (vp.width & 1 || vp.height & 1) ? mi13 : mi9; + auto hwOutView = driver.createTextureView(hwOut, i - 1, 1); + mi->setParameter("source", hwOutView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST }); + mi->commit(driver); mi->use(driver); - driver.setMinMaxLevels(hwOut, i - 1, i - 1); // this offsets baseLevel to i-1 render(hwDstRT, pipeline, driver); + driver.destroyTexture(hwOutView); } - driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1); }); // output of bloom downsample pass becomes input of next (flare) pass @@ -2111,11 +2133,6 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, auto const& material = getPostProcessMaterial("bloomUpsample"); auto* mi = material.getMaterialInstance(mEngine); - mi->setParameter("source", hwOut, { - .filterMag = SamplerMagFilter::LINEAR, - .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); - mi->commit(driver); - mi->use(driver); auto pipeline = material.getPipelineState(mEngine); pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE; @@ -2127,12 +2144,16 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg, hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE; auto w = FTexture::valueForLevel(i - 1, outDesc.width); auto h = FTexture::valueForLevel(i - 1, outDesc.height); + auto hwOutView = driver.createTextureView(hwOut, i, 1); mi->setParameter("resolution", float4{ w, h, 1.0f / w, 1.0f / h }); + mi->setParameter("source", hwOutView, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST}); mi->commit(driver); - driver.setMinMaxLevels(hwOut, i, i); // this offsets baseLevel to i + mi->use(driver); render(hwDstRT, pipeline, driver); + driver.destroyTexture(hwOutView); } - driver.setMinMaxLevels(hwOut, 0, inoutBloomOptions.levels - 1); }); return { bloomUpsamplePass->out, flare }; @@ -2269,6 +2290,9 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver, // the UBO has been set and committed in colorGradingPrepareSubpass() FMaterialInstance* mi = material.getMaterialInstance(mEngine); mi->use(driver); + + bindPostProcessDescriptorSet(driver); + const Variant::type_t variant = Variant::type_t(colorGradingConfig.translucent ? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE); @@ -2317,6 +2341,7 @@ FrameGraphId PostProcessManager::customResolveUncompressPass( customResolvePrepareSubpass(driver, CustomResolveOp::UNCOMPRESS); auto out = resources.getRenderPassInfo(); out.params.subpassMask = 1; + bindPostProcessDescriptorSet(driver); driver.beginRenderPass(out.target, out.params); customResolveSubpass(driver); driver.endRenderPass(); @@ -2341,7 +2366,7 @@ FrameGraphId PostProcessManager::colorGrading(FrameGraph& fg, bloomStrength = clamp(bloomOptions.strength, 0.0f, 1.0f); if (bloomOptions.dirt) { FTexture* fdirt = downcast(bloomOptions.dirt); - FrameGraphTexture const frameGraphTexture{ .handle = fdirt->getHwHandle() }; + FrameGraphTexture const frameGraphTexture{ .handle = fdirt->getHwHandleForSampling() }; bloomDirt = fg.import("dirt", { .width = (uint32_t)fdirt->getWidth(0u), .height = (uint32_t)fdirt->getHeight(0u), @@ -2525,13 +2550,12 @@ FrameGraphId PostProcessManager::fxaa(FrameGraph& fg, return ppFXAA->output; } -void PostProcessManager::prepareTaa(FrameGraph& fg, +void PostProcessManager::TaaJitterCamera( filament::Viewport const& svp, TemporalAntiAliasingOptions const& taaOptions, FrameHistory& frameHistory, FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa, - CameraInfo* inoutCameraInfo, - PerViewUniforms& uniforms) const noexcept { + CameraInfo* inoutCameraInfo) const noexcept { auto const& previous = frameHistory.getPrevious().*pTaa; auto& current = frameHistory.getCurrent().*pTaa; @@ -2576,12 +2600,6 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, // VERTEX_DOMAIN_DEVICE doesn't apply the projection, but it still needs this // clip transform, so we apply it separately (see main.vs) inoutCameraInfo->clipTransform.zw -= jitterInClipSpace; - - fg.addTrivialSideEffectPass("Jitter Camera", - [=, &uniforms] (DriverApi& driver) { - uniforms.prepareCamera(mEngine, *inoutCameraInfo); - uniforms.commit(driver); - }); } void PostProcessManager::configureTemporalAntiAliasingMaterial( @@ -2741,6 +2759,7 @@ FrameGraphId PostProcessManager::taa(FrameGraph& fg, out.params.subpassMask = 1; } auto const pipeline = material.getPipelineState(mEngine, variant); + bindPostProcessDescriptorSet(driver); driver.beginRenderPass(out.target, out.params); driver.scissor(pipeline.second); driver.draw(pipeline.first, mEngine.getFullScreenRenderPrimitive(), 0, 3, 1); @@ -2974,6 +2993,7 @@ FrameGraphId PostProcessManager::upscale(FrameGraph& fg, bool enableTranslucentBlending(pipeline0.first); enableTranslucentBlending(pipeline1.first); } + bindPostProcessDescriptorSet(driver); driver.beginRenderPass(out.target, out.params); driver.scissor(pipeline0.second); driver.draw(pipeline0.first, fullScreenRenderPrimitive, 0, 3, 1); @@ -3249,7 +3269,7 @@ FrameGraphId PostProcessManager::resolveDepth(FrameGraph& fg, FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg, FrameGraphId input, uint8_t layer, size_t level, - math::float4 clearColor, bool finalize) noexcept { + math::float4 clearColor) noexcept { struct VsmMipData { FrameGraphId in; @@ -3273,7 +3293,7 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { - auto in = resources.getTexture(data.in); + auto in = driver.createTextureView(resources.getTexture(data.in), level, 1); auto out = resources.getRenderPassInfo(); auto const& inDesc = resources.getDescriptor(data.in); @@ -3281,8 +3301,6 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg assert_invariant(width == inDesc.height); int const dim = width >> (level + 1); - driver.setMinMaxLevels(in, level, level); - auto& material = getPostProcessMaterial("vsmMipmap"); // When generating shadow map mip levels, we want to preserve the 1 texel border. @@ -3300,10 +3318,7 @@ FrameGraphId PostProcessManager::vsmMipmapPass(FrameGraph& fg mi->commit(driver); mi->use(driver); render(out, pipeline, scissor, driver); - - if (finalize) { - driver.setMinMaxLevels(in, 0, level); - } + driver.destroyTexture(in); // `in` is just a view on `data.in` }); return depthMipmapPass->in; diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index 76f5bcd325f..0aef0cf8ef4 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -21,26 +21,42 @@ #include "FrameHistory.h" +#include "ds/DescriptorSetLayout.h" +#include "ds/PostProcessDescriptorSet.h" +#include "ds/SsrPassDescriptorSet.h" +#include "ds/TypedUniformBuffer.h" + #include #include +#include #include +#include #include +#include #include #include -#include +#include +#include + +#include #include #include #include +#include #include #include +#include #include +#include +#include + namespace filament { class FColorGrading; @@ -48,7 +64,6 @@ class FEngine; class FMaterial; class FMaterialInstance; class FrameGraph; -class PerViewUniforms; class RenderPass; class RenderPassBuilder; struct CameraInfo; @@ -88,7 +103,6 @@ class PostProcessManager { void init() noexcept; void terminate(backend::DriverApi& driver) noexcept; - void configureTemporalAntiAliasingMaterial( TemporalAntiAliasingOptions const& taaOptions) noexcept; @@ -108,7 +122,6 @@ class PostProcessManager { RenderPassBuilder const& passBuilder, FrameHistory const& frameHistory, CameraInfo const& cameraInfo, - PerViewUniforms& uniforms, FrameGraphId structure, ScreenSpaceReflectionsOptions const& options, FrameGraphTexture::Descriptor const& desc) noexcept; @@ -215,13 +228,12 @@ class PostProcessManager { backend::TextureFormat outFormat, bool translucent) noexcept; // Temporal Anti-aliasing - void prepareTaa(FrameGraph& fg, + void TaaJitterCamera( filament::Viewport const& svp, TemporalAntiAliasingOptions const& taaOptions, FrameHistory& frameHistory, FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa, - CameraInfo* inoutCameraInfo, - PerViewUniforms& uniforms) const noexcept; + CameraInfo* inoutCameraInfo) const noexcept; FrameGraphId taa(FrameGraph& fg, FrameGraphId input, @@ -276,7 +288,7 @@ class PostProcessManager { // VSM shadow mipmap pass FrameGraphId vsmMipmapPass(FrameGraph& fg, FrameGraphId input, uint8_t layer, size_t level, - math::float4 clearColor, bool finalize) noexcept; + math::float4 clearColor) noexcept; FrameGraphId gaussianBlurPass(FrameGraph& fg, FrameGraphId input, @@ -361,9 +373,17 @@ class PostProcessManager { render(out, combo.first, combo.second, driver); } + void setFrameUniforms(backend::DriverApi& driver, + TypedUniformBuffer& uniforms) noexcept; + + void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept; + private: FEngine& mEngine; + mutable SsrPassDescriptorSet mSsrPassDescriptorSet; + mutable PostProcessDescriptorSet mPostProcessDescriptorSet; + struct BilateralPassConfig { uint8_t kernelSize = 11; bool bentNormals = false; diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 55ec3264b2d..bec8e313275 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -26,6 +26,8 @@ #include "components/RenderableManager.h" +#include "ds/DescriptorSet.h" + #include #include #include @@ -81,8 +83,7 @@ RenderPassBuilder& RenderPassBuilder::customCommand( } RenderPass RenderPassBuilder::build(FEngine& engine) { - FILAMENT_CHECK_POSTCONDITION(mRenderableSoa) - << "RenderPassBuilder::geometry() hasn't been called"; + assert_invariant(mRenderableSoa); assert_invariant(mScissorViewport.width <= std::numeric_limits::max()); assert_invariant(mScissorViewport.height <= std::numeric_limits::max()); return RenderPass{ engine, *this }; @@ -92,15 +93,23 @@ RenderPass RenderPassBuilder::build(FEngine& engine) { void RenderPass::BufferObjectHandleDeleter::operator()( backend::BufferObjectHandle handle) noexcept { - if (handle) { + if (handle) { // this is common case driver.get().destroyBufferObject(handle); } } +void RenderPass::DescriptorSetHandleDeleter::operator()( + backend::DescriptorSetHandle handle) noexcept { + if (handle) { // this is common case + driver.get().destroyDescriptorSet(handle); + } +} + // ------------------------------------------------------------------------------------------------ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexcept : mRenderableSoa(*builder.mRenderableSoa), + mColorPassDescriptorSet(builder.mColorPassDescriptorSet), mScissorViewport(builder.mScissorViewport), mCustomCommands(engine.getPerRenderPassArena()) { @@ -132,7 +141,6 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce } appendCommands(engine, { commandBegin, commandCount }, - builder.mUboHandle, builder.mVisibleRenderables, builder.mCommandTypeFlags, builder.mFlags, @@ -143,7 +151,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce if (builder.mCustomCommands.has_value()) { Command* p = commandBegin + commandCount; - for (auto [channel, passId, command, order, fn]: builder.mCustomCommands.value()) { + for (auto const& [channel, passId, command, order, fn]: builder.mCustomCommands.value()) { appendCustomCommand(p++, channel, passId, command, order, fn); } } @@ -176,7 +184,6 @@ RenderPass::Command* RenderPass::resize(Arena& arena, Command* const last) noexc void RenderPass::appendCommands(FEngine& engine, Slice commands, - backend::BufferObjectHandle const uboHandle, utils::Range const vr, CommandTypeFlags const commandTypeFlags, RenderFlags const renderFlags, @@ -209,12 +216,11 @@ void RenderPass::appendCommands(FEngine& engine, auto stereoscopicEyeCount = engine.getConfig().stereoscopicEyeCount; auto work = [commandTypeFlags, curr, &soa, - boh = uboHandle, variant, renderFlags, visibilityMask, cameraPosition, cameraForwardVector, stereoscopicEyeCount] (uint32_t startIndex, uint32_t indexCount) { RenderPass::generateCommands(commandTypeFlags, curr, - soa, { startIndex, startIndex + indexCount }, boh, + soa, { startIndex, startIndex + indexCount }, variant, renderFlags, visibilityMask, cameraPosition, cameraForwardVector, stereoscopicEyeCount); }; @@ -313,18 +319,22 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine, constexpr size_t maxInstanceCount = CONFIG_MAX_INSTANCES; while (curr != last) { - - // Currently, if we have skinnning or morphing, we can't use auto instancing. This is - // because the morphing/skinning data for comparison is not easily accessible. - // Additionally, we can't have a different skinning/morphing per instance anyway. - // And thirdly, the info.index meaning changes with instancing, it is the index into - // the instancing buffer no longer the index into the soa. + // Currently, if we have skinning or morphing, we can't use auto instancing. This is + // because the morphing/skinning data for comparison is not easily accessible; and also + // because we're assuming that the per-renderable descriptor-set only has the + // OBJECT_UNIFORMS descriptor active (i.e. the skinning/morphing descriptors are unused). + // We also can't use auto-instancing if manual- or hybrid- instancing is used. + // TODO: support auto-instancing for skinning/morphing Command const* e = curr + 1; - if (UTILS_LIKELY(!curr->info.hasSkinning && !curr->info.hasMorphing)) { + if (UTILS_LIKELY( + !curr->info.hasSkinning && !curr->info.hasMorphing && + curr->info.instanceCount <= 1)) + { + assert_invariant(!curr->info.hasHybridInstancing); // we can't have nice things! No more than maxInstanceCount due to UBO size limits e = std::find_if_not(curr, std::min(last, curr + maxInstanceCount), [lhs = *curr](Command const& rhs) { - // primitives must be identical to be instanced. + // primitives must be identical to be instanced // Currently, instancing doesn't support skinning/morphing. return lhs.info.mi == rhs.info.mi && lhs.info.rph == rhs.info.rph && @@ -342,23 +352,43 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine, if (UTILS_UNLIKELY(instanceCount > 1)) { drawCallsSavedCount += instanceCount - 1; + auto& driver = engine.getDriverApi(); + // allocate our staging buffer only if needed if (UTILS_UNLIKELY(!stagingBuffer)) { + // Create a temporary UBO for holding the per-renderable data of each primitive, + // The `curr->info.index` is updated so that this (now instanced) command can + // bind the UBO in the right place (where the per-instance data is). + // The lifetime of this object is the longest of this RenderPass and all its + // executors. // create a temporary UBO for instancing mInstancedUboHandle = BufferObjectSharedHandle{ - engine.getDriverApi().createBufferObject( + driver.createBufferObject( count * sizeof(PerRenderableData) + sizeof(PerRenderableUib), - BufferObjectBinding::UNIFORM, BufferUsage::STATIC), - engine.getDriverApi() }; + BufferObjectBinding::UNIFORM, BufferUsage::STATIC), driver }; // TODO: use stream inline buffer for small sizes // TODO: use a pool for larger heap buffers // buffer large enough for all instances data - stagingBufferSize = sizeof(PerRenderableData) * (last - curr); + stagingBufferSize = count * sizeof(PerRenderableData); stagingBuffer = (PerRenderableData*)::malloc(stagingBufferSize); uboData = mRenderableSoa.data(); assert_invariant(uboData); + + // We also need a descriptor-set to hold the custom UBO. This works because + // we currently assume the descriptor-set only needs to hold this UBO in the + // instancing case (it wouldn't be true if we supported skinning/morphing, and + // in this case we would need to preserve the default descriptor-set content). + // This has the same lifetime as the UBO (see above). + mInstancedDescriptorSetHandle = DescriptorSetSharedHandle{ + driver.createDescriptorSet( + engine.getPerRenderableDescriptorSetLayout().getHandle()), + driver + }; + driver.updateDescriptorSetBuffer(mInstancedDescriptorSetHandle, + +PerRenderableBindingPoints::OBJECT_UNIFORMS, + mInstancedUboHandle, 0, sizeof(PerRenderableUib)); } // copy the ubo data to a staging buffer @@ -371,7 +401,7 @@ RenderPass::Command* RenderPass::instanceify(FEngine& engine, // make the first command instanced curr[0].info.instanceCount = instanceCount * eyeCount; curr[0].info.index = instancedPrimitiveOffset; - curr[0].info.boh = mInstancedUboHandle; + curr[0].info.dsh = mInstancedDescriptorSetHandle; instancedPrimitiveOffset += instanceCount; @@ -469,10 +499,10 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant, /* static */ UTILS_NOINLINE void RenderPass::generateCommands(CommandTypeFlags commandTypeFlags, Command* const commands, - FScene::RenderableSoa const& soa, Range range, - backend::BufferObjectHandle renderablesUbo, - Variant variant, RenderFlags renderFlags, - FScene::VisibleMaskType visibilityMask, float3 cameraPosition, float3 cameraForward, + FScene::RenderableSoa const& soa, Range const range, + Variant const variant, RenderFlags const renderFlags, + FScene::VisibleMaskType const visibilityMask, + float3 const cameraPosition, float3 const cameraForward, uint8_t stereoEyeCount) noexcept { SYSTRACE_CALL(); @@ -504,13 +534,13 @@ void RenderPass::generateCommands(CommandTypeFlags commandTypeFlags, Command* co switch (commandTypeFlags & (CommandTypeFlags::COLOR | CommandTypeFlags::DEPTH)) { case CommandTypeFlags::COLOR: curr = generateCommandsImpl(commandTypeFlags, curr, - soa, range, renderablesUbo, + soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward, stereoEyeCount); break; case CommandTypeFlags::DEPTH: curr = generateCommandsImpl(commandTypeFlags, curr, - soa, range, renderablesUbo, + soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward, stereoEyeCount); break; @@ -534,7 +564,6 @@ UTILS_NOINLINE RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFlags extraFlags, Command* UTILS_RESTRICT curr, FScene::RenderableSoa const& UTILS_RESTRICT soa, Range range, - backend::BufferObjectHandle renderablesUbo, Variant const variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, float3 cameraPosition, float3 cameraForward, uint8_t stereoEyeCount) noexcept { @@ -572,6 +601,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla auto const* const UTILS_RESTRICT soaMorphing = soa.data(); auto const* const UTILS_RESTRICT soaVisibilityMask = soa.data(); auto const* const UTILS_RESTRICT soaInstanceInfo = soa.data(); + auto const* const UTILS_RESTRICT soaDescriptorSet = soa.data(); Command cmd; @@ -655,6 +685,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla cmd.info.hasMorphing = (bool)morphing.handle; cmd.info.hasSkinning = (bool)skinning.handle; + assert_invariant(cmd.info.hasHybridInstancing || cmd.info.instanceCount <= 1); + // soaInstanceInfo[i].count is the number of instances the user has requested, either for // manual or hybrid instancing. Instanced stereo multiplies the number of instances by the // eye count. @@ -662,13 +694,12 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla cmd.info.instanceCount *= stereoEyeCount; } - if (cmd.info.hasHybridInstancing) { - // with hybrid instancing, we already know which UBO to use - cmd.info.boh = soaInstanceInfo[i].handle; - } else { - // with no- or user- instancing, we can only know after instanceify() - cmd.info.boh = renderablesUbo; - } + // soaDescriptorSet[i] is either populated with a common descriptor-set or truly with + // a per-renderable one, depending on for e.g. skinning/morphing/instancing. + cmd.info.dsh = soaDescriptorSet[i]; + + // always set the skinningOffset, even when skinning is off, this doesn't cost anything. + cmd.info.skinningOffset = soaSkinning[i].offset * sizeof(PerRenderableBoneUib::BoneData); const bool shadowCaster = soaVisibility[i].castShadows & hasShadowing; const bool writeDepthForShadowCasters = depthContainsShadowCasters & shadowCaster; @@ -678,8 +709,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla * This is our hot loop. It's written to avoid branches. * When modifying this code, always ensure it stays efficient. */ - for (size_t pi = 0, c = primitives.size(); pi < c; ++pi) { - auto const& primitive = primitives[pi]; + for (auto const& primitive: primitives) { FMaterialInstance const* const mi = primitive.getMaterialInstance(); FMaterial const* const ma = mi->getMaterial(); @@ -692,9 +722,10 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla cmd.info.indexOffset = primitive.getIndexOffset(); cmd.info.indexCount = primitive.getIndexCount(); cmd.info.type = primitive.getPrimitiveType(); - cmd.info.morphTargetBuffer = morphing.morphTargetBuffer ? - morphing.morphTargetBuffer->getHwHandle() : SamplerGroupHandle{}; cmd.info.morphingOffset = primitive.getMorphingBufferOffset(); +// FIXME: morphtarget buffer +// cmd.info.morphTargetBuffer = morphing.morphTargetBuffer ? +// morphing.morphTargetBuffer->getHwHandle() : SamplerGroupHandle{}; if constexpr (isColorPass) { RenderPass::setupColorCommand(cmd, renderableVariant, mi, @@ -867,6 +898,7 @@ void RenderPass::Executor::execute(FEngine& engine, SYSTRACE_CONTEXT(); DriverApi& driver = engine.getDriverApi(); + size_t const capacity = engine.getMinCommandBufferSize(); CircularBuffer const& circularBuffer = driver.getCircularBuffer(); @@ -875,7 +907,7 @@ void RenderPass::Executor::execute(FEngine& engine, bool const scissorOverride = mScissorOverride; if (UTILS_UNLIKELY(scissorOverride)) { - // initialize with scissor overide + // initialize with scissor override driver.scissor(mScissor); } @@ -885,9 +917,11 @@ void RenderPass::Executor::execute(FEngine& engine, .polygonOffset = mPolygonOffset, }; + pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_RENDERABLE] = + engine.getPerRenderableDescriptorSetLayout().getHandle(); + PipelineState currentPipeline{}; Handle currentPrimitiveHandle{}; - bool rebindPipeline = true; FMaterialInstance const* UTILS_RESTRICT mi = nullptr; FMaterial const* UTILS_RESTRICT ma = nullptr; @@ -895,24 +929,17 @@ void RenderPass::Executor::execute(FEngine& engine, // Maximum space occupied in the CircularBuffer by a single `Command`. This must be // reevaluated when the inner loop below adds DriverApi commands or when we change the - // CommandStream protocol. Currently, the maximum is 320 bytes. + // CommandStream protocol. Currently, the maximum is 248 bytes. // The batch size is calculated by adding the size of all commands that can possibly be // emitted per draw call: constexpr size_t const maxCommandSizeInBytes = - sizeof(CustomCommand) + sizeof(COMMAND_TYPE(scissor)) + - sizeof(COMMAND_TYPE(bindUniformBuffer)) + - sizeof(COMMAND_TYPE(bindSamplers)) + - sizeof(COMMAND_TYPE(bindBufferRange)) + - sizeof(COMMAND_TYPE(bindBufferRange)) + - sizeof(COMMAND_TYPE(bindSamplers)) + - sizeof(COMMAND_TYPE(bindSamplers)) + - sizeof(COMMAND_TYPE(bindUniformBuffer)) + - sizeof(COMMAND_TYPE(bindSamplers)) + - sizeof(COMMAND_TYPE(bindSamplers)) + + sizeof(COMMAND_TYPE(bindDescriptorSet)) + + sizeof(COMMAND_TYPE(bindDescriptorSet)) + sizeof(COMMAND_TYPE(bindPipeline)) + - sizeof(COMMAND_TYPE(setPushConstant)) + sizeof(COMMAND_TYPE(bindRenderPrimitive)) + + sizeof(COMMAND_TYPE(bindDescriptorSet)) + backend::CustomCommand::align(sizeof(NoopCommand) + 8) + + sizeof(COMMAND_TYPE(setPushConstant)) + sizeof(COMMAND_TYPE(draw2)); @@ -969,105 +996,82 @@ void RenderPass::Executor::execute(FEngine& engine, ma = mi->getMaterial(); - if (UTILS_LIKELY(!scissorOverride)) { - backend::Viewport scissor = mi->getScissor(); - if (UTILS_UNLIKELY(mi->hasScissor())) { - scissor = applyScissorViewport(mScissorViewport, scissor); - } - driver.scissor(scissor); - } - - if (UTILS_LIKELY(!polygonOffsetOverride)) { - pipeline.polygonOffset = mi->getPolygonOffset(); - } + if (UTILS_LIKELY(!scissorOverride)) { + backend::Viewport scissor = mi->getScissor(); + if (UTILS_UNLIKELY(mi->hasScissor())) { + scissor = applyScissorViewport(mScissorViewport, scissor); + } + driver.scissor(scissor); + } + + if (UTILS_LIKELY(!polygonOffsetOverride)) { + pipeline.polygonOffset = mi->getPolygonOffset(); + } pipeline.stencilState = mi->getStencilState(); - mi->use(driver); - // FIXME: MaterialInstance changed (not necessarily the program though), - // however, texture bindings may have changed and currently we need to - // rebind the pipeline when that happens. - rebindPipeline = true; + // Each material has its own version of the per-view descriptor-set layout, + // because it depends on the material features (e.g. lit/unlit) + pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_VIEW] = + ma->getPerViewDescriptorSetLayout(info.materialVariant).getHandle(); + + // Each material has a per-material descriptor-set layout which encodes the + // material's parameters (ubo and samplers) + pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] = + ma->getDescriptorSetLayout().getHandle(); + + if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) { + // It is possible to get a post-process material here (even though it's + // not technically a public API yet, it is used by the IBLPrefilterLibrary. + // Ideally we would have a more formal compute API). In this case, we need + // to set the post-process descriptor-set. + engine.getPostProcessManager().bindPostProcessDescriptorSet(driver); + } else { + // If we have a ColorPassDescriptorSet we use it to bind the per-view + // descriptor-set (ideally only if it changed). If we don't, it means + // the descriptor-set is already bound and the layout we got from the + // material above should match. This is the case for situations where we + // have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and + // structure passes. + if (mColorPassDescriptorSet) { + // We have a ColorPassDescriptorSet, we need to go through it for binding + // the per-view descriptor-set because its layout can change based on the + // material. + mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex()); + } + } + + // Each MaterialInstance has its own descriptor set. This binds it. + mi->use(driver); } assert_invariant(ma); pipeline.program = ma->getProgram(info.materialVariant); + if (UTILS_UNLIKELY(memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) { + currentPipeline = pipeline; + driver.bindPipeline(pipeline); + } + + if (UTILS_UNLIKELY(info.rph != currentPrimitiveHandle)) { + currentPrimitiveHandle = info.rph; + driver.bindRenderPrimitive(info.rph); + } + // Bind per-renderable uniform block. There is no need to attempt to skip this command // because the backends already do this. - size_t const offset = info.hasHybridInstancing ? + uint32_t const offset = info.hasHybridInstancing ? 0 : info.index * sizeof(PerRenderableData); - assert_invariant(info.boh); - - driver.bindBufferRange(BufferObjectBinding::UNIFORM, - +UniformBindingPoints::PER_RENDERABLE, - info.boh, offset, sizeof(PerRenderableUib)); - - if (UTILS_UNLIKELY(info.hasSkinning)) { - - FScene::RenderableSoa const& soa = *mRenderableSoa; - - const FRenderableManager::SkinningBindingInfo& skinning = - soa.elementAt(info.index); - - // note: we can't bind less than sizeof(PerRenderableBoneUib) due to glsl limitations - driver.bindBufferRange(BufferObjectBinding::UNIFORM, - +UniformBindingPoints::PER_RENDERABLE_BONES, - skinning.handle, - skinning.offset * sizeof(PerRenderableBoneUib::BoneData), - sizeof(PerRenderableBoneUib)); - // note: always bind the skinningTexture because the shader needs it. - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, - skinning.handleSampler); - // note: even if only skinning is enabled, binding morphTargetBuffer is needed. - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, - info.morphTargetBuffer); - - // FIXME: Currently we need to rebind the PipelineState when texture or - // UBO binding change. - rebindPipeline = true; - } + assert_invariant(info.dsh); + driver.bindDescriptorSet(info.dsh, + +DescriptorSetBindingPoints::PER_RENDERABLE, + {{ offset, info.skinningOffset }, driver}); if (UTILS_UNLIKELY(info.hasMorphing)) { - - FScene::RenderableSoa const& soa = *mRenderableSoa; - - const FRenderableManager::SkinningBindingInfo& skinning = - soa.elementAt(info.index); - - const FRenderableManager::MorphingBindingInfo& morphing = - soa.elementAt(info.index); - - // Instead of using a UBO per primitive, we could also have a single UBO for all - // primitives and use bindUniformBufferRange which might be more efficient. - driver.bindUniformBuffer(+UniformBindingPoints::PER_RENDERABLE_MORPHING, - morphing.handle); - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING, - info.morphTargetBuffer); - // note: even if only morphing is enabled, binding skinningTexture is needed. - driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING, - skinning.handleSampler); - - // FIXME: Currently we need to rebind the PipelineState when texture or - // UBO binding change. - rebindPipeline = true; - } - - if (rebindPipeline || - (memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) { - rebindPipeline = false; - currentPipeline = pipeline; - driver.bindPipeline(pipeline); - driver.setPushConstant(ShaderStage::VERTEX, +PushConstantIds::MORPHING_BUFFER_OFFSET, int32_t(info.morphingOffset)); } - if (info.rph != currentPrimitiveHandle) { - currentPrimitiveHandle = info.rph; - driver.bindRenderPrimitive(info.rph); - } - driver.draw2(info.indexOffset, info.indexCount, info.instanceCount); } } @@ -1082,17 +1086,17 @@ void RenderPass::Executor::execute(FEngine& engine, // ------------------------------------------------------------------------------------------------ -RenderPass::Executor::Executor(RenderPass const* pass, Command const* b, Command const* e, - BufferObjectSharedHandle instancedUbo) noexcept - : mRenderableSoa(&pass->mRenderableSoa), - mCommands(b, e), - mCustomCommands(pass->mCustomCommands.data(), pass->mCustomCommands.size()), - mInstancedUboHandle(std::move(instancedUbo)), - mScissorViewport(pass->mScissorViewport), +RenderPass::Executor::Executor(RenderPass const& pass, Command const* b, Command const* e) noexcept + : mCommands(b, e), + mCustomCommands(pass.mCustomCommands.data(), pass.mCustomCommands.size()), + mInstancedUboHandle(pass.mInstancedUboHandle), + mInstancedDescriptorSetHandle(pass.mInstancedDescriptorSetHandle), + mColorPassDescriptorSet(pass.mColorPassDescriptorSet), + mScissorViewport(pass.mScissorViewport), mPolygonOffsetOverride(false), mScissorOverride(false) { - assert_invariant(b >= pass->begin()); - assert_invariant(e <= pass->end()); + assert_invariant(b >= pass.begin()); + assert_invariant(e <= pass.end()); } RenderPass::Executor::Executor() noexcept diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 485fd905894..fc4370aa8b9 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -58,6 +58,7 @@ class CommandBufferQueue; class FMaterialInstance; class FRenderPrimitive; class RenderPassBuilder; +class ColorPassDescriptorSet; class RenderPass { public: @@ -244,25 +245,25 @@ class RenderPass { FMaterialInstance const* mi; uint64_t padding; // make this field 64 bits on all platforms }; - backend::RenderPrimitiveHandle rph; // 4 bytes - backend::VertexBufferInfoHandle vbih; // 4 bytes - backend::BufferObjectHandle boh; // 4 bytes - uint32_t indexOffset; // 4 bytes - uint32_t indexCount; // 4 bytes - uint32_t index = 0; // 4 bytes - backend::SamplerGroupHandle morphTargetBuffer; // 4 bytes - uint32_t morphingOffset = 0; // 4 bytes - - backend::RasterState rasterState; // 4 bytes - - uint16_t instanceCount; // 2 bytes [MSb: user] - Variant materialVariant; // 1 byte - backend::PrimitiveType type : 3; // 1 byte 3 bits - bool hasSkinning : 1; // 1 bit - bool hasMorphing : 1; // 1 bit - bool hasHybridInstancing : 1; // 1 bit - - uint32_t rfu[2]; // 16 bytes + backend::RenderPrimitiveHandle rph; // 4 bytes + backend::VertexBufferInfoHandle vbih; // 4 bytes + backend::DescriptorSetHandle dsh; // 4 bytes + uint32_t indexOffset; // 4 bytes + uint32_t indexCount; // 4 bytes + uint32_t index = 0; // 4 bytes + uint32_t skinningOffset = 0; // 4 bytes + uint32_t morphingOffset = 0; // 4 bytes + + backend::RasterState rasterState; // 4 bytes + + uint16_t instanceCount; // 2 bytes [MSb: user] + Variant materialVariant; // 1 byte + backend::PrimitiveType type : 3; // 1 byte 3 bits + bool hasSkinning : 1; // 1 bit + bool hasMorphing : 1; // 1 bit + bool hasHybridInstancing : 1; // 1 bit + + uint32_t rfu[2]; // 16 bytes }; static_assert(sizeof(PrimitiveInfo) == 56); @@ -322,9 +323,19 @@ class RenderPass { void operator()(backend::BufferObjectHandle handle) noexcept; }; + class DescriptorSetHandleDeleter { + std::reference_wrapper driver; + public: + explicit DescriptorSetHandleDeleter(backend::DriverApi& driver) noexcept : driver(driver) { } + void operator()(backend::DescriptorSetHandle handle) noexcept; + }; + using BufferObjectSharedHandle = SharedHandle< backend::HwBufferObject, BufferObjectHandleDeleter>; + using DescriptorSetSharedHandle = SharedHandle< + backend::HwDescriptorSet, DescriptorSetHandleDeleter>; + /* * Executor holds the range of commands to execute for a given pass */ @@ -334,10 +345,11 @@ class RenderPass { friend class RenderPassBuilder; // these fields are constant after creation - FScene::RenderableSoa const* mRenderableSoa = nullptr; utils::Slice mCommands; utils::Slice mCustomCommands; BufferObjectSharedHandle mInstancedUboHandle; + DescriptorSetSharedHandle mInstancedDescriptorSetHandle; + ColorPassDescriptorSet const* mColorPassDescriptorSet = nullptr; backend::Viewport mScissorViewport; backend::Viewport mScissor{}; // value of scissor override @@ -345,8 +357,7 @@ class RenderPass { bool mPolygonOffsetOverride : 1; // whether to override the polygon offset setting bool mScissorOverride : 1; // whether to override the polygon offset setting - Executor(RenderPass const* pass, Command const* b, Command const* e, - BufferObjectSharedHandle instancedUbo) noexcept; + Executor(RenderPass const& pass, Command const* b, Command const* e) noexcept; void execute(FEngine& engine, const Command* first, const Command* last) const noexcept; @@ -355,6 +366,7 @@ class RenderPass { backend::Viewport const& scissor) noexcept; public: + // fixme: needed in ShadowMapManager Executor() noexcept; // can't be copied @@ -383,7 +395,7 @@ class RenderPass { } Executor getExecutor(Command const* b, Command const* e) const { - return { this, b, e, mInstancedUboHandle }; + return { *this, b, e }; } private: @@ -395,8 +407,7 @@ class RenderPass { // the current camera, geometry and flags set. This can be called multiple times if needed. void appendCommands(FEngine& engine, utils::Slice commands, - backend::BufferObjectHandle uboHandle, - utils::Range const visibleRenderables, + utils::Range visibleRenderables, CommandTypeFlags commandTypeFlags, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, @@ -430,16 +441,14 @@ class RenderPass { static inline void generateCommands(CommandTypeFlags commandTypeFlags, Command* commands, FScene::RenderableSoa const& soa, utils::Range range, - backend::BufferObjectHandle renderablesUbo, Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, math::float3 cameraPosition, math::float3 cameraForward, uint8_t instancedStereoEyeCount) noexcept; template - static inline Command* generateCommandsImpl(RenderPass::CommandTypeFlags extraFlags, Command* curr, - FScene::RenderableSoa const& soa, utils::Range range, - backend::BufferObjectHandle renderablesUbo, + static inline RenderPass::Command* generateCommandsImpl(RenderPass::CommandTypeFlags extraFlags, + Command* curr, FScene::RenderableSoa const& soa, utils::Range range, Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask, math::float3 cameraPosition, math::float3 cameraForward, uint8_t instancedStereoEyeCount) noexcept; @@ -451,11 +460,12 @@ class RenderPass { FScene::RenderableSoa& renderableData, utils::Range vr) noexcept; FScene::RenderableSoa const& mRenderableSoa; + ColorPassDescriptorSet const* const mColorPassDescriptorSet; backend::Viewport const mScissorViewport; Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command - // a UBO for instanced primitives - mutable BufferObjectSharedHandle mInstancedUboHandle; + mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives + mutable DescriptorSetSharedHandle mInstancedDescriptorSetHandle; // a descriptor-set to hold the ubo // a vector for our custom commands using CustomCommandVector = std::vector>; @@ -470,11 +480,11 @@ class RenderPassBuilder { backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX }; FScene::RenderableSoa const* mRenderableSoa = nullptr; utils::Range mVisibleRenderables{}; - backend::Handle mUboHandle; math::float3 mCameraPosition{}; math::float3 mCameraForwardVector{}; RenderPass::RenderFlags mFlags{}; Variant mVariant{}; + ColorPassDescriptorSet const* mColorPassDescriptorSet = nullptr; FScene::VisibleMaskType mVisibilityMask = std::numeric_limits::max(); using CustomCommandRecord = std::tuple< @@ -505,11 +515,10 @@ class RenderPassBuilder { } // specifies the geometry to generate commands for - RenderPassBuilder& geometry(FScene::RenderableSoa const& soa, utils::Range vr, - backend::Handle uboHandle) noexcept { + RenderPassBuilder& geometry( + FScene::RenderableSoa const& soa, utils::Range vr) noexcept { mRenderableSoa = &soa; mVisibleRenderables = vr; - mUboHandle = uboHandle; return *this; } @@ -541,6 +550,12 @@ class RenderPassBuilder { return *this; } + // variant to use + RenderPassBuilder& colorPassDescriptorSet(ColorPassDescriptorSet const* colorPassDescriptorSet) noexcept { + mColorPassDescriptorSet = colorPassDescriptorSet; + return *this; + } + // Sets the visibility mask, which is AND-ed against each Renderable's VISIBLE_MASK to // determine if the renderable is visible for this pass. // Defaults to all 1's, which means all renderables in this render pass will be rendered. diff --git a/filament/src/RenderPrimitive.cpp b/filament/src/RenderPrimitive.cpp index 8bd5e5ad573..42918c0b429 100644 --- a/filament/src/RenderPrimitive.cpp +++ b/filament/src/RenderPrimitive.cpp @@ -33,7 +33,7 @@ namespace filament { void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver, - const RenderableManager::Builder::Entry& entry) noexcept { + FRenderableManager::Entry const& entry) noexcept { assert_invariant(entry.materialInstance); diff --git a/filament/src/RenderPrimitive.h b/filament/src/RenderPrimitive.h index a8600dd3a6f..a4716399e18 100644 --- a/filament/src/RenderPrimitive.h +++ b/filament/src/RenderPrimitive.h @@ -41,7 +41,7 @@ class FRenderPrimitive { FRenderPrimitive() noexcept = default; void init(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver, - const RenderableManager::Builder::Entry& entry) noexcept; + FRenderableManager::Entry const& entry) noexcept; void set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver, RenderableManager::PrimitiveType type, diff --git a/filament/src/RendererUtils.cpp b/filament/src/RendererUtils.cpp index c25530f8811..2e57af691f4 100644 --- a/filament/src/RendererUtils.cpp +++ b/filament/src/RendererUtils.cpp @@ -233,7 +233,7 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass( view.prepareViewport(static_cast(out.params.viewport), config.logicalViewport); - view.commitUniforms(driver); + view.commitUniformsAndSamplers(driver); // TODO: this should be a parameter of FrameGraphRenderPass::Descriptor out.params.clearStencil = config.clearStencil; @@ -307,7 +307,11 @@ std::optional RendererUtils::refractionPass( config, { .asSubpass = false, .customResolve = false }, pass.getExecutor(pass.begin(), refraction)); - // generate the mipmap chain + + // Generate the mipmap chain + // Note: we can run some post-processing effects while the "color pass" descriptor set + // in bound because only the descriptor 0 (frame uniforms) matters, and it's + // present in both. PostProcessManager::generateMipmapSSR(ppm, fg, opaquePassOutput.linearColor, ssrConfig.refraction, diff --git a/filament/src/ResourceAllocator.cpp b/filament/src/ResourceAllocator.cpp index 4699def134e..05379c98500 100644 --- a/filament/src/ResourceAllocator.cpp +++ b/filament/src/ResourceAllocator.cpp @@ -183,23 +183,23 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name, textureCache.erase(it); } else { // we don't, allocate a new texture and populate the in-use list - if (swizzle == defaultSwizzle) { - handle = mBackend.createTexture( - target, levels, format, samples, width, height, depth, usage); - } else { - handle = mBackend.createTextureSwizzled( - target, levels, format, samples, width, height, depth, usage, - swizzle[0], swizzle[1], swizzle[2], swizzle[3]); + handle = mBackend.createTexture( + target, levels, format, samples, width, height, depth, usage); + if (swizzle != defaultSwizzle) { + TextureHandle swizzledHandle = mBackend.createTextureViewSwizzle( + handle, swizzle[0], swizzle[1], swizzle[2], swizzle[3]); + mBackend.destroyTexture(handle); + handle = swizzledHandle; } } } else { - if (swizzle == defaultSwizzle) { - handle = mBackend.createTexture( - target, levels, format, samples, width, height, depth, usage); - } else { - handle = mBackend.createTextureSwizzled( - target, levels, format, samples, width, height, depth, usage, - swizzle[0], swizzle[1], swizzle[2], swizzle[3]); + handle = mBackend.createTexture( + target, levels, format, samples, width, height, depth, usage); + if (swizzle != defaultSwizzle) { + TextureHandle swizzledHandle = mBackend.createTextureViewSwizzle( + handle, swizzle[0], swizzle[1], swizzle[2], swizzle[3]); + mBackend.destroyTexture(handle); + handle = swizzledHandle; } } mDisposer->checkout(handle, key); diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index bbf008e4a04..fd5d3145805 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -1362,32 +1362,32 @@ math::float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) void ShadowMap::prepareCamera(Transaction const& transaction, FEngine& engine, const CameraInfo& cameraInfo) noexcept { - PerShadowMapUniforms::prepareCamera(transaction, engine, cameraInfo); - PerShadowMapUniforms::prepareLodBias(transaction, 0.0f); + ShadowMapDescriptorSet::prepareCamera(transaction, engine, cameraInfo); + ShadowMapDescriptorSet::prepareLodBias(transaction, 0.0f); } void ShadowMap::prepareViewport(Transaction const& transaction, backend::Viewport const& viewport) noexcept { - PerShadowMapUniforms::prepareViewport(transaction, viewport); + ShadowMapDescriptorSet::prepareViewport(transaction, viewport); } void ShadowMap::prepareTime(Transaction const& transaction, FEngine& engine, math::float4 const& userTime) noexcept { - PerShadowMapUniforms::prepareTime(transaction, engine, userTime); + ShadowMapDescriptorSet::prepareTime(transaction, engine, userTime); } void ShadowMap::prepareShadowMapping(Transaction const& transaction, bool highPrecision) noexcept { - PerShadowMapUniforms::prepareShadowMapping(transaction, highPrecision); + ShadowMapDescriptorSet::prepareShadowMapping(transaction, highPrecision); } -PerShadowMapUniforms::Transaction ShadowMap::open(DriverApi& driver) noexcept { - return PerShadowMapUniforms::open(driver); +ShadowMapDescriptorSet::Transaction ShadowMap::open(DriverApi& driver) noexcept { + return ShadowMapDescriptorSet::open(driver); } void ShadowMap::commit(Transaction& transaction, - backend::DriverApi& driver) const noexcept { - mPerShadowMapUniforms.commit(transaction, driver); + FEngine& engine, backend::DriverApi& driver) const noexcept { + mPerShadowMapUniforms.commit(transaction, engine, driver); } void ShadowMap::bind(backend::DriverApi& driver) const noexcept { diff --git a/filament/src/ShadowMap.h b/filament/src/ShadowMap.h index 58e8f9bfbc1..ed113f0a3a6 100644 --- a/filament/src/ShadowMap.h +++ b/filament/src/ShadowMap.h @@ -20,7 +20,7 @@ #include #include "Culler.h" -#include "PerShadowMapUniforms.h" +#include "ds/ShadowMapDescriptorSet.h" #include "details/Camera.h" #include "details/Scene.h" @@ -190,7 +190,7 @@ class ShadowMap { ShadowType getShadowType() const noexcept { return mShadowType; } uint8_t getFace() const noexcept { return mFace; } - using Transaction = PerShadowMapUniforms::Transaction; + using Transaction = ShadowMapDescriptorSet::Transaction; static void prepareCamera(Transaction const& transaction, FEngine& engine, const CameraInfo& cameraInfo) noexcept; @@ -200,9 +200,8 @@ class ShadowMap { FEngine& engine, math::float4 const& userTime) noexcept; static void prepareShadowMapping(Transaction const& transaction, bool highPrecision) noexcept; - static PerShadowMapUniforms::Transaction open(backend::DriverApi& driver) noexcept; - void commit(Transaction& transaction, - backend::DriverApi& driver) const noexcept; + static ShadowMapDescriptorSet::Transaction open(backend::DriverApi& driver) noexcept; + void commit(Transaction& transaction, FEngine& engine, backend::DriverApi& driver) const noexcept; void bind(backend::DriverApi& driver) const noexcept; private: @@ -340,7 +339,7 @@ class ShadowMap { { 2, 6, 7, 3 }, // top }; - mutable PerShadowMapUniforms mPerShadowMapUniforms; // 4 + mutable ShadowMapDescriptorSet mPerShadowMapUniforms; // 4 FCamera* mCamera = nullptr; // 8 FCamera* mDebugCamera = nullptr; // 8 diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 6997f56a791..51b29065063 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -359,7 +359,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG ShadowMap::prepareTime(transaction, engine, userTime); ShadowMap::prepareShadowMapping(transaction, vsmShadowOptions.highPrecision); - shadowMap.commit(transaction, driver); + shadowMap.commit(transaction, engine, driver); // updatePrimitivesLod must be run before RenderPass::appendCommands. view.updatePrimitivesLod(engine, @@ -383,9 +383,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG .renderFlags(RenderPass::HAS_DEPTH_CLAMP, renderPassFlags) .camera(cameraInfo) .visibilityMask(entry.visibilityMask) - .geometry(scene->getRenderableData(), - entry.range, - view.getRenderableUBO()) + .geometry(scene->getRenderableData(), entry.range) .commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW) .build(engine); @@ -539,9 +537,7 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // So generate the mipmaps for each layer if (textureRequirements.levels > 1) { for (size_t level = 0; level < textureRequirements.levels - 1; level++) { - const bool finalize = level == textureRequirements.levels - 2; - ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, - vsmClearColor, finalize); + ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, vsmClearColor); } } } diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index 95ad0127741..0d4b68324df 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -19,7 +19,7 @@ #include "Culler.h" #include "ShadowMap.h" -#include "TypedUniformBuffer.h" +#include "ds/TypedBuffer.h" #include #include @@ -217,7 +217,7 @@ class ShadowMapManager { SoftShadowOptions mSoftShadowOptions; - mutable TypedUniformBuffer mShadowUb; + mutable TypedBuffer mShadowUb; backend::Handle mShadowUbh; ShadowMappingUniforms mShadowMappingUniforms = {}; diff --git a/filament/src/TypedUniformBuffer.h b/filament/src/TypedUniformBuffer.h index 264784dbf68..ea55b7c43d2 100644 --- a/filament/src/TypedUniformBuffer.h +++ b/filament/src/TypedUniformBuffer.h @@ -14,21 +14,20 @@ * limitations under the License. */ -#ifndef TNT_FILAMENT_TYPEDUNIFORMBUFFER_H -#define TNT_FILAMENT_TYPEDUNIFORMBUFFER_H +#ifndef TNT_FILAMENT_TYPEDBUFFER_H +#define TNT_FILAMENT_TYPEDBUFFER_H -#include "private/backend/DriverApi.h" - -#include +#include #include #include +#include namespace filament { template -class TypedUniformBuffer { // NOLINT(cppcoreguidelines-pro-type-member-init) +class TypedBuffer { // NOLINT(cppcoreguidelines-pro-type-member-init) public: T& itemAt(size_t i) noexcept { @@ -73,4 +72,4 @@ class TypedUniformBuffer { // NOLINT(cppcoreguidelines-pro-type-member-init) } // namespace filament -#endif // TNT_FILAMENT_TYPEDUNIFORMBUFFER_H +#endif // TNT_FILAMENT_TYPEDBUFFER_H diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 7b79477bcdc..35abe8d947d 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -20,6 +20,8 @@ #include "components/RenderableManager.h" +#include "ds/DescriptorSet.h" + #include "details/Engine.h" #include "details/VertexBuffer.h" #include "details/IndexBuffer.h" @@ -69,7 +71,7 @@ namespace filament { using namespace backend; struct RenderableManager::BuilderDetails { - using Entry = RenderableManager::Builder::Entry; + using Entry = FRenderableManager::Entry; std::vector mEntries; Box mAABB; uint8_t mLayerMask = 0x1; @@ -139,7 +141,7 @@ RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index, RenderableManager::Builder& RenderableManager::Builder::geometry(size_t index, PrimitiveType type, VertexBuffer* vertices, IndexBuffer* indices, size_t offset, UTILS_UNUSED size_t minIndex, UTILS_UNUSED size_t maxIndex, size_t count) noexcept { - std::vector& entries = mImpl->mEntries; + std::vector& entries = mImpl->mEntries; if (index < entries.size()) { entries[index].vertices = vertices; entries[index].indices = indices; @@ -554,7 +556,7 @@ void FRenderableManager::create( if (ci) { // create and initialize all needed RenderPrimitives using size_type = Slice::size_type; - Builder::Entry const * const entries = builder->mEntries.data(); + auto const * const entries = builder->mEntries.data(); const size_t entryCount = builder->mEntries.size(); FRenderPrimitive* rp = new FRenderPrimitive[entryCount]; auto& factory = mHwRenderPrimitiveFactory; @@ -670,15 +672,15 @@ void FRenderableManager::create( // the shader always handles both. See Variant::SKINNING_OR_MORPHING. if (UTILS_UNLIKELY(boneCount > 0 || targetCount > 0)) { - auto [sampler, texture] = FSkinningBuffer::createIndicesAndWeightsHandle( - downcast(engine), builder->mBoneIndicesAndWeightsCount); + Bones& bones = manager[ci].bones; + bones.handleTexture = FSkinningBuffer::createIndicesAndWeightsHandle( + engine, builder->mBoneIndicesAndWeightsCount); if (builder->mBoneIndicesAndWeightsCount > 0) { - FSkinningBuffer::setIndicesAndWeightsData(downcast(engine), texture, - builder->mBoneIndicesAndWeights, builder->mBoneIndicesAndWeightsCount); + FSkinningBuffer::setIndicesAndWeightsData(engine, + bones.handleTexture, + builder->mBoneIndicesAndWeights, + builder->mBoneIndicesAndWeightsCount); } - Bones& bones = manager[ci].bones; - bones.handleSamplerGroup = sampler; - bones.handleTexture = texture; // Instead of using a UBO per primitive, we could also have a single UBO for all primitives // and use bindUniformBufferRange which might be more efficient. @@ -753,13 +755,17 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept { // See create(RenderableManager::Builder&, Entity) destroyComponentPrimitives(mHwRenderPrimitiveFactory, driver, manager[ci].primitives); + // destroy the per-renderable descriptor set if we have one + DescriptorSet& descriptorSet = manager[ci].descriptorSet; + descriptorSet.terminate(driver); + // destroy the bones structures if any Bones const& bones = manager[ci].bones; if (bones.handle && !bones.skinningBufferMode) { + // when not in skinningBufferMode, we now the handle, so we destroy it driver.destroyBufferObject(bones.handle); } - if (bones.handleSamplerGroup){ - driver.destroySamplerGroup(bones.handleSamplerGroup); + if (bones.handleTexture) { driver.destroyTexture(bones.handleTexture); } diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 649d38829d8..d774996f275 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -21,6 +21,8 @@ #include "HwRenderPrimitiveFactory.h" +#include "ds/DescriptorSet.h" + #include
#include @@ -168,11 +170,12 @@ class FRenderableManager : public RenderableManager { inline uint8_t getLayerMask(Instance instance) const noexcept; inline uint8_t getPriority(Instance instance) const noexcept; inline uint8_t getChannels(Instance instance) const noexcept; + inline DescriptorSet& getDescriptorSet(Instance instance) noexcept; struct SkinningBindingInfo { backend::Handle handle; uint32_t offset; - backend::Handle handleSampler; + backend::Handle boneIndicesAndWeightHandle; }; inline SkinningBindingInfo getSkinningBufferInfo(Instance instance) const noexcept; @@ -211,6 +214,20 @@ class FRenderableManager : public RenderableManager { inline utils::Slice const& getRenderPrimitives(Instance instance, uint8_t level) const noexcept; inline utils::Slice& getRenderPrimitives(Instance instance, uint8_t level) noexcept; + struct Entry { + VertexBuffer* vertices = nullptr; + IndexBuffer* indices = nullptr; + uint32_t offset = 0; + uint32_t count = 0; + MaterialInstance const* materialInstance = nullptr; + PrimitiveType type = PrimitiveType::TRIANGLES; + uint16_t blendOrder = 0; + bool globalBlendOrderEnabled = false; + struct { + uint32_t offset = 0; + } morphing; + }; + private: void destroyComponent(Instance ci) noexcept; static void destroyComponentPrimitives( @@ -219,13 +236,12 @@ class FRenderableManager : public RenderableManager { struct Bones { backend::Handle handle; + backend::Handle handleTexture; uint16_t count = 0; uint16_t offset = 0; - bool skinningBufferMode = false; - backend::Handle handleSamplerGroup; - backend::Handle handleTexture; + bool skinningBufferMode = false; // whether we own (false) handle or not (true) }; - static_assert(sizeof(Bones) == 20); + static_assert(sizeof(Bones) == 16); struct MorphWeights { backend::Handle handle; @@ -242,7 +258,8 @@ class FRenderableManager : public RenderableManager { VISIBILITY, // user data PRIMITIVES, // user data BONES, // filament data, UBO storing a pointer to the bones information - MORPHTARGET_BUFFER // morphtarget buffer for the component + MORPHTARGET_BUFFER, // morphtarget buffer for the component + DESCRIPTOR_SET // per-renderable descriptor set }; using Base = utils::SingleInstanceComponentManager< @@ -254,7 +271,8 @@ class FRenderableManager : public RenderableManager { Visibility, // VISIBILITY utils::Slice, // PRIMITIVES Bones, // BONES - FMorphTargetBuffer* // MORPHTARGET_BUFFER + FMorphTargetBuffer*, // MORPHTARGET_BUFFER + filament::DescriptorSet // DESCRIPTOR_SET >; struct Sim : public Base { @@ -278,6 +296,7 @@ class FRenderableManager : public RenderableManager { Field primitives; Field bones; Field morphTargetBuffer; + Field descriptorSet; }; }; @@ -438,7 +457,7 @@ Box const& FRenderableManager::getAABB(Instance instance) const noexcept { FRenderableManager::SkinningBindingInfo FRenderableManager::getSkinningBufferInfo(Instance instance) const noexcept { Bones const& bones = mManager[instance].bones; - return { bones.handle, bones.offset, bones.handleSamplerGroup }; + return { bones.handle, bones.offset, bones.handleTexture }; } inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexcept { @@ -468,6 +487,10 @@ utils::Slice& FRenderableManager::getRenderPrimitives( return mManager[instance].primitives; } +DescriptorSet& FRenderableManager::getDescriptorSet(Instance instance) noexcept { + return mManager[instance].descriptorSet; +} + } // namespace filament #endif // TNT_FILAMENT_COMPONENTS_RENDERABLEMANAGER_H diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 682612369f9..c6598409538 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -38,6 +38,8 @@ #include +#include + #include #include @@ -341,6 +343,22 @@ void FEngine::init() { driverApi.update3DImage(mDummyZeroTexture, 0, 0, 0, 0, 1, 1, 1, { zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE }); + + mPerViewDescriptorSetLayoutSsrVariant = { + mHwDescriptorSetLayoutFactory, + driverApi, + descriptor_sets::getSsrVariantLayout() }; + + mPerViewDescriptorSetLayoutDepthVariant = { + mHwDescriptorSetLayoutFactory, + driverApi, + descriptor_sets::getDepthVariantLayout() }; + + mPerRenderableDescriptorSetLayout = { + mHwDescriptorSetLayoutFactory, + driverApi, + descriptor_sets::getPerRenderableLayout() }; + #ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0 if (UTILS_UNLIKELY(mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0)) { FMaterial::DefaultMaterialBuilder defaultMaterialBuilder; @@ -468,7 +486,12 @@ void FEngine::shutdown() { mLightManager.terminate(); // free-up all lights mCameraManager.terminate(*this); // free-up all cameras + mPerViewDescriptorSetLayoutDepthVariant.terminate(mHwDescriptorSetLayoutFactory, driver); + mPerViewDescriptorSetLayoutSsrVariant.terminate(mHwDescriptorSetLayoutFactory, driver); + mPerRenderableDescriptorSetLayout.terminate(mHwDescriptorSetLayoutFactory, driver); + driver.destroyRenderPrimitive(mFullScreenTriangleRph); + destroy(mFullScreenTriangleIb); destroy(mFullScreenTriangleVb); destroy(mDummyMorphTargetBuffer); @@ -566,12 +589,10 @@ void FEngine::prepare() { }); } - // Commit default material instances. - mMaterials.forEach([&driver](FMaterial* material) { + mMaterials.forEach([](FMaterial* material) { #if FILAMENT_ENABLE_MATDBG material->checkProgramEdits(); #endif - material->getDefaultInstance()->commit(driver); }); } @@ -814,6 +835,15 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, return p; } +FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material) noexcept { + FMaterialInstance* p = mHeapAllocator.make(*this, material); + if (UTILS_UNLIKELY(p)) { // should never happen + auto pos = mMaterialInstances.emplace(material, "MaterialInstance"); + pos.first->second.insert(p); + } + return p; +} + /* * Objects created without a Builder */ @@ -1068,29 +1098,32 @@ bool FEngine::destroy(const FInstanceBuffer* p){ UTILS_NOINLINE bool FEngine::destroy(const FMaterial* ptr) { if (ptr == nullptr) return true; - auto pos = mMaterialInstances.find(ptr); - if (pos != mMaterialInstances.cend()) { - // ensure we've destroyed all instances before destroying the material - if (!ASSERT_PRECONDITION_NON_FATAL(pos->second.empty(), - "destroying material \"%s\" but %u instances still alive", - ptr->getName().c_str(), (*pos).second.size())) { - return false; + bool const success = terminateAndDestroy(ptr, mMaterials); + if (success) { + auto pos = mMaterialInstances.find(ptr); + if (pos != mMaterialInstances.cend()) { + // ensure we've destroyed all instances before destroying the material + if (!ASSERT_PRECONDITION_NON_FATAL(pos->second.empty(), + "destroying material \"%s\" but %u instances still alive", + ptr->getName().c_str(), (*pos).second.size())) { + return false; + } } } - return terminateAndDestroy(ptr, mMaterials); + return success; } UTILS_NOINLINE bool FEngine::destroy(const FMaterialInstance* ptr) { if (ptr == nullptr) return true; + if (ptr->isDefaultInstance()) return false; auto pos = mMaterialInstances.find(ptr->getMaterial()); assert_invariant(pos != mMaterialInstances.cend()); if (pos != mMaterialInstances.cend()) { return terminateAndDestroy(ptr, pos->second); } - // if we don't find this instance's material it might be because it's the default instance - // in which case it fine to ignore. - return true; + // this shouldn't happen, this would be double-free + return false; } UTILS_NOINLINE diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index fa963704177..331f934d993 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -23,6 +23,7 @@ #include "DFG.h" #include "PostProcessManager.h" #include "ResourceList.h" +#include "HwDescriptorSetLayoutFactory.h" #include "HwVertexBufferInfoFactory.h" #include "components/CameraManager.h" @@ -30,6 +31,8 @@ #include "components/TransformManager.h" #include "components/RenderableManager.h" +#include "ds/DescriptorSetLayout.h" + #include "details/BufferObject.h" #include "details/Camera.h" #include "details/ColorGrading.h" @@ -296,9 +299,12 @@ class FEngine : public Engine { void createLight(const LightManager::Builder& builder, utils::Entity entity); FRenderer* createRenderer() noexcept; + FMaterialInstance* createMaterialInstance(const FMaterial* material, const FMaterialInstance* other, const char* name) noexcept; + FMaterialInstance* createMaterialInstance(const FMaterial* material) noexcept; + FScene* createScene() noexcept; FView* createView() noexcept; FFence* createFence() noexcept; @@ -445,6 +451,22 @@ class FEngine : public Engine { return mHwVertexBufferInfoFactory; } + HwDescriptorSetLayoutFactory& getDescriptorSetLayoutFactory() noexcept { + return mHwDescriptorSetLayoutFactory; + } + + DescriptorSetLayout const& getPerViewDescriptorSetLayoutDepthVariant() const noexcept { + return mPerViewDescriptorSetLayoutDepthVariant; + } + + DescriptorSetLayout const& getPerViewDescriptorSetLayoutSsrVariant() const noexcept { + return mPerViewDescriptorSetLayoutSsrVariant; + } + + DescriptorSetLayout const& getPerRenderableDescriptorSetLayout() const noexcept { + return mPerRenderableDescriptorSetLayout; + } + backend::Handle getOneTexture() const { return mDummyOneTexture; } backend::Handle getZeroTexture() const { return mDummyZeroTexture; } backend::Handle getOneTextureArray() const { return mDummyOneTextureArray; } @@ -514,6 +536,10 @@ class FEngine : public Engine { FCameraManager mCameraManager; std::shared_ptr mResourceAllocatorDisposer; HwVertexBufferInfoFactory mHwVertexBufferInfoFactory; + HwDescriptorSetLayoutFactory mHwDescriptorSetLayoutFactory; + DescriptorSetLayout mPerViewDescriptorSetLayoutDepthVariant; + DescriptorSetLayout mPerViewDescriptorSetLayoutSsrVariant; + DescriptorSetLayout mPerRenderableDescriptorSetLayout; ResourceList mBufferObjects{ "BufferObject" }; ResourceList mRenderers{ "Renderer" }; diff --git a/filament/src/details/IndirectLight.cpp b/filament/src/details/IndirectLight.cpp index 2822266c72b..6d78d564088 100644 --- a/filament/src/details/IndirectLight.cpp +++ b/filament/src/details/IndirectLight.cpp @@ -200,11 +200,13 @@ void FIndirectLight::terminate(FEngine& engine) { } backend::Handle FIndirectLight::getReflectionHwHandle() const noexcept { - return mReflectionsTexture ? mReflectionsTexture->getHwHandle() : backend::Handle {}; + return mReflectionsTexture ? mReflectionsTexture->getHwHandleForSampling() + : backend::Handle{}; } backend::Handle FIndirectLight::getIrradianceHwHandle() const noexcept { - return mIrradianceTexture ? mIrradianceTexture->getHwHandle() : backend::Handle {}; + return mIrradianceTexture ? mIrradianceTexture->getHwHandleForSampling() + : backend::Handle{}; } math::float3 FIndirectLight::getDirectionEstimate(math::float3 const* f) noexcept { diff --git a/filament/src/details/Material.cpp b/filament/src/details/Material.cpp index b27c6608731..d60087c4bd4 100644 --- a/filament/src/details/Material.cpp +++ b/filament/src/details/Material.cpp @@ -20,9 +20,12 @@ #include "Froxelizer.h" #include "MaterialParser.h" +#include "ds/ColorPassDescriptorSet.h" + #include "FilamentAPI-impl.h" #include +#include #include #include #include @@ -259,21 +262,13 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder, assert_invariant(success); if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) { - success = parser->getBindingUniformInfo(&mBindingUniformInfo); - assert_invariant(success); - success = parser->getAttributeInfo(&mAttributeInfo); assert_invariant(success); - } else if (mFeatureLevel <= FeatureLevel::FEATURE_LEVEL_1) { - // this chunk is not needed for materials at feature level 2 and above - success = parser->getUniformBlockBindings(&mUniformBlockBindings); + + success = parser->getBindingUniformInfo(&mBindingUniformInfo); assert_invariant(success); } - success = parser->getSamplerBlockBindings( - &mSamplerGroupBindingInfoList, &mSamplerBindingToNameMap); - assert_invariant(success); - // Older materials will not have a subpass chunk; this should not be an error. if (!parser->getSubpasses(&mSubpassInfo)) { mSubpassInfo.isValid = false; @@ -330,10 +325,13 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder, processSpecializationConstants(engine, builder, parser); processPushConstants(engine, parser); processDepthVariants(engine, parser); + processDescriptorSets(engine, parser); - // we can only initialize the default instance once we're initialized ourselves - new(&mDefaultInstanceStorage) FMaterialInstance(engine, this); - + mPerViewLayoutIndex = ColorPassDescriptorSet::getIndex( + mIsVariantLit, + mReflectionMode == ReflectionMode::SCREEN_SPACE || + mRefractionMode == RefractionMode::SCREEN_SPACE, + !(mVariantFilterMask & +UserVariantFilterBit::FOG)); #if FILAMENT_ENABLE_MATDBG // Register the material with matdbg. @@ -345,9 +343,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder, #endif } -FMaterial::~FMaterial() noexcept { - std::destroy_at(getDefaultInstance()); -} +FMaterial::~FMaterial() noexcept = default; void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept { if (mMaterialDomain == MaterialDomain::SURFACE) { @@ -406,7 +402,16 @@ void FMaterial::terminate(FEngine& engine) { destroyPrograms(engine); - getDefaultInstance()->terminate(engine); + if (mDefaultMaterialInstance) { + mDefaultMaterialInstance->setDefaultInstance(false); + engine.destroy(mDefaultMaterialInstance); + mDefaultMaterialInstance = nullptr; + } + + mPerViewDescriptorSetLayout.terminate( + engine.getDescriptorSetLayoutFactory(), engine.getDriverApi()); + mDescriptorSetLayout.terminate( + engine.getDescriptorSetLayoutFactory(), engine.getDriverApi()); } void FMaterial::compile(CompilerPriorityQueue priority, @@ -452,7 +457,21 @@ void FMaterial::compile(CompilerPriorityQueue priority, } FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept { - return FMaterialInstance::duplicate(getDefaultInstance(), name); + if (mDefaultMaterialInstance) { + // if we have a default instance, use it to create a new one + return FMaterialInstance::duplicate(mDefaultMaterialInstance, name); + } else { + // but if we don't, just create an instance with all the default parameters + return mEngine.createMaterialInstance(this); + } +} + +FMaterialInstance* FMaterial::getDefaultInstance() noexcept { + if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) { + mDefaultMaterialInstance = mEngine.createMaterialInstance(this); + mDefaultMaterialInstance->setDefaultInstance(true); + } + return mDefaultMaterialInstance; } bool FMaterial::hasParameter(const char* name) const noexcept { @@ -575,37 +594,28 @@ Program FMaterial::getProgramWithVariants( Program program; program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size()) - .shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size()) - .shaderLanguage(mMaterialParser->getShaderLanguage()) - .uniformBlockBindings(mUniformBlockBindings) - .diagnostics(mName, - [this, variant](io::ostream& out) -> io::ostream& { - return out << mName.c_str_safe() - << ", variant=(" << io::hex << variant.key << io::dec << ")"; + .shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size()) + .shaderLanguage(mMaterialParser->getShaderLanguage()) + .diagnostics(mName, + [this, variant, vertexVariant, fragmentVariant]( + io::ostream& out) -> io::ostream& { + return out << mName.c_str_safe() << ", variant=(" << io::hex + << (int)variant.key << io::dec << "), vertexVariant=(" << io::hex + << (int)vertexVariant.key << io::dec << "), fragmentVariant=(" + << io::hex << (int)fragmentVariant.key << io::dec << ")"; }); - UTILS_NOUNROLL - for (size_t i = 0; i < Enum::count(); i++) { - SamplerBindingPoints const bindingPoint = (SamplerBindingPoints)i; - auto const& info = mSamplerGroupBindingInfoList[i]; - if (info.count) { - std::array samplers{}; - for (size_t j = 0, c = info.count; j < c; ++j) { - uint8_t const binding = info.bindingOffset + j; - samplers[j] = { mSamplerBindingToNameMap[binding], binding }; - } - program.setSamplerGroup(+bindingPoint, info.shaderStageFlags, - samplers.data(), info.count); - } - } if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) { assert_invariant(!mBindingUniformInfo.empty()); - for (auto const& [index, uniforms] : mBindingUniformInfo) { - program.uniforms(uint32_t(index), uniforms); + for (auto const& [index, name, uniforms] : mBindingUniformInfo) { + program.uniforms(uint32_t(index), name, uniforms); } program.attributes(mAttributeInfo); } + program.descriptorBindings(0, mProgramDescriptorBindings[0]); + program.descriptorBindings(1, mProgramDescriptorBindings[1]); + program.descriptorBindings(2, mProgramDescriptorBindings[2]); program.specializationConstants(mSpecializationConstants); program.pushConstants(ShaderStage::VERTEX, mPushConstants[(uint8_t) ShaderStage::VERTEX]); @@ -1037,6 +1047,30 @@ void FMaterial::processDepthVariants(FEngine& engine, MaterialParser const* cons } } +void FMaterial::processDescriptorSets(FEngine& engine, MaterialParser const* const parser) { + UTILS_UNUSED_IN_RELEASE bool success; + + success = parser->getDescriptorBindings(&mProgramDescriptorBindings); + assert_invariant(success); + + std::array descriptorSetLayout; + success = parser->getDescriptorSetLayout(&descriptorSetLayout); + assert_invariant(success); + + mDescriptorSetLayout = { + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), std::move(descriptorSetLayout[0]) }; + + mPerViewDescriptorSetLayout = { + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), std::move(descriptorSetLayout[1]) }; +} + +backend::descriptor_binding_t FMaterial::getSamplerBinding( + std::string_view const& name) const { + return mSamplerInterfaceBlock.getSamplerInfo(name)->binding; +} + template bool FMaterial::setConstant(uint32_t id, int32_t value) noexcept; template bool FMaterial::setConstant(uint32_t id, float value) noexcept; template bool FMaterial::setConstant(uint32_t id, bool value) noexcept; diff --git a/filament/src/details/Material.h b/filament/src/details/Material.h index 617f9699501..ba19427f1cc 100644 --- a/filament/src/details/Material.h +++ b/filament/src/details/Material.h @@ -21,12 +21,13 @@ #include "details/MaterialInstance.h" +#include "ds/DescriptorSetLayout.h" + #include #include #include #include -#include #include #include #include @@ -45,12 +46,13 @@ #include #include -#include #include #include #include #include #include +#include +#include #include #include @@ -86,9 +88,25 @@ class FMaterial : public Material { return mUniformInterfaceBlock; } - // return the uniform interface block for this material - const SamplerInterfaceBlock& getSamplerInterfaceBlock() const noexcept { - return mSamplerInterfaceBlock; + DescriptorSetLayout const& getPerViewDescriptorSetLayout() const noexcept { + assert_invariant(mMaterialDomain == MaterialDomain::POST_PROCESS); + return mPerViewDescriptorSetLayout; + } + + DescriptorSetLayout const& getPerViewDescriptorSetLayout(Variant variant) const noexcept { + if (Variant::isValidDepthVariant(variant)) { + assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); + return mEngine.getPerViewDescriptorSetLayoutDepthVariant(); + } + if (Variant::isSSRVariant(variant)) { + assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); + return mEngine.getPerViewDescriptorSetLayoutSsrVariant(); + } + return mPerViewDescriptorSetLayout; + } + + DescriptorSetLayout const& getDescriptorSetLayout() const noexcept { + return mDescriptorSetLayout; } void compile(CompilerPriorityQueue priority, @@ -109,9 +127,7 @@ class FMaterial : public Material { return const_cast(this)->getDefaultInstance(); } - FMaterialInstance* getDefaultInstance() noexcept { - return std::launder(reinterpret_cast(&mDefaultInstanceStorage)); - } + FMaterialInstance* getDefaultInstance() noexcept; FEngine& getEngine() const noexcept { return mEngine; } @@ -182,10 +198,17 @@ class FMaterial : public Material { float getSpecularAntiAliasingVariance() const noexcept { return mSpecularAntiAliasingVariance; } float getSpecularAntiAliasingThreshold() const noexcept { return mSpecularAntiAliasingThreshold; } + backend::descriptor_binding_t getSamplerBinding( + std::string_view const& name) const; + bool hasMaterialProperty(Property property) const noexcept { return bool(mMaterialProperties & uint64_t(property)); } + SamplerInterfaceBlock const& getSamplerInterfaceBlock() const noexcept { + return mSamplerInterfaceBlock; + } + size_t getParameterCount() const noexcept { return mUniformInterfaceBlock.getFieldInfoList().size() + mSamplerInterfaceBlock.getSamplerInfoList().size() + @@ -205,6 +228,10 @@ class FMaterial : public Material { template> bool setConstant(uint32_t id, T value) noexcept; + uint8_t getPerViewLayoutIndex() const noexcept { + return mPerViewLayoutIndex; + } + #if FILAMENT_ENABLE_MATDBG void applyPendingEdits() noexcept; @@ -257,10 +284,15 @@ class FMaterial : public Material { void processDepthVariants(FEngine& engine, MaterialParser const* parser); + void processDescriptorSets(FEngine& engine, MaterialParser const* parser); + void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept; // try to order by frequency of use mutable std::array, VARIANT_COUNT> mCachedPrograms; + DescriptorSetLayout mPerViewDescriptorSetLayout; + DescriptorSetLayout mDescriptorSetLayout; + backend::Program::DescriptorSetInfo mProgramDescriptorBindings; backend::RasterState mRasterState; TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; @@ -280,6 +312,7 @@ class FMaterial : public Material { RefractionType mRefractionType = RefractionType::SOLID; ReflectionMode mReflectionMode = ReflectionMode::DEFAULT; uint64_t mMaterialProperties = 0; + uint8_t mPerViewLayoutIndex = 0; float mMaskThreshold = 0.4f; float mSpecularAntiAliasingVariance = 0.0f; @@ -293,17 +326,15 @@ class FMaterial : public Material { bool mSpecularAntiAliasing = false; // reserve some space to construct the default material instance - std::aligned_storage::type mDefaultInstanceStorage; - static_assert(sizeof(mDefaultInstanceStorage) >= sizeof(mDefaultInstanceStorage)); + mutable FMaterialInstance* mDefaultMaterialInstance = nullptr; SamplerInterfaceBlock mSamplerInterfaceBlock; BufferInterfaceBlock mUniformInterfaceBlock; SubpassInfo mSubpassInfo; - utils::FixedCapacityVector> mUniformBlockBindings; utils::FixedCapacityVector mDepthVariants; // only populated with default material - using BindingUniformInfoContainer = utils::FixedCapacityVector< - std::pair>; + using BindingUniformInfoContainer = utils::FixedCapacityVector>; BindingUniformInfoContainer mBindingUniformInfo; @@ -311,8 +342,6 @@ class FMaterial : public Material { AttributeInfoContainer mAttributeInfo; - SamplerGroupBindingInfoList mSamplerGroupBindingInfoList; - SamplerBindingToNameMap mSamplerBindingToNameMap; // Constants defined by this Material utils::FixedCapacityVector mMaterialConstants; // A map from the Constant name to the mMaterialConstant index diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index 152ecd480a4..afc02418aa4 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -16,18 +16,39 @@ #include -#include - -#include "details/MaterialInstance.h" - #include "RenderPass.h" +#include "ds/DescriptorSetLayout.h" + #include "details/Engine.h" #include "details/Material.h" +#include "details/MaterialInstance.h" #include "details/Texture.h" +#include "private/filament/EngineEnums.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include #include +#include + +#include +#include +#include +#include +#include + using namespace filament::math; using namespace utils; @@ -37,12 +58,14 @@ using namespace backend; FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept : mMaterial(material), + mDescriptorSet(material->getDescriptorSetLayout()), mCulling(CullingMode::BACK), mDepthFunc(RasterState::DepthFunc::LE), mColorWrite(false), mDepthWrite(false), mHasScissor(false), mIsDoubleSided(false), + mIsDefaultInstance(false), mTransparencyMode(TransparencyMode::DEFAULT) { FEngine::DriverApi& driver = engine.getDriverApi(); @@ -54,11 +77,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material) driver.setDebugTag(mUbHandle.getId(), material->getName()); } - if (!material->getSamplerInterfaceBlock().isEmpty()) { - mSamplers = SamplerGroup(material->getSamplerInterfaceBlock().getSize()); - mSbHandle = driver.createSamplerGroup( - mSamplers.getSize(), utils::FixedSizeString<32>(mMaterial->getName().c_str_safe())); - } + // set the UBO, always descriptor 0 + mDescriptorSet.setBuffer(0, mUbHandle, 0, mUniforms.getSize()); const RasterState& rasterState = material->getRasterState(); // At the moment, only MaterialInstances have a stencil state, but in the future it should be @@ -96,6 +116,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material) FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name) : mMaterial(other->mMaterial), + mTextureParameters(other->mTextureParameters), + mDescriptorSet(other->mDescriptorSet.duplicate(mMaterial->getDescriptorSetLayout())), mPolygonOffset(other->mPolygonOffset), mStencilState(other->mStencilState), mMaskThreshold(other->mMaskThreshold), @@ -107,6 +129,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, mDepthWrite(other->mDepthWrite), mHasScissor(false), mIsDoubleSided(other->mIsDoubleSided), + mIsDefaultInstance(false), mScissorRect(other->mScissorRect), mName(name ? CString(name) : other->mName) { @@ -120,11 +143,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, driver.setDebugTag(mUbHandle.getId(), material->getName()); } - if (!material->getSamplerInterfaceBlock().isEmpty()) { - mSamplers = other->getSamplerGroup(); - mSbHandle = driver.createSamplerGroup( - mSamplers.getSize(), utils::FixedSizeString<32>(mMaterial->getName().c_str_safe())); - } + // set the UBO, always descriptor 0 + mDescriptorSet.setBuffer(0, mUbHandle, 0, mUniforms.getSize()); if (material->hasDoubleSidedCapability()) { setDoubleSided(mIsDoubleSided); @@ -143,6 +163,11 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, mMaterialSortingKey = RenderPass::makeMaterialSortingKey( material->getId(), material->generateMaterialInstanceId()); + + // If the original descriptor set has been commited, the copy needs to commit as well. + if (other->mDescriptorSet.getHandle()) { + mDescriptorSet.commitSlow(mMaterial->getDescriptorSetLayout(), driver); + } } FMaterialInstance* FMaterialInstance::duplicate( @@ -156,26 +181,41 @@ FMaterialInstance::~FMaterialInstance() noexcept = default; void FMaterialInstance::terminate(FEngine& engine) { FEngine::DriverApi& driver = engine.getDriverApi(); + mDescriptorSet.terminate(driver); driver.destroyBufferObject(mUbHandle); - driver.destroySamplerGroup(mSbHandle); } -void FMaterialInstance::commitSlow(DriverApi& driver) const { +void FMaterialInstance::commit(DriverApi& driver) const { // update uniforms if needed if (mUniforms.isDirty()) { driver.updateBufferObject(mUbHandle, mUniforms.toBufferDescriptor(driver), 0); } - if (mSamplers.isDirty()) { - driver.updateSamplerGroup(mSbHandle, mSamplers.toBufferDescriptor(driver)); + if (!mTextureParameters.empty()) { + for (auto const& [binding, p]: mTextureParameters) { + assert_invariant(p.texture); + // TODO: figure out a way to do this more efficiently (isValid() is a hashmap lookup) + FEngine& engine = mMaterial->getEngine(); + FILAMENT_CHECK_PRECONDITION(engine.isValid(p.texture)) + << "Invalid texture still bound to MaterialInstance: '" << getName() << "'\n"; + Handle handle = p.texture->getHwHandleForSampling(); + assert_invariant(handle); + mDescriptorSet.setSampler(binding, handle, p.params); + } } + + // TODO: eventually we should remove this in RELEASE builds + fixMissingSamplers(); + + // Commit descriptors if needed (e.g. when textures are updated,or the first time) + mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver); } // ------------------------------------------------------------------------------------------------ void FMaterialInstance::setParameter(std::string_view name, - backend::Handle texture, backend::SamplerParams params) noexcept { - size_t const index = mMaterial->getSamplerInterfaceBlock().getSamplerInfo(name)->offset; - mSamplers.setSampler(index, { texture, params }); + backend::Handle texture, backend::SamplerParams params) { + auto binding = mMaterial->getSamplerBinding(name); + mDescriptorSet.setSampler(binding, texture, params); } void FMaterialInstance::setParameterImpl(std::string_view name, @@ -195,17 +235,25 @@ void FMaterialInstance::setParameterImpl(std::string_view name, PANIC_LOG("Depth textures can't be sampled with a linear filter " "unless the comparison mode is set to COMPARE_TO_TEXTURE. " "(material: \"%s\", parameter: \"%.*s\")", - getMaterial()->getName().c_str(), name.size(), name.data()); + getMaterial()->getName().c_str(), name.size(), name.data()); } } } #endif - Handle handle{}; - if (UTILS_LIKELY(texture)) { - handle = texture->getHwHandle(); + auto binding = mMaterial->getSamplerBinding(name); + if (texture && texture->textureHandleCanMutate()) { + mTextureParameters[binding] = { texture, sampler.getSamplerParams() }; + } else { + Handle handle{}; + if (texture) { + handle = texture->getHwHandleForSampling(); + assert_invariant(handle == texture->getHwHandle()); + } else { + mTextureParameters.erase(binding); + } + mDescriptorSet.setSampler(binding, handle, sampler.getSamplerParams()); } - setParameter(name, handle, sampler.getSamplerParams()); } void FMaterialInstance::setMaskThreshold(float threshold) noexcept { @@ -273,4 +321,81 @@ const char* FMaterialInstance::getName() const noexcept { return mName.c_str(); } +// ------------------------------------------------------------------------------------------------ + +void FMaterialInstance::use(FEngine::DriverApi& driver) const { + + if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) { + std::call_once(mMissingSamplersFlag, [this]() { + auto const& list = mMaterial->getSamplerInterfaceBlock().getSamplerInfoList(); + slog.w << "sampler parameters not set in MaterialInstance \"" + << mName.c_str_safe() << "\" or Material \"" + << mMaterial->getName().c_str_safe() << "\":\n"; + mMissingSamplerDescriptors.forEachSetBit([&list](descriptor_binding_t binding) { + auto pos = std::find_if(list.begin(), list.end(), [binding](const auto& item) { + return item.binding == binding; + }); + // just safety-check, should never fail + if (UTILS_LIKELY(pos != list.end())) { + slog.w << "[" << +binding << "] " << pos->name.c_str() << '\n'; + } + }); + flush(slog.w); + }); + mMissingSamplerDescriptors.clear(); + } + + mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL); +} + +void FMaterialInstance::fixMissingSamplers() const { + // Here we check that all declared sampler parameters are set, this is required by + // Vulkan and Metal; GL is more permissive. If a sampler parameter is not set, we will + // log a warning once per MaterialInstance in the system log and patch-in a dummy + // texture. + auto const& layout = mMaterial->getDescriptorSetLayout(); + auto const samplersDescriptors = layout.getSamplerDescriptors(); + auto const validDescriptors = mDescriptorSet.getValidDescriptors(); + auto const missingSamplerDescriptors = + (validDescriptors & samplersDescriptors) ^ samplersDescriptors; + + // always record the missing samplers state at commit() time + mMissingSamplerDescriptors = missingSamplerDescriptors; + + if (UTILS_UNLIKELY(missingSamplerDescriptors.any())) { + // here we need to set the samplers that are missing + auto const& list = mMaterial->getSamplerInterfaceBlock().getSamplerInfoList(); + missingSamplerDescriptors.forEachSetBit([this, &list](descriptor_binding_t binding) { + auto pos = std::find_if(list.begin(), list.end(), [binding](const auto& item) { + return item.binding == binding; + }); + + FEngine const& engine = mMaterial->getEngine(); + + // just safety-check, should never fail + if (UTILS_LIKELY(pos != list.end())) { + switch (pos->type) { + case SamplerType::SAMPLER_2D: + mDescriptorSet.setSampler(binding, + engine.getZeroTexture(), {}); + break; + case SamplerType::SAMPLER_2D_ARRAY: + mDescriptorSet.setSampler(binding, + engine.getZeroTextureArray(), {}); + break; + case SamplerType::SAMPLER_CUBEMAP: + mDescriptorSet.setSampler(binding, + engine.getDummyCubemap()->getHwHandle(), {}); + break; + case SamplerType::SAMPLER_EXTERNAL: + case SamplerType::SAMPLER_3D: + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + // we're currently not able to fix-up those + break; + } + } + }); + } +} + } // namespace filament diff --git a/filament/src/details/MaterialInstance.h b/filament/src/details/MaterialInstance.h index fc1d9a35f04..b31c0a65273 100644 --- a/filament/src/details/MaterialInstance.h +++ b/filament/src/details/MaterialInstance.h @@ -18,26 +18,42 @@ #define TNT_FILAMENT_DETAILS_MATERIALINSTANCE_H #include "downcast.h" + #include "UniformBuffer.h" + +#include "ds/DescriptorSet.h" + #include "details/Engine.h" #include "private/backend/DriverApi.h" -#include +#include -#include +#include +#include #include -#include +#include +#include -#include +#include + +#include +#include +#include +#include + +#include +#include namespace filament { class FMaterial; +class FTexture; class FMaterialInstance : public MaterialInstance { public: + FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept; FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name); FMaterialInstance(const FMaterialInstance& rhs) = delete; FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete; @@ -48,27 +64,15 @@ class FMaterialInstance : public MaterialInstance { void terminate(FEngine& engine); - void commit(FEngine::DriverApi& driver) const { - if (UTILS_UNLIKELY(mUniforms.isDirty() || mSamplers.isDirty())) { - commitSlow(driver); - } - } + void commit(FEngine::DriverApi& driver) const; - void use(FEngine::DriverApi& driver) const { - if (mUbHandle) { - driver.bindUniformBuffer(+UniformBindingPoints::PER_MATERIAL_INSTANCE, mUbHandle); - } - if (mSbHandle) { - driver.bindSamplers(+SamplerBindingPoints::PER_MATERIAL_INSTANCE, mSbHandle); - } - } + void use(FEngine::DriverApi& driver) const; FMaterial const* getMaterial() const noexcept { return mMaterial; } uint64_t getSortingKey() const noexcept { return mMaterialSortingKey; } UniformBuffer const& getUniformBuffer() const noexcept { return mUniforms; } - backend::SamplerGroup const& getSamplerGroup() const noexcept { return mSamplers; } void setScissor(uint32_t left, uint32_t bottom, uint32_t width, uint32_t height) noexcept { constexpr uint32_t maxvalu = std::numeric_limits::max(); @@ -205,10 +209,21 @@ class FMaterialInstance : public MaterialInstance { } } + void setDefaultInstance(bool value) noexcept { + mIsDefaultInstance = value; + } + + bool isDefaultInstance() const noexcept { + return mIsDefaultInstance; + } + + // Called by the engine to ensure that unset samplers are initialized with placedholders. + void fixMissingSamplers() const; + const char* getName() const noexcept; void setParameter(std::string_view name, - backend::Handle texture, backend::SamplerParams params) noexcept; + backend::Handle texture, backend::SamplerParams params); using MaterialInstance::setParameter; @@ -234,18 +249,18 @@ class FMaterialInstance : public MaterialInstance { template T getParameterImpl(std::string_view name) const; - // initialize the default instance - FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept; - - void commitSlow(FEngine::DriverApi& driver) const; - // keep these grouped, they're accessed together in the render-loop FMaterial const* mMaterial = nullptr; + struct TextureParameter { + FTexture const* texture; + backend::SamplerParams params; + }; + backend::Handle mUbHandle; - backend::Handle mSbHandle; + tsl::robin_map mTextureParameters; + mutable filament::DescriptorSet mDescriptorSet; UniformBuffer mUniforms; - backend::SamplerGroup mSamplers; backend::PolygonOffset mPolygonOffset{}; backend::StencilState mStencilState{}; @@ -260,6 +275,7 @@ class FMaterialInstance : public MaterialInstance { bool mDepthWrite : 1; bool mHasScissor : 1; bool mIsDoubleSided : 1; + bool mIsDefaultInstance : 1; TransparencyMode mTransparencyMode : 2; uint64_t mMaterialSortingKey = 0; @@ -271,6 +287,8 @@ class FMaterialInstance : public MaterialInstance { }; utils::CString mName; + mutable utils::bitset64 mMissingSamplerDescriptors{}; + mutable std::once_flag mMissingSamplersFlag; }; FILAMENT_DOWNCAST(MaterialInstance) diff --git a/filament/src/details/MorphTargetBuffer.cpp b/filament/src/details/MorphTargetBuffer.cpp index d3323b92c3b..f7c9444e041 100644 --- a/filament/src/details/MorphTargetBuffer.cpp +++ b/filament/src/details/MorphTargetBuffer.cpp @@ -130,25 +130,14 @@ FMorphTargetBuffer::FMorphTargetBuffer(FEngine& engine, const Builder& builder) driver.setDebugTag(mPbHandle.getId(), name); driver.setDebugTag(mTbHandle.getId(), std::move(name)); } - - // create and update sampler group - mSbHandle = driver.createSamplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT, - utils::FixedSizeString<32>("Morph target samplers")); - SamplerGroup samplerGroup(PerRenderPrimitiveMorphingSib::SAMPLER_COUNT); - samplerGroup.setSampler(PerRenderPrimitiveMorphingSib::POSITIONS, { mPbHandle, {}}); - samplerGroup.setSampler(PerRenderPrimitiveMorphingSib::TANGENTS, { mTbHandle, {}}); - driver.updateSamplerGroup(mSbHandle, samplerGroup.toBufferDescriptor(driver)); } void FMorphTargetBuffer::terminate(FEngine& engine) { FEngine::DriverApi& driver = engine.getDriverApi(); - if (UTILS_LIKELY(mSbHandle)) { - driver.destroySamplerGroup(mSbHandle); - } - if (UTILS_LIKELY(mTbHandle)) { + if (mTbHandle) { driver.destroyTexture(mTbHandle); } - if (UTILS_LIKELY(mPbHandle)) { + if (mPbHandle) { driver.destroyTexture(mPbHandle); } } diff --git a/filament/src/details/MorphTargetBuffer.h b/filament/src/details/MorphTargetBuffer.h index b1d45b2e2eb..6e42dcace88 100644 --- a/filament/src/details/MorphTargetBuffer.h +++ b/filament/src/details/MorphTargetBuffer.h @@ -21,13 +21,12 @@ #include -#include "backend/DriverApiForward.h" - -#include "private/backend/SamplerGroup.h" - +#include +#include #include -#include +#include +#include #include #include @@ -68,18 +67,15 @@ class FMorphTargetBuffer : public MorphTargetBuffer { return mTbHandle; } - inline backend::Handle getHwHandle() const noexcept { return mSbHandle; } - private: void updateDataAt(backend::DriverApi& driver, backend::Handle handle, backend::PixelDataFormat format, backend::PixelDataType type, const char* out, size_t elementSize, size_t targetIndex, size_t count, size_t offset); - backend::Handle mSbHandle; - backend::Handle mPbHandle; - backend::Handle mTbHandle; - size_t mVertexCount; - size_t mCount; + backend::TextureHandle mPbHandle; + backend::TextureHandle mTbHandle; + uint32_t mVertexCount; + uint32_t mCount; }; FILAMENT_DOWNCAST(MorphTargetBuffer) diff --git a/filament/src/details/RenderTarget.cpp b/filament/src/details/RenderTarget.cpp index 6e460133e48..6c232e43e5c 100644 --- a/filament/src/details/RenderTarget.cpp +++ b/filament/src/details/RenderTarget.cpp @@ -164,7 +164,8 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build backend::MRT mrt{}; TargetBufferInfo dinfo{}; - auto setAttachment = [&](TargetBufferInfo& info, AttachmentPoint attachmentPoint) { + auto setAttachment = [this, &driver = engine.getDriverApi()] + (TargetBufferInfo& info, AttachmentPoint attachmentPoint) { Attachment const& attachment = mAttachments[(size_t)attachmentPoint]; auto t = downcast(attachment.texture); info.handle = t->getHwHandle(); @@ -174,6 +175,7 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build } else { info.layer = attachment.layer; } + t->updateLodRange(info.level); }; UTILS_NOUNROLL diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index b210884c2b1..f056976833a 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -594,6 +594,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { JobSystem& js = engine.getJobSystem(); FEngine::DriverApi& driver = engine.getDriverApi(); PostProcessManager& ppm = engine.getPostProcessManager(); + ppm.setFrameUniforms(driver, view.getFrameUniforms()); // DEBUG: driver commands must all happen from the same thread. Enforce that on debug builds. driver.debugThreading(); @@ -912,9 +913,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { scene.getRenderableData(), view.getVisibleRenderables()); passBuilder.camera(cameraInfo); - passBuilder.geometry(scene.getRenderableData(), - view.getVisibleRenderables(), - view.getRenderableUBO()); + passBuilder.geometry(scene.getRenderableData(), view.getVisibleRenderables()); // view set-ups that need to happen before rendering fg.addTrivialSideEffectPass("Prepare View Uniforms", @@ -948,10 +947,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { uint32_t(float(xvp.width ) * aoOptions.resolution), uint32_t(float(xvp.height) * aoOptions.resolution)}); - view.commitUniforms(driver); - - // set uniforms and samplers for the color passes - view.bindPerViewUniformsAndSamplers(driver); + view.commitUniformsAndSamplers(driver); }); // -------------------------------------------------------------------------------------------- @@ -991,8 +987,15 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // Store this frame's camera projection in the frame history. if (UTILS_UNLIKELY(taaOptions.enabled)) { // Apply the TAA jitter to everything after the structure pass, starting with the color pass. - ppm.prepareTaa(fg, svp, taaOptions, view.getFrameHistory(), &FrameHistoryEntry::taa, - &cameraInfo, view.getPerViewUniforms()); + ppm.TaaJitterCamera(svp, taaOptions, view.getFrameHistory(), + &FrameHistoryEntry::taa, &cameraInfo); + + fg.addTrivialSideEffectPass("Jitter Camera", + [&engine, &cameraInfo, &descriptorSet = view.getColorPassDescriptorSet()] + (DriverApi& driver) { + descriptorSet.prepareCamera(engine, cameraInfo); + descriptorSet.commit(driver); + }); } // -------------------------------------------------------------------------------------------- @@ -1019,16 +1022,11 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { if (ssReflectionsOptions.enabled) { auto reflections = ppm.ssr(fg, passBuilder, view.getFrameHistory(), cameraInfo, - view.getPerViewUniforms(), structure, ssReflectionsOptions, { .width = svp.width, .height = svp.height }); if (UTILS_LIKELY(reflections)) { - fg.addTrivialSideEffectPass("SSR Cleanup", [&view](DriverApi& driver) { - view.getPerViewUniforms().prepareStructure({}); - view.commitUniforms(driver); - }); // generate the mipchain PostProcessManager::generateMipmapSSR(ppm, fg, reflections, ssrConfig.reflection, false, ssrConfig); @@ -1049,6 +1047,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // (i.e. it won't be culled, unless everything is culled), so no need to complexify things. passBuilder.variant(variant); + // This is optional, if not set, the per-view descriptor-set must be set before calling execute() + passBuilder.colorPassDescriptorSet(&view.getColorPassDescriptorSet()); + // color-grading as subpass is done either by the color pass or the TAA pass if any auto colorGradingConfigForColor = colorGradingConfig; colorGradingConfigForColor.asSubpass = colorGradingConfigForColor.asSubpass && !taaOptions.enabled; @@ -1144,12 +1145,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { } } - fg.addTrivialSideEffectPass("Finish Color Passes", [&view](DriverApi& driver) { - // Unbind SSAO sampler, b/c the FrameGraph will delete the texture at the end of the pass. - view.cleanupRenderPasses(); - view.commitUniforms(driver); - }); - if (colorGradingConfig.customResolve) { assert_invariant(fg.getDescriptor(colorPassOutput.linearColor).samples <= 1); // TODO: we have to "uncompress" (i.e. detonemap) the color buffer here because it's used diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index 9690e6386e2..03898570e2b 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -22,6 +22,8 @@ #include "Allocators.h" #include "Culler.h" +#include "ds/DescriptorSet.h" + #include "components/LightManager.h" #include "components/RenderableManager.h" #include "components/TransformManager.h" @@ -105,6 +107,7 @@ class FScene : public Scene { PRIMITIVES, // 8 | level-of-detail'ed primitives SUMMED_PRIMITIVE_COUNT, // 4 | summed visible primitive counts UBO, // 128 | + DESCRIPTOR_SET_HANDLE, // FIXME: We need a better way to handle this USER_DATA, // 4 | user data currently used to store the scale @@ -125,6 +128,7 @@ class FScene : public Scene { utils::Slice, // PRIMITIVES uint32_t, // SUMMED_PRIMITIVE_COUNT PerRenderableData, // UBO + backend::DescriptorSetHandle, // DESCRIPTOR_SET_HANDLE // FIXME: We need a better way to handle this float // USER_DATA >; diff --git a/filament/src/details/SkinningBuffer.cpp b/filament/src/details/SkinningBuffer.cpp index c4c79c3a992..0c5759323ab 100644 --- a/filament/src/details/SkinningBuffer.cpp +++ b/filament/src/details/SkinningBuffer.cpp @@ -18,8 +18,6 @@ #include "components/RenderableManager.h" -#include "private/filament/SibStructs.h" - #include "details/Engine.h" #include "FilamentAPI-impl.h" @@ -29,7 +27,9 @@ #include -#include +#include +#include +#include namespace filament { @@ -233,29 +233,15 @@ void updateDataAt(backend::DriverApi& driver, } } -FSkinningBuffer::HandleIndicesAndWeights FSkinningBuffer::createIndicesAndWeightsHandle( +backend::TextureHandle FSkinningBuffer::createIndicesAndWeightsHandle( FEngine& engine, size_t count) { - backend::Handle samplerHandle; - backend::Handle textureHandle; - FEngine::DriverApi& driver = engine.getDriverApi(); // create a texture for skinning pairs data (bone index and weight) - textureHandle = driver.createTexture(SamplerType::SAMPLER_2D, 1, + return driver.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RG32F, 1, getSkinningBufferWidth(count), getSkinningBufferHeight(count), 1, TextureUsage::DEFAULT); - samplerHandle = driver.createSamplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT, - utils::FixedSizeString<32>("Skinning buffer samplers")); - SamplerGroup samplerGroup(PerRenderPrimitiveSkinningSib::SAMPLER_COUNT); - samplerGroup.setSampler(PerRenderPrimitiveSkinningSib::BONE_INDICES_AND_WEIGHTS, - { textureHandle, {}}); - driver.updateSamplerGroup(samplerHandle, - samplerGroup.toBufferDescriptor(driver)); - return { - .sampler = samplerHandle, - .texture = textureHandle - }; } void FSkinningBuffer::setIndicesAndWeightsData(FEngine& engine, diff --git a/filament/src/details/SkinningBuffer.h b/filament/src/details/SkinningBuffer.h index 8fa056a5796..fd831ef122e 100644 --- a/filament/src/details/SkinningBuffer.h +++ b/filament/src/details/SkinningBuffer.h @@ -17,19 +17,24 @@ #ifndef TNT_FILAMENT_DETAILS_SKINNINGBUFFER_H #define TNT_FILAMENT_DETAILS_SKINNINGBUFFER_H -#include "downcast.h" #include -#include "private/filament/EngineEnums.h" -#include "private/filament/UibStructs.h" +#include "downcast.h" + +#include +#include #include - #include -#include +#include + +#include #include +#include +#include + // for gtest class FilamentTest_Bones_Test; @@ -55,9 +60,6 @@ class FSkinningBuffer : public SkinningBuffer { return (count + CONFIG_MAX_BONE_COUNT - 1) & ~(CONFIG_MAX_BONE_COUNT - 1); } - backend::Handle setIndicesAndWeights(FEngine& engine, - math::float2 const* pairs, size_t count); - private: friend class ::FilamentTest_Bones_Test; friend class SkinningBuffer; @@ -75,12 +77,8 @@ class FSkinningBuffer : public SkinningBuffer { return mHandle; } - struct HandleIndicesAndWeights{ - backend::Handle sampler; - backend::Handle texture; - }; - static HandleIndicesAndWeights createIndicesAndWeightsHandle(FEngine& engine, - size_t count); + static backend::TextureHandle createIndicesAndWeightsHandle(FEngine& engine, size_t count); + static void setIndicesAndWeightsData(FEngine& engine, backend::Handle textureHandle, const utils::FixedCapacityVector& pairs, diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index b2cfbf68391..dc011cdfa35 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -221,6 +222,7 @@ Texture* Texture::Builder::build(Engine& engine) { FTexture::FTexture(FEngine& engine, const Builder& builder) { FEngine::DriverApi& driver = engine.getDriverApi(); + mDriver = &driver; // this is unfortunately needed for getHwHandleForSampling() mWidth = static_cast(builder->mWidth); mHeight = static_cast(builder->mHeight); mDepth = static_cast(builder->mDepth); @@ -228,21 +230,33 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) { mUsage = builder->mUsage; mTarget = builder->mTarget; mLevelCount = builder->mLevels; + mSwizzle = builder->mSwizzle; + mTextureIsSwizzled = builder->mTextureIsSwizzled; + + if (mTarget == SamplerType::SAMPLER_EXTERNAL) { + // mHandle and mHandleForSampling will be created in setExternalImage() + // If this Texture is used for sampling before setExternalImage() is called, + // we'll lazily create a 1x1 placeholder texture. + return; + } if (UTILS_LIKELY(builder->mImportedId == 0)) { - if (UTILS_LIKELY(!builder->mTextureIsSwizzled)) { - mHandle = driver.createTexture( - mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage); - } else { - mHandle = driver.createTextureSwizzled( - mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage, - builder->mSwizzle[0], builder->mSwizzle[1], builder->mSwizzle[2], - builder->mSwizzle[3]); - } + mHandle = driver.createTexture( + mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage); } else { mHandle = driver.importTexture(builder->mImportedId, mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage); } + + if (UTILS_UNLIKELY(builder->mTextureIsSwizzled)) { + auto const& s = builder->mSwizzle; + auto swizzleView = driver.createTextureViewSwizzle(mHandle, s[0], s[1], s[2], s[3]); + driver.destroyTexture(mHandle); + mHandle = swizzleView; + } + + mHandleForSampling = mHandle; + if (auto name = builder.getName(); !name.empty()) { driver.setDebugTag(mHandle.getId(), std::move(name)); } else { @@ -252,8 +266,7 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) { // frees driver resources, object becomes invalid void FTexture::terminate(FEngine& engine) { - FEngine::DriverApi& driver = engine.getDriverApi(); - driver.destroyTexture(mHandle); + setHandles({}); } size_t FTexture::getWidth(size_t level) const noexcept { @@ -354,6 +367,9 @@ void FTexture::setImage(FEngine& engine, size_t level, engine.getDriverApi().update3DImage(mHandle, uint8_t(level), xoffset, yoffset, zoffset, width, height, depth, std::move(p)); + + // this method shouldn't have been const + const_cast(this)->updateLodRange(level); } // deprecated @@ -420,36 +436,78 @@ void FTexture::setImage(FEngine& engine, size_t level, engine.getDriverApi().queueCommand( make_copyable_function([buffer = std::move(buffer)]() {})); } + + // this method shouldn't been const + const_cast(this)->updateLodRange(level); } void FTexture::setExternalImage(FEngine& engine, void* image) noexcept { - if (mTarget == Sampler::SAMPLER_EXTERNAL) { - // The call to setupExternalImage is synchronous, and allows the driver to take ownership of - // the external image on this thread, if necessary. - engine.getDriverApi().setupExternalImage(image); - engine.getDriverApi().setExternalImage(mHandle, image); + if (mTarget != Sampler::SAMPLER_EXTERNAL) { + return; + } + // The call to setupExternalImage is synchronous, and allows the driver to take ownership of the + // external image on this thread, if necessary. + auto& api = engine.getDriverApi(); + api.setupExternalImage(image); + + auto texture = api.createTextureExternalImage(mFormat, mWidth, mHeight, mUsage, image); + + if (mTextureIsSwizzled) { + auto const& s = mSwizzle; + auto swizzleView = api.createTextureViewSwizzle(texture, s[0], s[1], s[2], s[3]); + api.destroyTexture(texture); + texture = swizzleView; } + + setHandles(texture); } void FTexture::setExternalImage(FEngine& engine, void* image, size_t plane) noexcept { - if (mTarget == Sampler::SAMPLER_EXTERNAL) { - // The call to setupExternalImage is synchronous, and allows the driver to take ownership of - // the external image on this thread, if necessary. - engine.getDriverApi().setupExternalImage(image); - engine.getDriverApi().setExternalImagePlane(mHandle, image, plane); + if (mTarget != Sampler::SAMPLER_EXTERNAL) { + return; } + // The call to setupExternalImage is synchronous, and allows the driver to take ownership of + // the external image on this thread, if necessary. + auto& api = engine.getDriverApi(); + api.setupExternalImage(image); + + auto texture = + api.createTextureExternalImagePlane(mFormat, mWidth, mHeight, mUsage, image, plane); + + if (mTextureIsSwizzled) { + auto const& s = mSwizzle; + auto swizzleView = api.createTextureViewSwizzle(texture, s[0], s[1], s[2], s[3]); + api.destroyTexture(texture); + texture = swizzleView; + } + + setHandles(texture); } void FTexture::setExternalStream(FEngine& engine, FStream* stream) noexcept { - if (stream) { - FILAMENT_CHECK_PRECONDITION(mTarget == Sampler::SAMPLER_EXTERNAL) - << "Texture target must be SAMPLER_EXTERNAL"; + if (mTarget != Sampler::SAMPLER_EXTERNAL) { + return; + } + + auto& api = engine.getDriverApi(); + auto texture = api.createTexture( + mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage); + + if (mTextureIsSwizzled) { + auto const& s = mSwizzle; + auto swizzleView = api.createTextureViewSwizzle(texture, s[0], s[1], s[2], s[3]); + api.destroyTexture(texture); + texture = swizzleView; + } + + setHandles(texture); + if (stream) { mStream = stream; - engine.getDriverApi().setExternalStream(mHandle, stream->getHandle()); + api.setExternalStream(mHandle, stream->getHandle()); } else { mStream = nullptr; - engine.getDriverApi().setExternalStream(mHandle, backend::Handle()); + api.setExternalStream(mHandle, backend::Handle()); } } @@ -469,6 +527,87 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept { } engine.getDriverApi().generateMipmaps(mHandle); + // this method shouldn't have been const + const_cast(this)->updateLodRange(0, mLevelCount); +} + +bool FTexture::textureHandleCanMutate() const noexcept { + return (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) || + mTarget == SamplerType::SAMPLER_EXTERNAL; +} + +void FTexture::updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept { + assert_invariant(mTarget != SamplerType::SAMPLER_EXTERNAL); + if (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) { + auto& range = mLodRange; + uint8_t const last = int8_t(baseLevel + levelCount); + if (range.first > baseLevel || range.last < last) { + if (range.empty()) { + range = { baseLevel, last }; + } else { + range.first = std::min(range.first, baseLevel); + range.last = std::max(range.last, last); + } + // We defer the creation of the texture view to getHwHandleForSampling() because it + // is a common case that by then, the view won't be needed. Creating the first view on a + // texture has a backend cost. + } + } +} + +void FTexture::setHandles(backend::Handle handle) noexcept { + assert_invariant(!mHandle || mHandleForSampling); + if (mHandle) { + mDriver->destroyTexture(mHandle); + } + if (mHandleForSampling != mHandle) { + mDriver->destroyTexture(mHandleForSampling); + } + mHandle = handle; + mHandleForSampling = handle; +} + +backend::Handle FTexture::setHandleForSampling( + backend::Handle handle) const noexcept { + assert_invariant(!mHandle || mHandleForSampling); + if (mHandleForSampling && mHandleForSampling != mHandle) { + mDriver->destroyTexture(mHandleForSampling); + } + return mHandleForSampling = handle; +} + +backend::Handle FTexture::createPlaceholderTexture( + backend::DriverApi& driver) noexcept { + auto handle = driver.createTexture( + Sampler::SAMPLER_2D, 1, InternalFormat::RGBA8, 1, 1, 1, 1, Usage::DEFAULT); + static uint8_t pixels[4] = { 0, 0, 0, 0 }; + driver.update3DImage(handle, 0, 0, 0, 0, 1, 1, 1, + { (char*)&pixels[0], sizeof(pixels), + Texture::PixelBufferDescriptor::PixelDataFormat::RGBA, + Texture::PixelBufferDescriptor::PixelDataType::UBYTE }); + return handle; +} + +backend::Handle FTexture::getHwHandleForSampling() const noexcept { + if (UTILS_UNLIKELY(mTarget == SamplerType::SAMPLER_EXTERNAL && !mHandleForSampling)) { + return setHandleForSampling(createPlaceholderTexture(*mDriver)); + } + auto const& range = mLodRange; + auto& activeRange = mActiveLodRange; + bool const lodRangeChanged = activeRange.first != range.first || activeRange.last != range.last; + if (UTILS_UNLIKELY(lodRangeChanged)) { + activeRange = range; + if (range.empty() || hasAllLods(range)) { + setHandleForSampling(mHandle); + } else { + setHandleForSampling(mDriver->createTextureView(mHandle, range.first, range.size())); + } + } + return mHandleForSampling; +} + +void FTexture::updateLodRange(uint8_t level) noexcept { + updateLodRange(level, 1); } bool FTexture::isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept { diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index 0e0fe9320ee..7c0234bb535 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -19,12 +19,17 @@ #include "downcast.h" +#include +#include #include #include #include +#include +#include + namespace filament { class FEngine; @@ -38,6 +43,7 @@ class FTexture : public Texture { void terminate(FEngine& engine); backend::Handle getHwHandle() const noexcept { return mHandle; } + backend::Handle getHwHandleForSampling() const noexcept; size_t getWidth(size_t level = 0) const noexcept; size_t getHeight(size_t level = 0) const noexcept; @@ -114,10 +120,36 @@ class FTexture : public Texture { static bool validatePixelFormatAndType(backend::TextureFormat internalFormat, backend::PixelDataFormat format, backend::PixelDataType type) noexcept; + bool textureHandleCanMutate() const noexcept; + void updateLodRange(uint8_t level) noexcept; + private: friend class Texture; - FStream* mStream = nullptr; + struct LodRange { + // 0,0 means lod-range unset (all levels are available) + uint8_t first = 0; // first lod + uint8_t last = 0; // 1 past last lod + bool empty() const noexcept { return first == last; } + size_t size() const noexcept { return last - first; } + }; + + bool hasAllLods(LodRange const range) const noexcept { + return range.first == 0 && range.last == mLevelCount; + } + + void updateLodRange(uint8_t baseLevel, uint8_t levelCount) noexcept; + void setHandles(backend::Handle handle) noexcept; + backend::Handle setHandleForSampling( + backend::Handle handle) const noexcept; + static backend::Handle createPlaceholderTexture( + backend::DriverApi& driver) noexcept; + backend::Handle mHandle; + mutable backend::Handle mHandleForSampling; + backend::DriverApi* mDriver = nullptr; // this is only needed for getHwHandleForSampling() + LodRange mLodRange{}; + mutable LodRange mActiveLodRange{}; + uint32_t mWidth = 1; uint32_t mHeight = 1; uint32_t mDepth = 1; @@ -125,10 +157,16 @@ class FTexture : public Texture { Sampler mTarget = Sampler::SAMPLER_2D; uint8_t mLevelCount = 1; uint8_t mSampleCount = 1; + std::array mSwizzle = { + Swizzle::CHANNEL_0, Swizzle::CHANNEL_1, + Swizzle::CHANNEL_2, Swizzle::CHANNEL_3 }; + bool mTextureIsSwizzled; + Usage mUsage = Usage::DEFAULT; + // there is 7 bytes of padding here + FStream* mStream = nullptr; // only needed for streaming textures }; - FILAMENT_DOWNCAST(Texture) } // namespace filament diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index f9bf724921f..8d23837ccac 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -62,10 +63,12 @@ static constexpr float PID_CONTROLLER_Ki = 0.002f; static constexpr float PID_CONTROLLER_Kd = 0.0f; FView::FView(FEngine& engine) - : mFroxelizer(engine), + : mCommonRenderableDescriptorSet(engine.getPerRenderableDescriptorSetLayout()), + mFroxelizer(engine), mFogEntity(engine.getEntityManager().create()), mIsStereoSupported(engine.getDriverApi().isStereoSupported()), - mPerViewUniforms(engine) { + mUniforms(engine.getDriverApi()), + mColorPassDescriptorSet(engine, mUniforms) { DriverApi& driver = engine.getDriverApi(); @@ -113,6 +116,11 @@ FView::FView(FEngine& engine) mIsDynamicResolutionSupported = driver.isFrameTimeSupported(); mDefaultColorGrading = mColorGrading = engine.getDefaultColorGrading(); + + mColorPassDescriptorSet.init( + mLightUbh, + mFroxelizer.getRecordBuffer(), + mFroxelizer.getFroxelBuffer()); } FView::~FView() noexcept = default; @@ -133,8 +141,10 @@ void FView::terminate(FEngine& engine) { clearFrameHistory(engine); ShadowMapManager::terminate(engine, mShadowMapManager); - mPerViewUniforms.terminate(driver); + mUniforms.terminate(driver); + mColorPassDescriptorSet.terminate(engine.getDescriptorSetLayoutFactory(), driver); mFroxelizer.terminate(driver); + mCommonRenderableDescriptorSet.terminate(driver); engine.getEntityManager().destroy(mFogEntity); @@ -406,7 +416,7 @@ void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexc */ const float exposure = Exposure::exposure(cameraInfo.ev100); - mPerViewUniforms.prepareExposure(cameraInfo.ev100); + mColorPassDescriptorSet.prepareExposure(cameraInfo.ev100); /* * Indirect light (IBL) @@ -423,7 +433,7 @@ void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexc FSkybox const* const skybox = scene->getSkybox(); intensity = skybox ? skybox->getIntensity() : FIndirectLight::DEFAULT_INTENSITY; } - mPerViewUniforms.prepareAmbientLight(engine, *ibl, intensity, exposure); + mColorPassDescriptorSet.prepareAmbientLight(engine, *ibl, intensity, exposure); /* * Directional light (always at index 0) @@ -431,7 +441,7 @@ void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexc FLightManager::Instance const directionalLight = lightData.elementAt(0); const float3 sceneSpaceDirection = lightData.elementAt(0); // guaranteed normalized - mPerViewUniforms.prepareDirectionalLight(engine, exposure, sceneSpaceDirection, directionalLight); + mColorPassDescriptorSet.prepareDirectionalLight(engine, exposure, sceneSpaceDirection, directionalLight); } CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept { @@ -583,7 +593,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren cameraInfo.projection, cameraInfo.zn, cameraInfo.zf)) { // TODO: might be more consistent to do this in prepareLighting(), but it's not // strictly necessary - mPerViewUniforms.prepareDynamicLights(mFroxelizer); + mColorPassDescriptorSet.prepareDynamicLights(mFroxelizer); } // We need to pass viewMatrix by value here because it extends the scope of this // function. @@ -686,6 +696,67 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren } assert_invariant(mRenderableUbh); scene->updateUBOs(merged, mRenderableUbh); + + mCommonRenderableDescriptorSet.setBuffer( + +PerRenderableBindingPoints::OBJECT_UNIFORMS, mRenderableUbh, + 0, sizeof(PerRenderableUib)); + + mCommonRenderableDescriptorSet.commit( + engine.getPerRenderableDescriptorSetLayout(), driver); + } + } + + { // this must happen after mRenderableUbh is created/updated + // prepare skinning, morphing and hybrid instancing + auto& sceneData = scene->getRenderableData(); + for (uint32_t const i : merged) { + auto const& skinning = sceneData.elementAt(i); + auto const& morphing = sceneData.elementAt(i); + auto const& instance = sceneData.elementAt(i); + + // FIXME: when only one is active the UBO handle of the other is null + // (probably a problem on vulkan) + if (UTILS_UNLIKELY(skinning.handle || morphing.handle || instance.handle)) { + auto const ci = sceneData.elementAt(i); + FRenderableManager& rcm = engine.getRenderableManager(); + auto& descriptorSet = rcm.getDescriptorSet(ci); + + // initialize the descriptor set the first time it's needed + if (UTILS_UNLIKELY(!descriptorSet.getHandle())) { + descriptorSet = DescriptorSet{ engine.getPerRenderableDescriptorSetLayout() }; + } + + descriptorSet.setBuffer(+PerRenderableBindingPoints::OBJECT_UNIFORMS, + instance.handle ? instance.handle : mRenderableUbh, + 0, sizeof(PerRenderableUib)); + + if (UTILS_UNLIKELY(skinning.handle || morphing.handle)) { + + descriptorSet.setBuffer(+PerRenderableBindingPoints::BONES_UNIFORMS, + skinning.handle, 0, sizeof(PerRenderableBoneUib)); + + descriptorSet.setSampler(+PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS, + skinning.boneIndicesAndWeightHandle, {}); + + descriptorSet.setBuffer(+PerRenderableBindingPoints::MORPHING_UNIFORMS, + morphing.handle, 0, sizeof(PerRenderableMorphingUib)); + + descriptorSet.setSampler(+PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, + morphing.morphTargetBuffer->getPositionsHandle(), {}); + + descriptorSet.setSampler(+PerRenderableBindingPoints::MORPH_TARGET_TANGENTS, + morphing.morphTargetBuffer->getTangentsHandle(), {}); + } + + descriptorSet.commit(engine.getPerRenderableDescriptorSetLayout(), driver); + + // write the descriptor-set handle to the sceneData array for access later + sceneData.elementAt(i) = descriptorSet.getHandle(); + } else { + // use the shared descriptor-set + sceneData.elementAt(i) = + mCommonRenderableDescriptorSet.getHandle(); + } } } @@ -704,35 +775,12 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren auto const& tcm = engine.getTransformManager(); auto const fogTransform = tcm.getWorldTransformAccurate(tcm.getInstance(mFogEntity)); - mPerViewUniforms.prepareTime(engine, userTime); - mPerViewUniforms.prepareFog(engine, cameraInfo, fogTransform, mFogOptions, + mColorPassDescriptorSet.prepareTime(engine, userTime); + mColorPassDescriptorSet.prepareFog(engine, cameraInfo, fogTransform, mFogOptions, scene->getIndirectLight()); - mPerViewUniforms.prepareTemporalNoise(engine, mTemporalAntiAliasingOptions); - mPerViewUniforms.prepareBlending(needsAlphaChannel); - mPerViewUniforms.prepareMaterialGlobals(mMaterialGlobals); -} - -void FView::bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noexcept { - mPerViewUniforms.bind(driver); - - if (UTILS_UNLIKELY(driver.getFeatureLevel() == backend::FeatureLevel::FEATURE_LEVEL_0)) { - return; - } - - driver.bindUniformBuffer(+UniformBindingPoints::LIGHTS, - mLightUbh); - - if (needsShadowMap()) { - assert_invariant(mShadowMapManager->getShadowUniformsHandle()); - driver.bindUniformBuffer(+UniformBindingPoints::SHADOW, - mShadowMapManager->getShadowUniformsHandle()); - } - - driver.bindUniformBuffer(+UniformBindingPoints::FROXEL_RECORDS, - mFroxelizer.getRecordBuffer()); - - driver.bindUniformBuffer(+UniformBindingPoints::FROXELS, - mFroxelizer.getFroxelBuffer()); + mColorPassDescriptorSet.prepareTemporalNoise(engine, mTemporalAntiAliasingOptions); + mColorPassDescriptorSet.prepareBlending(needsAlphaChannel); + mColorPassDescriptorSet.prepareMaterialGlobals(mMaterialGlobals); } void FView::computeVisibilityMasks( @@ -793,12 +841,12 @@ void FView::prepareUpscaler(float2 scale, derivativesScale = 0.5f; } } - mPerViewUniforms.prepareLodBias(bias, derivativesScale); + mColorPassDescriptorSet.prepareLodBias(bias, derivativesScale); } void FView::prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept { SYSTRACE_CALL(); - mPerViewUniforms.prepareCamera(engine, cameraInfo); + mColorPassDescriptorSet.prepareCamera(engine, cameraInfo); } void FView::prepareViewport( @@ -807,23 +855,23 @@ void FView::prepareViewport( SYSTRACE_CALL(); // TODO: we should pass viewport.{left|bottom} to the backend, so it can offset the // scissor properly. - mPerViewUniforms.prepareViewport(physicalViewport, logicalViewport); + mColorPassDescriptorSet.prepareViewport(physicalViewport, logicalViewport); } void FView::prepareSSAO(Handle ssao) const noexcept { - mPerViewUniforms.prepareSSAO(ssao, mAmbientOcclusionOptions); + mColorPassDescriptorSet.prepareSSAO(ssao, mAmbientOcclusionOptions); } void FView::prepareSSR(Handle ssr, bool disableSSR, float refractionLodOffset, ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept { - mPerViewUniforms.prepareSSR(ssr, disableSSR, refractionLodOffset, ssrOptions); + mColorPassDescriptorSet.prepareSSR(ssr, disableSSR, refractionLodOffset, ssrOptions); } void FView::prepareStructure(Handle structure) const noexcept { // sampler must be NEAREST - mPerViewUniforms.prepareStructure(structure); + mColorPassDescriptorSet.prepareStructure(structure); } void FView::prepareShadow(Handle texture) const noexcept { @@ -835,33 +883,33 @@ void FView::prepareShadow(Handle texture) const noexcept { } switch (mShadowType) { case filament::ShadowType::PCF: - mPerViewUniforms.prepareShadowPCF(texture, uniforms); + mColorPassDescriptorSet.prepareShadowPCF(texture, uniforms); break; case filament::ShadowType::VSM: - mPerViewUniforms.prepareShadowVSM(texture, uniforms, mVsmShadowOptions); + mColorPassDescriptorSet.prepareShadowVSM(texture, uniforms, mVsmShadowOptions); break; case filament::ShadowType::DPCF: - mPerViewUniforms.prepareShadowDPCF(texture, uniforms, mSoftShadowOptions); + mColorPassDescriptorSet.prepareShadowDPCF(texture, uniforms, mSoftShadowOptions); break; case filament::ShadowType::PCSS: - mPerViewUniforms.prepareShadowPCSS(texture, uniforms, mSoftShadowOptions); + mColorPassDescriptorSet.prepareShadowPCSS(texture, uniforms, mSoftShadowOptions); break; case filament::ShadowType::PCFd: - mPerViewUniforms.prepareShadowPCFDebug(texture, uniforms); + mColorPassDescriptorSet.prepareShadowPCFDebug(texture, uniforms); break; } } void FView::prepareShadowMapping(bool highPrecision) const noexcept { - mPerViewUniforms.prepareShadowMapping(highPrecision); -} - -void FView::cleanupRenderPasses() const noexcept { - mPerViewUniforms.unbindSamplers(); + if (mHasShadowing) { + assert_invariant(mShadowMapManager); + mColorPassDescriptorSet.prepareShadowMapping( + mShadowMapManager->getShadowUniformsHandle(), highPrecision); + } } -void FView::commitUniforms(DriverApi& driver) const noexcept { - mPerViewUniforms.commit(driver); +void FView::commitUniformsAndSamplers(DriverApi& driver) const noexcept { + mColorPassDescriptorSet.commit(driver); } void FView::commitFroxels(DriverApi& driverApi) const noexcept { diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 2a2f9a414fa..0aeacd21b54 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -17,21 +17,24 @@ #ifndef TNT_FILAMENT_DETAILS_VIEW_H #define TNT_FILAMENT_DETAILS_VIEW_H -#include - -#include - #include "downcast.h" #include "Allocators.h" +#include "Culler.h" #include "FrameHistory.h" #include "FrameInfo.h" #include "Froxelizer.h" -#include "PerViewUniforms.h" #include "PIDController.h" -#include "ShadowMap.h" #include "ShadowMapManager.h" -#include "TypedUniformBuffer.h" + +#include "ds/ColorPassDescriptorSet.h" +#include "ds/DescriptorSet.h" +#include "ds/PostProcessDescriptorSet.h" +#include "ds/SsrPassDescriptorSet.h" +#include "ds/TypedUniformBuffer.h" + +#include "components/LightManager.h" +#include "components/RenderableManager.h" #include "details/Camera.h" #include "details/ColorGrading.h" @@ -39,13 +42,20 @@ #include "details/Scene.h" #include +#include -#include "private/backend/DriverApi.h" +#include +#include +#include +#include + +#include #include #include #include +#include #include #include #include @@ -55,6 +65,10 @@ #include #include +#include + +#include +#include namespace utils { class JobSystem; @@ -95,8 +109,6 @@ class FView : public View { filament::Viewport viewport, CameraInfo cameraInfo, math::float4 const& userTime, bool needsAlphaChannel) noexcept; - void bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noexcept; - void setScene(FScene* scene) { mScene = scene; } FScene const* getScene() const noexcept { return mScene; } FScene* getScene() noexcept { return mScene; } @@ -157,9 +169,8 @@ class FView : public View { void prepareShadow(backend::Handle structure) const noexcept; void prepareShadowMapping(bool highPrecision) const noexcept; - void cleanupRenderPasses() const noexcept; - void commitUniforms(backend::DriverApi& driver) const noexcept; void commitFroxels(backend::DriverApi& driverApi) const noexcept; + void commitUniformsAndSamplers(backend::DriverApi& driver) const noexcept; utils::JobSystem::Job* getFroxelizerSync() const noexcept { return mFroxelizerSync; } void setFroxelizerSync(utils::JobSystem::Job* sync) noexcept { mFroxelizerSync = sync; } @@ -394,7 +405,7 @@ class FView : public View { } backend::Handle getRenderTargetHandle() const noexcept { - backend::Handle kEmptyHandle; + backend::Handle const kEmptyHandle; return mRenderTarget == nullptr ? kEmptyHandle : mRenderTarget->getHwHandle(); } @@ -409,8 +420,7 @@ class FView : public View { static void cullRenderables(utils::JobSystem& js, FScene::RenderableSoa& renderableData, Frustum const& frustum, size_t bit) noexcept; - PerViewUniforms const& getPerViewUniforms() const noexcept { return mPerViewUniforms; } - PerViewUniforms& getPerViewUniforms() noexcept { return mPerViewUniforms; } + ColorPassDescriptorSet& getColorPassDescriptorSet() noexcept { return mColorPassDescriptorSet; } // Returns the frame history FIFO. This is typically used by the FrameGraph to access // previous frame data. @@ -441,8 +451,8 @@ class FView : public View { return mFogEntity; } - backend::Handle getRenderableUBO() const noexcept { - return mRenderableUbh; + TypedUniformBuffer& getFrameUniforms() noexcept { + return mUniforms; } private: @@ -457,7 +467,7 @@ class FView : public View { // TODO: use a small pool static FPickingQuery* get(uint32_t x, uint32_t y, backend::CallbackHandler* handler, View::PickingQueryResultCallback callback) noexcept { - return new FPickingQuery(x, y, handler, callback); + return new(std::nothrow) FPickingQuery(x, y, handler, callback); } static void put(FPickingQuery* pQuery) noexcept { delete pQuery; @@ -499,10 +509,11 @@ class FView : public View { // these are accessed in the render loop, keep together backend::Handle mLightUbh; backend::Handle mRenderableUbh; + filament::DescriptorSet mCommonRenderableDescriptorSet; FScene* mScene = nullptr; // The camera set by the user, used for culling and viewing - FCamera* /* UTILS_NONNULL */ mCullingCamera = nullptr; // FIXME: should alaways be non-null + FCamera* /* UTILS_NONNULL */ mCullingCamera = nullptr; // FIXME: should always be non-null // The optional (debug) camera, used only for viewing FCamera* mViewingCamera = nullptr; @@ -548,7 +559,8 @@ class FView : public View { RenderQuality mRenderQuality; - mutable PerViewUniforms mPerViewUniforms; + mutable TypedUniformBuffer mUniforms; + mutable ColorPassDescriptorSet mColorPassDescriptorSet; mutable FrameHistory mFrameHistory{}; diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/ds/ColorPassDescriptorSet.cpp similarity index 65% rename from filament/src/PerViewUniforms.cpp rename to filament/src/ds/ColorPassDescriptorSet.cpp index 35867ea855b..18d3f495d6c 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/ds/ColorPassDescriptorSet.cpp @@ -14,54 +14,141 @@ * limitations under the License. */ -#include "PerViewUniforms.h" +#include "ColorPassDescriptorSet.h" -#include "DFG.h" #include "Froxelizer.h" +#include "HwDescriptorSetLayoutFactory.h" #include "ShadowMapManager.h" +#include "TypedUniformBuffer.h" + +#include "components/LightManager.h" #include "details/Camera.h" #include "details/Engine.h" #include "details/IndirectLight.h" #include "details/Texture.h" +#include #include #include #include +#include +#include #include -#include +#include +#include + +#include +#include #include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include namespace filament { using namespace backend; using namespace math; -PerViewUniforms::PerViewUniforms(FEngine& engine) noexcept - : mSamplers(PerViewSib::SAMPLER_COUNT) { - DriverApi& driver = engine.getDriverApi(); +uint8_t ColorPassDescriptorSet::getIndex( + bool lit, bool ssr, bool fog) noexcept { + + uint8_t index = 0; + + if (!lit) { + // this will remove samplers unused when unit + index |= 0x1; + } + + if (ssr) { + // this will add samplers needed for screen-space SSR + index |= 0x2; + } + + if (!fog) { + // this will remove samplers needed for fog + index |= 0x4; + } + + assert_invariant(index < DESCRIPTOR_LAYOUT_COUNT); + return index; +} - mSamplerGroupHandle = driver.createSamplerGroup( - mSamplers.getSize(), utils::FixedSizeString<32>("Per-view samplers")); - mUniformBufferHandle = driver.createBufferObject(mUniforms.getSize(), - BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); +ColorPassDescriptorSet::ColorPassDescriptorSet(FEngine& engine, + TypedUniformBuffer& uniforms) noexcept + : mUniforms(uniforms) { + + constexpr UserVariantFilterMask filterFog = UserVariantFilterMask(UserVariantFilterBit::FOG); + constexpr UserVariantFilterMask keepFog = UserVariantFilterMask(0); + + for (bool const lit: { false, true }) { + for (bool const ssr: { false, true }) { + for (bool const fog: { false, true }) { + auto index = ColorPassDescriptorSet::getIndex(lit, ssr, fog); + mDescriptorSetLayout[index] = { + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), + descriptor_sets::getPerViewDescriptorSetLayout( + MaterialDomain::SURFACE, + fog ? keepFog : filterFog, + lit, + ssr ? ReflectionMode::SCREEN_SPACE : ReflectionMode::DEFAULT, + ssr ? RefractionMode::SCREEN_SPACE : RefractionMode::NONE) + }; + mDescriptorSet[index] = DescriptorSet{ mDescriptorSetLayout[index] }; + } + } + } + + setBuffer(+PerViewBindingPoints::FRAME_UNIFORMS, + uniforms.getUboHandle(), 0, uniforms.getSize()); if (engine.getDFG().isValid()) { TextureSampler const sampler(TextureSampler::MagFilter::LINEAR); - mSamplers.setSampler(PerViewSib::IBL_DFG_LUT, - { engine.getDFG().getTexture(), sampler.getSamplerParams() }); + setSampler(+PerViewBindingPoints::IBL_DFG_LUT, + engine.getDFG().getTexture(), sampler.getSamplerParams()); + } +} + +void ColorPassDescriptorSet::init( + BufferObjectHandle lights, + BufferObjectHandle recordBuffer, + BufferObjectHandle froxelBuffer) noexcept { + for (auto&& descriptorSet: mDescriptorSet) { + descriptorSet.setBuffer(+PerViewBindingPoints::LIGHTS, + lights, 0, sizeof(LightsUib)); + descriptorSet.setBuffer(+PerViewBindingPoints::RECORD_BUFFER, + recordBuffer, 0, sizeof(FroxelRecordUib)); + descriptorSet.setBuffer(+PerViewBindingPoints::FROXEL_BUFFER, + froxelBuffer, 0, sizeof(FroxelsUib)); } } -void PerViewUniforms::terminate(DriverApi& driver) { - driver.destroyBufferObject(mUniformBufferHandle); - driver.destroySamplerGroup(mSamplerGroupHandle); +void ColorPassDescriptorSet::terminate(HwDescriptorSetLayoutFactory& factory, DriverApi& driver) { + for (auto&& entry : mDescriptorSet) { + entry.terminate(driver); + } + for (auto&& entry : mDescriptorSetLayout) { + entry.terminate(factory, driver); + } } -void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept { +void ColorPassDescriptorSet::prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept { mat4f const& viewFromWorld = camera.view; mat4f const& worldFromView = camera.model; mat4f const& clipFromView = camera.projection; @@ -96,20 +183,20 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n s.clipControl = engine.getDriverApi().getClipSpaceParams(); } -void PerViewUniforms::prepareLodBias(float bias, float2 derivativesScale) noexcept { +void ColorPassDescriptorSet::prepareLodBias(float bias, float2 derivativesScale) noexcept { auto& s = mUniforms.edit(); s.lodBias = bias; s.derivativesScale = derivativesScale; } -void PerViewUniforms::prepareExposure(float ev100) noexcept { +void ColorPassDescriptorSet::prepareExposure(float ev100) noexcept { const float exposure = Exposure::exposure(ev100); auto& s = mUniforms.edit(); s.exposure = exposure; s.ev100 = ev100; } -void PerViewUniforms::prepareViewport( +void ColorPassDescriptorSet::prepareViewport( const filament::Viewport& physicalViewport, const filament::Viewport& logicalViewport) noexcept { float4 const physical{ physicalViewport.left, physicalViewport.bottom, @@ -122,7 +209,7 @@ void PerViewUniforms::prepareViewport( s.logicalViewportOffset = -logical.xy / logical.zw; } -void PerViewUniforms::prepareTime(FEngine& engine, math::float4 const& userTime) noexcept { +void ColorPassDescriptorSet::prepareTime(FEngine& engine, math::float4 const& userTime) noexcept { auto& s = mUniforms.edit(); const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1000000000; const float fraction = float(double(oneSecondRemainder) / 1000000000.0); @@ -130,7 +217,7 @@ void PerViewUniforms::prepareTime(FEngine& engine, math::float4 const& userTime) s.userTime = userTime; } -void PerViewUniforms::prepareTemporalNoise(FEngine& engine, +void ColorPassDescriptorSet::prepareTemporalNoise(FEngine& engine, TemporalAntiAliasingOptions const& options) noexcept { std::uniform_real_distribution uniformDistribution{ 0.0f, 1.0f }; auto& s = mUniforms.edit(); @@ -138,7 +225,7 @@ void PerViewUniforms::prepareTemporalNoise(FEngine& engine, s.temporalNoise = options.enabled ? temporalNoise : 0.0f; } -void PerViewUniforms::prepareFog(FEngine& engine, const CameraInfo& cameraInfo, +void ColorPassDescriptorSet::prepareFog(FEngine& engine, const CameraInfo& cameraInfo, mat4 const& userWorldFromFog, FogOptions const& options, FIndirectLight const* ibl) noexcept { auto packHalf2x16 = [](math::half2 v) -> uint32_t { @@ -174,7 +261,7 @@ void PerViewUniforms::prepareFog(FEngine& engine, const CameraInfo& cameraInfo, // currently they're inferred. Handle fogColorTextureHandle; if (options.skyColor) { - fogColorTextureHandle = downcast(options.skyColor)->getHwHandle(); + fogColorTextureHandle = downcast(options.skyColor)->getHwHandleForSampling(); math::half2 const minMaxMip{ 0.0f, float(options.skyColor->getLevels()) - 1.0f }; s.fogMinMaxMip = packHalf2x16(minMaxMip); s.fogOneOverFarMinusNear = 1.0f / (cameraInfo.zf - cameraInfo.zn); @@ -196,11 +283,12 @@ void PerViewUniforms::prepareFog(FEngine& engine, const CameraInfo& cameraInfo, } } - mSamplers.setSampler(PerViewSib::FOG, { - fogColorTextureHandle ? fogColorTextureHandle : engine.getDummyCubemap()->getHwHandle(), { + setSampler(+PerViewBindingPoints::FOG, + fogColorTextureHandle ? + fogColorTextureHandle : engine.getDummyCubemap()->getHwHandleForSampling(), { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR - }}); + }); s.fogStart = options.distance; s.fogMaxOpacity = options.maximumOpacity; @@ -214,7 +302,7 @@ void PerViewUniforms::prepareFog(FEngine& engine, const CameraInfo& cameraInfo, s.fogFromWorldMatrix = mat3f{ cof(fogFromWorld) }; } -void PerViewUniforms::prepareSSAO(Handle ssao, +void ColorPassDescriptorSet::prepareSSAO(Handle ssao, AmbientOcclusionOptions const& options) noexcept { // High quality sampling is enabled only if AO itself is enabled and upsampling quality is at // least set to high and of course only if upsampling is needed. @@ -222,10 +310,10 @@ void PerViewUniforms::prepareSSAO(Handle ssao, && options.resolution < 1.0f; // LINEAR filtering is only needed when AO is enabled and low-quality upsampling is used. - mSamplers.setSampler(PerViewSib::SSAO, { ssao, { + setSampler(+PerViewBindingPoints::SSAO, ssao, { .filterMag = options.enabled && !highQualitySampling ? SamplerMagFilter::LINEAR : SamplerMagFilter::NEAREST - }}); + }); const float edgeDistance = 1.0f / options.bilateralThreshold; auto& s = mUniforms.edit(); @@ -234,11 +322,11 @@ void PerViewUniforms::prepareSSAO(Handle ssao, s.aoBentNormals = options.enabled && options.bentNormals ? 1.0f : 0.0f; } -void PerViewUniforms::prepareBlending(bool needsAlphaChannel) noexcept { +void ColorPassDescriptorSet::prepareBlending(bool needsAlphaChannel) noexcept { mUniforms.edit().needsAlphaChannel = needsAlphaChannel ? 1.0f : 0.0f; } -void PerViewUniforms::prepareMaterialGlobals( +void ColorPassDescriptorSet::prepareMaterialGlobals( std::array const& materialGlobals) noexcept { mUniforms.edit().custom[0] = materialGlobals[0]; mUniforms.edit().custom[1] = materialGlobals[1]; @@ -246,30 +334,30 @@ void PerViewUniforms::prepareMaterialGlobals( mUniforms.edit().custom[3] = materialGlobals[3]; } -void PerViewUniforms::prepareSSR(Handle ssr, +void ColorPassDescriptorSet::prepareSSR(Handle ssr, bool disableSSR, float refractionLodOffset, ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { - mSamplers.setSampler(PerViewSib::SSR, { ssr, { + setSampler(+PerViewBindingPoints::SSR, ssr, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR - }}); + }); auto& s = mUniforms.edit(); s.refractionLodOffset = refractionLodOffset; s.ssrDistance = (ssrOptions.enabled && !disableSSR) ? ssrOptions.maxDistance : 0.0f; } -void PerViewUniforms::prepareHistorySSR(Handle ssr, +void ColorPassDescriptorSet::prepareHistorySSR(Handle ssr, math::mat4f const& historyProjection, math::mat4f const& uvFromViewMatrix, ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { - mSamplers.setSampler(PerViewSib::SSR, { ssr, { + setSampler(+PerViewBindingPoints::SSR, ssr, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR - }}); + }); auto& s = mUniforms.edit(); s.ssrReprojection = historyProjection; @@ -280,20 +368,19 @@ void PerViewUniforms::prepareHistorySSR(Handle ssr, s.ssrStride = ssrOptions.stride; } -void PerViewUniforms::prepareStructure(Handle structure) noexcept { +void ColorPassDescriptorSet::prepareStructure(Handle structure) noexcept { // sampler must be NEAREST - mSamplers.setSampler(PerViewSib::STRUCTURE, { structure, {}}); + setSampler(+PerViewBindingPoints::STRUCTURE, structure, {}); } -void PerViewUniforms::prepareDirectionalLight(FEngine& engine, +void ColorPassDescriptorSet::prepareDirectionalLight(FEngine& engine, float exposure, float3 const& sceneSpaceDirection, - PerViewUniforms::LightManagerInstance directionalLight) noexcept { + ColorPassDescriptorSet::LightManagerInstance directionalLight) noexcept { FLightManager const& lcm = engine.getLightManager(); auto& s = mUniforms.edit(); float const shadowFar = lcm.getShadowFar(directionalLight); - // TODO: make the falloff rate a parameter s.shadowFarAttenuationParams = shadowFar > 0.0f ? 0.5f * float2{ 10.0f, 10.0f / (shadowFar * shadowFar) } : float2{ 1.0f, 0.0f }; @@ -328,12 +415,12 @@ void PerViewUniforms::prepareDirectionalLight(FEngine& engine, } } -void PerViewUniforms::prepareAmbientLight(FEngine& engine, FIndirectLight const& ibl, +void ColorPassDescriptorSet::prepareAmbientLight(FEngine& engine, FIndirectLight const& ibl, float intensity, float exposure) noexcept { auto& s = mUniforms.edit(); // Set up uniforms and sampler for the IBL, guaranteed to be non-null at this point. - float const iblRoughnessOneLevel = ibl.getLevelCount() - 1.0f; + float const iblRoughnessOneLevel = float(ibl.getLevelCount() - 1); s.iblRoughnessOneLevel = iblRoughnessOneLevel; s.iblLuminance = intensity * exposure; std::transform(ibl.getSH(), ibl.getSH() + 9, s.iblSH, [](float3 v) { @@ -345,14 +432,14 @@ void PerViewUniforms::prepareAmbientLight(FEngine& engine, FIndirectLight const& if (!reflection) { reflection = engine.getDummyCubemap()->getHwHandle(); } - mSamplers.setSampler(PerViewSib::IBL_SPECULAR, { + setSampler(+PerViewBindingPoints::IBL_SPECULAR, reflection, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR - }}); + }); } -void PerViewUniforms::prepareDynamicLights(Froxelizer& froxelizer) noexcept { +void ColorPassDescriptorSet::prepareDynamicLights(Froxelizer& froxelizer) noexcept { auto& s = mUniforms.edit(); froxelizer.updateUniforms(s); float const f = froxelizer.getLightFar(); @@ -360,22 +447,23 @@ void PerViewUniforms::prepareDynamicLights(Froxelizer& froxelizer) noexcept { s.lightFarAttenuationParams = 0.5f * float2{ 10.0f, 10.0f / (f * f) }; } -void PerViewUniforms::prepareShadowMapping(bool highPrecision) noexcept { +void ColorPassDescriptorSet::prepareShadowMapping(backend::BufferObjectHandle shadowUniforms, bool highPrecision) noexcept { auto& s = mUniforms.edit(); constexpr float low = 5.54f; // ~ std::log(std::numeric_limits::max()) * 0.5f; constexpr float high = 42.0f; // ~ std::log(std::numeric_limits::max()) * 0.5f; s.vsmExponent = highPrecision ? high : low; + setBuffer(+PerViewBindingPoints::SHADOWS, shadowUniforms, 0, sizeof(ShadowUib)); } -void PerViewUniforms::prepareShadowSampling(PerViewUib& uniforms, +void ColorPassDescriptorSet::prepareShadowSampling(PerViewUib& uniforms, ShadowMappingUniforms const& shadowMappingUniforms) noexcept { uniforms.cascadeSplits = shadowMappingUniforms.cascadeSplits; uniforms.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance; - uniforms.directionalShadows = shadowMappingUniforms.directionalShadows; - uniforms.cascades = shadowMappingUniforms.cascades; + uniforms.directionalShadows = int32_t(shadowMappingUniforms.directionalShadows); + uniforms.cascades = int32_t(shadowMappingUniforms.cascades); } -void PerViewUniforms::prepareShadowVSM(Handle texture, +void ColorPassDescriptorSet::prepareShadowVSM(Handle texture, ShadowMappingUniforms const& shadowMappingUniforms, VsmShadowOptions const& options) noexcept { constexpr float low = 5.54f; // ~ std::log(std::numeric_limits::max()) * 0.5f; @@ -384,87 +472,93 @@ void PerViewUniforms::prepareShadowVSM(Handle texture, if (options.anisotropy > 0 || options.mipmapping) { filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR; } - mSamplers.setSampler(PerViewSib::SHADOW_MAP, { + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = filterMin, .anisotropyLog2 = options.anisotropy, - }}); + }); auto& s = mUniforms.edit(); s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_EVSM; s.vsmExponent = options.highPrecision ? high : low; s.vsmDepthScale = options.minVarianceScale * 0.01f * s.vsmExponent; s.vsmLightBleedReduction = options.lightBleedReduction; - PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); } -void PerViewUniforms::prepareShadowPCF(Handle texture, +void ColorPassDescriptorSet::prepareShadowPCF(Handle texture, ShadowMappingUniforms const& shadowMappingUniforms) noexcept { - mSamplers.setSampler(PerViewSib::SHADOW_MAP, { + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR, .compareMode = SamplerCompareMode::COMPARE_TO_TEXTURE, .compareFunc = SamplerCompareFunc::GE - }}); + }); auto& s = mUniforms.edit(); s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCF; - PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); } -void PerViewUniforms::prepareShadowDPCF(Handle texture, +void ColorPassDescriptorSet::prepareShadowDPCF(Handle texture, ShadowMappingUniforms const& shadowMappingUniforms, SoftShadowOptions const& options) noexcept { - mSamplers.setSampler(PerViewSib::SHADOW_MAP, { texture, {}}); + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, {}); auto& s = mUniforms.edit(); s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_DPCF; s.shadowPenumbraRatioScale = options.penumbraRatioScale; - PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); } -void PerViewUniforms::prepareShadowPCSS(Handle texture, +void ColorPassDescriptorSet::prepareShadowPCSS(Handle texture, ShadowMappingUniforms const& shadowMappingUniforms, SoftShadowOptions const& options) noexcept { - mSamplers.setSampler(PerViewSib::SHADOW_MAP, { texture, {}}); + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, {}); auto& s = mUniforms.edit(); s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCSS; s.shadowPenumbraRatioScale = options.penumbraRatioScale; - PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); } -void PerViewUniforms::prepareShadowPCFDebug(Handle texture, +void ColorPassDescriptorSet::prepareShadowPCFDebug(Handle texture, ShadowMappingUniforms const& shadowMappingUniforms) noexcept { - mSamplers.setSampler(PerViewSib::SHADOW_MAP, { texture, { + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, { .filterMag = SamplerMagFilter::NEAREST, .filterMin = SamplerMinFilter::NEAREST - }}); + }); auto& s = mUniforms.edit(); s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCF; - PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms); + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); } -void PerViewUniforms::commit(backend::DriverApi& driver) noexcept { +void ColorPassDescriptorSet::commit(backend::DriverApi& driver) noexcept { if (mUniforms.isDirty()) { - driver.updateBufferObject(mUniformBufferHandle, mUniforms.toBufferDescriptor(driver), 0); + driver.updateBufferObject(mUniforms.getUboHandle(), + mUniforms.toBufferDescriptor(driver), 0); } - if (mSamplers.isDirty()) { - driver.updateSamplerGroup(mSamplerGroupHandle, mSamplers.toBufferDescriptor(driver)); + for (size_t i = 0; i < DESCRIPTOR_LAYOUT_COUNT; i++) { + mDescriptorSet[i].commit(mDescriptorSetLayout[i], driver); } } -void PerViewUniforms::bind(backend::DriverApi& driver) noexcept { - driver.bindUniformBuffer(+UniformBindingPoints::PER_VIEW, mUniformBufferHandle); - driver.bindSamplers(+SamplerBindingPoints::PER_VIEW, mSamplerGroupHandle); +void ColorPassDescriptorSet::setSampler(backend::descriptor_binding_t binding, + TextureHandle th, SamplerParams params) noexcept { + for (size_t i = 0; i < DESCRIPTOR_LAYOUT_COUNT; i++) { + auto samplers = mDescriptorSetLayout[i].getSamplerDescriptors(); + if (samplers[binding]) { + mDescriptorSet[i].setSampler(binding, th, params); + } + } } -void PerViewUniforms::unbindSamplers() noexcept { - auto& samplerGroup = mSamplers; - samplerGroup.clearSampler(PerViewSib::SHADOW_MAP); - samplerGroup.clearSampler(PerViewSib::IBL_SPECULAR); - samplerGroup.clearSampler(PerViewSib::SSAO); - samplerGroup.clearSampler(PerViewSib::SSR); - samplerGroup.clearSampler(PerViewSib::STRUCTURE); - samplerGroup.clearSampler(PerViewSib::FOG); +void ColorPassDescriptorSet::setBuffer(backend::descriptor_binding_t binding, + BufferObjectHandle boh, uint32_t offset, uint32_t size) noexcept { + for (size_t i = 0; i < DESCRIPTOR_LAYOUT_COUNT; i++) { + auto ubos = mDescriptorSetLayout[i].getUniformBufferDescriptors(); + if (ubos[binding]) { + mDescriptorSet[i].setBuffer(binding, boh, offset, size); + } + } } } // namespace filament diff --git a/filament/src/PerViewUniforms.h b/filament/src/ds/ColorPassDescriptorSet.h similarity index 76% rename from filament/src/PerViewUniforms.h rename to filament/src/ds/ColorPassDescriptorSet.h index c8df0661903..79086753d75 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/ds/ColorPassDescriptorSet.h @@ -19,19 +19,32 @@ #include -#include -#include +#include "DescriptorSet.h" #include "TypedUniformBuffer.h" +#include + +#include #include #include -#include +#include +#include +#include +#include + +#include + +#include +#include namespace filament { +class DescriptorSetLayout; +class HwDescriptorSetLayoutFactory; + struct AmbientOcclusionOptions; struct DynamicResolutionOptions; struct FogOptions; @@ -53,7 +66,8 @@ class LightManager; * holds onto handles for the PER_VIEW UBO and SamplerGroup. This class maintains a shadow copy * of the UBO/sampler data, so it is possible to partially update it between commits. */ -class PerViewUniforms { + +class ColorPassDescriptorSet { using LightManagerInstance = utils::EntityInstance; using TextureHandle = backend::Handle; @@ -64,9 +78,18 @@ class PerViewUniforms { static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCSS = 3u; public: - explicit PerViewUniforms(FEngine& engine) noexcept; - void terminate(backend::DriverApi& driver); + static uint8_t getIndex(bool lit, bool ssr, bool fog) noexcept; + + ColorPassDescriptorSet(FEngine& engine, + TypedUniformBuffer& uniforms) noexcept; + + void init( + backend::BufferObjectHandle lights, + backend::BufferObjectHandle recordBuffer, + backend::BufferObjectHandle froxelBuffer) noexcept; + + void terminate(HwDescriptorSetLayoutFactory& factory, backend::DriverApi& driver); void prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept; void prepareLodBias(float bias, math::float2 derivativesScale) noexcept; @@ -104,7 +127,7 @@ class PerViewUniforms { math::mat4f const& uvFromViewMatrix, ScreenSpaceReflectionsOptions const& ssrOptions) noexcept; - void prepareShadowMapping(bool highPrecision) noexcept; + void prepareShadowMapping(backend::BufferObjectHandle shadowUniforms, bool highPrecision) noexcept; void prepareDirectionalLight(FEngine& engine, float exposure, math::float3 const& sceneSpaceDirection, LightManagerInstance instance) noexcept; @@ -136,15 +159,22 @@ class PerViewUniforms { void commit(backend::DriverApi& driver) noexcept; // bind this UBO - void bind(backend::DriverApi& driver) noexcept; - - void unbindSamplers() noexcept; + void bind(backend::DriverApi& driver, uint8_t index) const noexcept { + mDescriptorSet[index].bind(driver, DescriptorSetBindingPoints::PER_VIEW); + } private: - TypedUniformBuffer mUniforms; - backend::SamplerGroup mSamplers; - backend::Handle mUniformBufferHandle; - backend::Handle mSamplerGroupHandle; + static constexpr size_t DESCRIPTOR_LAYOUT_COUNT = 8; + + void setSampler(backend::descriptor_binding_t binding, + backend::TextureHandle th, backend::SamplerParams params) noexcept; + + void setBuffer(backend::descriptor_binding_t binding, + backend::BufferObjectHandle boh, uint32_t offset, uint32_t size) noexcept; + + TypedUniformBuffer& mUniforms; + std::array mDescriptorSetLayout; + std::array mDescriptorSet; static void prepareShadowSampling(PerViewUib& uniforms, ShadowMappingUniforms const& shadowMappingUniforms) noexcept; }; diff --git a/filament/src/ds/DescriptorSet.cpp b/filament/src/ds/DescriptorSet.cpp new file mode 100644 index 00000000000..50667978408 --- /dev/null +++ b/filament/src/ds/DescriptorSet.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DescriptorSet.h" + +#include "DescriptorSetLayout.h" + +#include "details/Engine.h" + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +namespace filament { + +DescriptorSet::DescriptorSet() noexcept = default; + +DescriptorSet::~DescriptorSet() noexcept { + // make sure we're not leaking the descriptor set handle + assert_invariant(!mDescriptorSetHandle); +} + +DescriptorSet::DescriptorSet(DescriptorSetLayout const& descriptorSetLayout) noexcept + : mDescriptors(descriptorSetLayout.getMaxDescriptorBinding() + 1), + mDirty(std::numeric_limits::max()) { +} + +DescriptorSet::DescriptorSet(DescriptorSet&& rhs) noexcept = default; + +DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept { + if (this != &rhs) { + // make sure we're not leaking the descriptor set handle + assert_invariant(!mDescriptorSetHandle); + mDescriptors = std::move(rhs.mDescriptors); + mDescriptorSetHandle = std::move(rhs.mDescriptorSetHandle); + mDirty = rhs.mDirty; + mValid = rhs.mValid; + } + return *this; +} + +void DescriptorSet::terminate(FEngine::DriverApi& driver) noexcept { + if (mDescriptorSetHandle) { + driver.destroyDescriptorSet(mDescriptorSetHandle); + mDescriptorSetHandle.clear(); + } +} + +void DescriptorSet::commitSlow(DescriptorSetLayout const& layout, + FEngine::DriverApi& driver) noexcept { + mDirty.clear(); + // if we have a dirty descriptor set, + // we need to allocate a new one and reset all the descriptors + if (UTILS_LIKELY(mDescriptorSetHandle)) { + // note: if the descriptor-set is bound, doing this will essentially make it dangling. + // This can result in a use-after-free in the driver if the new one isn't bound at some + // point later. + driver.destroyDescriptorSet(mDescriptorSetHandle); + } + mDescriptorSetHandle = driver.createDescriptorSet(layout.getHandle()); + mValid.forEachSetBit([&layout, &driver, + dsh = mDescriptorSetHandle, descriptors = mDescriptors.data()] + (backend::descriptor_binding_t const binding) { + if (layout.isSampler(binding)) { + driver.updateDescriptorSetTexture(dsh, binding, + descriptors[binding].texture.th, + descriptors[binding].texture.params); + } else { + driver.updateDescriptorSetBuffer(dsh, binding, + descriptors[binding].buffer.boh, + descriptors[binding].buffer.offset, + descriptors[binding].buffer.size); + } + }); +} + +void DescriptorSet::bind(FEngine::DriverApi& driver, DescriptorSetBindingPoints set) const noexcept { + bind(driver, set, {}); +} + +void DescriptorSet::bind(FEngine::DriverApi& driver, DescriptorSetBindingPoints set, + backend::DescriptorSetOffsetArray dynamicOffsets) const noexcept { + // TODO: on debug check that dynamicOffsets is large enough + assert_invariant(mDirty.none()); + assert_invariant(mDescriptorSetHandle); + driver.bindDescriptorSet(mDescriptorSetHandle, +set, std::move(dynamicOffsets)); +} + +void DescriptorSet::setBuffer( + backend::descriptor_binding_t binding, + backend::Handle boh, uint32_t offset, uint32_t size) noexcept { + // TODO: validate it's the right kind of descriptor + if (mDescriptors[binding].buffer.boh != boh || mDescriptors[binding].buffer.size != size) { + // we don't set the dirty bit if only offset changes + mDirty.set(binding); + } + mDescriptors[binding].buffer = { boh, offset, size }; + mValid.set(binding, (bool)boh); +} + +void DescriptorSet::setSampler( + backend::descriptor_binding_t binding, + backend::Handle th, backend::SamplerParams params) noexcept { + // TODO: validate it's the right kind of descriptor + if (mDescriptors[binding].texture.th != th || mDescriptors[binding].texture.params != params) { + mDirty.set(binding); + } + mDescriptors[binding].texture = { th, params }; + mValid.set(binding, (bool)th); +} + +DescriptorSet DescriptorSet::duplicate(DescriptorSetLayout const& layout) const noexcept { + DescriptorSet set{layout}; + memcpy(set.mDescriptors.data(), mDescriptors.data(), mDescriptors.size() * sizeof(Desc)); + set.mDirty = mDirty; + set.mValid = mValid; + return set; +} + +} // namespace filament diff --git a/filament/src/ds/DescriptorSet.h b/filament/src/ds/DescriptorSet.h new file mode 100644 index 00000000000..4b8d6d02dfa --- /dev/null +++ b/filament/src/ds/DescriptorSet.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DETAILS_DESCRIPTORSET_H +#define TNT_FILAMENT_DETAILS_DESCRIPTORSET_H + +#include "DescriptorSetLayout.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace filament { + +class DescriptorSet { +public: + DescriptorSet() noexcept; + explicit DescriptorSet(DescriptorSetLayout const& descriptorSetLayout) noexcept; + DescriptorSet(DescriptorSet const&) = delete; + DescriptorSet(DescriptorSet&& rhs) noexcept; + DescriptorSet& operator=(DescriptorSet const&) = delete; + DescriptorSet& operator=(DescriptorSet&& rhs) noexcept; + ~DescriptorSet() noexcept; + + void terminate(backend::DriverApi& driver) noexcept; + + // update the descriptors if needed + void commit(DescriptorSetLayout const& layout, backend::DriverApi& driver) noexcept { + if (UTILS_UNLIKELY(mDirty.any())) { + commitSlow(layout, driver); + } + } + + void commitSlow(DescriptorSetLayout const& layout, backend::DriverApi& driver) noexcept; + + // bind the descriptor set + void bind(backend::DriverApi& driver, DescriptorSetBindingPoints set) const noexcept; + + void bind(backend::DriverApi& driver, DescriptorSetBindingPoints set, + backend::DescriptorSetOffsetArray dynamicOffsets) const noexcept; + + // sets a ubo/ssbo descriptor + void setBuffer(backend::descriptor_binding_t binding, + backend::Handle boh, + uint32_t offset, uint32_t size) noexcept; + + // sets a sampler descriptor + void setSampler(backend::descriptor_binding_t binding, + backend::Handle th, + backend::SamplerParams params) noexcept; + + // Used for duplicating material + DescriptorSet duplicate(DescriptorSetLayout const& layout) const noexcept; + + backend::DescriptorSetHandle getHandle() const noexcept { + return mDescriptorSetHandle; + } + + utils::bitset64 getValidDescriptors() const noexcept { + return mValid; + } + +private: + struct Desc { + Desc() noexcept { } + union { + struct { + backend::Handle boh; + uint32_t offset; + uint32_t size; + } buffer{}; + struct { + backend::Handle th; + backend::SamplerParams params; + uint32_t padding; + } texture; + }; + }; + + utils::FixedCapacityVector mDescriptors; // 16 + mutable utils::bitset64 mDirty; // 8 + mutable utils::bitset64 mValid; // 8 + backend::DescriptorSetHandle mDescriptorSetHandle; // 4 +}; + +} // namespace filament + +#endif //TNT_FILAMENT_DETAILS_DESCRIPTORSET_H diff --git a/filament/src/ds/DescriptorSetLayout.cpp b/filament/src/ds/DescriptorSetLayout.cpp new file mode 100644 index 00000000000..a2d1460692b --- /dev/null +++ b/filament/src/ds/DescriptorSetLayout.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DescriptorSetLayout.h" + +#include "HwDescriptorSetLayoutFactory.h" + +#include "details/Engine.h" + +#include + +#include +#include + +namespace filament { + +DescriptorSetLayout::DescriptorSetLayout() noexcept = default; + +DescriptorSetLayout::DescriptorSetLayout( + HwDescriptorSetLayoutFactory& factory, + backend::DriverApi& driver, + backend::DescriptorSetLayout descriptorSetLayout) noexcept { + for (auto&& desc : descriptorSetLayout.bindings) { + mMaxDescriptorBinding = std::max(mMaxDescriptorBinding, desc.binding); + mSamplers.set(desc.binding, desc.type == backend::DescriptorType::SAMPLER); + mUniformBuffers.set(desc.binding, desc.type == backend::DescriptorType::UNIFORM_BUFFER); + } + + mDescriptorSetLayoutHandle = factory.create(driver, + std::move(descriptorSetLayout)); +} + +void DescriptorSetLayout::terminate( + HwDescriptorSetLayoutFactory& factory, + backend::DriverApi& driver) noexcept { + if (mDescriptorSetLayoutHandle) { + factory.destroy(driver, mDescriptorSetLayoutHandle); + } +} + +DescriptorSetLayout::DescriptorSetLayout(DescriptorSetLayout&& rhs) noexcept = default; + +DescriptorSetLayout& DescriptorSetLayout::operator=(DescriptorSetLayout&& rhs) noexcept = default; + +} // namespace filament diff --git a/filament/src/ds/DescriptorSetLayout.h b/filament/src/ds/DescriptorSetLayout.h new file mode 100644 index 00000000000..c9958602199 --- /dev/null +++ b/filament/src/ds/DescriptorSetLayout.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DESCRIPTORSETLAYOUT_H +#define TNT_FILAMENT_DESCRIPTORSETLAYOUT_H + +#include + +#include +#include + +#include + +#include +#include + +namespace filament { + +class HwDescriptorSetLayoutFactory; + +class DescriptorSetLayout { +public: + DescriptorSetLayout() noexcept; + DescriptorSetLayout( + HwDescriptorSetLayoutFactory& factory, + backend::DriverApi& driver, + backend::DescriptorSetLayout descriptorSetLayout) noexcept; + + DescriptorSetLayout(DescriptorSetLayout const&) = delete; + DescriptorSetLayout(DescriptorSetLayout&& rhs) noexcept; + DescriptorSetLayout& operator=(DescriptorSetLayout const&) = delete; + DescriptorSetLayout& operator=(DescriptorSetLayout&& rhs) noexcept; + + void terminate( + HwDescriptorSetLayoutFactory& factory, + backend::DriverApi& driver) noexcept; + + backend::DescriptorSetLayoutHandle getHandle() const noexcept { + return mDescriptorSetLayoutHandle; + } + + size_t getMaxDescriptorBinding() const noexcept { + return mMaxDescriptorBinding; + } + + bool isSampler(backend::descriptor_binding_t binding) const noexcept { + return mSamplers[binding]; + } + + utils::bitset64 getSamplerDescriptors() const noexcept { + return mSamplers; + } + + utils::bitset64 getUniformBufferDescriptors() const noexcept { + return mUniformBuffers; + } + +private: + backend::DescriptorSetLayoutHandle mDescriptorSetLayoutHandle; + utils::bitset64 mSamplers; + utils::bitset64 mUniformBuffers; + uint8_t mMaxDescriptorBinding = 0; +}; + + +} // namespace filament + +#endif //TNT_FILAMENT_DESCRIPTORSETLAYOUT_H diff --git a/filament/src/ds/PostProcessDescriptorSet.cpp b/filament/src/ds/PostProcessDescriptorSet.cpp new file mode 100644 index 00000000000..0980de21f69 --- /dev/null +++ b/filament/src/ds/PostProcessDescriptorSet.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PostProcessDescriptorSet.h" + +#include "HwDescriptorSetLayoutFactory.h" +#include "TypedUniformBuffer.h" + +#include "details/Engine.h" + +#include +#include +#include + +#include + +namespace filament { + +using namespace backend; +using namespace math; + +PostProcessDescriptorSet::PostProcessDescriptorSet() noexcept = default; + +void PostProcessDescriptorSet::init(FEngine& engine) noexcept { + + // create the descriptor-set layout + mDescriptorSetLayout = filament::DescriptorSetLayout{ + engine.getDescriptorSetLayoutFactory(), + engine.getDriverApi(), descriptor_sets::getPostProcessLayout() }; + + // create the descriptor-set from the layout + mDescriptorSet = DescriptorSet{ mDescriptorSetLayout }; +} + +void PostProcessDescriptorSet::terminate(HwDescriptorSetLayoutFactory& factory, DriverApi& driver) { + mDescriptorSet.terminate(driver); + mDescriptorSetLayout.terminate(factory, driver); +} + +void PostProcessDescriptorSet::setFrameUniforms(DriverApi& driver, + TypedUniformBuffer& uniforms) noexcept { + // initialize the descriptor-set + mDescriptorSet.setBuffer(+PerViewBindingPoints::FRAME_UNIFORMS, + uniforms.getUboHandle(), 0, uniforms.getSize()); + + mDescriptorSet.commit(mDescriptorSetLayout, driver); +} + +void PostProcessDescriptorSet::bind(backend::DriverApi& driver) noexcept { + mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_VIEW); +} + +} // namespace filament + diff --git a/filament/src/ds/PostProcessDescriptorSet.h b/filament/src/ds/PostProcessDescriptorSet.h new file mode 100644 index 00000000000..e89d11180c6 --- /dev/null +++ b/filament/src/ds/PostProcessDescriptorSet.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_POSTPROCESSINGDESCRIPTORSET_H +#define TNT_FILAMENT_POSTPROCESSINGDESCRIPTORSET_H + +#include "DescriptorSet.h" + +#include "DescriptorSetLayout.h" + +#include "TypedUniformBuffer.h" + +#include + +#include + +namespace filament { + +class FEngine; +class HwDescriptorSetLayoutFactory; + +class PostProcessDescriptorSet { +public: + explicit PostProcessDescriptorSet() noexcept; + + void init(FEngine& engine) noexcept; + + void terminate(HwDescriptorSetLayoutFactory& factory, backend::DriverApi& driver); + + void setFrameUniforms(backend::DriverApi& driver, + TypedUniformBuffer& uniforms) noexcept; + + void bind(backend::DriverApi& driver) noexcept; + + DescriptorSetLayout const& getLayout() const noexcept { + return mDescriptorSetLayout; + } + +private: + DescriptorSetLayout mDescriptorSetLayout; + DescriptorSet mDescriptorSet; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_POSTPROCESSINGDESCRIPTORSET_H diff --git a/filament/src/PerShadowMapUniforms.cpp b/filament/src/ds/ShadowMapDescriptorSet.cpp similarity index 72% rename from filament/src/PerShadowMapUniforms.cpp rename to filament/src/ds/ShadowMapDescriptorSet.cpp index 458b15db836..ae6aacaaede 100644 --- a/filament/src/PerShadowMapUniforms.cpp +++ b/filament/src/ds/ShadowMapDescriptorSet.cpp @@ -14,12 +14,13 @@ * limitations under the License. */ -#include "PerShadowMapUniforms.h" +#include "ShadowMapDescriptorSet.h" #include "details/Camera.h" #include "details/Engine.h" #include +#include #include #include @@ -35,22 +36,31 @@ namespace filament { using namespace backend; using namespace math; -PerShadowMapUniforms::PerShadowMapUniforms(FEngine& engine) noexcept { +ShadowMapDescriptorSet::ShadowMapDescriptorSet(FEngine& engine) noexcept { DriverApi& driver = engine.getDriverApi(); + mUniformBufferHandle = driver.createBufferObject(sizeof(PerViewUib), BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC); + + // create the descriptor-set from the layout + mDescriptorSet = DescriptorSet{ engine.getPerViewDescriptorSetLayoutDepthVariant() }; + + // initialize the descriptor-set + mDescriptorSet.setBuffer(+PerViewBindingPoints::FRAME_UNIFORMS, + mUniformBufferHandle, 0, sizeof(PerViewUib)); } -void PerShadowMapUniforms::terminate(DriverApi& driver) { +void ShadowMapDescriptorSet::terminate(DriverApi& driver) { + mDescriptorSet.terminate(driver); driver.destroyBufferObject(mUniformBufferHandle); } -PerViewUib& PerShadowMapUniforms::edit(Transaction const& transaction) noexcept { +PerViewUib& ShadowMapDescriptorSet::edit(Transaction const& transaction) noexcept { assert_invariant(transaction.uniforms); return *transaction.uniforms; } -void PerShadowMapUniforms::prepareCamera(Transaction const& transaction, +void ShadowMapDescriptorSet::prepareCamera(Transaction const& transaction, FEngine& engine, const CameraInfo& camera) noexcept { mat4f const& viewFromWorld = camera.view; mat4f const& worldFromView = camera.model; @@ -78,12 +88,12 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction, s.clipControl = engine.getDriverApi().getClipSpaceParams(); } -void PerShadowMapUniforms::prepareLodBias(Transaction const& transaction, float bias) noexcept { +void ShadowMapDescriptorSet::prepareLodBias(Transaction const& transaction, float bias) noexcept { auto& s = edit(transaction); s.lodBias = bias; } -void PerShadowMapUniforms::prepareViewport(Transaction const& transaction, +void ShadowMapDescriptorSet::prepareViewport(Transaction const& transaction, backend::Viewport const& viewport) noexcept { float2 const dimensions{ viewport.width, viewport.height }; auto& s = edit(transaction); @@ -92,7 +102,7 @@ void PerShadowMapUniforms::prepareViewport(Transaction const& transaction, s.logicalViewportOffset = 0.0f; } -void PerShadowMapUniforms::prepareTime(Transaction const& transaction, +void ShadowMapDescriptorSet::prepareTime(Transaction const& transaction, FEngine& engine, math::float4 const& userTime) noexcept { auto& s = edit(transaction); const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1'000'000'000; @@ -101,7 +111,7 @@ void PerShadowMapUniforms::prepareTime(Transaction const& transaction, s.userTime = userTime; } -void PerShadowMapUniforms::prepareShadowMapping(Transaction const& transaction, +void ShadowMapDescriptorSet::prepareShadowMapping(Transaction const& transaction, bool highPrecision) noexcept { auto& s = edit(transaction); constexpr float low = 5.54f; // ~ std::log(std::numeric_limits::max()) * 0.5f; @@ -109,7 +119,7 @@ void PerShadowMapUniforms::prepareShadowMapping(Transaction const& transaction, s.vsmExponent = highPrecision ? high : low; } -PerShadowMapUniforms::Transaction PerShadowMapUniforms::open(backend::DriverApi& driver) noexcept { +ShadowMapDescriptorSet::Transaction ShadowMapDescriptorSet::open(backend::DriverApi& driver) noexcept { Transaction transaction; // TODO: use out-of-line buffer if too large transaction.uniforms = (PerViewUib *)driver.allocate(sizeof(PerViewUib), 16); @@ -117,15 +127,16 @@ PerShadowMapUniforms::Transaction PerShadowMapUniforms::open(backend::DriverApi& return transaction; } -void PerShadowMapUniforms::commit(Transaction& transaction, - backend::DriverApi& driver) noexcept { +void ShadowMapDescriptorSet::commit(Transaction& transaction, + FEngine& engine, backend::DriverApi& driver) noexcept { driver.updateBufferObject(mUniformBufferHandle, { transaction.uniforms, sizeof(PerViewUib) }, 0); + mDescriptorSet.commit(engine.getPerViewDescriptorSetLayoutDepthVariant(), driver); transaction.uniforms = nullptr; } -void PerShadowMapUniforms::bind(backend::DriverApi& driver) noexcept { - driver.bindUniformBuffer(+UniformBindingPoints::PER_VIEW, mUniformBufferHandle); +void ShadowMapDescriptorSet::bind(backend::DriverApi& driver) noexcept { + mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_VIEW); } } // namespace filament diff --git a/filament/src/PerShadowMapUniforms.h b/filament/src/ds/ShadowMapDescriptorSet.h similarity index 81% rename from filament/src/PerShadowMapUniforms.h rename to filament/src/ds/ShadowMapDescriptorSet.h index 05fcdd82e5f..c1f55c5c89d 100644 --- a/filament/src/PerShadowMapUniforms.h +++ b/filament/src/ds/ShadowMapDescriptorSet.h @@ -14,10 +14,14 @@ * limitations under the License. */ -#ifndef TNT_FILAMENT_PERSHADOWMAPUNIFORMS_H -#define TNT_FILAMENT_PERSHADOWMAPUNIFORMS_H +#ifndef TNT_FILAMENT_SHADOWMAPDESCRIPTORSET_H +#define TNT_FILAMENT_SHADOWMAPDESCRIPTORSET_H -#include +#include "DescriptorSet.h" + +#include "DescriptorSetLayout.h" + +#include "private/filament/UibStructs.h" #include #include @@ -38,16 +42,16 @@ class LightManager; * writes the data directly into the CommandStream, for this reason partial update of the data * is not possible. */ -class PerShadowMapUniforms { +class ShadowMapDescriptorSet { public: class Transaction { - friend PerShadowMapUniforms; + friend ShadowMapDescriptorSet; PerViewUib* uniforms = nullptr; Transaction() = default; // disallow creation by the caller }; - explicit PerShadowMapUniforms(FEngine& engine) noexcept; + explicit ShadowMapDescriptorSet(FEngine& engine) noexcept; void terminate(backend::DriverApi& driver); @@ -69,7 +73,7 @@ class PerShadowMapUniforms { static Transaction open(backend::DriverApi& driver) noexcept; // update local data into GPU UBO - void commit(Transaction& transaction, backend::DriverApi& driver) noexcept; + void commit(Transaction& transaction, FEngine& engine, backend::DriverApi& driver) noexcept; // bind this UBO void bind(backend::DriverApi& driver) noexcept; @@ -77,8 +81,9 @@ class PerShadowMapUniforms { private: static PerViewUib& edit(Transaction const& transaction) noexcept; backend::Handle mUniformBufferHandle; + DescriptorSet mDescriptorSet; }; } // namespace filament -#endif //TNT_FILAMENT_PERSHADOWMAPUNIFORMS_H +#endif //TNT_FILAMENT_SHADOWMAPDESCRIPTORSET_H diff --git a/filament/src/ds/SsrPassDescriptorSet.cpp b/filament/src/ds/SsrPassDescriptorSet.cpp new file mode 100644 index 00000000000..86e83436d9a --- /dev/null +++ b/filament/src/ds/SsrPassDescriptorSet.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SsrPassDescriptorSet.h" + +#include "TypedUniformBuffer.h" + +#include "details/Engine.h" + +#include +#include + +#include +#include + +#include + +#include + +#include + +namespace filament { + +using namespace backend; +using namespace math; + +SsrPassDescriptorSet::SsrPassDescriptorSet() noexcept = default; + +void SsrPassDescriptorSet::init(FEngine& engine) noexcept { + // create the descriptor-set from the layout + mDescriptorSet = DescriptorSet{ engine.getPerViewDescriptorSetLayoutSsrVariant() }; + + // create a dummy Shadow UBO (see comment in setFrameUniforms() below) + mShadowUbh = engine.getDriverApi().createBufferObject(sizeof(ShadowUib), + BufferObjectBinding::UNIFORM, BufferUsage::STATIC); +} + +void SsrPassDescriptorSet::terminate(DriverApi& driver) { + mDescriptorSet.terminate(driver); + driver.destroyBufferObject(mShadowUbh); +} + +void SsrPassDescriptorSet::setFrameUniforms(TypedUniformBuffer& uniforms) noexcept { + // initialize the descriptor-set + mDescriptorSet.setBuffer(+PerViewBindingPoints::FRAME_UNIFORMS, + uniforms.getUboHandle(), 0, uniforms.getSize()); + + // This is actually not used for the SSR variants, but the descriptor-set layout needs + // to have this UBO because the fragment shader used is the "generic" one. Both Metal + // and GL would be okay without this, but Vulkan's validation layer would complain. + mDescriptorSet.setBuffer(+PerViewBindingPoints::SHADOWS, mShadowUbh, 0, sizeof(ShadowUib)); + + mUniforms = std::addressof(uniforms); +} + +void SsrPassDescriptorSet::prepareHistorySSR(Handle ssr, + math::mat4f const& historyProjection, + math::mat4f const& uvFromViewMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { + + mDescriptorSet.setSampler(+PerViewBindingPoints::SSR, ssr, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + + assert_invariant(mUniforms); + auto& s = mUniforms->edit(); + s.ssrReprojection = historyProjection; + s.ssrUvFromViewMatrix = uvFromViewMatrix; + s.ssrThickness = ssrOptions.thickness; + s.ssrBias = ssrOptions.bias; + s.ssrDistance = ssrOptions.enabled ? ssrOptions.maxDistance : 0.0f; + s.ssrStride = ssrOptions.stride; +} + +void SsrPassDescriptorSet::prepareStructure(Handle structure) noexcept { + // sampler must be NEAREST + mDescriptorSet.setSampler(+PerViewBindingPoints::STRUCTURE, structure, {}); +} + +void SsrPassDescriptorSet::commit(FEngine& engine) noexcept { + assert_invariant(mUniforms); + DriverApi& driver = engine.getDriverApi(); + if (mUniforms->isDirty()) { + driver.updateBufferObject(mUniforms->getUboHandle(), + mUniforms->toBufferDescriptor(driver), 0); + } + mDescriptorSet.commit(engine.getPerViewDescriptorSetLayoutSsrVariant(), driver); +} + +void SsrPassDescriptorSet::bind(backend::DriverApi& driver) noexcept { + mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_VIEW); +} + +} // namespace filament + diff --git a/filament/src/ds/SsrPassDescriptorSet.h b/filament/src/ds/SsrPassDescriptorSet.h new file mode 100644 index 00000000000..526ae790119 --- /dev/null +++ b/filament/src/ds/SsrPassDescriptorSet.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_SSRPASSDESCRIPTORSET_H +#define TNT_FILAMENT_SSRPASSDESCRIPTORSET_H + +#include "DescriptorSet.h" + +#include "DescriptorSetLayout.h" + +#include "TypedUniformBuffer.h" + +#include + +#include +#include + +#include + +namespace filament { + +class FEngine; + +struct ScreenSpaceReflectionsOptions; + +class SsrPassDescriptorSet { + + using TextureHandle = backend::Handle; + +public: + SsrPassDescriptorSet() noexcept; + + void init(FEngine& engine) noexcept; + + void terminate(backend::DriverApi& driver); + + void setFrameUniforms(TypedUniformBuffer& uniforms) noexcept; + + void prepareStructure(TextureHandle structure) noexcept; + + void prepareHistorySSR(TextureHandle ssr, + math::mat4f const& historyProjection, + math::mat4f const& uvFromViewMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept; + + + // update local data into GPU UBO + void commit(FEngine& engine) noexcept; + + // bind this descriptor set + void bind(backend::DriverApi& driver) noexcept; + +private: + TypedUniformBuffer* mUniforms = nullptr; + DescriptorSet mDescriptorSet; + backend::BufferObjectHandle mShadowUbh; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_SSRPASSDESCRIPTORSET_H diff --git a/filament/src/ds/TypedBuffer.h b/filament/src/ds/TypedBuffer.h new file mode 100644 index 00000000000..ea55b7c43d2 --- /dev/null +++ b/filament/src/ds/TypedBuffer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_TYPEDBUFFER_H +#define TNT_FILAMENT_TYPEDBUFFER_H + +#include + +#include + +#include +#include + +namespace filament { + +template +class TypedBuffer { // NOLINT(cppcoreguidelines-pro-type-member-init) +public: + + T& itemAt(size_t i) noexcept { + mSomethingDirty = true; + return mBuffer[i]; + } + + T& edit() noexcept { + return itemAt(0); + } + + // size of the uniform buffer in bytes + size_t getSize() const noexcept { return sizeof(T) * N; } + + // return if any uniform has been changed + bool isDirty() const noexcept { return mSomethingDirty; } + + // mark the whole buffer as "clean" (no modified uniforms) + void clean() const noexcept { mSomethingDirty = false; } + + // helper functions + + backend::BufferDescriptor toBufferDescriptor(backend::DriverApi& driver) const noexcept { + return toBufferDescriptor(driver, 0, getSize()); + } + + // copy the UBO data and cleans the dirty bits + backend::BufferDescriptor toBufferDescriptor( + backend::DriverApi& driver, size_t offset, size_t size) const noexcept { + backend::BufferDescriptor p; + p.size = size; + p.buffer = driver.allocate(p.size); // TODO: use out-of-line buffer if too large + memcpy(p.buffer, reinterpret_cast(mBuffer) + offset, p.size); // inlined + clean(); + return p; + } + +private: + T mBuffer[N]; + mutable bool mSomethingDirty = false; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_TYPEDBUFFER_H diff --git a/filament/src/ds/TypedUniformBuffer.h b/filament/src/ds/TypedUniformBuffer.h new file mode 100644 index 00000000000..44d26995667 --- /dev/null +++ b/filament/src/ds/TypedUniformBuffer.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_TYPEDUNIFORMBUFFER_H +#define TNT_FILAMENT_TYPEDUNIFORMBUFFER_H + +#include "TypedBuffer.h" + +#include +#include +#include + +#include + +namespace filament { + +template +class TypedUniformBuffer { +public: + + explicit TypedUniformBuffer(backend::DriverApi& driver) noexcept { + mUboHandle = driver.createBufferObject( + mTypedBuffer.getSize(), + backend::BufferObjectBinding::UNIFORM, + backend::BufferUsage::DYNAMIC); + } + + void terminate(backend::DriverApi& driver) noexcept { + driver.destroyBufferObject(mUboHandle); + } + + TypedBuffer& getTypedBuffer() noexcept { + return mTypedBuffer; + } + + backend::BufferObjectHandle getUboHandle() const noexcept { + return mUboHandle; + } + + T& itemAt(size_t i) noexcept { + return mTypedBuffer.itemAt(i); + } + + T& edit() noexcept { + return mTypedBuffer.itemAt(0); + } + + // size of the uniform buffer in bytes + size_t getSize() const noexcept { return mTypedBuffer.getSize(); } + + // return if any uniform has been changed + bool isDirty() const noexcept { return mTypedBuffer.isDirty(); } + + // mark the whole buffer as "clean" (no modified uniforms) + void clean() const noexcept { mTypedBuffer.clean(); } + + // helper functions + backend::BufferDescriptor toBufferDescriptor(backend::DriverApi& driver) const noexcept { + return mTypedBuffer.toBufferDescriptor(driver); + } + + // copy the UBO data and cleans the dirty bits + backend::BufferDescriptor toBufferDescriptor( + backend::DriverApi& driver, size_t offset, size_t size) const noexcept { + return mTypedBuffer.toBufferDescriptor(driver, offset, size); + } + +private: + TypedBuffer mTypedBuffer; + backend::BufferObjectHandle mUboHandle; +}; + +} // namespace filament + + +#endif //TNT_FILAMENT_TYPEDUNIFORMBUFFER_H diff --git a/libs/filabridge/CMakeLists.txt b/libs/filabridge/CMakeLists.txt index ffcd1ce2367..6840ce536e7 100644 --- a/libs/filabridge/CMakeLists.txt +++ b/libs/filabridge/CMakeLists.txt @@ -10,8 +10,9 @@ set(PUBLIC_HDR_DIR include) file(GLOB_RECURSE PUBLIC_HDRS ${PUBLIC_HDR_DIR}/**/*.h) set(SRCS - src/SamplerInterfaceBlock.cpp src/BufferInterfaceBlock.cpp + src/DescriptorSets.cpp + src/SamplerInterfaceBlock.cpp src/Variant.cpp ) diff --git a/libs/filabridge/include/filament/MaterialChunkType.h b/libs/filabridge/include/filament/MaterialChunkType.h index d69e8595da2..4d9f6a7deb6 100644 --- a/libs/filabridge/include/filament/MaterialChunkType.h +++ b/libs/filabridge/include/filament/MaterialChunkType.h @@ -47,10 +47,10 @@ enum UTILS_PUBLIC ChunkType : uint64_t { MaterialMetal = charTo64bitNum("MAT_METL"), MaterialMetalLibrary = charTo64bitNum("MAT_MLIB"), MaterialShaderModels = charTo64bitNum("MAT_SMDL"), - MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"), - MaterialUniformBindings = charTo64bitNum("MAT_UNIF"), MaterialBindingUniformInfo = charTo64bitNum("MAT_UFRM"), MaterialAttributeInfo = charTo64bitNum("MAT_ATTR"), + MaterialDescriptorBindingsInfo = charTo64bitNum("MAT_DBDI"), + MaterialDescriptorSetLayoutInfo = charTo64bitNum("MAT_DSLI"), MaterialProperties = charTo64bitNum("MAT_PROP"), MaterialConstants = charTo64bitNum("MAT_CONS"), MaterialPushConstants = charTo64bitNum("MAT_PCON"), diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index a2e1d4568cf..79822f19830 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 54; +static constexpr size_t MATERIAL_VERSION = 55; /** * Supported shading models @@ -257,4 +257,7 @@ enum class UserVariantFilterBit : UserVariantFilterMask { template<> struct utils::EnableBitMaskOperators : public std::true_type {}; +template<> struct utils::EnableIntegerOperators + : public std::true_type {}; + #endif diff --git a/libs/filabridge/include/private/filament/DescriptorSets.h b/libs/filabridge/include/private/filament/DescriptorSets.h new file mode 100644 index 00000000000..171c8650d10 --- /dev/null +++ b/libs/filabridge/include/private/filament/DescriptorSets.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DESCRIPTORSETS_H +#define TNT_FILAMENT_DESCRIPTORSETS_H + +#include + +#include + +#include + +#include + +namespace filament::descriptor_sets { + +backend::DescriptorSetLayout const& getPostProcessLayout() noexcept; +backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept; +backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept; +backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept; + +backend::DescriptorSetLayout getPerViewDescriptorSetLayout( + MaterialDomain domain, + UserVariantFilterMask variantFilter, + bool isLit, + ReflectionMode reflectionMode, + RefractionMode refractionMode) noexcept; + +utils::CString getDescriptorName( + filament::DescriptorSetBindingPoints set, + backend::descriptor_binding_t binding) noexcept; + +} // namespace filament::descriptor_sets + + +#endif //TNT_FILAMENT_DESCRIPTORSETS_H diff --git a/libs/filabridge/include/private/filament/EngineEnums.h b/libs/filabridge/include/private/filament/EngineEnums.h index 9491f2e1181..273061a86cb 100644 --- a/libs/filabridge/include/private/filament/EngineEnums.h +++ b/libs/filabridge/include/private/filament/EngineEnums.h @@ -30,34 +30,45 @@ namespace filament { static constexpr size_t POST_PROCESS_VARIANT_BITS = 1; static constexpr size_t POST_PROCESS_VARIANT_COUNT = (1u << POST_PROCESS_VARIANT_BITS); static constexpr size_t POST_PROCESS_VARIANT_MASK = POST_PROCESS_VARIANT_COUNT - 1; + enum class PostProcessVariant : uint8_t { OPAQUE, TRANSLUCENT }; -// Binding points for uniform buffers -enum class UniformBindingPoints : uint8_t { - PER_VIEW = 0, // uniforms updated per view - PER_RENDERABLE = 1, // uniforms updated per renderable - PER_RENDERABLE_BONES = 2, // bones data, per renderable - PER_RENDERABLE_MORPHING = 3, // morphing uniform/sampler updated per render primitive - LIGHTS = 4, // lights data array - SHADOW = 5, // punctual shadow data - FROXEL_RECORDS = 6, - FROXELS = 7, - PER_MATERIAL_INSTANCE = 8, // uniforms updates per material - // Update utils::Enum::count<>() below when adding values here - // These are limited by CONFIG_BINDING_COUNT (currently 10) +enum class DescriptorSetBindingPoints : uint8_t { + PER_VIEW = 0, + PER_RENDERABLE = 1, + PER_MATERIAL = 2, +}; + +// binding point for the "per-view" descriptor set +enum class PerViewBindingPoints : uint8_t { + FRAME_UNIFORMS = 0, // uniforms updated per view + SHADOWS = 1, // punctual shadow data + LIGHTS = 2, // lights data array + RECORD_BUFFER = 3, // froxel record buffer + FROXEL_BUFFER = 4, // froxel buffer + STRUCTURE = 5, // variable, DEPTH + SHADOW_MAP = 6, // user defined (1024x1024) DEPTH, array + IBL_DFG_LUT = 7, // user defined (128x128), RGB16F + IBL_SPECULAR = 8, // user defined, user defined, CUBEMAP + SSAO = 9, // variable, RGB8 {AO, [depth]} + SSR = 10, // variable, RGB_11_11_10, mipmapped + FOG = 11 // variable, user defined, CUBEMAP }; -// Binding points for sampler buffers. -enum class SamplerBindingPoints : uint8_t { - PER_VIEW = 0, // samplers updated per view - PER_RENDERABLE_MORPHING = 1, // morphing sampler updated per render primitive - PER_MATERIAL_INSTANCE = 2, // samplers updates per material - PER_RENDERABLE_SKINNING = 3, // bone indices and weights sampler updated per render primitive - // Update utils::Enum::count<>() below when adding values here - // These are limited by CONFIG_SAMPLER_BINDING_COUNT (currently 4) +enum class PerRenderableBindingPoints : uint8_t { + OBJECT_UNIFORMS = 0, // uniforms updated per renderable + BONES_UNIFORMS = 1, + MORPHING_UNIFORMS = 2, + MORPH_TARGET_POSITIONS = 3, + MORPH_TARGET_TANGENTS = 4, + BONES_INDICES_AND_WEIGHTS = 5, +}; + +enum class PerMaterialBindingPoints : uint8_t { + MATERIAL_PARAMS = 0, // uniforms }; enum class ReservedSpecializationConstants : uint8_t { @@ -138,9 +149,14 @@ constexpr uint8_t CONFIG_MAX_STEREOSCOPIC_EYES = 4; } // namespace filament template<> -struct utils::EnableIntegerOperators : public std::true_type {}; +struct utils::EnableIntegerOperators : public std::true_type {}; +template<> +struct utils::EnableIntegerOperators : public std::true_type {}; template<> -struct utils::EnableIntegerOperators : public std::true_type {}; +struct utils::EnableIntegerOperators : public std::true_type {}; +template<> +struct utils::EnableIntegerOperators : public std::true_type {}; + template<> struct utils::EnableIntegerOperators : public std::true_type {}; template<> @@ -148,14 +164,7 @@ struct utils::EnableIntegerOperators : public std::tr template<> struct utils::EnableIntegerOperators : public std::true_type {}; -template<> -inline constexpr size_t utils::Enum::count() { return 9; } -template<> -inline constexpr size_t utils::Enum::count() { return 4; } template<> inline constexpr size_t utils::Enum::count() { return filament::POST_PROCESS_VARIANT_COUNT; } -static_assert(utils::Enum::count() <= filament::backend::CONFIG_UNIFORM_BINDING_COUNT); -static_assert(utils::Enum::count() <= filament::backend::CONFIG_SAMPLER_BINDING_COUNT); - #endif // TNT_FILAMENT_ENGINE_ENUM_H diff --git a/libs/filabridge/include/private/filament/SamplerBindingsInfo.h b/libs/filabridge/include/private/filament/SamplerBindingsInfo.h index e8f3164eda6..e69de29bb2d 100644 --- a/libs/filabridge/include/private/filament/SamplerBindingsInfo.h +++ b/libs/filabridge/include/private/filament/SamplerBindingsInfo.h @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_FILABRIDGE_SAMPLERBINDINGS_INFO_H -#define TNT_FILABRIDGE_SAMPLERBINDINGS_INFO_H - -#include - -#include - -#include -#include - -#include - -namespace filament { - -// binding information about a sampler group -struct SamplerGroupBindingInfo { - constexpr static uint8_t UNKNOWN_OFFSET = 0xff; - // global binding of this block, or UNKNOWN_OFFSET if not used. - uint8_t bindingOffset = UNKNOWN_OFFSET; - // shader stage flags for samplers in this block - backend::ShaderStageFlags shaderStageFlags = backend::ShaderStageFlags::NONE; - // number of samplers in this block. Can be zero. - uint8_t count = 0; -}; - -// list of binding information for all known binding points -using SamplerGroupBindingInfoList = - std::array()>; - -// map of sampler shader binding to sampler shader name -using SamplerBindingToNameMap = - utils::FixedCapacityVector; - -} // namespace filament - -#endif //TNT_FILABRIDGE_SAMPLERBINDINGS_INFO_H diff --git a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h index 03f5b5e82cb..243f2dcfb6c 100644 --- a/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h +++ b/libs/filabridge/include/private/filament/SamplerInterfaceBlock.h @@ -49,17 +49,20 @@ class SamplerInterfaceBlock { using Format = backend::SamplerFormat; using Precision = backend::Precision; using SamplerParams = backend::SamplerParams; + using Binding = backend::descriptor_binding_t; struct SamplerInfo { // NOLINT(cppcoreguidelines-pro-type-member-init) utils::CString name; // name of this sampler utils::CString uniformName; // name of the uniform holding this sampler (needed for glsl/MSL) - uint8_t offset; // offset in "Sampler" of this sampler in the buffer + Binding binding; // binding in the descriptor set Type type; // type of this sampler Format format; // format of this sampler Precision precision; // precision of this sampler bool multisample; // multisample capable }; + using SamplerInfoList = utils::FixedCapacityVector; + class Builder { public: Builder(); @@ -72,6 +75,7 @@ class SamplerInterfaceBlock { struct ListEntry { // NOLINT(cppcoreguidelines-pro-type-member-init) std::string_view name; // name of this sampler + Binding binding; // binding in the descriptor set Type type; // type of this sampler Format format; // format of this sampler Precision precision; // precision of this sampler @@ -84,7 +88,7 @@ class SamplerInterfaceBlock { Builder& stageFlags(backend::ShaderStageFlags stageFlags); // Add a sampler - Builder& add(std::string_view samplerName, Type type, Format format, + Builder& add(std::string_view samplerName, Binding binding, Type type, Format format, Precision precision = Precision::MEDIUM, bool multisample = false) noexcept; @@ -109,7 +113,7 @@ class SamplerInterfaceBlock { size_t getSize() const noexcept { return mSamplersInfoList.size(); } // list of information records for each sampler - utils::FixedCapacityVector const& getSamplerInfoList() const noexcept { + SamplerInfoList const& getSamplerInfoList() const noexcept { return mSamplersInfoList; } diff --git a/libs/filabridge/src/DescriptorSets.cpp b/libs/filabridge/src/DescriptorSets.cpp new file mode 100644 index 00000000000..b313fcc0567 --- /dev/null +++ b/libs/filabridge/src/DescriptorSets.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "private/filament/DescriptorSets.h" + +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +namespace filament::descriptor_sets { + +using namespace backend; + +static DescriptorSetLayout const postProcessDescriptorSetLayout{{ + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, +}}; + +static DescriptorSetLayout const depthVariantDescriptorSetLayout{{ + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, +}}; + +// ssrVariantDescriptorSetLayout must match perViewDescriptorSetLayout's vertex stage. This is +// because the SSR variant is always using the "standard" vertex shader (i.e. there is no +// dedicated SSR vertex shader), which uses perViewDescriptorSetLayout. +// This means that PerViewBindingPoints::SHADOWS must be in the layout even though it's not used +// by the SSR variant. +static DescriptorSetLayout const ssrVariantDescriptorSetLayout{{ + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, +}}; + +static DescriptorSetLayout perViewDescriptorSetLayout = {{ + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::LIGHTS }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::RECORD_BUFFER }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FROXEL_BUFFER }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOW_MAP }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::IBL_DFG_LUT }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::IBL_SPECULAR }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSAO }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR }, + { DescriptorType::SAMPLER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FOG }, +}}; + +static DescriptorSetLayout perRenderableDescriptorSetLayout = {{ + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::OBJECT_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::BONES_UNIFORMS, DescriptorFlags::DYNAMIC_OFFSET }, + { DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerRenderableBindingPoints::MORPHING_UNIFORMS }, + { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS }, + { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS }, + { DescriptorType::SAMPLER, ShaderStageFlags::VERTEX , +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS }, +}}; + +DescriptorSetLayout const& getPostProcessLayout() noexcept { + return postProcessDescriptorSetLayout; +} + +DescriptorSetLayout const& getDepthVariantLayout() noexcept { + return depthVariantDescriptorSetLayout; +} + +DescriptorSetLayout const& getSsrVariantLayout() noexcept { + return ssrVariantDescriptorSetLayout; +} + +DescriptorSetLayout const& getPerRenderableLayout() noexcept { + return perRenderableDescriptorSetLayout; +} + +utils::CString getDescriptorName(DescriptorSetBindingPoints set, + descriptor_binding_t binding) noexcept { + using namespace std::literals; + + static std::unordered_map const set0{{ + { +PerViewBindingPoints::FRAME_UNIFORMS, "FrameUniforms"sv }, + { +PerViewBindingPoints::SHADOWS, "ShadowUniforms"sv }, + { +PerViewBindingPoints::LIGHTS, "LightsUniforms"sv }, + { +PerViewBindingPoints::RECORD_BUFFER, "FroxelRecordUniforms"sv }, + { +PerViewBindingPoints::FROXEL_BUFFER, "FroxelsUniforms"sv }, + { +PerViewBindingPoints::STRUCTURE, "sampler0_structure"sv }, + { +PerViewBindingPoints::SHADOW_MAP, "sampler0_shadowMap"sv }, + { +PerViewBindingPoints::IBL_DFG_LUT, "sampler0_iblDFG"sv }, + { +PerViewBindingPoints::IBL_SPECULAR, "sampler0_iblSpecular"sv }, + { +PerViewBindingPoints::SSAO, "sampler0_ssao"sv }, + { +PerViewBindingPoints::SSR, "sampler0_ssr"sv }, + { +PerViewBindingPoints::FOG, "sampler0_fog"sv }, + }}; + + static std::unordered_map const set1{{ + { +PerRenderableBindingPoints::OBJECT_UNIFORMS, "ObjectUniforms"sv }, + { +PerRenderableBindingPoints::BONES_UNIFORMS, "BonesUniforms"sv }, + { +PerRenderableBindingPoints::MORPHING_UNIFORMS, "MorphingUniforms"sv }, + { +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, "sampler1_positions"sv }, + { +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS, "sampler1_tangents"sv }, + { +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS, "sampler1_indicesAndWeights"sv }, + }}; + + switch (set) { + case DescriptorSetBindingPoints::PER_VIEW: { + auto pos = set0.find(binding); + assert_invariant(pos != set0.end()); + return { pos->second.data(), pos->second.size() }; + } + case DescriptorSetBindingPoints::PER_RENDERABLE: { + auto pos = set1.find(binding); + assert_invariant(pos != set1.end()); + return { pos->second.data(), pos->second.size() }; + } + case DescriptorSetBindingPoints::PER_MATERIAL: { + assert_invariant(binding < 1); + return "MaterialParams"; + } + } +} + +DescriptorSetLayout getPerViewDescriptorSetLayout( + MaterialDomain domain, + UserVariantFilterMask variantFilter, + bool isLit, + ReflectionMode reflectionMode, + RefractionMode refractionMode) noexcept { + + bool const ssr = reflectionMode == ReflectionMode::SCREEN_SPACE || + refractionMode == RefractionMode::SCREEN_SPACE; + + switch (domain) { + case MaterialDomain::SURFACE: { + // + // CAVEAT: The logic here must match MaterialBuilder::checkMaterialLevelFeatures() + // + auto layout = perViewDescriptorSetLayout; + // remove descriptors not needed for unlit materials + if (!isLit) { + layout.bindings.erase( + std::remove_if(layout.bindings.begin(), layout.bindings.end(), + [](auto const& entry) { + return entry.binding == PerViewBindingPoints::IBL_DFG_LUT || + entry.binding == PerViewBindingPoints::IBL_SPECULAR; + }), + layout.bindings.end()); + } + // remove descriptors not needed for SSRs + if (!ssr) { + layout.bindings.erase( + std::remove_if(layout.bindings.begin(), layout.bindings.end(), + [](auto const& entry) { + return entry.binding == PerViewBindingPoints::SSR; + }), + layout.bindings.end()); + + } + // remove fog descriptor if filtered out + if (variantFilter & (UserVariantFilterMask)UserVariantFilterBit::FOG) { + layout.bindings.erase( + std::remove_if(layout.bindings.begin(), layout.bindings.end(), + [](auto const& entry) { + return entry.binding == PerViewBindingPoints::FOG; + }), + layout.bindings.end()); + } + return layout; + } + case MaterialDomain::POST_PROCESS: + return descriptor_sets::getPostProcessLayout(); + case MaterialDomain::COMPUTE: + // TODO: what's the layout for compute? + return descriptor_sets::getPostProcessLayout(); + } +} + +} // namespace filament::descriptor_sets diff --git a/libs/filabridge/src/SamplerInterfaceBlock.cpp b/libs/filabridge/src/SamplerInterfaceBlock.cpp index 43ad966ea48..1d1f4c387ff 100644 --- a/libs/filabridge/src/SamplerInterfaceBlock.cpp +++ b/libs/filabridge/src/SamplerInterfaceBlock.cpp @@ -16,11 +16,17 @@ #include "private/filament/SamplerInterfaceBlock.h" + +#include + #include -#include +#include +#include +#include #include +#include #include using namespace utils; @@ -43,11 +49,12 @@ SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags) } SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add( - std::string_view samplerName, Type type, Format format, + std::string_view samplerName, Binding binding, Type type, Format format, Precision precision, bool multisample) noexcept { mEntries.push_back({ - { samplerName.data(), samplerName.size() }, { }, - uint8_t(mEntries.size()), type, format, precision, multisample }); + { samplerName.data(), samplerName.size() }, // name + { }, // uniform name + binding, type, format, precision, multisample }); return *this; } @@ -58,7 +65,7 @@ SamplerInterfaceBlock SamplerInterfaceBlock::Builder::build() { SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add( std::initializer_list list) noexcept { for (auto& e : list) { - add(e.name, e.type, e.format, e.precision, e.multisample); + add(e.name, e.binding, e.type, e.format, e.precision, e.multisample); } return *this; } @@ -79,15 +86,13 @@ SamplerInterfaceBlock::SamplerInterfaceBlock(Builder const& builder) noexcept auto& samplersInfoList = mSamplersInfoList; - size_t i = 0; for (auto const& e : builder.mEntries) { - assert_invariant(i == e.offset); - SamplerInfo& info = samplersInfoList[i++]; + size_t const i = std::distance(builder.mEntries.data(), &e); + SamplerInfo& info = samplersInfoList[i]; info = e; info.uniformName = generateUniformName(mName.c_str(), e.name.c_str()); - infoMap[{ info.name.data(), info.name.size() }] = info.offset; // info.name.c_str() guaranteed constant + infoMap[{ info.name.data(), info.name.size() }] = i; // info.name.c_str() guaranteed constant } - assert_invariant(i == samplersInfoList.size()); } const SamplerInterfaceBlock::SamplerInfo* SamplerInterfaceBlock::getSamplerInfo( diff --git a/libs/filamat/CMakeLists.txt b/libs/filamat/CMakeLists.txt index 2fc20d7e916..c74ad631fa6 100644 --- a/libs/filamat/CMakeLists.txt +++ b/libs/filamat/CMakeLists.txt @@ -42,7 +42,6 @@ set(COMMON_SRCS src/Includes.cpp src/MaterialBuilder.cpp src/MaterialVariants.cpp - src/SamplerBindingMap.cpp ) # Sources and headers for filamat @@ -84,7 +83,7 @@ include_directories(${CMAKE_BINARY_DIR}) add_library(${TARGET} STATIC ${HDRS} ${PRIVATE_HDRS} ${SRCS}) target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) set_target_properties(${TARGET} PROPERTIES FOLDER Libs) -target_link_libraries(${TARGET} shaders filabridge utils smol-v) +target_link_libraries(${TARGET} backend_headers shaders filabridge utils smol-v) # We are being naughty and accessing private headers here # For spirv-tools, we're just following glslang's example diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 3a4a4824e0e..9cb9ae6b4bf 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -24,6 +24,7 @@ #include #include "backend/DriverEnums.h" +#include "private/filament/DescriptorSets.h" #include "sca/builtinResource.h" #include "sca/GLSLTools.h" @@ -57,26 +58,224 @@ namespace msl { // this is only used for MSL using BindingIndexMap = std::unordered_map; -static void collectSibs(const GLSLPostProcessor::Config& config, SibVector& sibs) { - switch (config.domain) { - case MaterialDomain::SURFACE: - UTILS_NOUNROLL - for (uint8_t blockIndex = 0; blockIndex < CONFIG_SAMPLER_BINDING_COUNT; blockIndex++) { - if (blockIndex == SamplerBindingPoints::PER_MATERIAL_INSTANCE) { - continue; +#ifndef DEBUG_LOG_DESCRIPTOR_SETS +#define DEBUG_LOG_DESCRIPTOR_SETS 0 +#endif + +const char* toString(DescriptorType type) { + switch (type) { + case DescriptorType::UNIFORM_BUFFER: + return "UNIFORM_BUFFER"; + case DescriptorType::SHADER_STORAGE_BUFFER: + return "SHADER_STORAGE_BUFFER"; + case DescriptorType::SAMPLER: + return "SAMPLER"; + case DescriptorType::INPUT_ATTACHMENT: + return "INPUT_ATTACHMENT"; + } +} + +const char* toString(ShaderStageFlags flags) { + std::vector stages; + if (any(flags & ShaderStageFlags::VERTEX)) { + stages.push_back("VERTEX"); + } + if (any(flags & ShaderStageFlags::FRAGMENT)) { + stages.push_back("FRAGMENT"); + } + if (any(flags & ShaderStageFlags::COMPUTE)) { + stages.push_back("COMPUTE"); + } + if (stages.empty()) { + return "NONE"; + } + static char buffer[64]; + buffer[0] = '\0'; + for (size_t i = 0; i < stages.size(); i++) { + if (i > 0) { + strcat(buffer, " | "); + } + strcat(buffer, stages[i]); + } + return buffer; +} + +const char* prettyDescriptorFlags(DescriptorFlags flags) { + if (flags == DescriptorFlags::DYNAMIC_OFFSET) { + return "DYNAMIC_OFFSET"; + } + return "NONE"; +} + +const char* prettyPrintSamplerType(SamplerType type) { + switch (type) { + case SamplerType::SAMPLER_2D: + return "SAMPLER_2D"; + case SamplerType::SAMPLER_2D_ARRAY: + return "SAMPLER_2D_ARRAY"; + case SamplerType::SAMPLER_CUBEMAP: + return "SAMPLER_CUBEMAP"; + case SamplerType::SAMPLER_EXTERNAL: + return "SAMPLER_EXTERNAL"; + case SamplerType::SAMPLER_3D: + return "SAMPLER_3D"; + case SamplerType::SAMPLER_CUBEMAP_ARRAY: + return "SAMPLER_CUBEMAP_ARRAY"; + } +} + +DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib) noexcept { + auto const& samplers = sib.getSamplerInfoList(); + + DescriptorSetLayout layout; + layout.bindings.reserve(1 + samplers.size()); + + layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::UNIFORM_BUFFER, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, + +PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 }); + + for (auto const& sampler : samplers) { + layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::SAMPLER, + ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, sampler.binding, + DescriptorFlags::NONE, 0 }); + } + + return layout; +} + +static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, + const GLSLPostProcessor::Config& config, DescriptorSetInfo& descriptors) { + const MaterialInfo& material = *config.materialInfo; + + DescriptorSetLayout const info = [&]() { + switch (set) { + case DescriptorSetBindingPoints::PER_VIEW: { + if (filament::Variant::isValidDepthVariant(config.variant)) { + return descriptor_sets::getDepthVariantLayout(); } - auto const* sib = - SibGenerator::getSib((SamplerBindingPoints)blockIndex, config.variant); - if (sib && hasShaderType(sib->getStageFlags(), config.shaderType)) { - sibs.emplace_back(blockIndex, sib); + if (filament::Variant::isSSRVariant(config.variant)) { + return descriptor_sets::getSsrVariantLayout(); } + return descriptor_sets::getPerViewDescriptorSetLayout(config.domain, + config.variantFilter, material.isLit, material.reflectionMode, + material.refractionMode); } - case MaterialDomain::POST_PROCESS: - case MaterialDomain::COMPUTE: - break; + case DescriptorSetBindingPoints::PER_RENDERABLE: + return descriptor_sets::getPerRenderableLayout(); + case DescriptorSetBindingPoints::PER_MATERIAL: + return getPerMaterialDescriptorSet(config.materialInfo->sib); + default: + return DescriptorSetLayout {}; + } + }(); + + auto samplerList = [&]() { + switch (set) { + case DescriptorSetBindingPoints::PER_VIEW: + return SibGenerator::getPerViewSib(config.variant).getSamplerInfoList(); + case DescriptorSetBindingPoints::PER_RENDERABLE: + return SibGenerator::getPerRenderableSib(config.variant).getSamplerInfoList(); + case DescriptorSetBindingPoints::PER_MATERIAL: + return config.materialInfo->sib.getSamplerInfoList(); + default: + return SamplerInterfaceBlock::SamplerInfoList {}; + } + }(); + + // remove all the samplers that are not included in the descriptor-set layout + samplerList.erase(std::remove_if(samplerList.begin(), samplerList.end(), + [&info](auto const& entry) { + auto pos = std::find_if(info.bindings.begin(), + info.bindings.end(), [&entry](const auto& item) { + return item.binding == entry.binding; + }); + return pos == info.bindings.end(); + }), + samplerList.end()); + + auto getDescriptorName = [&](DescriptorSetBindingPoints set, descriptor_binding_t binding) { + if (set == DescriptorSetBindingPoints::PER_MATERIAL) { + auto pos = std::find_if(samplerList.begin(), samplerList.end(), + [&](const auto& entry) { return entry.binding == binding; }); + if (pos == samplerList.end()) { + return descriptor_sets::getDescriptorName(set, binding); + } + SamplerInterfaceBlock::SamplerInfo& sampler = *pos; + return sampler.uniformName; + } + return descriptor_sets::getDescriptorName(set, binding); + }; + + for (size_t i = 0; i < info.bindings.size(); i++) { + backend::descriptor_binding_t binding = info.bindings[i].binding; + auto name = getDescriptorName(set, binding); + if (info.bindings[i].type == DescriptorType::SAMPLER) { + auto pos = std::find_if(samplerList.begin(), samplerList.end(), + [&](const auto& entry) { return entry.binding == binding; }); + assert_invariant(pos != samplerList.end()); + SamplerInterfaceBlock::SamplerInfo& sampler = *pos; + descriptors.emplace_back(name, info.bindings[i], sampler); + } else { + descriptors.emplace_back(name, info.bindings[i], std::nullopt); + } + } + + std::sort(descriptors.begin(), descriptors.end(), [](const auto& a, const auto& b) { + return std::get<1>(a).binding < std::get<1>(b).binding; + }); +} + +void prettyPrintDescriptorSetInfoVector(DescriptorSets const& sets) { + auto getName = [](uint8_t set) { + switch (set) { + case +DescriptorSetBindingPoints::PER_VIEW: + return "perViewDescriptorSetLayout"; + case +DescriptorSetBindingPoints::PER_RENDERABLE: + return "perRenderableDescriptorSetLayout"; + case +DescriptorSetBindingPoints::PER_MATERIAL: + return "perMaterialDescriptorSetLayout"; + default: + return "unknown"; + } + }; + for (size_t setIndex = 0; setIndex < MAX_DESCRIPTOR_SET_COUNT; setIndex++) { + auto const& descriptors = sets[setIndex]; + printf("[DS] info (%s) = [\n", getName(setIndex)); + for (auto const& descriptor : descriptors) { + auto const& [name, info, sampler] = descriptor; + if (info.type == DescriptorType::SAMPLER) { + assert_invariant(sampler.has_value()); + printf(" {name = %s, binding = %d, type = %s, count = %d, stage = %s, flags = " + "%s, samplerType = %s}", + name.c_str_safe(), info.binding, toString(info.type), info.count, + toString(info.stageFlags), prettyDescriptorFlags(info.flags), + prettyPrintSamplerType(sampler->type)); + } else { + printf(" {name = %s, binding = %d, type = %s, count = %d, stage = %s, flags = " + "%s}", + name.c_str_safe(), info.binding, toString(info.type), info.count, + toString(info.stageFlags), prettyDescriptorFlags(info.flags)); + } + printf(",\n"); + } + printf("]\n"); } - sibs.emplace_back((uint8_t) SamplerBindingPoints::PER_MATERIAL_INSTANCE, - &config.materialInfo->sib); +} + +static void collectDescriptorSets(const GLSLPostProcessor::Config& config, DescriptorSets& sets) { + auto perViewDescriptors = DescriptorSetInfo::with_capacity(MAX_DESCRIPTOR_COUNT); + collectDescriptorsForSet(DescriptorSetBindingPoints::PER_VIEW, config, perViewDescriptors); + sets[+DescriptorSetBindingPoints::PER_VIEW] = std::move(perViewDescriptors); + + auto perRenderableDescriptors = DescriptorSetInfo::with_capacity(MAX_DESCRIPTOR_COUNT); + collectDescriptorsForSet( + DescriptorSetBindingPoints::PER_RENDERABLE, config, perRenderableDescriptors); + sets[+DescriptorSetBindingPoints::PER_RENDERABLE] = std::move(perRenderableDescriptors); + + auto perMaterialDescriptors = DescriptorSetInfo::with_capacity(MAX_DESCRIPTOR_COUNT); + collectDescriptorsForSet( + DescriptorSetBindingPoints::PER_MATERIAL, config, perMaterialDescriptors); + sets[+DescriptorSetBindingPoints::PER_MATERIAL] = std::move(perMaterialDescriptors); } } // namespace msl @@ -140,10 +339,10 @@ static std::string stringifySpvOptimizerMessage(spv_message_level_t level, const return oss.str(); } -void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, - filament::backend::ShaderModel shaderModel, bool useFramebufferFetch, const SibVector& sibs, +void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl, + filament::backend::ShaderStage stage, filament::backend::ShaderModel shaderModel, + bool useFramebufferFetch, const DescriptorSets& descriptorSets, const ShaderMinifier* minifier) { - using namespace msl; CompilerMSL mslCompiler(*spirv); @@ -170,8 +369,33 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, mslOptions.argument_buffers = true; mslOptions.ios_support_base_vertex_instance = true; + mslOptions.dynamic_offsets_buffer_index = 25; + + mslCompiler.set_msl_options(mslOptions); + + + + auto executionModel = mslCompiler.get_execution_model(); + + // Map each descriptor set (argument buffer) to a [[buffer(n)]] binding. + // For example, mapDescriptorSet(0, 21) says "map descriptor set 0 to [[buffer(21)]]" + auto mapDescriptorSet = [&mslCompiler](uint32_t set, uint32_t buffer) { + MSLResourceBinding argBufferBinding; + argBufferBinding.basetype = SPIRType::BaseType::Float; + argBufferBinding.stage = mslCompiler.get_execution_model(); + argBufferBinding.desc_set = set; + argBufferBinding.binding = kArgumentBufferBinding; + argBufferBinding.count = 1; + argBufferBinding.msl_buffer = buffer; + mslCompiler.add_msl_resource_binding(argBufferBinding); + }; + for (int i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) { + mapDescriptorSet(i, CodeGenerator::METAL_DESCRIPTOR_SET_BINDING_START + i); + } + + auto resources = mslCompiler.get_shader_resources(); - // We're using argument buffers for texture resources, however, we cannot rely on spirv-cross to + // We're using argument buffers for descriptor sets, however, we cannot rely on spirv-cross to // generate the argument buffer definitions. // // Consider a shader with 3 textures: @@ -194,65 +418,54 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, // shader doesn't precisely match the one generated at runtime. // // So, we use the MetalArgumentBuffer class to replace spirv-cross' argument buffer definitions - // with our own that contain all the textures/samples, even those optimized away. + // with our own that contain all the descriptors, even those optimized away. std::vector argumentBuffers; + size_t dynamicOffsetsBufferIndex = 0; + for (size_t setIndex = 0; setIndex < MAX_DESCRIPTOR_SET_COUNT; setIndex++) { + auto const& descriptors = descriptorSets[setIndex]; + auto argBufferBuilder = MetalArgumentBuffer::Builder().name( + "spvDescriptorSetBuffer" + std::to_string(int(setIndex))); + for (auto const& descriptor : descriptors) { + auto const& [name, info, sampler] = descriptor; + if (!hasShaderType(info.stageFlags, stage)) { + if (any(info.flags & DescriptorFlags::DYNAMIC_OFFSET)) { + // We still need to increment the dynamic offset index + dynamicOffsetsBufferIndex++; + } + continue; + } + switch (info.type) { + case DescriptorType::INPUT_ATTACHMENT: + // TODO: Handle INPUT_ATTACHMENT case + break; + case DescriptorType::UNIFORM_BUFFER: + case DescriptorType::SHADER_STORAGE_BUFFER: { + std::string lowercasedName = name.c_str(); + assert_invariant(!lowercasedName.empty()); + lowercasedName[0] = std::tolower(lowercasedName[0]); + argBufferBuilder + .buffer(info.binding * 2 + 0, name.c_str(), lowercasedName); + if (any(info.flags & DescriptorFlags::DYNAMIC_OFFSET)) { + // Note: this requires that the sets and descriptors are sorted (at least + // the uniforms). + mslCompiler.add_dynamic_buffer( + setIndex, info.binding * 2 + 0, dynamicOffsetsBufferIndex++); + } + break; + } - mslCompiler.set_msl_options(mslOptions); - - auto executionModel = mslCompiler.get_execution_model(); - - // Metal Descriptor Sets - // Descriptor set Name Binding - // ---------------------------------------------------------------------- - // 0 Uniforms Individual bindings - // 1-4 Sampler groups [[buffer(27-30)]] - // 5-7 Unused - // - // Here we enumerate each sampler in each sampler group and map it to a Metal resource. Each - // sampler group is its own descriptor set, and each descriptor set becomes an argument buffer. - // - // For example, in GLSL, we might have the following: - // layout( set = 1, binding = 0 ) uniform sampler2D textureA; - // layout( set = 1, binding = 1 ) uniform sampler2D textureB; - // - // This becomes the following MSL argument buffer: - // struct spvDescriptorSetBuffer1 { - // texture2d textureA [[id(0)]]; - // sampler textureASmplr [[id(1)]]; - // texture2d textureB [[id(2)]]; - // sampler textureBSmplr [[id(3)]]; - // }; - // - // Which is then bound to the vertex/fragment functions: - // constant spvDescriptorSetBuffer1& spvDescriptorSet1 [[buffer(27)]] - for (auto [bindingPoint, sib] : sibs) { - const auto& infoList = sib->getSamplerInfoList(); - - // bindingPoint + 1, because the first descriptor set is for uniforms - auto argBufferBuilder = MetalArgumentBuffer::Builder() - .name("spvDescriptorSetBuffer" + std::to_string(int(bindingPoint + 1))); - - for (const auto& info: infoList) { - const std::string name = info.uniformName.c_str(); - argBufferBuilder - .texture(info.offset * 2, name, info.type, info.format, info.multisample) - .sampler(info.offset * 2 + 1, name + "Smplr"); + case DescriptorType::SAMPLER: { + assert_invariant(sampler.has_value()); + const std::string samplerName = std::string(name.c_str()) + "Smplr"; + argBufferBuilder + .texture(info.binding * 2 + 0, name.c_str(), sampler->type, + sampler->format, sampler->multisample) + .sampler(info.binding * 2 + 1, samplerName); + break; + } + } } - argumentBuffers.push_back(argBufferBuilder.build()); - - // This MSLResourceBinding is how we control the [[buffer(n)]] binding of the argument - // buffer itself; - MSLResourceBinding argBufferBinding; - // the baseType doesn't matter, but can't be UNKNOWN - argBufferBinding.basetype = SPIRType::BaseType::Float; - argBufferBinding.stage = executionModel; - argBufferBinding.desc_set = bindingPoint + 1; - argBufferBinding.binding = kArgumentBufferBinding; - argBufferBinding.count = 1; - argBufferBinding.msl_buffer = - CodeGenerator::METAL_SAMPLER_GROUP_BINDING_START + bindingPoint; - mslCompiler.add_msl_resource_binding(argBufferBinding); } // Bind push constants to [buffer(26)] @@ -263,37 +476,9 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, pushConstantBinding.desc_set = kPushConstDescSet; pushConstantBinding.binding = kPushConstBinding; pushConstantBinding.count = 1; - pushConstantBinding.msl_buffer = 26; + pushConstantBinding.msl_buffer = CodeGenerator::METAL_PUSH_CONSTANT_BUFFER_INDEX; mslCompiler.add_msl_resource_binding(pushConstantBinding); - auto updateResourceBindingDefault = [executionModel, &mslCompiler](const auto& resource) { - auto set = mslCompiler.get_decoration(resource.id, spv::DecorationDescriptorSet); - auto binding = mslCompiler.get_decoration(resource.id, spv::DecorationBinding); - MSLResourceBinding newBinding; - newBinding.basetype = SPIRType::BaseType::Void; - newBinding.stage = executionModel; - newBinding.desc_set = set; - newBinding.binding = binding; - newBinding.count = 1; - newBinding.msl_texture = - newBinding.msl_sampler = - newBinding.msl_buffer = binding; - mslCompiler.add_msl_resource_binding(newBinding); - }; - - auto uniformResources = mslCompiler.get_shader_resources(); - for (const auto& resource : uniformResources.uniform_buffers) { - updateResourceBindingDefault(resource); - } - auto ssboResources = mslCompiler.get_shader_resources(); - for (const auto& resource : ssboResources.storage_buffers) { - updateResourceBindingDefault(resource); - } - - // Descriptor set 0 is uniforms. The add_discrete_descriptor_set call here prevents the uniforms - // from becoming argument buffers. - mslCompiler.add_discrete_descriptor_set(0); - *outMsl = mslCompiler.compile(); if (minifier) { *outMsl = minifier->removeWhitespace(*outMsl); @@ -394,9 +579,13 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co fixupClipDistance(*internalConfig.spirvOutput, config); if (internalConfig.mslOutput) { auto sibs = SibVector::with_capacity(CONFIG_SAMPLER_BINDING_COUNT); - msl::collectSibs(config, sibs); + DescriptorSets descriptors {}; + msl::collectDescriptorSets(config, descriptors); +#if DEBUG_LOG_DESCRIPTOR_SETS == 1 + msl::prettyPrintDescriptorSetInfoVector(descriptors); +#endif spirvToMsl(internalConfig.spirvOutput, internalConfig.mslOutput, - config.shaderModel, config.hasFramebufferFetch, sibs, + config.shaderType, config.shaderModel, config.hasFramebufferFetch, descriptors, mGenerateDebugInfo ? &internalConfig.minifier : nullptr); } } else { @@ -482,10 +671,13 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader, } if (internalConfig.mslOutput) { - auto sibs = SibVector::with_capacity(CONFIG_SAMPLER_BINDING_COUNT); - msl::collectSibs(config, sibs); - spirvToMsl(internalConfig.spirvOutput, internalConfig.mslOutput, config.shaderModel, - config.hasFramebufferFetch, sibs, + DescriptorSets descriptors {}; + msl::collectDescriptorSets(config, descriptors); +#if DEBUG_LOG_DESCRIPTOR_SETS == 1 + msl::prettyPrintDescriptorSetInfoVector(descriptors); +#endif + spirvToMsl(internalConfig.spirvOutput, internalConfig.mslOutput, config.shaderType, + config.shaderModel, config.hasFramebufferFetch, descriptors, mGenerateDebugInfo ? &internalConfig.minifier : nullptr); } @@ -523,10 +715,14 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader, } if (internalConfig.mslOutput) { - auto sibs = SibVector::with_capacity(CONFIG_SAMPLER_BINDING_COUNT); - msl::collectSibs(config, sibs); - spirvToMsl(&spirv, internalConfig.mslOutput, config.shaderModel, config.hasFramebufferFetch, - sibs, mGenerateDebugInfo ? &internalConfig.minifier : nullptr); + DescriptorSets descriptors {}; + msl::collectDescriptorSets(config, descriptors); +#if DEBUG_LOG_DESCRIPTOR_SETS == 1 + msl::prettyPrintDescriptorSetInfoVector(descriptors); +#endif + spirvToMsl(&spirv, internalConfig.mslOutput, config.shaderType, config.shaderModel, + config.hasFramebufferFetch, descriptors, + mGenerateDebugInfo ? &internalConfig.minifier : nullptr); } // Transpile back to GLSL diff --git a/libs/filamat/src/GLSLPostProcessor.h b/libs/filamat/src/GLSLPostProcessor.h index c13dece6369..8616d0e4dc8 100644 --- a/libs/filamat/src/GLSLPostProcessor.h +++ b/libs/filamat/src/GLSLPostProcessor.h @@ -20,6 +20,7 @@ #include // for MaterialBuilder:: enums #include +#include #include "ShaderMinifier.h" @@ -32,19 +33,23 @@ #include #include +#include #include #include -namespace filament { -class SamplerInterfaceBlock; -}; - namespace filamat { using SpirvBlob = std::vector; using BindingPointAndSib = std::pair; using SibVector = utils::FixedCapacityVector; +using DescriptorInfo = std::tuple< + utils::CString, + filament::backend::DescriptorSetLayoutBinding, + std::optional>; +using DescriptorSetInfo = utils::FixedCapacityVector; +using DescriptorSets = std::array; + class GLSLPostProcessor { public: enum Flags : uint32_t { @@ -58,6 +63,7 @@ class GLSLPostProcessor { struct Config { filament::Variant variant; + filament::UserVariantFilterMask variantFilter; MaterialBuilder::TargetApi targetApi; MaterialBuilder::TargetLanguage targetLanguage; filament::backend::ShaderStage shaderType; @@ -79,8 +85,9 @@ class GLSLPostProcessor { // public so backend_test can also use it static void spirvToMsl(const SpirvBlob* spirv, std::string* outMsl, - filament::backend::ShaderModel shaderModel, bool useFramebufferFetch, - const SibVector& sibs, const ShaderMinifier* minifier); + filament::backend::ShaderStage stage, filament::backend::ShaderModel shaderModel, + bool useFramebufferFetch, const DescriptorSets& descriptorSets, + const ShaderMinifier* minifier); private: struct InternalConfig { diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 1905c86cc00..816671aae5c 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -39,26 +39,37 @@ #include "eiff/SimpleFieldChunk.h" #include "eiff/DictionaryTextChunk.h" #include "eiff/DictionarySpirvChunk.h" -#include "eiff/DictionaryMetalLibraryChunk.h" #include #include #include #include +#include +#include +#include #include +#include +#include +#include #include #include #include #include #include +#include #include +#include #include #include +#include #include +#include +#include + namespace filamat { using namespace utils; @@ -597,12 +608,13 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { // Build the per-material sampler block and uniform block. SamplerInterfaceBlock::Builder sbb; BufferInterfaceBlock::Builder ibb; - for (size_t i = 0, c = mParameterCount; i < c; i++) { + // sampler bindings start at 1, 0 is the ubo + for (size_t i = 0, binding = 1, c = mParameterCount; i < c; i++) { auto const& param = mParameters[i]; assert_invariant(!param.isSubpass()); if (param.isSampler()) { sbb.add({ param.name.data(), param.name.size() }, - param.samplerType, param.format, param.precision, param.multisample); + binding++, param.samplerType, param.format, param.precision, param.multisample); } else if (param.isUniform()) { ibb.add({{{ param.name.data(), param.name.size() }, uint32_t(param.size == 1u ? 0u : param.size), param.uniformType, @@ -926,20 +938,21 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector> list({ - { UniformBindingPoints::PER_VIEW, - extractUniforms(UibGenerator::getPerViewUib()) }, - { UniformBindingPoints::PER_RENDERABLE, - extractUniforms(UibGenerator::getPerRenderableUib()) }, - { UniformBindingPoints::PER_MATERIAL_INSTANCE, - extractUniforms(info.uib) }, - }); - // FIXME: don't hardcode this - auto& uniforms = list[1].second; + FixedCapacityVector> list({ + { 0, "FrameUniforms", extractUniforms(UibGenerator::getPerViewUib()) }, + { 1, "ObjectUniforms", extractUniforms(UibGenerator::getPerRenderableUib()) }, + { 2, "MaterialParams", extractUniforms(info.uib) }, + }); + auto& uniforms = std::get<2>(list[1]); uniforms.clear(); uniforms.reserve(6); uniforms.push_back({ @@ -1534,37 +1541,22 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& container.push(std::move(attributes)); } - // TODO: currently, the feature level used is determined by the material because we - // don't have "feature level" variants. In other words, a feature level 0 material - // won't work with a feature level 1 engine. However, we do embed the feature level 1 - // meta-data, as it should. - - if (info.featureLevel <= FeatureLevel::FEATURE_LEVEL_1) { - // note: this chunk is only needed for OpenGL backends, which don't all support layout(binding=) - FixedCapacityVector> list = { - { PerViewUib::_name, UniformBindingPoints::PER_VIEW }, - { PerRenderableUib::_name, UniformBindingPoints::PER_RENDERABLE }, - { LightsUib::_name, UniformBindingPoints::LIGHTS }, - { ShadowUib::_name, UniformBindingPoints::SHADOW }, - { FroxelRecordUib::_name, UniformBindingPoints::FROXEL_RECORDS }, - { FroxelsUib::_name, UniformBindingPoints::FROXELS }, - { PerRenderableBoneUib::_name, UniformBindingPoints::PER_RENDERABLE_BONES }, - { PerRenderableMorphingUib::_name, UniformBindingPoints::PER_RENDERABLE_MORPHING }, - { info.uib.getName(), UniformBindingPoints::PER_MATERIAL_INSTANCE } - }; - container.push(std::move(list)); - } - - // note: this chunk is needed for Vulkan and GL backends. Metal shouldn't need it (but - // still does as of now). - container.push(info.samplerBindings); - - // User Material UIB + // User parameters (UBO) container.push(info.uib); - // User Material SIB + // User texture parameters container.push(info.sib); + + backend::DescriptorSetLayout const perViewDescriptorSetLayout = + descriptor_sets::getPerViewDescriptorSetLayout( + mMaterialDomain, mVariantFilter, + info.isLit, info.reflectionMode, info.refractionMode); + + // Descriptor layout and descriptor name/binding mapping + container.push(info.sib, perViewDescriptorSetLayout); + container.push(info.sib, perViewDescriptorSetLayout); + // User constant parameters utils::FixedCapacityVector constantsEntry(mConstants.size()); std::transform(mConstants.begin(), mConstants.end(), constantsEntry.begin(), diff --git a/libs/filamat/src/MaterialVariants.cpp b/libs/filamat/src/MaterialVariants.cpp index e71f8b4fa8b..06a70d60fc8 100644 --- a/libs/filamat/src/MaterialVariants.cpp +++ b/libs/filamat/src/MaterialVariants.cpp @@ -16,7 +16,24 @@ #include "MaterialVariants.h" +#include "shaders/ShaderGenerator.h" + #include +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include namespace filamat { @@ -36,13 +53,65 @@ std::vector determineSurfaceVariants( filteredVariant = filament::Variant::filterVariant( filteredVariant, isLit || shadowMultiplier); - if (filament::Variant::filterVariantVertex(filteredVariant) == variant) { + auto const vertexVariant = filament::Variant::filterVariantVertex(filteredVariant); + if (vertexVariant == variant) { variants.emplace_back(variant, filament::backend::ShaderStage::VERTEX); } - if (filament::Variant::filterVariantFragment(filteredVariant) == variant) { + auto const fragmentVariant = filament::Variant::filterVariantFragment(filteredVariant); + if (fragmentVariant == variant) { variants.emplace_back(variant, filament::backend::ShaderStage::FRAGMENT); } + + // Here we make sure that the combination of vertex and fragment variants have compatible + // PER_VIEW descriptor-set layouts. This could actually be a static/compile-time check + // because it is entirely decided in DescriptorSets.cpp. Unfortunately it's not possible + // to write this entirely as a constexpr. + + if (UTILS_UNLIKELY(vertexVariant != fragmentVariant)) { + // fragment and vertex variants are different, we need to check the layouts are + // compatible. + using filament::ReflectionMode; + using filament::RefractionMode; + using filament::backend::ShaderStage; + + // And we need to do that for all configurations of the "PER_VIEW" descriptor set + // layouts (there are eight). + // See ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant. + for (auto reflection: { + ReflectionMode::SCREEN_SPACE, + ReflectionMode::DEFAULT }) { + for (auto refraction: { + RefractionMode::SCREEN_SPACE, + RefractionMode::CUBEMAP, + RefractionMode::NONE }) { + auto const vdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( + vertexVariant, userVariantFilter, isLit, reflection, refraction); + auto const fdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( + fragmentVariant, userVariantFilter, isLit, reflection, refraction); + // Check that all bindings present in the vertex shader DescriptorSetLayout + // are also present in the fragment shader DescriptorSetLayout. + for (auto const& r: vdsl.bindings) { + if (!hasShaderType(r.stageFlags, ShaderStage::VERTEX)) { + // ignore descriptors that are of the fragment stage only + continue; + } + auto const pos = std::find_if(fdsl.bindings.begin(), fdsl.bindings.end(), + [r](auto const& l) { + return l.count == r.count && l.type == r.type && + l.binding == r.binding && l.flags == r.flags && + l.stageFlags == r.stageFlags; + }); + + // A mismatch is fatal. The material is ill-formed. This typically + // mean a bug / inconsistency in DescriptorsSets.cpp + FILAMENT_CHECK_POSTCONDITION(pos != fdsl.bindings.end()) + << "Variant " << +k << " has mismatched descriptorset layouts"; + } + } + } + } + } return variants; } diff --git a/libs/filamat/src/MetalArgumentBuffer.cpp b/libs/filamat/src/MetalArgumentBuffer.cpp index 506cb8e480a..ffe1aa1568d 100644 --- a/libs/filamat/src/MetalArgumentBuffer.cpp +++ b/libs/filamat/src/MetalArgumentBuffer.cpp @@ -18,6 +18,7 @@ #include #include +#include namespace filamat { @@ -52,6 +53,12 @@ MetalArgumentBuffer::Builder& MetalArgumentBuffer::Builder::sampler( return *this; } +MetalArgumentBuffer::Builder& MetalArgumentBuffer::Builder::buffer( + size_t index, const std::string& type, const std::string& name) noexcept { + mArguments.emplace_back(BufferArgument { name, index, type }); + return *this; +} + MetalArgumentBuffer* MetalArgumentBuffer::Builder::build() { assert_invariant(!mName.empty()); return new MetalArgumentBuffer(*this); @@ -114,12 +121,14 @@ std::ostream& MetalArgumentBuffer::Builder::SamplerArgument::write(std::ostream& return os; } +std::ostream& MetalArgumentBuffer::Builder::BufferArgument::write(std::ostream& os) const { + os << "constant " << type << "* " << name << " [[id(" << index << ")]];" << std::endl; + return os; +} + MetalArgumentBuffer::MetalArgumentBuffer(Builder& builder) { mName = builder.mName; - std::stringstream ss; - ss << "struct " << mName << " {" << std::endl; - auto& args = builder.mArguments; // Sort the arguments by index. @@ -134,6 +143,18 @@ MetalArgumentBuffer::MetalArgumentBuffer(Builder& builder) { [](auto const& x, auto const& y) { return x.index == y.index; }, lhs, rhs); }) == args.end()); + std::stringstream ss; + + // Add forward declarations of buffers. + for (const auto& a : builder.mArguments) { + if (std::holds_alternative(a)) { + const auto& bufferArg = std::get(a); + ss << "struct " << bufferArg.type << ";" << std::endl; + } + } + + ss << "struct " << mName << " {" << std::endl; + for (const auto& a : builder.mArguments) { std::visit([&](auto&& arg) { arg.write(ss); diff --git a/libs/filamat/src/MetalArgumentBuffer.h b/libs/filamat/src/MetalArgumentBuffer.h index 608645fca12..75f4d1a3fc3 100644 --- a/libs/filamat/src/MetalArgumentBuffer.h +++ b/libs/filamat/src/MetalArgumentBuffer.h @@ -58,6 +58,14 @@ class MetalArgumentBuffer { */ Builder& sampler(size_t index, const std::string& name) noexcept; + /** + * Add a buffer argument to the argument buffer structure. + * @param index the [[id(n)]] index of the buffer argument + * @param type the type of data the buffer points to + * @param name the name of the buffer argument + */ + Builder& buffer(size_t index, const std::string& type, const std::string& name) noexcept; + MetalArgumentBuffer* build(); friend class MetalArgumentBuffer; @@ -82,7 +90,15 @@ class MetalArgumentBuffer { std::ostream& write(std::ostream& os) const; }; - using ArgumentType = std::variant; + struct BufferArgument { + std::string name; + size_t index; + std::string type; + + std::ostream& write(std::ostream& os) const; + }; + + using ArgumentType = std::variant; std::vector mArguments; }; diff --git a/libs/filamat/src/SamplerBindingMap.cpp b/libs/filamat/src/SamplerBindingMap.cpp index b2770a20726..e69de29bb2d 100644 --- a/libs/filamat/src/SamplerBindingMap.cpp +++ b/libs/filamat/src/SamplerBindingMap.cpp @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SamplerBindingMap.h" - -#include "shaders/SibGenerator.h" - -#include - -#include - -#include - -namespace filament { - -using namespace utils; -using namespace backend; - -void SamplerBindingMap::init(MaterialDomain materialDomain, - SamplerInterfaceBlock const& perMaterialSib) { - - assert_invariant(mActiveSamplerCount == 0); - - mSamplerNamesBindingMap.reserve(MAX_SAMPLER_COUNT); - mSamplerNamesBindingMap.resize(MAX_SAMPLER_COUNT); - - // Note: the material variant affects only the sampler types, but cannot affect - // the actual bindings. For this reason it is okay to use the dummyVariant here. - uint8_t offset = 0; - size_t vertexSamplerCount = 0; - size_t fragmentSamplerCount = 0; - - auto processSamplerGroup = [&](SamplerBindingPoints bindingPoint){ - SamplerInterfaceBlock const* const sib = - (bindingPoint == SamplerBindingPoints::PER_MATERIAL_INSTANCE) ? - &perMaterialSib : SibGenerator::getSib(bindingPoint, {}); - if (sib) { - const auto stageFlags = sib->getStageFlags(); - auto const& list = sib->getSamplerInfoList(); - const size_t samplerCount = list.size(); - - if (any(stageFlags & ShaderStageFlags::VERTEX)) { - vertexSamplerCount += samplerCount; - } - if (any(stageFlags & ShaderStageFlags::FRAGMENT)) { - fragmentSamplerCount += samplerCount; - } - - mSamplerBlockOffsets[+bindingPoint] = { offset, stageFlags, uint8_t(samplerCount) }; - for (size_t i = 0; i < samplerCount; i++) { - assert_invariant(mSamplerNamesBindingMap[offset + i].empty()); - mSamplerNamesBindingMap[offset + i] = list[i].uniformName; - } - - offset += samplerCount; - } - }; - - switch(materialDomain) { - case MaterialDomain::SURFACE: - UTILS_NOUNROLL - for (size_t i = 0; i < Enum::count(); i++) { - processSamplerGroup((SamplerBindingPoints)i); - } - break; - case MaterialDomain::POST_PROCESS: - case MaterialDomain::COMPUTE: - processSamplerGroup(SamplerBindingPoints::PER_MATERIAL_INSTANCE); - break; - } - - mActiveSamplerCount = offset; - - // we shouldn't be using more total samplers than supported - assert_invariant(vertexSamplerCount + fragmentSamplerCount <= MAX_SAMPLER_COUNT); - - // Here we cannot check for overflow for a given feature level because we don't know - // what feature level the backend will support. We only know the feature level declared - // by the material. However, we can at least assert for the highest feature level. - - constexpr size_t MAX_VERTEX_SAMPLER_COUNT = - backend::FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3].MAX_VERTEX_SAMPLER_COUNT; - - assert_invariant(vertexSamplerCount <= MAX_VERTEX_SAMPLER_COUNT); - - constexpr size_t MAX_FRAGMENT_SAMPLER_COUNT = - backend::FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3].MAX_FRAGMENT_SAMPLER_COUNT; - - assert_invariant(fragmentSamplerCount <= MAX_FRAGMENT_SAMPLER_COUNT); -} - -} // namespace filament diff --git a/libs/filamat/src/SamplerBindingMap.h b/libs/filamat/src/SamplerBindingMap.h index d76078138d6..e69de29bb2d 100644 --- a/libs/filamat/src/SamplerBindingMap.h +++ b/libs/filamat/src/SamplerBindingMap.h @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H -#define TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H - -#include - -#include - -namespace filament { - -class SamplerInterfaceBlock; - -/* - * SamplerBindingMap maps filament's (BindingPoints, offset) to a global offset. - * This global offset is used in shaders to set the `layout(binding=` of each sampler. - * - * It also keeps a map of global offsets to the sampler name in the shader. - * - * SamplerBindingMap is flattened into the material file and used on the filament side to - * create the backend's programs. - */ -class SamplerBindingMap { -public: - - using SamplerGroupBindingInfo = SamplerGroupBindingInfo; - - // Initializes the SamplerBindingMap. - // Assigns a range of finalized binding points to each sampler block. - // If a per-material SIB is provided, then material samplers are also inserted (always at the - // end). - void init(MaterialDomain materialDomain, - SamplerInterfaceBlock const& perMaterialSib); - - SamplerGroupBindingInfo const& getSamplerGroupBindingInfo( - SamplerBindingPoints bindingPoint) const noexcept { - return mSamplerBlockOffsets[+bindingPoint]; - } - - // Gets the global offset of the first sampler in the given sampler block. - inline uint8_t getBlockOffset(SamplerBindingPoints bindingPoint) const noexcept { - assert_invariant(mSamplerBlockOffsets[+bindingPoint].bindingOffset != UNKNOWN_OFFSET); - return getSamplerGroupBindingInfo(bindingPoint).bindingOffset; - } - - size_t getActiveSamplerCount() const noexcept { - return mActiveSamplerCount; - } - - utils::CString const& getSamplerName(size_t binding) const noexcept { - return mSamplerNamesBindingMap[binding]; - } - -private: - constexpr static uint8_t UNKNOWN_OFFSET = SamplerGroupBindingInfo::UNKNOWN_OFFSET; - SamplerGroupBindingInfoList mSamplerBlockOffsets{}; - SamplerBindingToNameMap mSamplerNamesBindingMap{}; - uint8_t mActiveSamplerCount = 0; -}; - -} // namespace filament - -#endif // TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp index 5f92ca7356b..23913f27183 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.cpp @@ -17,15 +17,22 @@ #include "filament/MaterialChunkType.h" -#include "../SamplerBindingMap.h" -#include #include -#include #include +#include +#include #include +#include +#include + +#include + +#include #include +#include + using namespace filament; namespace filamat { @@ -62,6 +69,7 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) { f.writeUint64(sibFields.size()); for (auto sInfo: sibFields) { f.writeString(sInfo.name.c_str()); + f.writeUint8(static_cast(sInfo.binding)); f.writeUint8(static_cast(sInfo.type)); f.writeUint8(static_cast(sInfo.format)); f.writeUint8(static_cast(sInfo.precision)); @@ -123,64 +131,16 @@ void MaterialPushConstantParametersChunk::flatten(Flattener& f) { // ------------------------------------------------------------------------------------------------ -MaterialUniformBlockBindingsChunk::MaterialUniformBlockBindingsChunk( - utils::FixedCapacityVector> list) - : Chunk(ChunkType::MaterialUniformBindings), - mBindingList(std::move(list)) { -} - -void MaterialUniformBlockBindingsChunk::flatten(Flattener& f) { - f.writeUint8(mBindingList.size()); - for (auto const& item: mBindingList) { - f.writeString(item.first); - f.writeUint8(uint8_t(item.second)); - } -} - -// ------------------------------------------------------------------------------------------------ - -MaterialSamplerBlockBindingChunk::MaterialSamplerBlockBindingChunk( - SamplerBindingMap const& samplerBindings) - : Chunk(ChunkType::MaterialSamplerBindings), - mSamplerBindings(samplerBindings) { -} - -void MaterialSamplerBlockBindingChunk::flatten(Flattener& f) { - f.writeUint8(utils::Enum::count()); - UTILS_NOUNROLL - for (size_t i = 0; i < utils::Enum::count(); i++) { - SamplerBindingPoints const bindingPoint = (SamplerBindingPoints)i; - auto const& bindingInfo = mSamplerBindings.getSamplerGroupBindingInfo(bindingPoint); - f.writeUint8(bindingInfo.bindingOffset); - f.writeUint8((uint8_t)bindingInfo.shaderStageFlags); - f.writeUint8(bindingInfo.count); - } - f.writeUint8(mSamplerBindings.getActiveSamplerCount()); - UTILS_UNUSED_IN_RELEASE size_t c = 0; - UTILS_NOUNROLL - for (size_t i = 0; i < backend::MAX_SAMPLER_COUNT; i++) { - auto const& uniformName = mSamplerBindings.getSamplerName(i); - if (!uniformName.empty()) { - f.writeUint8((uint8_t)i); - f.writeString(uniformName.c_str()); - c++; - } - } - assert_invariant(c == mSamplerBindings.getActiveSamplerCount()); -} - -// ------------------------------------------------------------------------------------------------ - MaterialBindingUniformInfoChunk::MaterialBindingUniformInfoChunk(Container list) noexcept : Chunk(ChunkType::MaterialBindingUniformInfo), - mBindingUniformInfo(std::move(list)) -{ + mBindingUniformInfo(std::move(list)) { } void MaterialBindingUniformInfoChunk::flatten(Flattener& f) { f.writeUint8(mBindingUniformInfo.size()); - for (auto const& [index, uniforms] : mBindingUniformInfo) { + for (auto const& [index, name, uniforms] : mBindingUniformInfo) { f.writeUint8(uint8_t(index)); + f.writeString({ name.data(), name.size() }); f.writeUint8(uint8_t(uniforms.size())); for (auto const& uniform: uniforms) { f.writeString({ uniform.name.data(), uniform.name.size() }); @@ -207,4 +167,113 @@ void MaterialAttributesInfoChunk::flatten(Flattener& f) { } } +// ------------------------------------------------------------------------------------------------ + +MaterialDescriptorBindingsChuck::MaterialDescriptorBindingsChuck(Container const& sib, + backend::DescriptorSetLayout const& perViewLayout) noexcept + : Chunk(ChunkType::MaterialDescriptorBindingsInfo), + mSamplerInterfaceBlock(sib), + mPerViewLayout(perViewLayout) { +} + +void MaterialDescriptorBindingsChuck::flatten(Flattener& f) { + assert_invariant(sizeof(backend::descriptor_set_t) == sizeof(uint8_t)); + assert_invariant(sizeof(backend::descriptor_binding_t) == sizeof(uint8_t)); + + using namespace backend; + + + // number of descriptor-sets + f.writeUint8(3); + + // set + f.writeUint8(+DescriptorSetBindingPoints::PER_MATERIAL); + + // samplers + 1 descriptor for the UBO + f.writeUint8(mSamplerInterfaceBlock.getSize() + 1); + + // our UBO descriptor is always at binding 0 + CString const uboName = + descriptor_sets::getDescriptorName(DescriptorSetBindingPoints::PER_MATERIAL, 0); + f.writeString({ uboName.data(), uboName.size() }); + f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER)); + f.writeUint8(0); + + // all the material's sampler descriptors + for (auto const& entry: mSamplerInterfaceBlock.getSamplerInfoList()) { + f.writeString({ entry.uniformName.data(), entry.uniformName.size() }); + f.writeUint8(uint8_t(DescriptorType::SAMPLER)); + f.writeUint8(entry.binding); + } + + // set + f.writeUint8(+DescriptorSetBindingPoints::PER_RENDERABLE); + f.writeUint8(descriptor_sets::getPerRenderableLayout().bindings.size()); + for (auto const& entry: descriptor_sets::getPerRenderableLayout().bindings) { + auto const& name = descriptor_sets::getDescriptorName( + DescriptorSetBindingPoints::PER_RENDERABLE, entry.binding); + f.writeString({ name.data(), name.size() }); + f.writeUint8(uint8_t(entry.type)); + f.writeUint8(entry.binding); + } + + // set + f.writeUint8(+DescriptorSetBindingPoints::PER_VIEW); + f.writeUint8(mPerViewLayout.bindings.size()); + for (auto const& entry: mPerViewLayout.bindings) { + auto const& name = descriptor_sets::getDescriptorName( + DescriptorSetBindingPoints::PER_VIEW, entry.binding); + f.writeString({ name.data(), name.size() }); + f.writeUint8(uint8_t(entry.type)); + f.writeUint8(entry.binding); + } +} + +// ------------------------------------------------------------------------------------------------ + +MaterialDescriptorSetLayoutChunk::MaterialDescriptorSetLayoutChunk(Container const& sib, + backend::DescriptorSetLayout const& perViewLayout) noexcept + : Chunk(ChunkType::MaterialDescriptorSetLayoutInfo), + mSamplerInterfaceBlock(sib), + mPerViewLayout(perViewLayout) { +} + +void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) { + assert_invariant(sizeof(backend::descriptor_set_t) == sizeof(uint8_t)); + assert_invariant(sizeof(backend::descriptor_binding_t) == sizeof(uint8_t)); + + using namespace backend; + + // samplers + 1 descriptor for the UBO + f.writeUint8(mSamplerInterfaceBlock.getSize() + 1); + + // our UBO descriptor is always at binding 0 + f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER)); + f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)); + f.writeUint8(0); + f.writeUint8(uint8_t(DescriptorFlags::NONE)); + f.writeUint16(0); + + // all the material's sampler descriptors + for (auto const& entry: mSamplerInterfaceBlock.getSamplerInfoList()) { + f.writeUint8(uint8_t(DescriptorType::SAMPLER)); + f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)); + f.writeUint8(entry.binding); + f.writeUint8(uint8_t(DescriptorFlags::NONE)); + f.writeUint16(0); + } + + // samplers + 1 descriptor for the UBO + f.writeUint8(mPerViewLayout.bindings.size()); + + // all the material's sampler descriptors + for (auto const& entry: mPerViewLayout.bindings) { + f.writeUint8(uint8_t(entry.type)); + f.writeUint8(uint8_t(entry.stageFlags)); + f.writeUint8(entry.binding); + f.writeUint8(uint8_t(entry.flags)); + f.writeUint16(entry.count); + } +} + } // namespace filamat diff --git a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h index 5faee0147a9..cbb3a85446c 100644 --- a/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h +++ b/libs/filamat/src/eiff/MaterialInterfaceBlockChunk.h @@ -21,12 +21,18 @@ #include +#include #include +#include #include +#include +#include + +#include + namespace filament { -class SamplerBindingMap; class SamplerInterfaceBlock; class BufferInterfaceBlock; struct SubpassInfo; @@ -104,59 +110,63 @@ class MaterialPushConstantParametersChunk final : public Chunk { // ------------------------------------------------------------------------------------------------ -class MaterialUniformBlockBindingsChunk final : public Chunk { - using Container = utils::FixedCapacityVector< - std::pair>; +class MaterialBindingUniformInfoChunk final : public Chunk { + using Container = FixedCapacityVector>; public: - explicit MaterialUniformBlockBindingsChunk(Container list); - ~MaterialUniformBlockBindingsChunk() final = default; + explicit MaterialBindingUniformInfoChunk(Container list) noexcept; + ~MaterialBindingUniformInfoChunk() final = default; private: - void flatten(Flattener&) final; + void flatten(Flattener &) final; - Container mBindingList; + Container mBindingUniformInfo; }; // ------------------------------------------------------------------------------------------------ -class MaterialSamplerBlockBindingChunk final : public Chunk { +class MaterialAttributesInfoChunk final : public Chunk { + using Container = FixedCapacityVector>; public: - explicit MaterialSamplerBlockBindingChunk(filament::SamplerBindingMap const& samplerBindings); - ~MaterialSamplerBlockBindingChunk() final = default; + explicit MaterialAttributesInfoChunk(Container list) noexcept; + ~MaterialAttributesInfoChunk() final = default; private: void flatten(Flattener &) final; - filament::SamplerBindingMap const& mSamplerBindings; + Container mAttributeInfo; }; // ------------------------------------------------------------------------------------------------ -class MaterialBindingUniformInfoChunk final : public Chunk { - using Container = FixedCapacityVector< - std::pair>; +class MaterialDescriptorBindingsChuck final : public Chunk { + using Container = filament::SamplerInterfaceBlock; public: - explicit MaterialBindingUniformInfoChunk(Container list) noexcept; - ~MaterialBindingUniformInfoChunk() final = default; + explicit MaterialDescriptorBindingsChuck(Container const& sib, + filament::backend::DescriptorSetLayout const& perViewLayout) noexcept; + ~MaterialDescriptorBindingsChuck() final = default; private: - void flatten(Flattener &) final; + void flatten(Flattener&) final; - Container mBindingUniformInfo; + Container const& mSamplerInterfaceBlock; + filament::backend::DescriptorSetLayout mPerViewLayout; }; // ------------------------------------------------------------------------------------------------ -class MaterialAttributesInfoChunk final : public Chunk { - using Container = FixedCapacityVector>; +class MaterialDescriptorSetLayoutChunk final : public Chunk { + using Container = filament::SamplerInterfaceBlock; public: - explicit MaterialAttributesInfoChunk(Container list) noexcept; - ~MaterialAttributesInfoChunk() final = default; + explicit MaterialDescriptorSetLayoutChunk(Container const& sib, + filament::backend::DescriptorSetLayout const& perViewLayout) noexcept; + ~MaterialDescriptorSetLayoutChunk() final = default; private: - void flatten(Flattener &) final; + void flatten(Flattener&) final; - Container mAttributeInfo; + Container const& mSamplerInterfaceBlock; + filament::backend::DescriptorSetLayout mPerViewLayout; }; } // namespace filamat diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index 2b90c299410..5fa9db08785 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -21,6 +21,8 @@ #include "generated/shaders.h" +#include + #include #include @@ -602,17 +604,33 @@ const char* CodeGenerator::getUniformPrecisionQualifier(UniformType type, Precis utils::io::sstream& CodeGenerator::generateBuffers(utils::io::sstream& out, MaterialInfo::BufferContainer const& buffers) const { - uint32_t binding = 0; + for (auto const* buffer : buffers) { - generateBufferInterfaceBlock(out, ShaderStage::COMPUTE, binding, *buffer); - binding++; + + // FIXME: we need to get the bindings for the SSBOs and that will depend on the samplers + backend::descriptor_binding_t binding = 0; + + if (mTargetApi == TargetApi::OPENGL) { + // For OpenGL, the set is not used bug the binding must be unique. + binding = getUniqueSsboBindingPoint(); + } + generateBufferInterfaceBlock(out, ShaderStage::COMPUTE, + DescriptorSetBindingPoints::PER_MATERIAL, binding, *buffer); } return out; } io::sstream& CodeGenerator::generateUniforms(io::sstream& out, ShaderStage stage, - UniformBindingPoints binding, const BufferInterfaceBlock& uib) const { - return generateBufferInterfaceBlock(out, stage, +binding, uib); + filament::DescriptorSetBindingPoints set, + filament::backend::descriptor_binding_t binding, + const BufferInterfaceBlock& uib) const { + + if (mTargetApi == TargetApi::OPENGL) { + // For OpenGL, the set is not used bug the binding must be unique. + binding = getUniqueUboBindingPoint(); + } + + return generateBufferInterfaceBlock(out, stage, set, binding, uib); } io::sstream& CodeGenerator::generateInterfaceFields(io::sstream& out, @@ -667,7 +685,9 @@ io::sstream& CodeGenerator::generateUboAsPlainUniforms(io::sstream& out, ShaderS } io::sstream& CodeGenerator::generateBufferInterfaceBlock(io::sstream& out, ShaderStage stage, - uint32_t binding, const BufferInterfaceBlock& uib) const { + filament::DescriptorSetBindingPoints set, + filament::backend::descriptor_binding_t binding, + const BufferInterfaceBlock& uib) const { if (uib.isEmptyForFeatureLevel(mFeatureLevel)) { return out; } @@ -687,28 +707,19 @@ io::sstream& CodeGenerator::generateBufferInterfaceBlock(io::sstream& out, Shade blockName.front() = char(std::toupper((unsigned char)blockName.front())); instanceName.front() = char(std::tolower((unsigned char)instanceName.front())); - auto metalBufferBindingOffset = 0; - switch (uib.getTarget()) { - case BufferInterfaceBlock::Target::UNIFORM: - metalBufferBindingOffset = METAL_UNIFORM_BUFFER_BINDING_START; - break; - case BufferInterfaceBlock::Target::SSBO: - metalBufferBindingOffset = METAL_SSBO_BINDING_START; - break; - } - out << "\nlayout("; if (mTargetLanguage == TargetLanguage::SPIRV || mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) { switch (mTargetApi) { case TargetApi::METAL: - out << "binding = " << metalBufferBindingOffset + binding << ", "; + case TargetApi::VULKAN: + out << "set = " << +set << ", binding = " << +binding << ", "; break; case TargetApi::OPENGL: // GLSL 4.5 / ESSL 3.1 require the 'binding' layout qualifier - case TargetApi::VULKAN: - out << "binding = " << binding << ", "; + // in the GLSL 4.5 / ESSL 3.1 case, the set is not used and binding is unique + out << "binding = " << +binding << ", "; break; case TargetApi::ALL: @@ -762,15 +773,14 @@ io::sstream& CodeGenerator::generateBufferInterfaceBlock(io::sstream& out, Shade return out; } -io::sstream& CodeGenerator::generateSamplers( - io::sstream& out, SamplerBindingPoints bindingPoint, uint8_t firstBinding, - const SamplerInterfaceBlock& sib) const { - auto const& infos = sib.getSamplerInfoList(); - if (infos.empty()) { +io::sstream& CodeGenerator::generateSamplers(utils::io::sstream& out, + filament::DescriptorSetBindingPoints set, + filament::SamplerInterfaceBlock::SamplerInfoList const& list) const { + if (list.empty()) { return out; } - for (auto const& info : infos) { + for (auto const& info : list) { auto type = info.type; if (type == SamplerType::SAMPLER_EXTERNAL && mShaderModel != ShaderModel::MOBILE) { // we're generating the shader for the desktop, where we assume external textures @@ -780,27 +790,27 @@ io::sstream& CodeGenerator::generateSamplers( char const* const typeName = getSamplerTypeName(type, info.format, info.multisample); char const* const precision = getPrecisionQualifier(info.precision); if (mTargetLanguage == TargetLanguage::SPIRV) { - const uint32_t bindingIndex = (uint32_t) firstBinding + info.offset; switch (mTargetApi) { - // For Vulkan, we place uniforms in set 0 (the default set) and samplers in set 1. This - // allows the sampler bindings to live in a separate "namespace" that starts at zero. // Note that the set specifier is not covered by the desktop GLSL spec, including // recent versions. It is only documented in the GL_KHR_vulkan_glsl extension. case TargetApi::VULKAN: - out << "layout(binding = " << bindingIndex << ", set = 1) "; + out << "layout(binding = " << +info.binding << ", set = " << +set << ") "; break; // For Metal, each sampler group gets its own descriptor set, each of which will // become an argument buffer. The first descriptor set is reserved for uniforms, // hence the +1 here. case TargetApi::METAL: - out << "layout(binding = " << (uint32_t) info.offset - << ", set = " << (uint32_t) bindingPoint + 1 << ") "; + out << "layout(binding = " << +info.binding << ", set = " << +set << ") "; break; - default: case TargetApi::OPENGL: - out << "layout(binding = " << bindingIndex << ") "; + // GLSL 4.5 / ESSL 3.1 require the 'binding' layout qualifier + out << "layout(binding = " << getUniqueSamplerBindingPoint() << ") "; + break; + + case TargetApi::ALL: + // should not happen break; } } diff --git a/libs/filamat/src/shaders/CodeGenerator.h b/libs/filamat/src/shaders/CodeGenerator.h index f7bb4af5ebb..dac8553c444 100644 --- a/libs/filamat/src/shaders/CodeGenerator.h +++ b/libs/filamat/src/shaders/CodeGenerator.h @@ -19,6 +19,7 @@ #include "MaterialInfo.h" +#include "UibGenerator.h" #include @@ -42,6 +43,8 @@ #include #include +#include + namespace filamat { class UTILS_PRIVATE CodeGenerator { @@ -123,8 +126,14 @@ class UTILS_PRIVATE CodeGenerator { // generate samplers utils::io::sstream& generateSamplers(utils::io::sstream& out, - filament::SamplerBindingPoints bindingPoint, uint8_t firstBinding, - const filament::SamplerInterfaceBlock& sib) const; + filament::DescriptorSetBindingPoints set, + filament::SamplerInterfaceBlock::SamplerInfoList const& list) const; + + utils::io::sstream& generateSamplers(utils::io::sstream& out, + filament::DescriptorSetBindingPoints set, + const filament::SamplerInterfaceBlock& sib) const { + return generateSamplers(out, set, sib.getSamplerInfoList()); + } // generate subpass static utils::io::sstream& generateSubpass(utils::io::sstream& out, @@ -132,7 +141,9 @@ class UTILS_PRIVATE CodeGenerator { // generate uniforms utils::io::sstream& generateUniforms(utils::io::sstream& out, ShaderStage stage, - filament::UniformBindingPoints binding, const filament::BufferInterfaceBlock& uib) const; + filament::DescriptorSetBindingPoints set, + filament::backend::descriptor_binding_t binding, + const filament::BufferInterfaceBlock& uib) const; // generate buffers utils::io::sstream& generateBuffers(utils::io::sstream& out, @@ -140,7 +151,9 @@ class UTILS_PRIVATE CodeGenerator { // generate an interface block utils::io::sstream& generateBufferInterfaceBlock(utils::io::sstream& out, ShaderStage stage, - uint32_t binding, const filament::BufferInterfaceBlock& uib) const; + filament::DescriptorSetBindingPoints set, + filament::backend::descriptor_binding_t binding, + const filament::BufferInterfaceBlock& uib) const; // generate material properties getters static utils::io::sstream& generateMaterialProperty(utils::io::sstream& out, @@ -172,9 +185,21 @@ class UTILS_PRIVATE CodeGenerator { // These constants must match the equivalent in MetalState.h. // These values represent the starting index for uniform, ssbo, and sampler group [[buffer(n)]] // bindings. See the chart at the top of MetalState.h. - static constexpr uint32_t METAL_UNIFORM_BUFFER_BINDING_START = 17u; - static constexpr uint32_t METAL_SAMPLER_GROUP_BINDING_START = 27u; - static constexpr uint32_t METAL_SSBO_BINDING_START = 0; + static constexpr uint32_t METAL_PUSH_CONSTANT_BUFFER_INDEX = 20u; + static constexpr uint32_t METAL_DESCRIPTOR_SET_BINDING_START = 21u; + static constexpr uint32_t METAL_DYNAMIC_OFFSET_BINDING = 25u; + + uint32_t getUniqueSamplerBindingPoint() const noexcept { + return mUniqueSamplerBindingPoint++; + } + + uint32_t getUniqueUboBindingPoint() const noexcept { + return mUniqueUboBindingPoint++; + } + + uint32_t getUniqueSsboBindingPoint() const noexcept { + return mUniqueSsboBindingPoint++; + } private: filament::backend::Precision getDefaultPrecision(ShaderStage stage) const; @@ -219,6 +244,9 @@ class UTILS_PRIVATE CodeGenerator { TargetApi mTargetApi; TargetLanguage mTargetLanguage; FeatureLevel mFeatureLevel; + mutable uint32_t mUniqueSamplerBindingPoint = 0; + mutable uint32_t mUniqueUboBindingPoint = 0; + mutable uint32_t mUniqueSsboBindingPoint = 0; }; } // namespace filamat diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h index 4979bed5c3c..4324d1a24fd 100644 --- a/libs/filamat/src/shaders/MaterialInfo.h +++ b/libs/filamat/src/shaders/MaterialInfo.h @@ -19,8 +19,6 @@ #include -#include "../SamplerBindingMap.h" - #include #include @@ -66,7 +64,6 @@ struct UTILS_PUBLIC MaterialInfo { filament::BufferInterfaceBlock uib; filament::SamplerInterfaceBlock sib; filament::SubpassInfo subpass; - filament::SamplerBindingMap samplerBindings; filament::ShaderQuality quality; filament::backend::FeatureLevel featureLevel; filament::backend::StereoscopicType stereoscopicType; diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 6ad24c01481..ff9486ad193 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -16,21 +16,30 @@ #include "ShaderGenerator.h" +#include "CodeGenerator.h" +#include "SibGenerator.h" +#include "UibGenerator.h" + #include +#include #include #include -#include +#include -#include "backend/DriverEnums.h" -#include "filamat/MaterialBuilder.h" -#include "CodeGenerator.h" -#include "SibGenerator.h" -#include "UibGenerator.h" +#include + +#include +#include +#include +#include #include +#include +#include + namespace filamat { using namespace filament; @@ -443,42 +452,45 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel, // uniforms cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FRAME_UNIFORMS, + UibGenerator::getPerViewUib()); cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib()); + DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::OBJECT_UNIFORMS, + UibGenerator::getPerRenderableUib()); const bool litVariants = material.isLit || material.hasShadowMultiplier; if (litVariants && filament::Variant::isShadowReceiverVariant(variant)) { cg.generateUniforms(vs, ShaderStage::FRAGMENT, - UniformBindingPoints::SHADOW, UibGenerator::getShadowUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::SHADOWS, + UibGenerator::getShadowUib()); } if (hasSkinningOrMorphing(variant, featureLevel)) { cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_RENDERABLE_BONES, + DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::BONES_UNIFORMS, UibGenerator::getPerRenderableBonesUib()); - cg.generateSamplers(vs, SamplerBindingPoints::PER_RENDERABLE_SKINNING, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_RENDERABLE_SKINNING), - SibGenerator::getPerRenderPrimitiveBonesSib(variant)); cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_RENDERABLE_MORPHING, + DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::MORPHING_UNIFORMS, UibGenerator::getPerRenderableMorphingUib()); - - cg.generateSamplers(vs, SamplerBindingPoints::PER_RENDERABLE_MORPHING, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_RENDERABLE_MORPHING), - SibGenerator::getPerRenderPrimitiveMorphingSib(variant)); + cg.generateSamplers(vs, DescriptorSetBindingPoints::PER_RENDERABLE, + SibGenerator::getPerRenderableSib(variant)); } cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib); + DescriptorSetBindingPoints::PER_MATERIAL, + +PerMaterialBindingPoints::MATERIAL_PARAMS, + material.uib); CodeGenerator::generateSeparator(vs); // TODO: should we generate per-view SIB in the vertex shader? - cg.generateSamplers(vs, SamplerBindingPoints::PER_MATERIAL_INSTANCE, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE), - material.sib); + cg.generateSamplers(vs, DescriptorSetBindingPoints::PER_MATERIAL, material.sib); // shader code CodeGenerator::generateCommon(vs, ShaderStage::VERTEX); @@ -498,7 +510,7 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage, MaterialBuilder::FeatureLevel featureLevel, MaterialInfo const& material, const filament::Variant variant, - Interpolation interpolation) const noexcept { + Interpolation interpolation, UserVariantFilterMask variantFilter) const noexcept { assert_invariant(filament::Variant::isValid(variant)); assert_invariant(mMaterialDomain != MaterialBuilder::MaterialDomain::COMPUTE); @@ -548,43 +560,76 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, // uniforms and samplers cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FRAME_UNIFORMS, + UibGenerator::getPerViewUib()); cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib()); + DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::OBJECT_UNIFORMS, + UibGenerator::getPerRenderableUib()); if (variant.hasDynamicLighting()) { cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::LIGHTS, UibGenerator::getLightsUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::LIGHTS, + UibGenerator::getLightsUib()); } bool const litVariants = material.isLit || material.hasShadowMultiplier; if (litVariants && filament::Variant::isShadowReceiverVariant(variant)) { cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::SHADOW, UibGenerator::getShadowUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::SHADOWS, + UibGenerator::getShadowUib()); } if (variant.hasDynamicLighting()) { cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::FROXEL_RECORDS, UibGenerator::getFroxelRecordUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::RECORD_BUFFER, + UibGenerator::getFroxelRecordUib()); + cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::FROXELS, UibGenerator::getFroxelsUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FROXEL_BUFFER, + UibGenerator::getFroxelsUib()); } cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib); + DescriptorSetBindingPoints::PER_MATERIAL, + +PerMaterialBindingPoints::MATERIAL_PARAMS, + material.uib); CodeGenerator::generateSeparator(fs); - if (featureLevel >= FeatureLevel::FEATURE_LEVEL_1) { // FIXME: generate only what we need - cg.generateSamplers(fs, SamplerBindingPoints::PER_VIEW, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_VIEW), - SibGenerator::getPerViewSib(variant)); + if (featureLevel >= FeatureLevel::FEATURE_LEVEL_1) { + assert_invariant(mMaterialDomain == MaterialDomain::SURFACE); + + auto const perViewDescriptorSetLayout = getPerViewDescriptorSetLayoutWithVariant( + variant, variantFilter, + material.isLit, material.reflectionMode, material.refractionMode); + + // this is the list of samplers we need to filter + auto list = SibGenerator::getPerViewSib(variant).getSamplerInfoList(); + + // remove all the samplers that are not included in the descriptor-set layout + list.erase( + std::remove_if(list.begin(), list.end(), + [&perViewDescriptorSetLayout](auto const& entry) { + auto pos = std::find_if( + perViewDescriptorSetLayout.bindings.begin(), + perViewDescriptorSetLayout.bindings.end(), + [&entry](const auto& item) { + return item.binding == entry.binding; + }); + return pos == perViewDescriptorSetLayout.bindings.end(); + }), list.end()); + + cg.generateSamplers(fs, DescriptorSetBindingPoints::PER_VIEW, list); } - cg.generateSamplers(fs, SamplerBindingPoints::PER_MATERIAL_INSTANCE, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE), - material.sib); + cg.generateSamplers(fs, DescriptorSetBindingPoints::PER_MATERIAL, material.sib); fs << "float filament_lodBias;\n"; @@ -648,14 +693,16 @@ std::string ShaderGenerator::createComputeProgram(filament::backend::ShaderModel CodeGenerator::generateCommonTypes(s, ShaderStage::COMPUTE); cg.generateUniforms(s, ShaderStage::COMPUTE, - UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FRAME_UNIFORMS, + UibGenerator::getPerViewUib()); cg.generateUniforms(s, ShaderStage::COMPUTE, - UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib); + DescriptorSetBindingPoints::PER_MATERIAL, + +PerMaterialBindingPoints::MATERIAL_PARAMS, + material.uib); - cg.generateSamplers(s, SamplerBindingPoints::PER_MATERIAL_INSTANCE, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE), - material.sib); + cg.generateSamplers(s, DescriptorSetBindingPoints::PER_MATERIAL, material.sib); // generate SSBO cg.generateBuffers(s, material.buffers); @@ -696,14 +743,16 @@ std::string ShaderGenerator::createPostProcessVertexProgram(ShaderModel sm, generatePostProcessMaterialVariantDefines(vs, PostProcessVariant(variantKey)); cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FRAME_UNIFORMS, + UibGenerator::getPerViewUib()); cg.generateUniforms(vs, ShaderStage::VERTEX, - UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib); + DescriptorSetBindingPoints::PER_MATERIAL, + +PerMaterialBindingPoints::MATERIAL_PARAMS, + material.uib); - cg.generateSamplers(vs, SamplerBindingPoints::PER_MATERIAL_INSTANCE, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE), - material.sib); + cg.generateSamplers(vs, DescriptorSetBindingPoints::PER_MATERIAL, material.sib); CodeGenerator::generatePostProcessCommon(vs, ShaderStage::VERTEX); CodeGenerator::generatePostProcessGetters(vs, ShaderStage::VERTEX); @@ -735,14 +784,16 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel sm, } cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib()); + DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FRAME_UNIFORMS, + UibGenerator::getPerViewUib()); cg.generateUniforms(fs, ShaderStage::FRAGMENT, - UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib); + DescriptorSetBindingPoints::PER_MATERIAL, + +PerMaterialBindingPoints::MATERIAL_PARAMS, + material.uib); - cg.generateSamplers(fs, SamplerBindingPoints::PER_MATERIAL_INSTANCE, - material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE), - material.sib); + cg.generateSamplers(fs, DescriptorSetBindingPoints::PER_MATERIAL, material.sib); // subpass CodeGenerator::generateSubpass(fs, material.subpass); @@ -787,4 +838,22 @@ bool ShaderGenerator::hasStereo( && featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0; } +backend::DescriptorSetLayout ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( + filament::Variant variant, + UserVariantFilterMask variantFilter, + bool isLit, + ReflectionMode reflectionMode, + RefractionMode refractionMode) { + if (filament::Variant::isValidDepthVariant(variant)) { + return descriptor_sets::getDepthVariantLayout(); + } + if (filament::Variant::isSSRVariant(variant)) { + return descriptor_sets::getSsrVariantLayout(); + } + // We need to filter out all the descriptors not included in the "resolved" layout below + return descriptor_sets::getPerViewDescriptorSetLayout( + MaterialDomain::SURFACE, variantFilter, + isLit, reflectionMode, refractionMode); +} + } // namespace filament diff --git a/libs/filamat/src/shaders/ShaderGenerator.h b/libs/filamat/src/shaders/ShaderGenerator.h index 1b419f3b06c..1c81411ca37 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.h +++ b/libs/filamat/src/shaders/ShaderGenerator.h @@ -20,16 +20,24 @@ #include "MaterialInfo.h" +#include "UibGenerator.h" + #include #include +#include #include +#include + #include #include -#include +#include + +#include +#include namespace filamat { @@ -61,7 +69,8 @@ class ShaderGenerator { MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage, MaterialBuilder::FeatureLevel featureLevel, MaterialInfo const& material, filament::Variant variant, - filament::Interpolation interpolation) const noexcept; + filament::Interpolation interpolation, + filament::UserVariantFilterMask variantFilter) const noexcept; std::string createComputeProgram(filament::backend::ShaderModel shaderModel, MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage, @@ -79,6 +88,13 @@ class ShaderGenerator { MaterialBuilder::FeatureLevel featureLevel, MaterialInfo const& material) noexcept; + static filament::backend::DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant( + filament::Variant variant, + filament::UserVariantFilterMask variantFilter, + bool isLit, + filament::ReflectionMode reflectionMode, + filament::RefractionMode refractionMode); + private: static void generateVertexDomainDefines(utils::io::sstream& out, filament::VertexDomain domain) noexcept; diff --git a/libs/filamat/src/shaders/SibGenerator.cpp b/libs/filamat/src/shaders/SibGenerator.cpp index b03a6f3035e..ec07d9b186f 100644 --- a/libs/filamat/src/shaders/SibGenerator.cpp +++ b/libs/filamat/src/shaders/SibGenerator.cpp @@ -21,6 +21,10 @@ #include "private/filament/SamplerInterfaceBlock.h" #include "private/filament/SibStructs.h" +#include + +#include + namespace filament { SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexcept { @@ -37,52 +41,42 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce // // For the SSR (reflections) SamplerInterfaceBlock, only two samplers are ever used, for this // reason we name them "unused*" to ensure we're not using them by mistake (type/format don't - // matter). This will not affect SamplerBindingMap because it always uses the default variant, - // and so the correct information will be stored in the material file. + // matter). static SamplerInterfaceBlock const sibPcf{ SamplerInterfaceBlock::Builder() - .name("Light") + .name("sampler0") .stageFlags(backend::ShaderStageFlags::FRAGMENT) - .add( {{ "shadowMap", Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM }, - { "iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, - { "iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }, - { "ssao", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, - { "ssr", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, - { "structure", Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }, - { "fog", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }} + .add( {{ "shadowMap", +PerViewBindingPoints::SHADOW_MAP, Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM }, + { "iblDFG", +PerViewBindingPoints::IBL_DFG_LUT, Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, + { "iblSpecular", +PerViewBindingPoints::IBL_SPECULAR, Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }, + { "ssao", +PerViewBindingPoints::SSAO, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, + { "ssr", +PerViewBindingPoints::SSR, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, + { "structure", +PerViewBindingPoints::STRUCTURE, Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }, + { "fog", +PerViewBindingPoints::FOG, Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }} ) .build() }; static SamplerInterfaceBlock const sibVsm{ SamplerInterfaceBlock::Builder() - .name("Light") + .name("sampler0") .stageFlags(backend::ShaderStageFlags::FRAGMENT) - .add( {{ "shadowMap", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH }, - { "iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, - { "iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }, - { "ssao", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, - { "ssr", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, - { "structure", Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }, - { "fog", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }} + .add( {{ "shadowMap", +PerViewBindingPoints::SHADOW_MAP, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH }, + { "iblDFG", +PerViewBindingPoints::IBL_DFG_LUT, Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, + { "iblSpecular", +PerViewBindingPoints::IBL_SPECULAR, Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }, + { "ssao", +PerViewBindingPoints::SSAO, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, + { "ssr", +PerViewBindingPoints::SSR, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::MEDIUM }, + { "structure", +PerViewBindingPoints::STRUCTURE, Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }, + { "fog", +PerViewBindingPoints::FOG, Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM }} ) .build() }; static SamplerInterfaceBlock const sibSsr{ SamplerInterfaceBlock::Builder() - .name("Light") + .name("sampler0") .stageFlags(backend::ShaderStageFlags::FRAGMENT) - .add( {{ "unused0" }, - { "unused1" }, - { "unused2" }, - { "unused3" }, - { "ssr", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, - { "structure", Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }, - { "unused5" }} + .add( {{ "ssr", +PerViewBindingPoints::SSR, Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM }, + { "structure", +PerViewBindingPoints::STRUCTURE, Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }} ) .build() }; - assert_invariant(sibPcf.getSize() == PerViewSib::SAMPLER_COUNT); - assert_invariant(sibVsm.getSize() == PerViewSib::SAMPLER_COUNT); - assert_invariant(sibSsr.getSize() == PerViewSib::SAMPLER_COUNT); - if (Variant::isSSRVariant(variant)) { return sibSsr; } else if (Variant::isVSMVariant(variant)) { @@ -92,43 +86,28 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce } } -SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveMorphingSib(Variant) noexcept { +SamplerInterfaceBlock const& SibGenerator::getPerRenderableSib(Variant) noexcept { using Type = SamplerInterfaceBlock::Type; using Format = SamplerInterfaceBlock::Format; using Precision = SamplerInterfaceBlock::Precision; static SamplerInterfaceBlock const sib = SamplerInterfaceBlock::Builder() - .name("MorphTargetBuffer") - .stageFlags(backend::ShaderStageFlags::VERTEX) - .add({ { "positions", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH }, - { "tangents", Type::SAMPLER_2D_ARRAY, Format::INT, Precision::HIGH }}) - .build(); - - return sib; -} - -SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveBonesSib(Variant variant) noexcept { - using Type = SamplerInterfaceBlock::Type; - using Format = SamplerInterfaceBlock::Format; - using Precision = SamplerInterfaceBlock::Precision; - - static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder() - .name("BonesBuffer") + .name("sampler1") .stageFlags(backend::ShaderStageFlags::VERTEX) - .add({{"indicesAndWeights", Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }}) + .add({ {"positions", +PerRenderableBindingPoints::MORPH_TARGET_POSITIONS, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH }, + {"tangents", +PerRenderableBindingPoints::MORPH_TARGET_TANGENTS, Type::SAMPLER_2D_ARRAY, Format::INT, Precision::HIGH }, + {"indicesAndWeights", +PerRenderableBindingPoints::BONES_INDICES_AND_WEIGHTS, Type::SAMPLER_2D, Format::FLOAT, Precision::HIGH }}) .build(); return sib; } -SamplerInterfaceBlock const* SibGenerator::getSib(SamplerBindingPoints bindingPoint, Variant variant) noexcept { - switch (bindingPoint) { - case SamplerBindingPoints::PER_VIEW: +SamplerInterfaceBlock const* SibGenerator::getSib(DescriptorSetBindingPoints set, Variant variant) noexcept { + switch (set) { + case DescriptorSetBindingPoints::PER_VIEW: return &getPerViewSib(variant); - case SamplerBindingPoints::PER_RENDERABLE_MORPHING: - return &getPerRenderPrimitiveMorphingSib(variant); - case SamplerBindingPoints::PER_RENDERABLE_SKINNING: - return &getPerRenderPrimitiveBonesSib(variant); + case DescriptorSetBindingPoints::PER_RENDERABLE: + return &getPerRenderableSib(variant); default: return nullptr; } diff --git a/libs/filamat/src/shaders/SibGenerator.h b/libs/filamat/src/shaders/SibGenerator.h index eb874653c50..b510d02d308 100644 --- a/libs/filamat/src/shaders/SibGenerator.h +++ b/libs/filamat/src/shaders/SibGenerator.h @@ -30,9 +30,8 @@ class SamplerInterfaceBlock; class SibGenerator { public: static SamplerInterfaceBlock const& getPerViewSib(Variant variant) noexcept; - static SamplerInterfaceBlock const& getPerRenderPrimitiveMorphingSib(Variant variant) noexcept; - static SamplerInterfaceBlock const& getPerRenderPrimitiveBonesSib(Variant variant) noexcept; - static SamplerInterfaceBlock const* getSib(filament::SamplerBindingPoints bindingPoint, Variant variant) noexcept; + static SamplerInterfaceBlock const& getPerRenderableSib(Variant variant) noexcept; + static SamplerInterfaceBlock const* getSib(filament::DescriptorSetBindingPoints bindingPoint, Variant variant) noexcept; // When adding a sampler block here, make sure to also update // FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed }; diff --git a/libs/filamat/src/shaders/UibGenerator.cpp b/libs/filamat/src/shaders/UibGenerator.cpp index 37361440274..9d4d360b1a1 100644 --- a/libs/filamat/src/shaders/UibGenerator.cpp +++ b/libs/filamat/src/shaders/UibGenerator.cpp @@ -22,10 +22,70 @@ #include #include +#include + +#include + namespace filament { using namespace backend; +BufferInterfaceBlock const& UibGenerator::get(UibGenerator::Ubo ubo) noexcept { + assert_invariant(ubo != Ubo::MaterialParams); + switch (ubo) { + case Ubo::FrameUniforms: + return getPerViewUib(); + case Ubo::ObjectUniforms: + return getPerRenderableUib(); + case Ubo::BonesUniforms: + return getPerRenderableBonesUib(); + case Ubo::MorphingUniforms: + return getPerRenderableMorphingUib(); + case Ubo::LightsUniforms: + return getLightsUib(); + case Ubo::ShadowUniforms: + return getShadowUib(); + case Ubo::FroxelRecordUniforms: + return getFroxelRecordUib(); + case Ubo::FroxelsUniforms: + return getFroxelsUib(); + case Ubo::MaterialParams: + abort(); + } +} + +UibGenerator::Binding UibGenerator::getBinding(UibGenerator::Ubo ubo) noexcept { + switch (ubo) { + case Ubo::FrameUniforms: + return { +DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FRAME_UNIFORMS }; + case Ubo::ObjectUniforms: + return { +DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::OBJECT_UNIFORMS }; + case Ubo::BonesUniforms: + return { +DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::BONES_UNIFORMS }; + case Ubo::MorphingUniforms: + return { +DescriptorSetBindingPoints::PER_RENDERABLE, + +PerRenderableBindingPoints::MORPHING_UNIFORMS }; + case Ubo::LightsUniforms: + return { +DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::LIGHTS }; + case Ubo::ShadowUniforms: + return { +DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::SHADOWS }; + case Ubo::FroxelRecordUniforms: + return { +DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::RECORD_BUFFER }; + case Ubo::FroxelsUniforms: + return { +DescriptorSetBindingPoints::PER_VIEW, + +PerViewBindingPoints::FROXEL_BUFFER }; + case Ubo::MaterialParams: + return { +DescriptorSetBindingPoints::PER_MATERIAL, + +PerMaterialBindingPoints::MATERIAL_PARAMS }; + } +} + static_assert(CONFIG_MAX_SHADOW_CASCADES == 4, "Changing CONFIG_MAX_SHADOW_CASCADES affects PerView size and breaks materials."); diff --git a/libs/filamat/src/shaders/UibGenerator.h b/libs/filamat/src/shaders/UibGenerator.h index a39c44c1230..a2250bdad98 100644 --- a/libs/filamat/src/shaders/UibGenerator.h +++ b/libs/filamat/src/shaders/UibGenerator.h @@ -17,12 +17,50 @@ #ifndef TNT_FILAMAT_UIBGENERATOR_H #define TNT_FILAMAT_UIBGENERATOR_H +#include + +#include + +#include + +#include +#include + namespace filament { class BufferInterfaceBlock; class UibGenerator { public: + // tag to represent a generated ubo. + enum class Ubo : uint8_t { + FrameUniforms, // uniforms updated per view + ObjectUniforms, // uniforms updated per renderable + BonesUniforms, // bones data, per renderable + MorphingUniforms, // morphing uniform/sampler updated per render primitive + LightsUniforms, // lights data array + ShadowUniforms, // punctual shadow data + FroxelRecordUniforms, // froxel records + FroxelsUniforms, // froxels + MaterialParams, // material instance ubo + // Update utils::Enum::count<>() below when adding values here + // These are limited by CONFIG_BINDING_COUNT (currently 10) + // When adding an UBO here, make sure to also update + // MaterialBuilder::writeCommonChunks() if needed + }; + + struct Binding { + backend::descriptor_set_t set; + backend::descriptor_binding_t binding; + }; + + // return the BufferInterfaceBlock for the given UBO tag + static BufferInterfaceBlock const& get(Ubo ubo) noexcept; + + // return the {set, binding } for the given UBO tag + static Binding getBinding(Ubo ubo) noexcept; + + // deprecate these... static BufferInterfaceBlock const& getPerViewUib() noexcept; static BufferInterfaceBlock const& getPerRenderableUib() noexcept; static BufferInterfaceBlock const& getLightsUib() noexcept; @@ -31,10 +69,15 @@ class UibGenerator { static BufferInterfaceBlock const& getPerRenderableMorphingUib() noexcept; static BufferInterfaceBlock const& getFroxelRecordUib() noexcept; static BufferInterfaceBlock const& getFroxelsUib() noexcept; - // When adding an UBO here, make sure to also update - // MaterialBuilder::writeCommonChunks() if needed }; } // namespace filament +template<> +struct utils::EnableIntegerOperators : public std::true_type {}; + +template<> +inline constexpr size_t utils::Enum::count() { return 9; } + + #endif // TNT_FILAMAT_UIBGENERATOR_H diff --git a/libs/filamat/tests/test_argBufferFixup.cpp b/libs/filamat/tests/test_argBufferFixup.cpp index 7a214bca34d..549b5b07354 100644 --- a/libs/filamat/tests/test_argBufferFixup.cpp +++ b/libs/filamat/tests/test_argBufferFixup.cpp @@ -88,6 +88,47 @@ TEST(ArgBufferFixup, TextureAndSampler) { MetalArgumentBuffer::destroy(&argBuffer); } +TEST(ArgBufferFixup, Buffer) { + auto argBuffer = + MetalArgumentBuffer::Builder() + .name("myArgumentBuffer") + .buffer(0, "FrameUniforms", "frameUniforms") + .build(); + auto argBufferStr = argBuffer->getMsl(); + + const std::string expected = + "struct FrameUniforms;\n" + "struct myArgumentBuffer {\n" + "constant FrameUniforms* frameUniforms [[id(0)]];\n" + "}"; + + EXPECT_EQ(argBuffer->getMsl(), expected); + + MetalArgumentBuffer::destroy(&argBuffer); +} + +TEST(ArgBufferFixup, MultipleBuffers) { + auto argBuffer = + MetalArgumentBuffer::Builder() + .name("myArgumentBuffer") + .buffer(0, "FrameUniforms", "frameUniforms") + .buffer(1, "ObjectUniforms", "objectUniforms") + .build(); + auto argBufferStr = argBuffer->getMsl(); + + const std::string expected = + "struct FrameUniforms;\n" + "struct ObjectUniforms;\n" + "struct myArgumentBuffer {\n" + "constant FrameUniforms* frameUniforms [[id(0)]];\n" + "constant ObjectUniforms* objectUniforms [[id(1)]];\n" + "}"; + + EXPECT_EQ(argBuffer->getMsl(), expected); + + MetalArgumentBuffer::destroy(&argBuffer); +} + TEST(ArgBufferFixup, TextureAndSamplerMS) { auto argBuffer = MetalArgumentBuffer::Builder() @@ -114,17 +155,20 @@ TEST(ArgBufferFixup, Sorted) { .name("myArgumentBuffer") .sampler(3, "samplerB") .texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false) + .buffer(4, "FrameUniforms", "frameUniforms") .texture(2, "textureB", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false) .sampler(1, "samplerA") .build(); auto argBufferStr = argBuffer->getMsl(); const std::string expected = + "struct FrameUniforms;\n" "struct myArgumentBuffer {\n" "texture2d textureA [[id(0)]];\n" "sampler samplerA [[id(1)]];\n" "texture2d textureB [[id(2)]];\n" "sampler samplerB [[id(3)]];\n" + "constant FrameUniforms* frameUniforms [[id(4)]];\n" "}"; EXPECT_EQ(argBuffer->getMsl(), expected); diff --git a/libs/iblprefilter/src/IBLPrefilterContext.cpp b/libs/iblprefilter/src/IBLPrefilterContext.cpp index 8a3c3b046c2..9e889f36a76 100644 --- a/libs/iblprefilter/src/IBLPrefilterContext.cpp +++ b/libs/iblprefilter/src/IBLPrefilterContext.cpp @@ -226,7 +226,7 @@ Texture* IBLPrefilterContext::EquirectangularToCubemap::operator()( Engine& engine = mContext.mEngine; View* const view = mContext.mView; Renderer* const renderer = mContext.mRenderer; - MaterialInstance* const mi = mEquirectMaterial->getDefaultInstance(); + MaterialInstance* const mi = mEquirectMaterial->createInstance(); FILAMENT_CHECK_PRECONDITION(equirect != nullptr) << "equirect is null!"; @@ -289,6 +289,8 @@ Texture* IBLPrefilterContext::EquirectangularToCubemap::operator()( engine.destroy(rt); } + engine.destroy(mi); + return outCube; } @@ -321,7 +323,7 @@ IBLPrefilterContext::IrradianceFilter::IrradianceFilter(IBLPrefilterContext& con .height(mSampleCount) .build(engine); - MaterialInstance* const mi = mKernelMaterial->getDefaultInstance(); + MaterialInstance* const mi = mKernelMaterial->createInstance(); mi->setParameter("size", uint2{ 1, mSampleCount }); mi->setParameter("sampleCount", float(mSampleCount)); @@ -339,6 +341,7 @@ IBLPrefilterContext::IrradianceFilter::IrradianceFilter(IBLPrefilterContext& con renderer->renderStandaloneView(view); engine.destroy(rt); + engine.destroy(mi); } UTILS_NOINLINE @@ -404,7 +407,7 @@ filament::Texture* IBLPrefilterContext::IrradianceFilter::operator()( Engine& engine = mContext.mEngine; View* const view = mContext.mView; Renderer* const renderer = mContext.mRenderer; - MaterialInstance* const mi = mContext.mIrradianceIntegrationMaterial->getDefaultInstance(); + MaterialInstance* const mi = mContext.mIrradianceIntegrationMaterial->createInstance(); RenderableManager& rcm = engine.getRenderableManager(); rcm.setMaterialInstanceAt( @@ -452,6 +455,8 @@ filament::Texture* IBLPrefilterContext::IrradianceFilter::operator()( engine.destroy(rt); } + engine.destroy(mi); + return outIrradianceTexture; } @@ -522,7 +527,7 @@ IBLPrefilterContext::SpecularFilter::SpecularFilter(IBLPrefilterContext& context roughnessArray[i] = roughness; } - MaterialInstance* const mi = mKernelMaterial->getDefaultInstance(); + MaterialInstance* const mi = mKernelMaterial->createInstance(); mi->setParameter("size", uint2{ mLevelCount, mSampleCount }); mi->setParameter("sampleCount", float(mSampleCount)); mi->setParameter("roughness", roughnessArray, 16); @@ -541,6 +546,7 @@ IBLPrefilterContext::SpecularFilter::SpecularFilter(IBLPrefilterContext& context renderer->renderStandaloneView(view); engine.destroy(rt); + engine.destroy(mi); } UTILS_NOINLINE @@ -634,7 +640,7 @@ Texture* IBLPrefilterContext::SpecularFilter::operator()( Engine& engine = mContext.mEngine; View* const view = mContext.mView; Renderer* const renderer = mContext.mRenderer; - MaterialInstance* const mi = mContext.mIntegrationMaterial->getDefaultInstance(); + MaterialInstance* const mi = mContext.mIntegrationMaterial->createInstance(); RenderableManager& rcm = engine.getRenderableManager(); rcm.setMaterialInstanceAt( @@ -701,5 +707,7 @@ Texture* IBLPrefilterContext::SpecularFilter::operator()( dim >>= 1; } + engine.destroy(mi); + return outReflectionsTexture; } diff --git a/libs/matdbg/src/TextWriter.cpp b/libs/matdbg/src/TextWriter.cpp index fca5bb0ca19..baa189a1b84 100644 --- a/libs/matdbg/src/TextWriter.cpp +++ b/libs/matdbg/src/TextWriter.cpp @@ -223,6 +223,7 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container) for (uint64_t i = 0; i < sibCount; i++) { CString fieldName; + uint8_t fieldBinding; uint8_t fieldType; uint8_t fieldFormat; uint8_t fieldPrecision; @@ -232,6 +233,10 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container) return false; } + if (!sib.read(&fieldBinding)) { + return false; + } + if (!sib.read(&fieldType)) { return false; } @@ -249,6 +254,7 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container) text << " " << setw(alignment) << fieldName.c_str() + << setw(shortAlignment) << +fieldBinding << setw(shortAlignment) << toString(SamplerType(fieldType)) << setw(shortAlignment) << toString(Precision(fieldPrecision)) << toString(SamplerFormat(fieldFormat)) diff --git a/libs/utils/include/utils/FixedCapacityVector.h b/libs/utils/include/utils/FixedCapacityVector.h index 1221e7cc326..e45916aed3b 100644 --- a/libs/utils/include/utils/FixedCapacityVector.h +++ b/libs/utils/include/utils/FixedCapacityVector.h @@ -84,7 +84,7 @@ class UTILS_PUBLIC FixedCapacityVector { FixedCapacityVector() = default; explicit FixedCapacityVector(const allocator_type& allocator) noexcept - : mCapacityAllocator({}, allocator) { + : mCapacityAllocator(0, allocator) { } explicit FixedCapacityVector(size_type size, const allocator_type& allocator = allocator_type()) diff --git a/libs/utils/src/EntityManagerImpl.h b/libs/utils/src/EntityManagerImpl.h index adf64a80c85..0465dc1cb1a 100644 --- a/libs/utils/src/EntityManagerImpl.h +++ b/libs/utils/src/EntityManagerImpl.h @@ -68,9 +68,6 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { std::lock_guard const lock(mFreeListLock); Entity::Type currentIndex = mCurrentIndex; for (size_t i = 0; i < n; i++) { - // If we have more than a certain number of freed indices, get one from the list. - // this is a trade-off between how often we recycle indices and how large the free list - // can grow. if (UTILS_UNLIKELY(currentIndex >= RAW_INDEX_COUNT || freeList.size() >= MIN_FREE_INDICES)) { // this could only happen if we had gone through all the indices at least once @@ -83,10 +80,6 @@ class UTILS_PRIVATE EntityManagerImpl : public EntityManager { index = freeList.front(); freeList.pop_front(); } else { - // In the common case, we just grab the next index. - // This works only until all indices have been used once, at which point - // we're always in the slower case above. The idea is that we have enough indices - // that it doesn't happen in practice. index = currentIndex++; } entities[i] = Entity{ makeIdentity(gens[index], index) }; diff --git a/shaders/src/ambient_occlusion.fs b/shaders/src/ambient_occlusion.fs index 32f6421986e..54acfd0f8b3 100644 --- a/shaders/src/ambient_occlusion.fs +++ b/shaders/src/ambient_occlusion.fs @@ -35,18 +35,18 @@ float evaluateSSAO(inout SSAOInterpolationCache cache) { // filter. This adds about 2.0ms @ 250MHz on Pixel 4. if (frameUniforms.aoSamplingQualityAndEdgeDistance > 0.0) { - highp vec2 size = vec2(textureSize(light_ssao, 0)); + highp vec2 size = vec2(textureSize(sampler0_ssao, 0)); // Read four AO samples and their depths values #if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) - vec4 ao = textureGather(light_ssao, vec3(cache.uv, 0.0), 0); - vec4 dg = textureGather(light_ssao, vec3(cache.uv, 0.0), 1); - vec4 db = textureGather(light_ssao, vec3(cache.uv, 0.0), 2); + vec4 ao = textureGather(sampler0_ssao, vec3(cache.uv, 0.0), 0); + vec4 dg = textureGather(sampler0_ssao, vec3(cache.uv, 0.0), 1); + vec4 db = textureGather(sampler0_ssao, vec3(cache.uv, 0.0), 2); #else - vec3 s01 = textureLodOffset(light_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(0, 1)).rgb; - vec3 s11 = textureLodOffset(light_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(1, 1)).rgb; - vec3 s10 = textureLodOffset(light_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(1, 0)).rgb; - vec3 s00 = textureLodOffset(light_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(0, 0)).rgb; + vec3 s01 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(0, 1)).rgb; + vec3 s11 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(1, 1)).rgb; + vec3 s10 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(1, 0)).rgb; + vec3 s00 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 0.0), 0.0, ivec2(0, 0)).rgb; vec4 ao = vec4(s01.r, s11.r, s10.r, s00.r); vec4 dg = vec4(s01.g, s11.g, s10.g, s00.g); vec4 db = vec4(s01.b, s11.b, s10.b, s00.b); @@ -74,7 +74,7 @@ float evaluateSSAO(inout SSAOInterpolationCache cache) { cache.weights = w / (w.x + w.y + w.z + w.w); return dot(ao, cache.weights); } else { - return textureLod(light_ssao, vec3(cache.uv, 0.0), 0.0).r; + return textureLod(sampler0_ssao, vec3(cache.uv, 0.0), 0.0).r; } #else // SSAO is not applied when blending is enabled @@ -160,14 +160,14 @@ float specularAO(float NoV, float visibility, float roughness, const in SSAOInte vec3 bn; if (frameUniforms.aoSamplingQualityAndEdgeDistance > 0.0) { #if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) - vec4 bnr = textureGather(light_ssao, vec3(cache.uv, 1.0), 0); - vec4 bng = textureGather(light_ssao, vec3(cache.uv, 1.0), 1); - vec4 bnb = textureGather(light_ssao, vec3(cache.uv, 1.0), 2); + vec4 bnr = textureGather(sampler0_ssao, vec3(cache.uv, 1.0), 0); + vec4 bng = textureGather(sampler0_ssao, vec3(cache.uv, 1.0), 1); + vec4 bnb = textureGather(sampler0_ssao, vec3(cache.uv, 1.0), 2); #else - vec3 s01 = textureLodOffset(light_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(0, 1)).rgb; - vec3 s11 = textureLodOffset(light_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(1, 1)).rgb; - vec3 s10 = textureLodOffset(light_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(1, 0)).rgb; - vec3 s00 = textureLodOffset(light_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(0, 0)).rgb; + vec3 s01 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(0, 1)).rgb; + vec3 s11 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(1, 1)).rgb; + vec3 s10 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(1, 0)).rgb; + vec3 s00 = textureLodOffset(sampler0_ssao, vec3(cache.uv, 1.0), 0.0, ivec2(0, 0)).rgb; vec4 bnr = vec4(s01.r, s11.r, s10.r, s00.r); vec4 bng = vec4(s01.g, s11.g, s10.g, s00.g); vec4 bnb = vec4(s01.b, s11.b, s10.b, s00.b); @@ -176,7 +176,7 @@ float specularAO(float NoV, float visibility, float roughness, const in SSAOInte bn.g = dot(bng, cache.weights); bn.b = dot(bnb, cache.weights); } else { - bn = textureLod(light_ssao, vec3(cache.uv, 1.0), 0.0).xyz; + bn = textureLod(sampler0_ssao, vec3(cache.uv, 1.0), 0.0).xyz; } bn = unpackBentNormal(bn); diff --git a/shaders/src/fog.fs b/shaders/src/fog.fs index fbd41720a46..d724db11750 100644 --- a/shaders/src/fog.fs +++ b/shaders/src/fog.fs @@ -55,7 +55,7 @@ vec4 fog(vec4 color, highp vec3 view) { // a rigid transform, so we can take the transpose instead of the inverse, and for the // same reason we can use it directly instead of taking the cof() to transform a vector. highp mat3 worldFromUserWorldMatrix = transpose(mat3(frameUniforms.userWorldFromWorldMatrix)); - fogColor *= textureLod(light_fog, worldFromUserWorldMatrix * view, lod).rgb; + fogColor *= textureLod(sampler0_fog, worldFromUserWorldMatrix * view, lod).rgb; } #endif diff --git a/shaders/src/getters.vs b/shaders/src/getters.vs index 024ec9ff411..09569c61554 100644 --- a/shaders/src/getters.vs +++ b/shaders/src/getters.vs @@ -87,7 +87,7 @@ void skinPosition(inout vec3 p, const uvec4 ids, const vec4 weights) { uint pairStop = pairIndex + uint(ids.w - 3u); for (uint i = pairIndex; i < pairStop; ++i) { ivec2 texcoord = ivec2(i % MAX_SKINNING_BUFFER_WIDTH, i / MAX_SKINNING_BUFFER_WIDTH); - vec2 indexWeight = texelFetch(bonesBuffer_indicesAndWeights, texcoord, 0).rg; + vec2 indexWeight = texelFetch(sampler1_indicesAndWeights, texcoord, 0).rg; posSum += mulBoneVertex(p, uint(indexWeight.r)) * indexWeight.g; } p = posSum; @@ -110,7 +110,7 @@ void skinNormal(inout vec3 n, const uvec4 ids, const vec4 weights) { uint pairStop = pairIndex + uint(ids.w - 3u); for (uint i = pairIndex; i < pairStop; i = i + 1u) { ivec2 texcoord = ivec2(i % MAX_SKINNING_BUFFER_WIDTH, i / MAX_SKINNING_BUFFER_WIDTH); - vec2 indexWeight = texelFetch(bonesBuffer_indicesAndWeights, texcoord, 0).rg; + vec2 indexWeight = texelFetch(sampler1_indicesAndWeights, texcoord, 0).rg; normSum += mulBoneNormal(n, uint(indexWeight.r)) * indexWeight.g; } @@ -141,7 +141,7 @@ void skinNormalTangent(inout vec3 n, inout vec3 t, const uvec4 ids, const vec4 w uint pairStop = pairIndex + uint(ids.w - 3u); for (uint i = pairIndex; i < pairStop; i = i + 1u) { ivec2 texcoord = ivec2(i % MAX_SKINNING_BUFFER_WIDTH, i / MAX_SKINNING_BUFFER_WIDTH); - vec2 indexWeight = texelFetch(bonesBuffer_indicesAndWeights, texcoord, 0).rg; + vec2 indexWeight = texelFetch(sampler1_indicesAndWeights, texcoord, 0).rg; normSum += mulBoneNormal(n, uint(indexWeight.r)) * indexWeight.g; tangSum += mulBoneNormal(t, uint(indexWeight.r)) * indexWeight.g; @@ -160,7 +160,7 @@ void morphPosition(inout vec4 p) { float w = morphingUniforms.weights[i][0]; if (w != 0.0) { texcoord.z = i; - p += w * texelFetch(morphTargetBuffer_positions, texcoord, 0); + p += w * texelFetch(sampler1_positions, texcoord, 0); } } } @@ -174,7 +174,7 @@ void morphNormal(inout vec3 n) { float w = morphingUniforms.weights[i][0]; if (w != 0.0) { texcoord.z = i; - ivec4 tangent = texelFetch(morphTargetBuffer_tangents, texcoord, 0); + ivec4 tangent = texelFetch(sampler1_tangents, texcoord, 0); vec3 normal; toTangentFrame(float4(tangent) * (1.0 / 32767.0), normal); n += w * (normal - baseNormal); diff --git a/shaders/src/light_directional.fs b/shaders/src/light_directional.fs index b6d902d7440..9ef63c79e86 100644 --- a/shaders/src/light_directional.fs +++ b/shaders/src/light_directional.fs @@ -57,7 +57,7 @@ void evaluateDirectionalLight(const MaterialInputs material, bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1); if (hasDirectionalShadows && cascadeHasVisibleShadows) { highp vec4 shadowPosition = getShadowPosition(cascade); - visibility = shadow(true, light_shadowMap, cascade, shadowPosition, 0.0); + visibility = shadow(true, sampler0_shadowMap, cascade, shadowPosition, 0.0); // shadow far attenuation highp vec3 v = getWorldPosition() - getWorldCameraPosition(); // (viewFromWorld * v).z == dot(transpose(viewFromWorld), v) diff --git a/shaders/src/light_indirect.fs b/shaders/src/light_indirect.fs index 793e4501bc2..ac0dc1cb2f3 100644 --- a/shaders/src/light_indirect.fs +++ b/shaders/src/light_indirect.fs @@ -24,7 +24,7 @@ vec3 decodeDataForIBL(const vec4 data) { vec3 PrefilteredDFG_LUT(float lod, float NoV) { // coord = sqrt(linear_roughness), which is the mapping used by cmgen. - return textureLod(light_iblDFG, vec2(NoV, lod), 0.0).rgb; + return textureLod(sampler0_iblDFG, vec2(NoV, lod), 0.0).rgb; } //------------------------------------------------------------------------------ @@ -64,7 +64,7 @@ vec3 Irradiance_SphericalHarmonics(const vec3 n) { vec3 Irradiance_RoughnessOne(const vec3 n) { // note: lod used is always integer, hopefully the hardware skips tri-linear filtering - return decodeDataForIBL(textureLod(light_iblSpecular, n, frameUniforms.iblRoughnessOneLevel)); + return decodeDataForIBL(textureLod(sampler0_iblSpecular, n, frameUniforms.iblRoughnessOneLevel)); } //------------------------------------------------------------------------------ @@ -89,7 +89,7 @@ vec3 diffuseIrradiance(const vec3 n) { return Irradiance_RoughnessOne(n); } #else - ivec2 s = textureSize(light_iblSpecular, int(frameUniforms.iblRoughnessOneLevel)); + ivec2 s = textureSize(sampler0_iblSpecular, int(frameUniforms.iblRoughnessOneLevel)); float du = 1.0 / float(s.x); float dv = 1.0 / float(s.y); vec3 m0 = normalize(cross(n, vec3(0.0, 1.0, 0.0))); @@ -121,12 +121,12 @@ float perceptualRoughnessToLod(float perceptualRoughness) { vec3 prefilteredRadiance(const vec3 r, float perceptualRoughness) { float lod = perceptualRoughnessToLod(perceptualRoughness); - return decodeDataForIBL(textureLod(light_iblSpecular, r, lod)); + return decodeDataForIBL(textureLod(sampler0_iblSpecular, r, lod)); } vec3 prefilteredRadiance(const vec3 r, float roughness, float offset) { float lod = frameUniforms.iblRoughnessOneLevel * roughness; - return decodeDataForIBL(textureLod(light_iblSpecular, r, lod + offset)); + return decodeDataForIBL(textureLod(sampler0_iblSpecular, r, lod + offset)); } vec3 getSpecularDominantDirection(const vec3 n, const vec3 r, float roughness) { @@ -252,7 +252,7 @@ vec3 isEvaluateSpecularIBL(const PixelParams pixel, const vec3 n, const vec3 v, T *= R; float roughness = pixel.roughness; - float dim = float(textureSize(light_iblSpecular, 0).x); + float dim = float(textureSize(sampler0_iblSpecular, 0).x); float omegaP = (4.0 * PI) / (6.0 * dim * dim); vec3 indirectSpecular = vec3(0.0); @@ -273,7 +273,7 @@ vec3 isEvaluateSpecularIBL(const PixelParams pixel, const vec3 n, const vec3 v, // PDF inverse (we must use D_GGX() here, which is used to generate samples) float ipdf = (4.0 * LoH) / (D_GGX(roughness, NoH, h) * NoH); float mipLevel = prefilteredImportanceSampling(ipdf, omegaP); - vec3 L = decodeDataForIBL(textureLod(light_iblSpecular, l, mipLevel)); + vec3 L = decodeDataForIBL(textureLod(sampler0_iblSpecular, l, mipLevel)); float D = distribution(roughness, NoH, h); float V = visibility(roughness, NoV, NoL); @@ -310,7 +310,7 @@ vec3 isEvaluateDiffuseIBL(const PixelParams pixel, vec3 n, vec3 v) { R[2] = vec3( 0, 0, 1); T *= R; - float dim = float(textureSize(light_iblSpecular, 0).x); + float dim = float(textureSize(sampler0_iblSpecular, 0).x); float omegaP = (4.0 * PI) / (6.0 * dim * dim); vec3 indirectDiffuse = vec3(0.0); @@ -329,7 +329,7 @@ vec3 isEvaluateDiffuseIBL(const PixelParams pixel, vec3 n, vec3 v) { float ipdf = PI / NoL; // we have to bias the mipLevel (+1) to help with very strong highlights float mipLevel = prefilteredImportanceSampling(ipdf, omegaP) + 1.0; - vec3 L = decodeDataForIBL(textureLod(light_iblSpecular, l, mipLevel)); + vec3 L = decodeDataForIBL(textureLod(sampler0_iblSpecular, l, mipLevel)); indirectDiffuse += L; } } @@ -543,7 +543,7 @@ vec3 evaluateRefraction( // distance to camera plane const float invLog2sqrt5 = 0.8614; float lod = max(0.0, (2.0 * log2(perceptualRoughness) + frameUniforms.refractionLodOffset) * invLog2sqrt5); - Ft = textureLod(light_ssr, vec3(p.xy, 0.0), lod).rgb; + Ft = textureLod(sampler0_ssr, vec3(p.xy, 0.0), lod).rgb; #endif // base color changes the amount of light passing through the boundary @@ -585,7 +585,7 @@ void evaluateIBL(const MaterialInputs material, const PixelParams pixel, inout v const float invLog2sqrt5 = 0.8614; float d = -mulMat4x4Float3(getViewFromWorldMatrix(), getWorldPosition()).z; float lod = max(0.0, (log2(pixel.roughness / d) + frameUniforms.refractionLodOffset) * invLog2sqrt5); - ssrFr = textureLod(light_ssr, vec3(interpolationCache.uv, 1.0), lod); + ssrFr = textureLod(sampler0_ssr, vec3(interpolationCache.uv, 1.0), lod); } } #else // BLEND_MODE_OPAQUE diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index 0db8a4ebf23..fa55b057281 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -220,7 +220,7 @@ void evaluatePunctualLights(const MaterialInputs material, vec4(getWorldPosition(), 1.0)); } highp vec4 shadowPosition = getShadowPosition(shadowIndex, light.direction, light.zLight); - visibility = shadow(false, light_shadowMap, shadowIndex, + visibility = shadow(false, sampler0_shadowMap, shadowIndex, shadowPosition, light.zLight); } if (light.contactShadows && visibility > 0.0) { diff --git a/shaders/src/light_reflections.fs b/shaders/src/light_reflections.fs index ce6be8f7baa..b2f9d3cc729 100644 --- a/shaders/src/light_reflections.fs +++ b/shaders/src/light_reflections.fs @@ -220,7 +220,7 @@ vec4 evaluateScreenSpaceReflections(const highp vec3 wsRayDirection) { float maxRayTraceDistance = frameUniforms.ssrDistance; - highp vec2 res = vec2(textureSize(light_structure, 0).xy); + highp vec2 res = vec2(textureSize(sampler0_structure, 0).xy); highp mat4 uvFromViewMatrix = scaleMatrix(res.x, res.y) * frameUniforms.ssrUvFromViewMatrix; @@ -231,7 +231,7 @@ vec4 evaluateScreenSpaceReflections(const highp vec3 wsRayDirection) { highp vec2 hitPixel; // not currently used highp vec3 vsHitPoint; - if (traceScreenSpaceRay(vsOrigin, vsDirection, uvFromViewMatrix, light_structure, + if (traceScreenSpaceRay(vsOrigin, vsDirection, uvFromViewMatrix, sampler0_structure, vsZThickness, nearPlaneZ, stride, jitterFraction, maxSteps, maxRayTraceDistance, hitPixel, vsHitPoint)) { highp vec4 reprojected = mulMat4x4Float3(frameUniforms.ssrReprojection, vsHitPoint); @@ -256,7 +256,7 @@ vec4 evaluateScreenSpaceReflections(const highp vec3 wsRayDirection) { fade *= (1.0 - max(0.0, vsDirection.z)); // we output a premultiplied alpha color because this is going to be mipmapped - Fr = vec4(textureLod(light_ssr, reprojected.xy, 0.0).rgb * fade, fade); + Fr = vec4(textureLod(sampler0_ssr, reprojected.xy, 0.0).rgb * fade, fade); } return Fr; } diff --git a/shaders/src/main.fs b/shaders/src/main.fs index 1bc74372c13..c0d6bdb61f8 100644 --- a/shaders/src/main.fs +++ b/shaders/src/main.fs @@ -77,7 +77,7 @@ void main() { vec4 c = vec4(1.0, 0, 1.0, 1.0) * a; fragColor = mix(fragColor, c, 0.2); } else { - highp vec2 size = vec2(textureSize(light_shadowMap, 0)); + highp vec2 size = vec2(textureSize(sampler0_shadowMap, 0)); highp int ix = int(floor(p.x * size.x)); highp int iy = int(floor(p.y * size.y)); float t = float((ix ^ iy) & 1) * 0.2; diff --git a/shaders/src/shading_unlit.fs b/shaders/src/shading_unlit.fs index 33ea12cd80a..a3ff0059c86 100644 --- a/shaders/src/shading_unlit.fs +++ b/shaders/src/shading_unlit.fs @@ -46,7 +46,7 @@ vec4 evaluateMaterial(const MaterialInputs material) { bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1); if (hasDirectionalShadows && cascadeHasVisibleShadows) { highp vec4 shadowPosition = getShadowPosition(cascade); - visibility = shadow(true, light_shadowMap, cascade, shadowPosition, 0.0); + visibility = shadow(true, sampler0_shadowMap, cascade, shadowPosition, 0.0); // shadow far attenuation highp vec3 v = getWorldPosition() - getWorldCameraPosition(); // (viewFromWorld * v).z == dot(transpose(viewFromWorld), v) diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 9a3eb63302f..50f44e26c1d 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -400,7 +400,7 @@ float screenSpaceContactShadow(vec3 lightDirection) { highp vec3 ray; for (int i = 0 ; i < kStepCount ; i++, t += dt) { ray = rayData.uvRayStart + rayData.uvRay * t; - highp float z = textureLod(light_structure, uvToRenderTargetUV(ray.xy), 0.0).r; + highp float z = textureLod(sampler0_structure, uvToRenderTargetUV(ray.xy), 0.0).r; highp float dz = z - ray.z; if (abs(tolerance - dz) < tolerance) { occlusion = 1.0; From e85c22c6f5d54f72eb45a8c44b89c456fefebaa7 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Mon, 30 Sep 2024 10:54:10 -0700 Subject: [PATCH 06/36] Fix incomplete use of MI for debugCombineArrayTexture (#8168) `commit` call is required before `use`, which became a new norm for the new descriptor set design. --- filament/src/PostProcessManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 98150556f6b..df04a18c7ce 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -3410,6 +3410,7 @@ FrameGraphId PostProcessManager::debugCombineArrayTexture(Fra float(vp.width) / inputDesc.width, float(vp.height) / inputDesc.height }); + mi->commit(driver); mi->use(driver); auto pipeline = material.getPipelineState(mEngine); From 93d28bc16aa0cebf2239819119939d10f8509753 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 1 Oct 2024 09:53:14 -0700 Subject: [PATCH 07/36] Change std::memcpy to memcpy in SkinningBuffer (#8169) --- filament/src/details/SkinningBuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament/src/details/SkinningBuffer.cpp b/filament/src/details/SkinningBuffer.cpp index 0c5759323ab..a9f668c32af 100644 --- a/filament/src/details/SkinningBuffer.cpp +++ b/filament/src/details/SkinningBuffer.cpp @@ -200,7 +200,7 @@ void updateDataAt(backend::DriverApi& driver, size_t const elementSize = sizeof(float2); size_t const size = getSkinningBufferSize(count); auto* out = (float2*)malloc(size); - std::memcpy(out, pairs.begin(), size); + memcpy(out, pairs.begin(), size); size_t const textureWidth = getSkinningBufferWidth(count); size_t const lineCount = count / textureWidth; From 4f5369cefa6858e6445281ea3850a6676ddfaa4e Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 1 Oct 2024 17:09:02 +0000 Subject: [PATCH 08/36] Release Filament 1.55.0 --- README.md | 4 ++-- RELEASE_NOTES.md | 3 +++ android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6d1f5f7e1c9..1daff364f40 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.54.5' + implementation 'com.google.android.filament:filament-android:1.55.0' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.54.5' +pod 'Filament', '~> 1.55.0' ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e6727d791a7..b5522d40a21 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.55.1 + + ## v1.55.0 - Add descriptor sets to describe shader resources. [⚠️ **New Material Version**] diff --git a/android/gradle.properties b/android/gradle.properties index 85a92d8700c..19e07a87034 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.54.5 +VERSION_NAME=1.55.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index a4f0521f66a..cbddfbcb0c2 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.54.5" + spec.version = "1.55.0" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.54.5/filament-v1.54.5-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.0/filament-v1.55.0-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 9860fd38434..58c8ffdb202 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.54.5", + "version": "1.55.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From e77ae6ec2c76528422ba0da09a50e7442308cb98 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 2 Oct 2024 06:24:41 -0700 Subject: [PATCH 09/36] Work around client descriptor set issues (#8171) - We change GLDescriptorSet::Buffer default constructor to workaround a client's compiler set up issue. - We removed the assert_invariant that checks that ubo/samplers are not changed after committed in DescriptorSet. This caused an existing client's build to crash. --- filament/backend/src/opengl/GLDescriptorSet.h | 8 ++++++-- filament/src/ds/DescriptorSet.cpp | 20 +++++++++++++++++-- filament/src/ds/DescriptorSet.h | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/filament/backend/src/opengl/GLDescriptorSet.h b/filament/backend/src/opengl/GLDescriptorSet.h index 26a783b740c..51ac2afd884 100644 --- a/filament/backend/src/opengl/GLDescriptorSet.h +++ b/filament/backend/src/opengl/GLDescriptorSet.h @@ -78,8 +78,12 @@ struct GLDescriptorSet : public HwDescriptorSet { private: // a Buffer Descriptor such as SSBO or UBO with static offset struct Buffer { - Buffer() = default; - explicit Buffer(GLenum target) noexcept : target(target) { } + // Workaround: we cannot define the following as Buffer() = default because one of our + // clients has their compiler set up where such declaration (possibly coupled with explicit) + // will be considered a deleted constructor. + Buffer() {} + + explicit Buffer(GLenum target) noexcept : target(target) {} GLenum target; // 4 GLuint id = 0; // 4 uint32_t offset = 0; // 4 diff --git a/filament/src/ds/DescriptorSet.cpp b/filament/src/ds/DescriptorSet.cpp index 50667978408..9d41b20a33f 100644 --- a/filament/src/ds/DescriptorSet.cpp +++ b/filament/src/ds/DescriptorSet.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -44,7 +45,8 @@ DescriptorSet::~DescriptorSet() noexcept { DescriptorSet::DescriptorSet(DescriptorSetLayout const& descriptorSetLayout) noexcept : mDescriptors(descriptorSetLayout.getMaxDescriptorBinding() + 1), - mDirty(std::numeric_limits::max()) { + mDirty(std::numeric_limits::max()), + mSetAfterCommitWarning(false) { } DescriptorSet::DescriptorSet(DescriptorSet&& rhs) noexcept = default; @@ -57,6 +59,7 @@ DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept { mDescriptorSetHandle = std::move(rhs.mDescriptorSetHandle); mDirty = rhs.mDirty; mValid = rhs.mValid; + mSetAfterCommitWarning = rhs.mSetAfterCommitWarning; } return *this; } @@ -103,8 +106,21 @@ void DescriptorSet::bind(FEngine::DriverApi& driver, DescriptorSetBindingPoints void DescriptorSet::bind(FEngine::DriverApi& driver, DescriptorSetBindingPoints set, backend::DescriptorSetOffsetArray dynamicOffsets) const noexcept { // TODO: on debug check that dynamicOffsets is large enough - assert_invariant(mDirty.none()); + assert_invariant(mDescriptorSetHandle); + + // TODO: Make sure clients do the right thing and not change material instance parameters + // within the renderpass. We have to comment the assert out since it crashed a client's debug + // build. + // assert_invariant(mDirty.none()); + if (mDirty.any() && !mSetAfterCommitWarning) { + mDirty.forEachSetBit([&](uint8_t binding) { + utils::slog.w << "Descriptor set (handle=" << mDescriptorSetHandle.getId() + << ") binding=" << (int) binding + << " was set between begin/endRenderPass" << utils::io::endl; + }); + mSetAfterCommitWarning = true; + } driver.bindDescriptorSet(mDescriptorSetHandle, +set, std::move(dynamicOffsets)); } diff --git a/filament/src/ds/DescriptorSet.h b/filament/src/ds/DescriptorSet.h index 4b8d6d02dfa..2bfa8be6680 100644 --- a/filament/src/ds/DescriptorSet.h +++ b/filament/src/ds/DescriptorSet.h @@ -103,6 +103,7 @@ class DescriptorSet { mutable utils::bitset64 mDirty; // 8 mutable utils::bitset64 mValid; // 8 backend::DescriptorSetHandle mDescriptorSetHandle; // 4 + mutable bool mSetAfterCommitWarning; // 1 }; } // namespace filament From 3ed9e8f2c0ae8086c331584266ec3f13f148762b Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 2 Oct 2024 13:45:05 -0700 Subject: [PATCH 10/36] Initialize bool in DescriptorSet.h (#8173) This class has a ( = default) constructor and hence should have explicit initialization in its definition. --- filament/src/ds/DescriptorSet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament/src/ds/DescriptorSet.h b/filament/src/ds/DescriptorSet.h index 2bfa8be6680..7a9f57d202c 100644 --- a/filament/src/ds/DescriptorSet.h +++ b/filament/src/ds/DescriptorSet.h @@ -103,7 +103,7 @@ class DescriptorSet { mutable utils::bitset64 mDirty; // 8 mutable utils::bitset64 mValid; // 8 backend::DescriptorSetHandle mDescriptorSetHandle; // 4 - mutable bool mSetAfterCommitWarning; // 1 + mutable bool mSetAfterCommitWarning = false; // 1 }; } // namespace filament From 739d4007e2cc97712495d4cbd4caaa86d6b03c27 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 3 Oct 2024 15:16:56 -0700 Subject: [PATCH 11/36] Fix imported texture path (#8175) We need to also account for external image textures that are imported in texture creation. --- filament/src/details/Texture.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index dc011cdfa35..2f405fd2ab3 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -233,14 +233,15 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) { mSwizzle = builder->mSwizzle; mTextureIsSwizzled = builder->mTextureIsSwizzled; - if (mTarget == SamplerType::SAMPLER_EXTERNAL) { + bool const isImported = builder->mImportedId != 0; + if (mTarget == SamplerType::SAMPLER_EXTERNAL && !isImported) { // mHandle and mHandleForSampling will be created in setExternalImage() // If this Texture is used for sampling before setExternalImage() is called, // we'll lazily create a 1x1 placeholder texture. return; } - if (UTILS_LIKELY(builder->mImportedId == 0)) { + if (UTILS_LIKELY(!isImported)) { mHandle = driver.createTexture( mTarget, mLevelCount, mFormat, mSampleCount, mWidth, mHeight, mDepth, mUsage); } else { From 1809aa7b118a8c9c51800ea07768fbc82c77dafb Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Thu, 3 Oct 2024 17:23:20 -0700 Subject: [PATCH 12/36] Fix OpenGL ES 2.0 descriptor set crash (#8176) --- filament/backend/src/opengl/GLDescriptorSet.cpp | 4 +++- filament/backend/src/opengl/OpenGLDriver.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/filament/backend/src/opengl/GLDescriptorSet.cpp b/filament/backend/src/opengl/GLDescriptorSet.cpp index eb54fc90996..26dcdedb6e6 100644 --- a/filament/backend/src/opengl/GLDescriptorSet.cpp +++ b/filament/backend/src/opengl/GLDescriptorSet.cpp @@ -67,7 +67,9 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds bool const dynamicOffset = any(entry.flags & DescriptorFlags::DYNAMIC_OFFSET); dynamicBuffers.set(index, dynamicOffset); if (UTILS_UNLIKELY(gl.isES2())) { - dynamicBufferCount++; + if (dynamicOffset) { + dynamicBufferCount++; + } desc.emplace(dynamicOffset); } else { auto const type = GLUtils::getBufferBindingType(BufferObjectBinding::UNIFORM); diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index bfa04089ebb..20cd4f49c49 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -3892,6 +3892,7 @@ void OpenGLDriver::bindDescriptorSet( // `offsets` data's lifetime will end when this function returns. We have to make a copy. // (the data is allocated inside the CommandStream) mBoundDescriptorSets[set].dsh = dsh; + assert_invariant(offsets.data() != nullptr || ds->getDynamicBufferCount() == 0); std::copy_n(offsets.data(), ds->getDynamicBufferCount(), mBoundDescriptorSets[set].offsets.data()); } From 3bd4c45d6e217ebbbcfee2be2cb6183f6f5d9a7e Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 4 Oct 2024 07:41:11 -0700 Subject: [PATCH 13/36] workaround Mesa glDeleteBuffers() bug Mesa always clears the generic binding if the buffer deleted is bound to an indexed binding, even if it's not bound to the generic binding. BUGS=[371324321] --- filament/backend/src/opengl/OpenGLContext.cpp | 45 +++++++++++-------- filament/backend/src/opengl/OpenGLContext.h | 10 ++++- filament/backend/src/opengl/OpenGLDriver.cpp | 4 +- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index a7933407bb4..826994c3656 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -552,6 +552,14 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts, } else if (strstr(renderer, "AMD") || strstr(renderer, "ATI")) { // AMD/ATI GPU + } else if (strstr(vendor, "Mesa")) { + // Seen on + // [Mesa], + // [llvmpipe (LLVM 17.0.6, 256 bits)], + // [4.5 (Core Profile) Mesa 24.0.6-1], + // [4.50] + // not known which version are affected + bugs->rebind_buffer_after_deletion = true; } else if (strstr(renderer, "Mozilla")) { bugs->disable_invalidate_framebuffer = true; } @@ -929,15 +937,19 @@ void OpenGLContext::unbindSampler(GLuint sampler) noexcept { } } -void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept { - glDeleteBuffers(n, buffers); +void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept { + glDeleteBuffers(1, &buffer); + // bindings of bound buffers are reset to 0 - const size_t targetIndex = getIndexForBufferTarget(target); - auto& genericBuffer = state.buffers.genericBinding[targetIndex]; - UTILS_NOUNROLL - for (GLsizei i = 0; i < n; ++i) { - if (genericBuffer == buffers[i]) { - genericBuffer = 0; + size_t const targetIndex = getIndexForBufferTarget(target); + auto& genericBinding = state.buffers.genericBinding[targetIndex]; + if (genericBinding == buffer) { + genericBinding = 0; + } + + if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) { + if (genericBinding) { + glBindBuffer(target, genericBinding); } } @@ -946,16 +958,13 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe (target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER)); if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) { - auto& indexedBuffer = state.buffers.targets[targetIndex]; - UTILS_NOUNROLL // clang generates >1 KiB of code!! - for (GLsizei i = 0; i < n; ++i) { - UTILS_NOUNROLL - for (auto& buffer : indexedBuffer.buffers) { - if (buffer.name == buffers[i]) { - buffer.name = 0; - buffer.offset = 0; - buffer.size = 0; - } + auto& indexedBinding = state.buffers.targets[targetIndex]; + UTILS_NOUNROLL + for (auto& entry: indexedBinding.buffers) { + if (entry.name == buffer) { + entry.name = 0; + entry.offset = 0; + entry.size = 0; } } } diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index 4544ca104fc..39ceb346787 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -190,7 +190,7 @@ class OpenGLContext final : public TimerQueryFactoryInterface { inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; inline void depthRange(GLclampf near, GLclampf far) noexcept; - void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept; + void deleteBuffer(GLuint buffer, GLenum target) noexcept; void deleteVertexArray(GLuint vao) noexcept; void destroyWithContext(size_t index, std::function const& closure) noexcept; @@ -316,10 +316,15 @@ class OpenGLContext final : public TimerQueryFactoryInterface { // a glFinish. So we must delay the destruction until we know the GPU is finished. bool delay_fbo_destruction; + // Mesa sometimes clears the generic buffer binding when *another* buffer is destroyed, + // if that other buffer is bound on an *indexed* buffer binding. + bool rebind_buffer_after_deletion; + // Force feature level 0. Typically used for low end ES3 devices with significant driver // bugs or performance issues. bool force_feature_level0; + } bugs = {}; // state getters -- as needed. @@ -554,6 +559,9 @@ class OpenGLContext final : public TimerQueryFactoryInterface { { bugs.delay_fbo_destruction, "delay_fbo_destruction", ""}, + { bugs.rebind_buffer_after_deletion, + "rebind_buffer_after_deletion", + ""}, { bugs.force_feature_level0, "force_feature_level0", ""}, diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 20cd4f49c49..a606d090870 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1732,7 +1732,7 @@ void OpenGLDriver::destroyIndexBuffer(Handle ibh) { if (ibh) { auto& gl = mContext; GLIndexBuffer const* ib = handle_cast(ibh); - gl.deleteBuffers(1, &ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER); + gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER); destruct(ibh, ib); } } @@ -1745,7 +1745,7 @@ void OpenGLDriver::destroyBufferObject(Handle boh) { if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) { free(bo->gl.buffer); } else { - gl.deleteBuffers(1, &bo->gl.id, bo->gl.binding); + gl.deleteBuffer(bo->gl.id, bo->gl.binding); } destruct(boh, bo); } From 3b9fc8f7512408dd0b669e0c0b3997524f0963c1 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 4 Oct 2024 10:33:09 -0700 Subject: [PATCH 14/36] Remove textureUseAfterFreePoolSize (#8163) --- filament/backend/include/backend/Platform.h | 7 ----- filament/backend/src/metal/MetalContext.h | 11 -------- filament/backend/src/metal/MetalDriver.mm | 30 ++++++--------------- filament/backend/src/metal/MetalHandles.h | 22 --------------- filament/backend/src/metal/MetalHandles.mm | 8 ------ filament/include/filament/Engine.h | 10 ------- filament/src/details/Engine.cpp | 2 -- 7 files changed, 8 insertions(+), 82 deletions(-) diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index b8befb4cee6..af18b1488a4 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -68,13 +68,6 @@ class UTILS_PUBLIC Platform { */ size_t handleArenaSize = 0; - /** - * This number of most-recently destroyed textures will be tracked for use-after-free. - * Throws an exception when a texture is freed but still bound to a SamplerGroup and used in - * a draw call. 0 disables completely. Currently only respected by the Metal backend. - */ - size_t textureUseAfterFreePoolSize = 0; - size_t metalUploadBufferSizeBytes = 512 * 1024; /** diff --git a/filament/backend/src/metal/MetalContext.h b/filament/backend/src/metal/MetalContext.h index 3f6cbaa1a95..ef1175fa688 100644 --- a/filament/backend/src/metal/MetalContext.h +++ b/filament/backend/src/metal/MetalContext.h @@ -118,9 +118,6 @@ class MetalDynamicOffsets { }; struct MetalContext { - explicit MetalContext(size_t metalFreedTextureListSize) - : texturesToDestroy(metalFreedTextureListSize) {} - MetalDriver* driver; id device = nullptr; id commandQueue = nullptr; @@ -187,14 +184,6 @@ struct MetalContext { // Keeps track of all alive textures. tsl::robin_set textures; - // This circular buffer implements delayed destruction for Metal texture handles. It keeps a - // handle to a fixed number of the most recently destroyed texture handles. When we're asked to - // destroy a texture handle, we free its texture memory, but keep the MetalTexture object alive, - // marking it as "terminated". If we later are asked to use that texture, we can check its - // terminated status and throw an Objective-C error instead of crashing, which is helpful for - // debugging use-after-free issues in release builds. - utils::FixedCircularBuffer> texturesToDestroy; - MetalBufferPool* bufferPool; MetalBumpAllocator* bumpAllocator; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index f10df34039b..0bcb55aa10f 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -107,13 +107,13 @@ return ConcreteDispatcher::make(); } -MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept - : mPlatform(*platform), - mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)), - mHandleAllocator("Handles", - driverConfig.handleArenaSize, - driverConfig.disableHandleUseAfterFreeCheck), - mStereoscopicType(driverConfig.stereoscopicType) { +MetalDriver::MetalDriver( + MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept + : mPlatform(*platform), + mContext(new MetalContext), + mHandleAllocator( + "Handles", driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck), + mStereoscopicType(driverConfig.stereoscopicType) { mContext->driver = this; TrackedMetalBuffer::setPlatform(platform); @@ -808,15 +808,7 @@ auto* metalTexture = handle_cast(th); mContext->textures.erase(metalTexture); - // Free memory from the texture and mark it as freed. - metalTexture->terminate(); - - // Add this texture handle to our texturesToDestroy queue to be destroyed later. - if (auto handleToFree = mContext->texturesToDestroy.push(th)) { - // If texturesToDestroy is full, then .push evicts the oldest texture handle in the - // queue (or simply th, if use-after-free detection is disabled). - destruct_handle(handleToFree.value()); - } + destruct_handle(th); } void MetalDriver::destroyRenderTarget(Handle rth) { @@ -866,12 +858,6 @@ } void MetalDriver::terminate() { - // Terminate any outstanding MetalTextures. - while (!mContext->texturesToDestroy.empty()) { - Handle toDestroy = mContext->texturesToDestroy.pop(); - destruct_handle(toDestroy); - } - // finish() will flush the pending command buffer and will ensure all GPU work has finished. // This must be done before calling bufferPool->reset() to ensure no buffers are in flight. finish(); diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 4466f45cee7..8e2f7e253d0 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -267,26 +267,6 @@ class MetalTexture : public HwTexture { MTLPixelFormat devicePixelFormat; - // Frees memory associated with this texture and marks it as "terminated". - // Used to track "use after free" scenario. - void terminate() noexcept; - bool isTerminated() const noexcept { return terminated; } - inline void checkUseAfterFree(const char* samplerGroupDebugName, size_t textureIndex) const { - if (UTILS_LIKELY(!isTerminated())) { - return; - } - NSString* reason = - [NSString stringWithFormat: - @"Filament Metal texture use after free, sampler group = " - @"%s, texture index = %zu", - samplerGroupDebugName, textureIndex]; - NSException* useAfterFreeException = - [NSException exceptionWithName:@"MetalTextureUseAfterFree" - reason:reason - userInfo:nil]; - [useAfterFreeException raise]; - } - private: void loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffset, uint32_t slice, PixelBufferDescriptor const& data) noexcept; @@ -303,8 +283,6 @@ class MetalTexture : public HwTexture { // Filament swizzling only affects texture reads, so this should not be used when the texture is // bound as a render target attachment. id swizzledTextureView = nil; - - bool terminated = false; }; class MetalRenderTarget : public HwRenderTarget { diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index c61504d3c7c..c451264efac 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -648,14 +648,6 @@ static void func(void* user) { texture = externalImage->getMtlTexture(); } -void MetalTexture::terminate() noexcept { - texture = nil; - swizzledTextureView = nil; - msaaSidecar = nil; - externalImage = nullptr; - terminated = true; -} - id MetalTexture::getMtlTextureForRead() const noexcept { return swizzledTextureView ? swizzledTextureView : texture; } diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 641c9846eda..2686f25ea1c 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -288,16 +288,6 @@ class UTILS_PUBLIC Engine { */ uint32_t jobSystemThreadCount = 0; - /* - * Number of most-recently destroyed textures to track for use-after-free. - * - * This will cause the backend to throw an exception when a texture is freed but still bound - * to a SamplerGroup and used in a draw call. 0 disables completely. - * - * Currently only respected by the Metal backend. - */ - size_t textureUseAfterFreePoolSize = 0; - /** * When uploading vertex or index data, the Filament Metal backend copies data * into a shared staging area before transferring it to the GPU. This setting controls diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index c6598409538..b1aaa5c9f5f 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -103,7 +103,6 @@ Engine* FEngine::create(Engine::Builder const& builder) { } DriverConfig const driverConfig{ .handleArenaSize = instance->getRequestedDriverHandleArenaSize(), - .textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize, .metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes, .disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile, .disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck, @@ -701,7 +700,6 @@ int FEngine::loop() { DriverConfig const driverConfig { .handleArenaSize = getRequestedDriverHandleArenaSize(), - .textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize, .metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes, .disableParallelShaderCompile = mConfig.disableParallelShaderCompile, .disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck, From 4be172050dc994c6475e46758de7c85f8ca896b5 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Fri, 4 Oct 2024 13:45:22 -0700 Subject: [PATCH 15/36] Fix Android build --- android/filament-android/src/main/cpp/Engine.cpp | 4 +--- .../src/main/java/com/google/android/filament/Engine.java | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index f5714222559..314472557fd 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -520,8 +520,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*, jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB, jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB, - jlong jobSystemThreadCount, - jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile, + jlong jobSystemThreadCount, jboolean disableParallelShaderCompile, jint stereoscopicType, jlong stereoscopicEyeCount, jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge, jboolean disableHandleUseAfterFreeCheck, @@ -535,7 +534,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu .minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB, .perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB, .jobSystemThreadCount = (uint32_t) jobSystemThreadCount, - .textureUseAfterFreePoolSize = (uint32_t) textureUseAfterFreePoolSize, .disableParallelShaderCompile = (bool) disableParallelShaderCompile, .stereoscopicType = (Engine::StereoscopicType) stereoscopicType, .stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount, diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 6cadf7d5c7d..3b869c779d0 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -224,8 +224,7 @@ public Builder config(Config config) { nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB, config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB, config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB, - config.jobSystemThreadCount, - config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile, + config.jobSystemThreadCount, config.disableParallelShaderCompile, config.stereoscopicType.ordinal(), config.stereoscopicEyeCount, config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge, config.disableHandleUseAfterFreeCheck, @@ -1406,8 +1405,7 @@ private static void assertDestroy(boolean success) { private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB, long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB, long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount, - long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile, - int stereoscopicType, long stereoscopicEyeCount, + boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount, long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge, boolean disableHandleUseAfterFreeCheck, int preferredShaderLanguage, From e9aeb9312b18d9dab1866365e76253ba474b4371 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 4 Oct 2024 14:15:06 -0700 Subject: [PATCH 16/36] fix shadow multiplier mode when shadow multiplier was used, the material used the wrong variant (unlit). --- libs/filamat/src/GLSLPostProcessor.cpp | 4 +++- libs/filamat/src/MaterialBuilder.cpp | 2 +- libs/filamat/src/MaterialVariants.cpp | 6 ++++-- libs/filamat/src/shaders/ShaderGenerator.cpp | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 9cb9ae6b4bf..9dc98b00d3b 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -157,7 +157,9 @@ static void collectDescriptorsForSet(filament::DescriptorSetBindingPoints set, return descriptor_sets::getSsrVariantLayout(); } return descriptor_sets::getPerViewDescriptorSetLayout(config.domain, - config.variantFilter, material.isLit, material.reflectionMode, + config.variantFilter, + material.isLit || material.hasShadowMultiplier, + material.reflectionMode, material.refractionMode); } case DescriptorSetBindingPoints::PER_RENDERABLE: diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 816671aae5c..96563bfa6e1 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -1551,7 +1551,7 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo& backend::DescriptorSetLayout const perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout( mMaterialDomain, mVariantFilter, - info.isLit, info.reflectionMode, info.refractionMode); + info.isLit || info.hasShadowMultiplier, info.reflectionMode, info.refractionMode); // Descriptor layout and descriptor name/binding mapping container.push(info.sib, perViewDescriptorSetLayout); diff --git a/libs/filamat/src/MaterialVariants.cpp b/libs/filamat/src/MaterialVariants.cpp index 06a70d60fc8..b6ce7ccd745 100644 --- a/libs/filamat/src/MaterialVariants.cpp +++ b/libs/filamat/src/MaterialVariants.cpp @@ -86,9 +86,11 @@ std::vector determineSurfaceVariants( RefractionMode::CUBEMAP, RefractionMode::NONE }) { auto const vdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - vertexVariant, userVariantFilter, isLit, reflection, refraction); + vertexVariant, userVariantFilter, isLit || shadowMultiplier, + reflection, refraction); auto const fdsl = ShaderGenerator::getPerViewDescriptorSetLayoutWithVariant( - fragmentVariant, userVariantFilter, isLit, reflection, refraction); + fragmentVariant, userVariantFilter, isLit || shadowMultiplier, + reflection, refraction); // Check that all bindings present in the vertex shader DescriptorSetLayout // are also present in the fragment shader DescriptorSetLayout. for (auto const& r: vdsl.bindings) { diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index ff9486ad193..45b78cc0439 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -608,7 +608,8 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel, auto const perViewDescriptorSetLayout = getPerViewDescriptorSetLayoutWithVariant( variant, variantFilter, - material.isLit, material.reflectionMode, material.refractionMode); + material.isLit || material.hasShadowMultiplier, + material.reflectionMode, material.refractionMode); // this is the list of samplers we need to filter auto list = SibGenerator::getPerViewSib(variant).getSamplerInfoList(); From 4375ffb3e8ebab83721dd666235ff1d1197ea631 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Mon, 7 Oct 2024 12:06:38 -0700 Subject: [PATCH 17/36] vk: remove unnecessary hiding of impl for DescriptorSetManager (#8170) We remove one layer of indirection for clarity and very small bit performance saving. --- filament/backend/src/vulkan/VulkanDriver.cpp | 32 - filament/backend/src/vulkan/VulkanDriver.h | 4 - filament/backend/src/vulkan/VulkanHandles.cpp | 1 - filament/backend/src/vulkan/VulkanHandles.h | 17 +- .../caching/VulkanDescriptorSetManager.cpp | 565 +++++++----------- .../caching/VulkanDescriptorSetManager.h | 42 +- 6 files changed, 273 insertions(+), 388 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 97c6102e74d..c90760e9ba7 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -88,28 +88,6 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic return allocator; } -VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevice, - VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, - VulkanResourceAllocator* handleAllocator, VulkanStagePool& stagePool) { - VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator, - commands, handleAllocator, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1, - TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT, - stagePool, true /* heap allocated */); - uint32_t black = 0; - PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE); - emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0); - return emptyTexture; -} - -VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, - VulkanCommands* commands) { - VulkanBufferObject* obj = - new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM); - uint8_t byte = 0; - obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1); - return obj; -} - #if FVK_ENABLED(FVK_DEBUG_VALIDATION) VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, @@ -255,13 +233,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex #endif mTimestamps = std::make_unique(mPlatform->getDevice()); - - mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), - mContext, mAllocator, &mCommands, &mResourceAllocator, mStagePool); - mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands); - - mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture, - mEmptyBufferObject); } VulkanDriver::~VulkanDriver() noexcept = default; @@ -326,9 +297,6 @@ void VulkanDriver::terminate() { // to those commands are no longer referenced. finish(0); - delete mEmptyBufferObject; - delete mEmptyTexture; - // Command buffers should come first since it might have commands depending on resources that // are about to be destroyed. mCommands.terminate(); diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index d811bfebb2a..a3a9456a82b 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -115,10 +115,6 @@ class VulkanDriver final : public DriverBase { VulkanPlatform* mPlatform = nullptr; std::unique_ptr mTimestamps; - // Placeholder resources - VulkanTexture* mEmptyTexture; - VulkanBufferObject* mEmptyBufferObject; - VulkanSwapChain* mCurrentSwapChain = nullptr; VulkanRenderTarget* mDefaultRenderTarget = nullptr; VulkanRenderPass mCurrentRenderPass = {}; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index e2adea0f560..a2f63c4b60f 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -115,7 +115,6 @@ VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& void VulkanDescriptorSet::acquire(VulkanTexture* texture) { mResources.acquire(texture); - mTextures[mTextureCount++] = texture; } void VulkanDescriptorSet::acquire(VulkanBufferObject* bufferObject) { diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index 16f9c888f2e..1c8fe9d786c 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -151,17 +151,9 @@ struct VulkanDescriptorSet : public VulkanResource, HwDescriptorSet { void acquire(VulkanBufferObject* texture); - bool hasTexture(VulkanTexture* texture) { - return std::any_of(mTextures.begin(), mTextures.end(), - [texture](auto t) { return t == texture; }); - } - - // TODO: maybe change to fixed size for performance. VkDescriptorSet const vkSet; private: - std::array mTextures = { nullptr }; - uint8_t mTextureCount = 0; VulkanAcquireOnlyResourceManager mResources; OnRecycle mOnRecycleFn; }; @@ -202,10 +194,6 @@ struct VulkanProgram : public HwProgram, VulkanResource { inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; } - inline utils::FixedCapacityVector const& getBindingToSamplerIndex() const { - return mInfo->bindingToSamplerIndex; - } - // Get a list of the sampler binding indices so that we don't have to loop through all possible // samplers. inline BindingList const& getBindings() const { return mInfo->bindings; } @@ -236,8 +224,7 @@ struct VulkanProgram : public HwProgram, VulkanResource { private: struct PipelineInfo { explicit PipelineInfo(backend::Program const& program) noexcept - : bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff), - pushConstantDescription(program) + : pushConstantDescription(program) #if FVK_ENABLED_DEBUG_SAMPLER_NAME , bindingToName(MAX_SAMPLER_COUNT, "") #endif @@ -245,8 +232,6 @@ struct VulkanProgram : public HwProgram, VulkanResource { BindingList bindings; - // We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit. - utils::FixedCapacityVector bindingToSamplerIndex; VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE }; PushConstantDescription pushConstantDescription; diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp index eb72d1be908..660001083e8 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp @@ -193,64 +193,6 @@ class DescriptorPool { UnusedSetMap mUnused; }; -// This is an ever-expanding pool of sets where it -// 1. Keeps a list of smaller pools of different layout-dimensions. -// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out. -class DescriptorInfinitePool { -private: - static constexpr uint16_t EXPECTED_SET_COUNT = 10; - static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5; - -public: - DescriptorInfinitePool(VkDevice device) - : mDevice(device) {} - - VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) { - auto const vklayout = layout->getVkLayout(); - DescriptorPool* sameTypePool = nullptr; - for (auto& pool: mPools) { - if (!pool->canAllocate(layout->count)) { - continue; - } - if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) { - return set; - } - if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) { - sameTypePool = pool.get(); - } - } - - uint16_t capacity = EXPECTED_SET_COUNT; - if (sameTypePool) { - // Exponentially increase the size of the pool to ensure we don't hit this too often. - capacity = std::ceil(sameTypePool->capacity() * SET_COUNT_GROWTH_FACTOR); - } - - // We need to increase the set of pools by one. - mPools.push_back(std::make_unique(mDevice, - DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity)); - auto& pool = mPools.back(); - auto ret = pool->obtainSet(vklayout); - assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?"); - return ret; - } - - void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout, - VkDescriptorSet vkSet) { - for (auto& pool: mPools) { - if (!pool->canAllocate(count)) { - continue; - } - pool->recycle(vklayout, vkSet); - break; - } - } - -private: - VkDevice mDevice; - std::vector> mPools; -}; - template struct Equal { bool operator()(Key const& k1, Key const& k2) const { @@ -318,7 +260,67 @@ inline VkDescriptorSetLayout createLayout(VkDevice device, BitmaskGroup const& b return layout; } -class DescriptorSetLayoutManager { +} // anonymous namespace + +// This is an ever-expanding pool of sets where it +// 1. Keeps a list of smaller pools of different layout-dimensions. +// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out. +class VulkanDescriptorSetManager::DescriptorInfinitePool { +private: + static constexpr uint16_t EXPECTED_SET_COUNT = 10; + static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5; + +public: + DescriptorInfinitePool(VkDevice device) + : mDevice(device) {} + + VkDescriptorSet obtainSet(VulkanDescriptorSetLayout* layout) { + auto const vklayout = layout->getVkLayout(); + DescriptorPool* sameTypePool = nullptr; + for (auto& pool: mPools) { + if (!pool->canAllocate(layout->count)) { + continue; + } + if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) { + return set; + } + if (!sameTypePool || sameTypePool->capacity() < pool->capacity()) { + sameTypePool = pool.get(); + } + } + + uint16_t capacity = EXPECTED_SET_COUNT; + if (sameTypePool) { + // Exponentially increase the size of the pool to ensure we don't hit this too often. + capacity = std::ceil(sameTypePool->capacity() * SET_COUNT_GROWTH_FACTOR); + } + + // We need to increase the set of pools by one. + mPools.push_back(std::make_unique(mDevice, + DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity)); + auto& pool = mPools.back(); + auto ret = pool->obtainSet(vklayout); + assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?"); + return ret; + } + + void recycle(DescriptorCount const& count, VkDescriptorSetLayout vklayout, + VkDescriptorSet vkSet) { + for (auto& pool: mPools) { + if (!pool->canAllocate(count)) { + continue; + } + pool->recycle(vklayout, vkSet); + break; + } + } + +private: + VkDevice mDevice; + std::vector> mPools; +}; + +class VulkanDescriptorSetManager::DescriptorSetLayoutManager { public: DescriptorSetLayoutManager(VkDevice device) : mDevice(device) {} @@ -345,329 +347,226 @@ class DescriptorSetLayoutManager { mVkLayouts; }; -} // anonymous namespace - -class VulkanDescriptorSetManager::Impl { +class VulkanDescriptorSetManager::DescriptorSetHistory { private: - struct DescriptorSetHistory { - private: - using TextureBundle = std::pair; - public: - DescriptorSetHistory() - : dynamicUboCount(0), - mResources(nullptr) {} - - DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount, - VulkanResourceAllocator* allocator, VulkanDescriptorSet* set) - : dynamicUboMask(dynamicUbo), - dynamicUboCount(uniqueDynamicUboCount), - mResources(allocator), - mSet(set), - mBound(false) { - assert_invariant(set); - // initial state is unbound. - mResources.acquire(mSet); - unbind(); - } - - ~DescriptorSetHistory() { - if (mSet) { - mResources.clear(); - } - } - - void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept { - mOffsets = std::move(offsets); - mBound = false; - } - - void write(uint8_t binding) noexcept { - mBound = false; - } - - // Ownership will be transfered to the commandbuffer. - void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, uint8_t index) noexcept { - VkCommandBuffer const cmdbuffer = commands->buffer(); - vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, - index, 1, &mSet->vkSet, dynamicUboCount, mOffsets.data()); - - commands->acquire(mSet); - mResources.clear(); - mBound = true; - } - - void unbind() noexcept { - mResources.acquire(mSet); - mBound = false; - } - - bool bound() const noexcept { return mBound; } - - UniformBufferBitmask const dynamicUboMask; - uint8_t const dynamicUboCount; - - private: - FixedSizeVulkanResourceManager<1> mResources; - VulkanDescriptorSet* mSet = nullptr; - - backend::DescriptorSetOffsetArray mOffsets; - bool mBound = false; - }; - - using DescriptorSetHistoryArray = - std::array; - - struct BoundInfo { - VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; - DescriptorSetMask setMask; - DescriptorSetHistoryArray boundSets; - - bool operator==(BoundInfo const& info) const { - if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) { - return false; - } - bool equal = true; - setMask.forEachSetBit([&](size_t i) { - if (boundSets[i] != info.boundSets[i]) { - equal = false; - } - }); - return equal; - } - }; + using TextureBundle = std::pair; public: - Impl(VkDevice device, VulkanResourceAllocator* resourceAllocator) - : mDevice(device), - mResourceAllocator(resourceAllocator), - mLayoutManager(device), - mDescriptorPool(device) {} - - // bind() is not really binding the set but just stashing until we have all the info - // (pipelinelayout). - void bind(uint8_t setIndex, VulkanDescriptorSet* set, - backend::DescriptorSetOffsetArray&& offsets) { - auto& history = mHistory[set]; - history.setOffsets(std::move(offsets)); - - auto lastHistory = mStashedSets[setIndex]; - if (lastHistory) { - lastHistory->unbind(); - } - mStashedSets[setIndex] = &history; + DescriptorSetHistory() + : dynamicUboCount(0), + mResources(nullptr) {} + + DescriptorSetHistory(UniformBufferBitmask const& dynamicUbo, uint8_t uniqueDynamicUboCount, + VulkanResourceAllocator* allocator, VulkanDescriptorSet* set) + : dynamicUboMask(dynamicUbo), + dynamicUboCount(uniqueDynamicUboCount), + mResources(allocator), + mSet(set), + mBound(false) { + assert_invariant(set); + // initial state is unbound. + unbind(); } - void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, - DescriptorSetMask const& setMask) { - DescriptorSetHistoryArray& updateSets = mStashedSets; - - // setMask indicates the set of descriptor sets the driver wants to bind, curMask is the - // actual set of sets that *needs* to be bound. - DescriptorSetMask curMask = setMask; - - setMask.forEachSetBit([&](size_t index) { - if (!updateSets[index] || updateSets[index]->bound()) { - curMask.unset(index); - } - }); - - BoundInfo nextInfo = { - pipelineLayout, - setMask, - updateSets, - }; - if (curMask.none() && mLastBoundInfo == nextInfo) { - return; + ~DescriptorSetHistory() { + if (mSet) { + mResources.clear(); } + } - curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) { - updateSets[index]->bind(commands, pipelineLayout, index); - }); - mLastBoundInfo = nextInfo; + void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept { + mOffsets = std::move(offsets); + mBound = false; } - void updateBuffer(VulkanDescriptorSet* set, uint8_t binding, VulkanBufferObject* bufferObject, - VkDeviceSize offset, VkDeviceSize size) noexcept { - VkDescriptorBufferInfo const info = { - .buffer = bufferObject->buffer.getGpuBuffer(), - .offset = offset, - .range = size, - }; + void write(uint8_t binding) noexcept { mBound = false; } - VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - auto& history = mHistory[set]; + // Ownership will be transfered to the commandbuffer. + void bind(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout, + uint8_t index) noexcept { + VkCommandBuffer const cmdbuffer = commands->buffer(); + vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index, + 1, &mSet->vkSet, dynamicUboCount, mOffsets.data()); - if (history.dynamicUboMask.test(binding)) { - type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; - } - VkWriteDescriptorSet const descriptorWrite = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = set->vkSet, - .dstBinding = binding, - .descriptorCount = 1, - .descriptorType = type, - .pBufferInfo = &info, - }; - vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); - set->acquire(bufferObject); - history.write(binding); + commands->acquire(mSet); + mResources.clear(); + mBound = true; } - void updateSampler(VulkanDescriptorSet* set, uint8_t binding, VulkanTexture* texture, - VkSampler sampler) noexcept { - VkDescriptorImageInfo info{ - .sampler = sampler, - }; - VkImageSubresourceRange const range = texture->getPrimaryViewRange(); - VkImageViewType const expectedType = texture->getViewType(); - if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && - expectedType == VK_IMAGE_VIEW_TYPE_2D) { - // If the sampler is part of a mipmapped depth texture, where one of the level *can* be - // an attachment, then the sampler for this texture has the same view properties as a - // view for an attachment. Therefore, we can use getAttachmentView to get a - // corresponding VkImageView. - info.imageView = texture->getAttachmentView(range); - } else { - info.imageView = texture->getViewForType(range, expectedType); - } - info.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()); - VkWriteDescriptorSet const descriptorWrite = { - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .pNext = nullptr, - .dstSet = set->vkSet, - .dstBinding = binding, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .pImageInfo = &info, - }; - vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); - set->acquire(texture); - mHistory[set].write(binding); + void unbind() noexcept { + mResources.acquire(mSet); + mBound = false; } - void updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept { - // TOOD: fill-in this region - } + bool bound() const noexcept { return mBound; } - void setPlaceHolders(VkSampler sampler, VulkanTexture* texture, - VulkanBufferObject* bufferObject) noexcept { - mPlaceHolderBufferInfo = { - .buffer = bufferObject->buffer.getGpuBuffer(), - .offset = 0, - .range = 1, - }; - mPlaceHolderImageInfo = { - .sampler = sampler, - .imageView = texture->getPrimaryImageView(), - .imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()), - }; - } + UniformBufferBitmask const dynamicUboMask; + uint8_t const dynamicUboCount; - void createSet(Handle handle, VulkanDescriptorSetLayout* layout) { - auto const vkSet = mDescriptorPool.obtainSet(layout); - auto const& count = layout->count; - auto const vklayout = layout->getVkLayout(); - VulkanDescriptorSet* set = - mResourceAllocator->construct(handle, mResourceAllocator, - vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) { - eraseSetFromHistory(set); - mDescriptorPool.recycle(count, vklayout, vkSet); - }); - mHistory.emplace( - std::piecewise_construct, - std::forward_as_tuple(set), - std::forward_as_tuple(layout->bitmask.dynamicUbo, layout->count.dynamicUbo, - mResourceAllocator, set)); - } +private: + FixedSizeVulkanResourceManager<1> mResources; + VulkanDescriptorSet* mSet = nullptr; - void destroySet(Handle handle) { - VulkanDescriptorSet* set = mResourceAllocator->handle_cast(handle); - eraseSetFromHistory(set); - } + backend::DescriptorSetOffsetArray mOffsets; + bool mBound = false; +}; - void initVkLayout(VulkanDescriptorSetLayout* layout) { - layout->setVkLayout(mLayoutManager.getVkLayout(layout)); - } +VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device, + VulkanResourceAllocator* resourceAllocator) + : mDevice(device), + mResourceAllocator(resourceAllocator), + mLayoutManager(std::make_unique(device)), + mDescriptorPool(std::make_unique(device)) {} -private: - inline void eraseSetFromHistory(VulkanDescriptorSet* set) { - DescriptorSetHistory* history = &mHistory[set]; - mHistory.erase(set); +VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default; - for (uint8_t i = 0; i < mStashedSets.size(); ++i) { - if (mStashedSets[i] == history) { - mStashedSets[i] = nullptr; - } - } +void VulkanDescriptorSetManager::terminate() noexcept{ + mLayoutManager.reset(); + mDescriptorPool.reset(); + mHistory.clear(); +} + +// bind() is not really binding the set but just stashing until we have all the info +// (pipelinelayout). +void VulkanDescriptorSetManager::bind(uint8_t setIndex, VulkanDescriptorSet* set, + backend::DescriptorSetOffsetArray&& offsets) { + auto history = mHistory[set].get(); + history->setOffsets(std::move(offsets)); + + auto lastHistory = mStashedSets[setIndex]; + if (lastHistory) { + lastHistory->unbind(); } + mStashedSets[setIndex] = history; +} - VkDevice mDevice; - VulkanResourceAllocator* mResourceAllocator; - DescriptorSetLayoutManager mLayoutManager; - DescriptorInfinitePool mDescriptorPool; - std::pair mInputAttachment; - std::unordered_map mHistory; - DescriptorSetHistoryArray mStashedSets = {}; +void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands, + VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) { + DescriptorSetHistoryArray& updateSets = mStashedSets; - BoundInfo mLastBoundInfo; + // setMask indicates the set of descriptor sets the driver wants to bind, curMask is the + // actual set of sets that *needs* to be bound. + DescriptorSetMask curMask = setMask; - VkDescriptorBufferInfo mPlaceHolderBufferInfo; - VkDescriptorImageInfo mPlaceHolderImageInfo; -}; + setMask.forEachSetBit([&](size_t index) { + if (!updateSets[index] || updateSets[index]->bound()) { + curMask.unset(index); + } + }); -VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device, - VulkanResourceAllocator* resourceAllocator) - : mImpl(new Impl(device, resourceAllocator)) {} + BoundInfo nextInfo = { + pipelineLayout, + setMask, + updateSets, + }; + if (curMask.none() && mLastBoundInfo == nextInfo) { + return; + } -void VulkanDescriptorSetManager::terminate() noexcept { - assert_invariant(mImpl); - delete mImpl; - mImpl = nullptr; + curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) { + updateSets[index]->bind(commands, pipelineLayout, index); + }); + mLastBoundInfo = nextInfo; } void VulkanDescriptorSetManager::updateBuffer(VulkanDescriptorSet* set, uint8_t binding, VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { - mImpl->updateBuffer(set, binding, bufferObject, offset, size); + VkDescriptorBufferInfo const info = { + .buffer = bufferObject->buffer.getGpuBuffer(), + .offset = offset, + .range = size, + }; + + VkDescriptorType type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + auto& history = mHistory[set]; + + if (history->dynamicUboMask.test(binding)) { + type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + } + VkWriteDescriptorSet const descriptorWrite = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set->vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = type, + .pBufferInfo = &info, + }; + vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); + set->acquire(bufferObject); + history->write(binding); } void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t binding, VulkanTexture* texture, VkSampler sampler) noexcept { - mImpl->updateSampler(set, binding, texture, sampler); + VkDescriptorImageInfo info{ + .sampler = sampler, + }; + VkImageSubresourceRange const range = texture->getPrimaryViewRange(); + VkImageViewType const expectedType = texture->getViewType(); + if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) && + expectedType == VK_IMAGE_VIEW_TYPE_2D) { + // If the sampler is part of a mipmapped depth texture, where one of the level *can* be + // an attachment, then the sampler for this texture has the same view properties as a + // view for an attachment. Therefore, we can use getAttachmentView to get a + // corresponding VkImageView. + info.imageView = texture->getAttachmentView(range); + } else { + info.imageView = texture->getViewForType(range, expectedType); + } + info.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()); + VkWriteDescriptorSet const descriptorWrite = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set->vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &info, + }; + vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr); + set->acquire(texture); + mHistory[set]->write(binding); } void VulkanDescriptorSetManager::updateInputAttachment(VulkanDescriptorSet* set, VulkanAttachment attachment) noexcept { - mImpl->updateInputAttachment(set, attachment); -} - -void VulkanDescriptorSetManager::setPlaceHolders(VkSampler sampler, VulkanTexture* texture, - VulkanBufferObject* bufferObject) noexcept { - mImpl->setPlaceHolders(sampler, texture, bufferObject); -} - -void VulkanDescriptorSetManager::bind(uint8_t setIndex, VulkanDescriptorSet* set, - backend::DescriptorSetOffsetArray&& offsets) { - return mImpl->bind(setIndex, set, std::move(offsets)); -} - -void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands, - VkPipelineLayout pipelineLayout, DescriptorSetMask const& setMask) { - mImpl->commit(commands, pipelineLayout, setMask); + // TOOD: fill-in this region } void VulkanDescriptorSetManager::createSet(Handle handle, VulkanDescriptorSetLayout* layout) { - mImpl->createSet(handle, layout); + auto const vkSet = mDescriptorPool->obtainSet(layout); + auto const& count = layout->count; + auto const vklayout = layout->getVkLayout(); + VulkanDescriptorSet* set = mResourceAllocator->construct(handle, + mResourceAllocator, vkSet, [vkSet, count, vklayout, this](VulkanDescriptorSet* set) { + eraseSetFromHistory(set); + mDescriptorPool->recycle(count, vklayout, vkSet); + }); + mHistory[set] = std::make_unique(layout->bitmask.dynamicUbo, + layout->count.dynamicUbo, mResourceAllocator, set); } void VulkanDescriptorSetManager::destroySet(Handle handle) { - mImpl->destroySet(handle); + VulkanDescriptorSet* set = mResourceAllocator->handle_cast(handle); + eraseSetFromHistory(set); } void VulkanDescriptorSetManager::initVkLayout(VulkanDescriptorSetLayout* layout) { - mImpl->initVkLayout(layout); + layout->setVkLayout(mLayoutManager->getVkLayout(layout)); +} + +void VulkanDescriptorSetManager::eraseSetFromHistory(VulkanDescriptorSet* set) { + DescriptorSetHistory* history = mHistory[set].get(); + mHistory.erase(set); + + for (uint8_t i = 0; i < mStashedSets.size(); ++i) { + if (mStashedSets[i] == history) { + mStashedSets[i] = nullptr; + } + } } -}// namespace filament::backend +} // namespace filament::backend diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h index 6d78f31b244..a98cc84b454 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.h @@ -30,6 +30,8 @@ #include #include +#include + namespace filament::backend { // [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to @@ -45,6 +47,7 @@ class VulkanDescriptorSetManager { using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray; VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator); + ~VulkanDescriptorSetManager(); void terminate() noexcept; @@ -71,8 +74,43 @@ class VulkanDescriptorSetManager { void initVkLayout(VulkanDescriptorSetLayout* layout); private: - class Impl; - Impl* mImpl; + class DescriptorSetHistory; + class DescriptorSetLayoutManager; + class DescriptorInfinitePool; + + void eraseSetFromHistory(VulkanDescriptorSet* set); + + using DescriptorSetHistoryArray = + std::array; + + struct BoundInfo { + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + DescriptorSetMask setMask; + DescriptorSetHistoryArray boundSets; + + bool operator==(BoundInfo const& info) const { + if (pipelineLayout != info.pipelineLayout || setMask != info.setMask) { + return false; + } + bool equal = true; + setMask.forEachSetBit([&](size_t i) { + if (boundSets[i] != info.boundSets[i]) { + equal = false; + } + }); + return equal; + } + }; + + VkDevice mDevice; + VulkanResourceAllocator* mResourceAllocator; + std::unique_ptr mLayoutManager; + std::unique_ptr mDescriptorPool; + std::pair mInputAttachment; + std::unordered_map> mHistory; + DescriptorSetHistoryArray mStashedSets = {}; + + BoundInfo mLastBoundInfo; }; }// namespace filament::backend From 99b55cb88816942354f5284e03f089f9801180a7 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 4 Oct 2024 11:02:18 -0700 Subject: [PATCH 18/36] minor renaming to be more consistant --- filament/src/details/Renderer.cpp | 4 ++-- filament/src/details/View.cpp | 2 +- filament/src/details/View.h | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index f056976833a..e201c8dd63e 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -802,7 +802,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { passBuilder.renderFlags(renderFlags); Variant variant; - variant.setDirectionalLighting(view.hasDirectionalLight()); + variant.setDirectionalLighting(view.hasDirectionalLighting()); variant.setDynamicLighting(view.hasDynamicLighting()); variant.setFog(view.hasFog()); variant.setVsm(view.hasShadowing() && view.getShadowType() != ShadowType::PCF); @@ -1197,7 +1197,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // Debug: CSM visualisation if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades && - view.hasShadowing() && view.hasDirectionalLight())) { + view.hasShadowing() && view.hasDirectionalLighting())) { input = ppm.debugShadowCascades(fg, input, depth); } diff --git a/filament/src/details/View.cpp b/filament/src/details/View.cpp index 8d23837ccac..a50b05c8e43 100644 --- a/filament/src/details/View.cpp +++ b/filament/src/details/View.cpp @@ -584,7 +584,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren // we also know if we have a directional light FLightManager::Instance const directionalLight = lightData.elementAt(0); - mHasDirectionalLight = directionalLight.isValid(); + mHasDirectionalLighting = directionalLight.isValid(); // As soon as prepareVisibleLight finishes, we can kick-off the froxelization if (hasDynamicLighting()) { diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 0aeacd21b54..b416c1b784b 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -175,8 +175,12 @@ class FView : public View { utils::JobSystem::Job* getFroxelizerSync() const noexcept { return mFroxelizerSync; } void setFroxelizerSync(utils::JobSystem::Job* sync) noexcept { mFroxelizerSync = sync; } - bool hasDirectionalLight() const noexcept { return mHasDirectionalLight; } + // ultimately decides to use the DIR variant + bool hasDirectionalLighting() const noexcept { return mHasDirectionalLighting; } + + // ultimately decides to use the DYN variant bool hasDynamicLighting() const noexcept { return mHasDynamicLighting; } + bool hasShadowing() const noexcept { return mHasShadowing; } bool needsShadowMap() const noexcept { return mNeedsShadowMap; } bool hasFog() const noexcept { return mFogOptions.enabled && mFogOptions.density > 0.0f; } @@ -573,7 +577,7 @@ class FView : public View { Range mVisibleDirectionalShadowCasters; Range mSpotLightShadowCasters; uint32_t mRenderableUBOSize = 0; - mutable bool mHasDirectionalLight = false; + mutable bool mHasDirectionalLighting = false; mutable bool mHasDynamicLighting = false; mutable bool mHasShadowing = false; mutable bool mNeedsShadowMap = false; From 26b42a76856a2570e4945c8eb2cfe42cd54246b0 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 4 Oct 2024 11:02:28 -0700 Subject: [PATCH 19/36] fix typo when generating the SSR pass this caused the HAS_SHADOWS flag to not be disabled, this didn't actually cause a problem because shadowing and SSR share the same SRE variant bit. But both should never be active together. --- filament/src/PostProcessManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index df04a18c7ce..66aa50a7ed6 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -643,7 +643,7 @@ FrameGraphId PostProcessManager::ssr(FrameGraph& fg, auto out = resources.getRenderPassInfo(); // Remove the HAS_SHADOWING RenderFlags, since it's irrelevant when rendering reflections - passBuilder.renderFlags(~RenderPass::HAS_SHADOWING, 0); + passBuilder.renderFlags(RenderPass::HAS_SHADOWING, 0); // use our special SSR variant, it can only be applied to object that have // the SCREEN_SPACE ReflectionMode. From 58aa74b4aee3ac87dcfe52ec151c3f9c3cb510ec Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 4 Oct 2024 11:14:24 -0700 Subject: [PATCH 20/36] cleanup code so it's easier to understand this should make it clearer how we set the per primitive variant when SSR and shadowin is involved --- filament/src/RenderPass.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index bec8e313275..dd0dd262617 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -655,12 +655,18 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla bool const hasSkinning = soaVisibility[i].skinning; bool const hasSkinningOrMorphing = hasSkinning || hasMorphing; - // if we are already an SSR variant, the SRE bit is already set, - // there is no harm setting it again + // if we are already an SSR variant, the SRE bit is already set static_assert(Variant::SPECIAL_SSR & Variant::SRE); - Variant renderableVariant = variant; - renderableVariant.setShadowReceiver( - Variant::isSSRVariant(variant) || (soaVisibility[i].receiveShadows & hasShadowing)); + Variant renderableVariant{ variant }; + + // we can't have SSR and shadowing together by construction + bool const isSsrVariant = Variant::isSSRVariant(variant); + assert_invariant((isSsrVariant && !hasShadowing) || !isSsrVariant); + if (!isSsrVariant) { + // set the SRE variant, unless we're in SSR mode + renderableVariant.setShadowReceiver(soaVisibility[i].receiveShadows && hasShadowing); + } + renderableVariant.setSkinning(hasSkinningOrMorphing); FRenderableManager::SkinningBindingInfo const& skinning = soaSkinning[i]; From 2bbcb6de4ce5250cda1986b455033ea888db1b20 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 4 Oct 2024 12:03:37 -0700 Subject: [PATCH 21/36] fix potential shadow rendering bug the problem stems from a mismatch between the shader code and the cpu code. if the shader is configured to read the shadow map, then the cpu must generate it, otherwise we can get stale data. Wether the shader reads the shadow map depends on the shadow type. For directional shadows, the shader needs the SRE variant + a "shadow enabled" bit per cascade in the main UBO. For punctual shadows, the shader only needs the SRE variant. Because of all that, if the conditions are met on the CPU side for the shader to access the shadow map, we must make sure to generate it, but in the case the shadow map would be empty (e.g. no shadow receivers), we need to initialize it (and we can skip some work in the case of VSM). BUGS=[369908659] --- filament/src/ShadowMapManager.cpp | 82 ++++++++++++++++++------------- filament/src/details/View.h | 4 ++ 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 51b29065063..763e026db33 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -261,10 +261,15 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto& passList = data.passList; // Directional, cascaded shadow maps - auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters(); - if (!directionalShadowCastersRange.empty()) { + // if the view has shadowing enabled, the SRE variant could be used, so we must + // generate the shadow maps + if (view.needsDirectionalShadowMaps()) { + auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters(); for (auto& shadowMap : getCascadedShadowMap()) { - // for the directional light, we already know if it has visible shadows. + // For the directional light, we already know if it has visible shadows. + // if hasVisibleShadows() is false, we're guaranteed the shader won't + // sample this shadow map (see PerViewUib::cascades and + // ShadowMapManager::updateCascadeShadowMaps) if (shadowMap.hasVisibleShadows()) { passList.push_back({ {}, &shadowMap, directionalShadowCastersRange, @@ -274,8 +279,10 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG } // Point lights and Spotlight shadow maps - auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters(); - if (!spotShadowCastersRange.empty()) { + // if the view has shadowing enabled, the SRE variant could be used, so we must + // generate the shadow maps + if (view.needsPointShadowMaps()) { + auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters(); for (auto& shadowMap : getSpotShadowMaps()) { assert_invariant(!shadowMap.isDirectionalShadow()); @@ -293,11 +300,11 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG break; } - if (shadowMap.hasVisibleShadows()) { - passList.push_back({ - {}, &shadowMap, spotShadowCastersRange, - VISIBLE_DYN_SHADOW_RENDERABLE }); - } + // For spot/point lights, shadowMap.hasVisibleShadows() doesn't guarantee + // the shader won't access the shadow map, so we must generate it. + passList.push_back({ + {}, &shadowMap, spotShadowCastersRange, + VISIBLE_DYN_SHADOW_RENDERABLE }); } } @@ -311,7 +318,6 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG scene, mainCameraInfo, userTime, passBuilder = passBuilder]( FrameGraphResources const&, auto const& data, DriverApi& driver) mutable { - // Note: we could almost parallel_for the loop below, the problem currently is // that updatePrimitivesLod() updates temporary global state. // prepareSpotShadowMap() also update the visibility of renderable. These two @@ -321,7 +327,6 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // Generate a RenderPass for each shadow map for (auto const& entry : data.passList) { ShadowMap const& shadowMap = *entry.shadowMap; - assert_invariant(shadowMap.hasVisibleShadows()); // Note: this loop can generate a lot of commands that come out of the // "per frame command arena". The allocation persists until the @@ -339,14 +344,18 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG // we should never be here break; case ShadowType::SPOT: - ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view, - scene->getRenderableData(), entry.range, - scene->getLightData()); + if (shadowMap.hasVisibleShadows()) { + ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view, + scene->getRenderableData(), entry.range, + scene->getLightData()); + } break; case ShadowType::POINT: - ShadowMapManager::cullPointShadowMap(shadowMap, view, - scene->getRenderableData(), entry.range, - scene->getLightData()); + if (shadowMap.hasVisibleShadows()) { + ShadowMapManager::cullPointShadowMap(shadowMap, view, + scene->getRenderableData(), entry.range, + scene->getLightData()); + } break; } @@ -420,22 +429,21 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto const& passList = prepareShadowPass.getData().passList; for (auto const& entry: passList) { - assert_invariant(entry.shadowMap->hasVisibleShadows()); - const uint8_t layer = entry.shadowMap->getLayer(); const auto* options = entry.shadowMap->getShadowOptions(); const auto msaaSamples = textureRequirements.msaaSamples; + const bool blur = entry.shadowMap->hasVisibleShadows() && + view.hasVSM() && options->vsm.blurWidth > 0.0f; auto& shadowPass = fg.addPass("Shadow Pass", [&](FrameGraph::Builder& builder, auto& data) { - const bool blur = view.hasVSM() && options->vsm.blurWidth > 0.0f; FrameGraphRenderPass::Descriptor renderTargetDesc{}; data.output = builder.createSubresource(prepareShadowPass->shadows, "Shadowmap Layer", { .layer = layer }); - if (view.hasVSM()) { + if (UTILS_UNLIKELY(view.hasVSM())) { // Each shadow pass has its own sample count, but textures are created with // a default count of 1 because we're using "magic resolve" (sample count is // set on the render target). @@ -447,13 +455,6 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG .format = TextureFormat::DEPTH16, }); - // Temporary (resolved) texture used to render the shadowmap when blurring - // is needed -- it'll be used as the source of the blur. - data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", { - .width = textureRequirements.size, .height = textureRequirements.size, - .format = textureRequirements.format - }); - depth = builder.write(depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT); @@ -468,7 +469,15 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG renderTargetDesc.clearColor = vsmClearColor; renderTargetDesc.samples = msaaSamples; - if (blur) { + if (UTILS_UNLIKELY(blur)) { + // Temporary (resolved) texture used to render the shadowmap when blurring + // is needed -- it'll be used as the source of the blur. + data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", { + .width = textureRequirements.size, + .height = textureRequirements.size, + .format = textureRequirements.format + }); + data.tempBlurSrc = builder.write(data.tempBlurSrc, FrameGraphTexture::Usage::COLOR_ATTACHMENT); @@ -510,15 +519,20 @@ FrameGraphId ShadowMapManager::render(FEngine& engine, FrameG auto rt = resources.getRenderPassInfo(data.rt); driver.beginRenderPass(rt.target, rt.params); - entry.shadowMap->bind(driver); - entry.executor.overrideScissor(entry.shadowMap->getScissor()); - entry.executor.execute(engine, "Shadow Pass"); + // if we know there are no visible shadows, we can skip rendering, but + // we need the render-pass to clear/initialize the shadow-map + // Note: this is always true for directional/cascade shadows. + if (entry.shadowMap->hasVisibleShadows()) { + entry.shadowMap->bind(driver); + entry.executor.overrideScissor(entry.shadowMap->getScissor()); + entry.executor.execute(engine, "Shadow Pass"); + } driver.endRenderPass(); }); // now emit the blurring passes if needed - if (view.hasVSM()) { + if (UTILS_UNLIKELY(blur)) { auto& ppm = engine.getPostProcessManager(); const float blurWidth = options->vsm.blurWidth; diff --git a/filament/src/details/View.h b/filament/src/details/View.h index b416c1b784b..4f548ded94a 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -181,7 +181,11 @@ class FView : public View { // ultimately decides to use the DYN variant bool hasDynamicLighting() const noexcept { return mHasDynamicLighting; } + // ultimately decides to use the SRE variant bool hasShadowing() const noexcept { return mHasShadowing; } + + bool needsDirectionalShadowMaps() const noexcept { return mHasShadowing && mHasDirectionalLighting; } + bool needsPointShadowMaps() const noexcept { return mHasShadowing && mHasDynamicLighting; } bool needsShadowMap() const noexcept { return mNeedsShadowMap; } bool hasFog() const noexcept { return mFogOptions.enabled && mFogOptions.density > 0.0f; } bool hasVSM() const noexcept { return mShadowType == ShadowType::VSM; } From 164a25cac4b2e99ee65d1d66ccea2e14c65b5eef Mon Sep 17 00:00:00 2001 From: Haneul Kim Date: Fri, 4 Oct 2024 11:25:22 +0900 Subject: [PATCH 22/36] Update FreeFlightManipulator.h --- libs/camutils/src/FreeFlightManipulator.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/camutils/src/FreeFlightManipulator.h b/libs/camutils/src/FreeFlightManipulator.h index c20b2578295..e700713ee98 100644 --- a/libs/camutils/src/FreeFlightManipulator.h +++ b/libs/camutils/src/FreeFlightManipulator.h @@ -226,6 +226,8 @@ class FreeFlightManipulator : public Manipulator { void jumpToBookmark(const Bookmark& bookmark) override { Base::mEye = bookmark.flight.position; + mTargetEuler.x = bookmark.flight.pitch; + mTargetEuler.y = bookmark.flight.yaw; updateTarget(bookmark.flight.pitch, bookmark.flight.yaw); } From 1b0ff3aadb24d04a4db7e7303c701380cf12c237 Mon Sep 17 00:00:00 2001 From: mdagois Date: Wed, 9 Oct 2024 08:20:35 +0900 Subject: [PATCH 23/36] vk: Transient attachment support (#8021) --- .../backend/include/backend/DriverEnums.h | 3 ++- filament/backend/src/vulkan/VulkanContext.h | 5 ++++ filament/backend/src/vulkan/VulkanDriver.cpp | 16 ++++++++--- .../backend/src/vulkan/VulkanFboCache.cpp | 3 ++- filament/backend/src/vulkan/VulkanFboCache.h | 7 ++--- filament/backend/src/vulkan/VulkanHandles.cpp | 7 ++++- filament/backend/src/vulkan/VulkanTexture.cpp | 27 +++++++++++++++---- filament/backend/src/vulkan/VulkanTexture.h | 6 +++++ .../src/vulkan/platform/VulkanPlatform.cpp | 14 ++++++++++ 9 files changed, 74 insertions(+), 14 deletions(-) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 51926ad2def..191e7ac2262 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -765,7 +765,8 @@ enum class TextureUsage : uint16_t { BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() PROTECTED = 0x0100, //!< Texture can be used for protected content - DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage + DEFAULT = UPLOADABLE | SAMPLEABLE, //!< Default texture usage + ALL_ATTACHMENTS = COLOR_ATTACHMENT | DEPTH_ATTACHMENT | STENCIL_ATTACHMENT | SUBPASS_INPUT, //!< Mask of all attachments }; inline const char* stringify(TextureUsage usage) { diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index a820087d7a2..e7a18141962 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -144,6 +144,10 @@ struct VulkanContext { return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE; } + inline bool isLazilyAllocatedMemorySupported() const noexcept { + return mLazilyAllocatedMemorySupported; + } + private: VkPhysicalDeviceMemoryProperties mMemoryProperties = {}; VkPhysicalDeviceProperties mPhysicalDeviceProperties = {}; @@ -151,6 +155,7 @@ struct VulkanContext { bool mDebugMarkersSupported = false; bool mDebugUtilsSupported = false; bool mMultiviewEnabled = false; + bool mLazilyAllocatedMemorySupported = false; VkFormatList mDepthStencilFormats; VkFormatList mBlittableDepthStencilFormats; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index c90760e9ba7..f1a41cc7a62 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1283,12 +1283,14 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP // Create the VkRenderPass or fetch it from cache. VulkanFboCache::RenderPassKey rpkey = { - .initialDepthLayout = currentDepthLayout, .depthFormat = depth.getFormat(), .clear = clearVal, .discardStart = discardStart, .discardEnd = discardEndVal, + .initialDepthLayout = currentDepthLayout, .samples = rt->getSamples(), + .needsResolveMask = 0, + .usesLazilyAllocatedMemory = 0, .subpassMask = uint8_t(params.subpassMask), .viewCount = renderTargetLayerCount, }; @@ -1297,8 +1299,16 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP if (info.texture) { assert_invariant(info.layerCount == renderTargetLayerCount); rpkey.colorFormat[i] = info.getFormat(); - if (rpkey.samples > 1 && info.texture->samples == 1) { - rpkey.needsResolveMask |= (1 << i); + if (rpkey.samples > 1) { + const VulkanTexture* sidecar = info.texture->getSidecar(); + assert_invariant(sidecar); + assert_invariant(sidecar->samples > 1); + if (sidecar && sidecar->isTransientAttachment()) { + rpkey.usesLazilyAllocatedMemory |= (1 << i); + } + if (info.texture->samples == 1) { + rpkey.needsResolveMask |= (1 << i); + } } } else { rpkey.colorFormat[i] = VK_FORMAT_UNDEFINED; diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 927dd4bcedf..980131025e5 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -41,6 +41,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1, if (k1.discardEnd != k2.discardEnd) return false; if (k1.samples != k2.samples) return false; if (k1.needsResolveMask != k2.needsResolveMask) return false; + if (k1.usesLazilyAllocatedMemory != k2.usesLazilyAllocatedMemory) return false; if (k1.subpassMask != k2.subpassMask) return false; if (k1.viewCount != k2.viewCount) return false; return true; @@ -254,7 +255,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { .format = config.colorFormat[i], .samples = (VkSampleCountFlagBits) config.samples, .loadOp = clear ? kClear : (discard ? kDontCare : kKeep), - .storeOp = kEnableStore, + .storeOp = (config.usesLazilyAllocatedMemory & (1 << i)) ? kDisableStore : kEnableStore, .stencilLoadOp = kDontCare, .stencilStoreOp = kDisableStore, .initialLayout = imgutil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT), diff --git a/filament/backend/src/vulkan/VulkanFboCache.h b/filament/backend/src/vulkan/VulkanFboCache.h index 3335db7c1c9..2b186106f68 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.h +++ b/filament/backend/src/vulkan/VulkanFboCache.h @@ -42,18 +42,19 @@ class VulkanFboCache { // RenderPassKey is a small POD representing the immutable state that is used to construct // a VkRenderPass. It is hashed and used as a lookup key. struct alignas(8) RenderPassKey { - VulkanLayout initialDepthLayout; - uint8_t padding[3] = {}; - VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes VkFormat depthFormat; // 4 bytes TargetBufferFlags clear; // 4 bytes TargetBufferFlags discardStart; // 4 bytes TargetBufferFlags discardEnd; // 4 bytes + + VulkanLayout initialDepthLayout; // 1 byte uint8_t samples; // 1 byte uint8_t needsResolveMask; // 1 byte + uint8_t usesLazilyAllocatedMemory; // 1 byte uint8_t subpassMask; // 1 byte uint8_t viewCount; // 1 byte + uint8_t padding[2]; }; struct RenderPassVal { VkRenderPass handle; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index a2f63c4b60f..d7fc1d76ec7 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -282,11 +282,16 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica if (texture && texture->samples == 1) { auto msTexture = texture->getSidecar(); if (UTILS_UNLIKELY(!msTexture)) { + // Clear all usage flags that are not related to attachments, so that we can + // use the transient usage flag. + const TextureUsage usage = texture->usage & TextureUsage::ALL_ATTACHMENTS; + assert_invariant(static_cast(usage) != 0U); + // TODO: This should be allocated with the ResourceAllocator. msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands, handleAllocator, texture->target, ((VulkanTexture const*) texture)->levels, texture->format, - samples, texture->width, texture->height, texture->depth, texture->usage, + samples, texture->width, texture->height, texture->depth, usage, stagePool, true /* heap allocated */); texture->setSidecar(msTexture); } diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index 2181ee59053..e33fd6faa00 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -128,7 +128,8 @@ VulkanTextureState::VulkanTextureState( mStagePool(stagePool), mDevice(device), mAllocator(allocator), - mCommands(commands) { + mCommands(commands), + mIsTransientAttachment(false) { } VulkanTextureState* VulkanTexture::getSharedState() { @@ -209,6 +210,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } + // Determine if we can use the transient usage flag combined with lazily allocated memory. + const bool useTransientAttachment = + // Lazily allocated memory is available. + context.isLazilyAllocatedMemorySupported() && + // Usage consists of attachment flags only. + none(tusage & ~TextureUsage::ALL_ATTACHMENTS) && + // Usage contains at least one attachment flag. + any(tusage & TextureUsage::ALL_ATTACHMENTS); + state->mIsTransientAttachment = useTransientAttachment; + + const VkImageUsageFlags transientFlag = + useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U; + if (any(usage & TextureUsage::SAMPLEABLE)) { #if FVK_ENABLED(FVK_DEBUG_TEXTURE) @@ -224,19 +238,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT; } if (any(usage & TextureUsage::COLOR_ATTACHMENT)) { - imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag; if (any(usage & TextureUsage::SUBPASS_INPUT)) { imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; } } if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) { - imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag; } if (any(usage & TextureUsage::UPLOADABLE)) { imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) { - imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag; // Depth resolves uses a custom shader and therefore needs to be sampleable. if (samples > 1) { @@ -285,8 +299,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VkMemoryRequirements memReqs = {}; vkGetImageMemoryRequirements(state->mDevice, state->mTextureImage, &memReqs); + const VkFlags requiredMemoryFlags = + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + (useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U); uint32_t memoryTypeIndex - = context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + = context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags); FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES) << "VulkanTexture: unable to find a memory type that meets requirements."; diff --git a/filament/backend/src/vulkan/VulkanTexture.h b/filament/backend/src/vulkan/VulkanTexture.h index e3d1babbbd0..4b76eb5329e 100644 --- a/filament/backend/src/vulkan/VulkanTexture.h +++ b/filament/backend/src/vulkan/VulkanTexture.h @@ -79,6 +79,7 @@ struct VulkanTextureState : public VulkanResource { VmaAllocator mAllocator; VulkanCommands* mCommands; std::shared_ptr mTransitionFence; + bool mIsTransientAttachment; }; @@ -170,6 +171,11 @@ struct VulkanTexture : public HwTexture, VulkanResource { return state->mSidecarMSAA.get(); } + bool isTransientAttachment() const { + VulkanTextureState const* state = getSharedState(); + return state->mIsTransientAttachment; + } + bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range, VulkanLayout newLayout); diff --git a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp index 2e88542080f..6ddfb94bd31 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatform.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatform.cpp @@ -740,6 +740,20 @@ Driver* VulkanPlatform::createDriver(void* sharedContext, context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME); + // Check the availability of lazily allocated memory + { + context.mLazilyAllocatedMemorySupported = false; + const uint32_t typeCount = context.mMemoryProperties.memoryTypeCount; + for(uint32_t i = 0; i < typeCount; ++i) { + const VkMemoryType type = context.mMemoryProperties.memoryTypes[i]; + if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) { + context.mLazilyAllocatedMemorySupported = true; + assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + break; + } + } + } + #ifdef NDEBUG // If we are in release build, we should not have turned on debug extensions FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported) From 40c0d594646de620c5ab30130f6a9753136ace56 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Wed, 9 Oct 2024 16:54:14 -0700 Subject: [PATCH 24/36] Metal: enhance assertions for buffer uploads (#8188) --- filament/backend/src/metal/MetalBuffer.h | 12 ++++--- filament/backend/src/metal/MetalBuffer.mm | 38 ++++++++++++++++------ filament/backend/src/metal/MetalDriver.mm | 19 ++++++++--- filament/backend/src/metal/MetalHandles.h | 8 +++-- filament/backend/src/metal/MetalHandles.mm | 10 +++--- 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 4fc725bb046..f6fa90c1990 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -174,8 +174,10 @@ class MetalBuffer { * Update the buffer with data inside src. Potentially allocates a new buffer allocation to hold * the bytes which will be released when the current frame is finished. */ - void copyIntoBuffer(void* src, size_t size, size_t byteOffset); - void copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset); + using TagResolver = utils::Invocable; + void copyIntoBuffer(void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag); + void copyIntoBufferUnsynchronized( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag); /** * Denotes that this buffer is used for a draw call ensuring that its allocation remains valid @@ -214,8 +216,10 @@ class MetalBuffer { BUMP_ALLOCATOR, }; - void uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const; - void uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const; + void uploadWithPoolBuffer( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const; + void uploadWithBumpAllocator( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const; UploadStrategy mUploadStrategy; TrackedMetalBuffer mBuffer; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index d6f68c33af0..6347ab70c6f 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -66,15 +66,20 @@ } } -void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) { +void MetalBuffer::copyIntoBuffer( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) { if (size <= 0) { return; } + + FILAMENT_CHECK_PRECONDITION(src) + << "copyIntoBuffer called with a null src, tag=" << getHandleTag(); FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize) << "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize - << " at offset " << byteOffset; + << " at offset " << byteOffset << ", tag=" << getHandleTag(); // The copy blit requires that byteOffset be a multiple of 4. - FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) << "byteOffset must be a multiple of 4"; + FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) + << "byteOffset must be a multiple of 4, tag=" << getHandleTag(); // If we have a cpu buffer, we can directly copy into it. if (mCpuBuffer) { @@ -84,17 +89,18 @@ switch (mUploadStrategy) { case UploadStrategy::BUMP_ALLOCATOR: - uploadWithBumpAllocator(src, size, byteOffset); + uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag)); break; case UploadStrategy::POOL: - uploadWithPoolBuffer(src, size, byteOffset); + uploadWithPoolBuffer(src, size, byteOffset, std::move(getHandleTag)); break; } } -void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) { +void MetalBuffer::copyIntoBufferUnsynchronized( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) { // TODO: implement the unsynchronized version - copyIntoBuffer(src, size, byteOffset); + copyIntoBuffer(src, size, byteOffset, std::move(getHandleTag)); } id MetalBuffer::getGpuBufferForDraw() noexcept { @@ -200,9 +206,13 @@ } } -void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const { +void MetalBuffer::uploadWithPoolBuffer( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const { MetalBufferPool* bufferPool = mContext.bufferPool; const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size); + FILAMENT_CHECK_POSTCONDITION(staging) + << "uploadWithPoolbuffer unable to acquire staging buffer of size " << size + << ", tag=" << getHandleTag(); memcpy(staging->buffer.get().contents, src, size); // Encode a blit from the staging buffer into the private GPU buffer. @@ -220,10 +230,18 @@ }]; } -void MetalBuffer::uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const { +void MetalBuffer::uploadWithBumpAllocator( + void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) const { MetalBumpAllocator& allocator = *mContext.bumpAllocator; auto [buffer, offset] = allocator.allocateStagingArea(size); - memcpy(static_cast(buffer.contents) + offset, src, size); + FILAMENT_CHECK_POSTCONDITION(buffer) + << "uploadWithBumpAllocator unable to acquire staging area of size " << size + << ", tag=" << getHandleTag(); + void* const contents = buffer.contents; + FILAMENT_CHECK_POSTCONDITION(contents) + << "uploadWithBumpAllocator unable to acquire pointer to staging area, size " << size + << ", tag=" << getHandleTag(); + memcpy(static_cast(contents) + offset, src, size); // Encode a blit from the staging buffer into the private GPU buffer. id cmdBuffer = getPendingCommandBuffer(&mContext); diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 0bcb55aa10f..fb17e99c76b 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -40,6 +40,7 @@ #include #include #include +#include #include @@ -1081,24 +1082,34 @@ void MetalDriver::updateIndexBuffer(Handle ibh, BufferDescriptor&& data, uint32_t byteOffset) { + FILAMENT_CHECK_PRECONDITION(data.buffer) + << "updateIndexBuffer called with a null buffer."; auto* ib = handle_cast(ibh); - ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset); + ib->buffer.copyIntoBuffer(data.buffer, data.size, byteOffset, + [&]() { return mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe(); }); scheduleDestroy(std::move(data)); } void MetalDriver::updateBufferObject(Handle boh, BufferDescriptor&& data, uint32_t byteOffset) { FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext)) - << "updateBufferObject must be called outside of a render pass."; + << "updateBufferObject must be called outside of a render pass. tag=" + << mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); + FILAMENT_CHECK_PRECONDITION(data.buffer) + << "updateBufferObject called with a null buffer. tag=" + << mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); auto* bo = handle_cast(boh); - bo->updateBuffer(data.buffer, data.size, byteOffset); + + bo->updateBuffer(data.buffer, data.size, byteOffset, + [&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); }); scheduleDestroy(std::move(data)); } void MetalDriver::updateBufferObjectUnsynchronized(Handle boh, BufferDescriptor&& data, uint32_t byteOffset) { auto* bo = handle_cast(boh); - bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset); + bo->updateBufferUnsynchronized(data.buffer, data.size, byteOffset, + [&]() { return mHandleAllocator.getHandleTag(boh.getId()).c_str_safe(); }); scheduleDestroy(std::move(data)); } diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 8e2f7e253d0..0c02b213edb 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -135,11 +135,15 @@ class MetalSwapChain : public HwSwapChain { class MetalBufferObject : public HwBufferObject { public: + + using TagResolver = MetalBuffer::TagResolver; + MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage, uint32_t byteCount); - void updateBuffer(void* data, size_t size, uint32_t byteOffset); - void updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset); + void updateBuffer(void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag); + void updateBufferUnsynchronized( + void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag); MetalBuffer* getBuffer() { return &buffer; } private: diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index c451264efac..e0d40f5cdc6 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -383,12 +383,14 @@ static void func(void* user) { BufferUsage usage, uint32_t byteCount) : HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {} -void MetalBufferObject::updateBuffer(void* data, size_t size, uint32_t byteOffset) { - buffer.copyIntoBuffer(data, size, byteOffset); +void MetalBufferObject::updateBuffer( + void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) { + buffer.copyIntoBuffer(data, size, byteOffset, std::move(getHandleTag)); } -void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint32_t byteOffset) { - buffer.copyIntoBufferUnsynchronized(data, size, byteOffset); +void MetalBufferObject::updateBufferUnsynchronized( + void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) { + buffer.copyIntoBufferUnsynchronized(data, size, byteOffset, std::move(getHandleTag)); } MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount, From 614dbb44d532c1da2b04f26b6331fd3c7a862ff0 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 10 Oct 2024 09:02:11 -0700 Subject: [PATCH 25/36] Make sure color attachment textures have copy-able usage set (#8184) To ensure the source of readPixels() is properly copy-able, we want the backing textures to be created with the right BLIT_SRC usage. However, this was not documented in the API. We workaround the issue to tag all color attachment textures as BLIT_SRC. This workaround will be removed in the future. For now, violations of this condition will elicit a warning being printed out. --- .../platform/VulkanPlatformSwapChainImpl.cpp | 34 +++++++++---------- filament/include/filament/Renderer.h | 5 ++- filament/src/details/RenderTarget.cpp | 14 ++++++-- filament/src/details/RenderTarget.h | 5 +++ filament/src/details/Renderer.cpp | 20 ++++++++--- filament/src/details/Texture.cpp | 11 ++++++ filament/src/details/Texture.h | 13 ++++++- 7 files changed, 77 insertions(+), 25 deletions(-) diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp index 6c643f754d8..cfec76e3bc9 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp @@ -31,22 +31,22 @@ namespace { std::tuple createImageAndMemory(VulkanContext const& context, VkDevice device, VkExtent2D extent, VkFormat format) { bool const isDepth = isVkDepthFormat(format); - // Filament expects blit() to work with any texture, so we almost always set these usage flags. - // TODO: investigate performance implications of setting these flags. - VkImageUsageFlags const blittable - = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - VkImageCreateInfo imageInfo{ - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .imageType = VK_IMAGE_TYPE_2D, - .format = format, - .extent = {extent.width, extent.height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = blittable - | (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT - : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT), + // Filament expects blit() to work with any texture, so we almost always set these usage flags + // (see copyFrame() and readPixels()). + VkImageUsageFlags const blittable = + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + VkImageCreateInfo imageInfo { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = {extent.width, extent.height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = blittable | (isDepth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT + : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT), }; VkImage image; VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image); @@ -220,7 +220,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() { .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT - | VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination. + | VK_IMAGE_USAGE_TRANSFER_DST_BIT // Allows use as a blit destination (for copyFrame) | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,// Allows use as a blit source (for readPixels) // TODO: Setting the preTransform to IDENTITY means we are letting the Android diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index a4e919610db..7e66c898c45 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -508,7 +508,7 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * * Framebuffer as seen on User buffer (PixelBufferDescriptor&) * screen - * + * * +--------------------+ * | | .stride .alignment * | | ----------------------->--> @@ -538,6 +538,9 @@ class UTILS_PUBLIC Renderer : public FilamentAPI { * uploaded to it via setImage, the data returned from readPixels will be y-flipped with respect * to the setImage call. * + * Note: the texture that backs the COLOR attachment for `renderTarget` must have + * TextureUsage::BLIT_SRC as part of its usage. + * * @remark * readPixels() is intended for debugging and testing. It will impact performance significantly. * diff --git a/filament/src/details/RenderTarget.cpp b/filament/src/details/RenderTarget.cpp index 6c232e43e5c..53697057b7d 100644 --- a/filament/src/details/RenderTarget.cpp +++ b/filament/src/details/RenderTarget.cpp @@ -156,8 +156,8 @@ RenderTarget* RenderTarget::Builder::build(Engine& engine) { // ------------------------------------------------------------------------------------------------ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& builder) - : mSupportedColorAttachmentsCount(engine.getDriverApi().getMaxDrawBuffers()) { - + : mSupportedColorAttachmentsCount(engine.getDriverApi().getMaxDrawBuffers()), + mSupportsReadPixels(false) { std::copy(std::begin(builder.mImpl->mAttachments), std::end(builder.mImpl->mAttachments), std::begin(mAttachments)); @@ -189,6 +189,16 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build (TextureUsage::SAMPLEABLE | Texture::Usage::SUBPASS_INPUT))) { mSampleableAttachmentsMask |= targetBufferBit; } + + // readPixels() only applies to the color attachment that binds at index 0. + if (i == 0 && any(attachment.texture->getUsage() & TextureUsage::COLOR_ATTACHMENT)) { + + // TODO: the following will be changed to + // mSupportsReadPixels = + // any(attachment.texture->getUsage() & TextureUsage::BLIT_SRC); + // in a later filament version when clients have properly added the right usage. + mSupportsReadPixels = attachment.texture->hasBlitSrcUsage(); + } } } diff --git a/filament/src/details/RenderTarget.h b/filament/src/details/RenderTarget.h index 1b57b5acc9b..9fba1f60ff1 100644 --- a/filament/src/details/RenderTarget.h +++ b/filament/src/details/RenderTarget.h @@ -68,6 +68,10 @@ class FRenderTarget : public RenderTarget { bool hasSampleableDepth() const noexcept; + bool supportsReadPixels() const noexcept { + return mSupportsReadPixels; + } + private: friend class RenderTarget; static constexpr size_t ATTACHMENT_COUNT = MAX_SUPPORTED_COLOR_ATTACHMENTS_COUNT + 1u; @@ -76,6 +80,7 @@ class FRenderTarget : public RenderTarget { backend::TargetBufferFlags mAttachmentMask = {}; backend::TargetBufferFlags mSampleableAttachmentsMask = {}; const uint8_t mSupportedColorAttachmentsCount; + bool mSupportsReadPixels = false; }; FILAMENT_DOWNCAST(RenderTarget) diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index e201c8dd63e..494486e2a14 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -455,6 +455,15 @@ void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, u void FRenderer::readPixels(FRenderTarget* renderTarget, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer) { + + // TODO: change the following to an assert when client call sites have addressed the issue. + if (!renderTarget->supportsReadPixels()) { + utils::slog.w << "readPixels() must be called with a renderTarget with COLOR0 created with " + "TextureUsage::BLIT_SRC. This precondition will be asserted in a later " + "release of Filament." + << utils::io::endl; + } + RendererUtils::readPixels(mEngine.getDriverApi(), renderTarget->getHwHandle(), xoffset, yoffset, width, height, std::move(buffer)); } @@ -963,15 +972,18 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { }); const auto picking = picking_; - if (view.hasPicking()) { struct PickingResolvePassData { FrameGraphId picking; }; - fg.addPass("Picking Resolve Pass", + fg.addPass( + "Picking Resolve Pass", [&](FrameGraph::Builder& builder, auto& data) { - data.picking = builder.read(picking, - FrameGraphTexture::Usage::COLOR_ATTACHMENT); + // Note that BLIT_SRC is needed because this texture will be read later (via + // readPixels()). + data.picking = + builder.read(picking, FrameGraphTexture::Usage::COLOR_ATTACHMENT | + FrameGraphTexture::Usage::BLIT_SRC); builder.declareRenderPass("Picking Resolve Target", { .attachments = { .color = { data.picking }} }); diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index 2f405fd2ab3..176907345b7 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -79,6 +79,7 @@ struct Texture::BuilderDetails { Sampler mTarget = Sampler::SAMPLER_2D; InternalFormat mFormat = InternalFormat::RGBA8; Usage mUsage = Usage::NONE; + bool mHasBlitSrc = false; bool mTextureIsSwizzled = false; std::array mSwizzle = { Swizzle::CHANNEL_0, Swizzle::CHANNEL_1, @@ -183,6 +184,15 @@ Texture* Texture::Builder::build(Engine& engine) { } } + // TODO: remove in a future filament release. + // Clients might not have known that textures that are read need to have BLIT_SRC as usages. For + // now, we workaround the issue by making sure any color attachment can be the source of a copy + // for readPixels(). + mImpl->mHasBlitSrc = any(mImpl->mUsage & TextureUsage::BLIT_SRC); + if (!mImpl->mHasBlitSrc && any(mImpl->mUsage & TextureUsage::COLOR_ATTACHMENT)) { + mImpl->mUsage |= TextureUsage::BLIT_SRC; + } + const bool sampleable = bool(mImpl->mUsage & TextureUsage::SAMPLEABLE); const bool swizzled = mImpl->mTextureIsSwizzled; const bool imported = mImpl->mImportedId; @@ -232,6 +242,7 @@ FTexture::FTexture(FEngine& engine, const Builder& builder) { mLevelCount = builder->mLevels; mSwizzle = builder->mSwizzle; mTextureIsSwizzled = builder->mTextureIsSwizzled; + mHasBlitSrc = builder->mHasBlitSrc; bool const isImported = builder->mImportedId != 0; if (mTarget == SamplerType::SAMPLER_EXTERNAL && !isImported) { diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index 7c0234bb535..6bf04dae4ac 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -123,6 +123,11 @@ class FTexture : public Texture { bool textureHandleCanMutate() const noexcept; void updateLodRange(uint8_t level) noexcept; + // TODO: remove in a future filament release. See below for description. + inline bool hasBlitSrcUsage() const noexcept { + return mHasBlitSrc; + } + private: friend class Texture; struct LodRange { @@ -163,7 +168,13 @@ class FTexture : public Texture { bool mTextureIsSwizzled; Usage mUsage = Usage::DEFAULT; - // there is 7 bytes of padding here + + // TODO: remove in a future filament release. + // Indicates whether the user has set the TextureUsage::BLIT_SRC usage. This will be used to + // temporarily validate whether this texture can be used for readPixels. + bool mHasBlitSrc = false; + // there is 5 bytes of padding here + FStream* mStream = nullptr; // only needed for streaming textures }; From 014e6bcbde7764a3d72bf448ea460be43435dcfc Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Thu, 10 Oct 2024 10:12:22 -0700 Subject: [PATCH 26/36] Update hardware Handle (#8187) In certain compilers, the assignment operators defined as default doesn't automatically make a call to the parent's method if it's user-defined. Make this behavior explicit to avoid this edge case. BUGS=[371980551] --- filament/backend/include/backend/Handle.h | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index 2cf52244149..d5e88206b8f 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -23,6 +23,7 @@ #include #include // FIXME: STL headers are not allowed in public headers +#include #include @@ -106,8 +107,18 @@ struct Handle : public HandleBase { Handle(Handle const& rhs) noexcept = default; Handle(Handle&& rhs) noexcept = default; - Handle& operator=(Handle const& rhs) noexcept = default; - Handle& operator=(Handle&& rhs) noexcept = default; + // Explicitly redefine copy/move assignment operators rather than just using default here. + // Because it doesn't make a call to the parent's method automatically during the std::move + // function call(https://en.cppreference.com/w/cpp/algorithm/move) in certain compilers like + // NDK 25.1.8937393 and below (see b/371980551) + Handle& operator=(Handle const& rhs) noexcept { + HandleBase::operator=(rhs); + return *this; + } + Handle& operator=(Handle&& rhs) noexcept { + HandleBase::operator=(std::move(rhs)); + return *this; + } explicit Handle(HandleId id) noexcept : HandleBase(id) { } From b1b2d7072d7a1f6f48148600321b81f124a997e4 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 10 Oct 2024 12:16:00 -0700 Subject: [PATCH 27/36] Revert "Release Filament 1.55.0" (#8191) This reverts commit 4f5369cefa6858e6445281ea3850a6676ddfaa4e. Reason: Initial attempt to release 1.55.0 failed due to a few small bugs. --- README.md | 4 ++-- RELEASE_NOTES.md | 3 --- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1daff364f40..6d1f5f7e1c9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.55.0' + implementation 'com.google.android.filament:filament-android:1.54.5' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.55.0' +pod 'Filament', '~> 1.54.5' ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b5522d40a21..e6727d791a7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,9 +7,6 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). -## v1.55.1 - - ## v1.55.0 - Add descriptor sets to describe shader resources. [⚠️ **New Material Version**] diff --git a/android/gradle.properties b/android/gradle.properties index 19e07a87034..85a92d8700c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.55.0 +VERSION_NAME=1.54.5 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index cbddfbcb0c2..a4f0521f66a 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.55.0" + spec.version = "1.54.5" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.0/filament-v1.55.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.54.5/filament-v1.54.5-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 58c8ffdb202..9860fd38434 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.55.0", + "version": "1.54.5", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 875b2959673ad83f78fc6d2de0c781516342cc64 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Thu, 10 Oct 2024 12:31:20 -0700 Subject: [PATCH 28/36] vk: refactor texture default layout (#8183) Previously, default layout is based on usage, but this actually has two paths (Filament's TextureUsage and the computed VkTextureUsage) that do not always agree. We simplify so that default layout is stored in the texture itself. Also remove some unnecessary code that is no longer necessary. In particular, we shouldn't be doing a flush and wait for the transition to complete before updating a sampler descriptor. We just need to make sure the layout before it is accessed is correctly given in the update struct. --- filament/backend/src/vulkan/VulkanBlitter.cpp | 8 +- filament/backend/src/vulkan/VulkanDriver.cpp | 10 -- filament/backend/src/vulkan/VulkanDriver.h | 1 - .../backend/src/vulkan/VulkanImageUtility.h | 30 ----- .../backend/src/vulkan/VulkanReadPixels.cpp | 4 +- filament/backend/src/vulkan/VulkanTexture.cpp | 114 ++++++++++++------ filament/backend/src/vulkan/VulkanTexture.h | 24 ++-- .../caching/VulkanDescriptorSetManager.cpp | 2 +- 8 files changed, 91 insertions(+), 102 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index 7100b026352..db68b94b58b 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -66,10 +66,10 @@ inline void blitFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect, V 1, blitRegions, filter); if (oldSrcLayout == VulkanLayout::UNDEFINED) { - oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage); + oldSrcLayout = src.texture->getDefaultLayout(); } if (oldDstLayout == VulkanLayout::UNDEFINED) { - oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage); + oldDstLayout = dst.texture->getDefaultLayout(); } src.texture->transitionLayout(commands, srcRange, oldSrcLayout); dst.texture->transitionLayout(commands, dstRange, oldDstLayout); @@ -109,10 +109,10 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect 1, resolveRegions); if (oldSrcLayout == VulkanLayout::UNDEFINED) { - oldSrcLayout = imgutil::getDefaultLayout(src.texture->usage); + oldSrcLayout = src.texture->getDefaultLayout(); } if (oldDstLayout == VulkanLayout::UNDEFINED) { - oldDstLayout = imgutil::getDefaultLayout(dst.texture->usage); + oldDstLayout = dst.texture->getDefaultLayout(); } src.texture->transitionLayout(commands, srcRange, oldSrcLayout); dst.texture->transitionLayout(commands, dstRange, oldDstLayout); diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index f1a41cc7a62..18f6bf53596 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -404,14 +404,6 @@ void VulkanDriver::updateDescriptorSetTexture( SamplerParams params) { VulkanDescriptorSet* set = mResourceAllocator.handle_cast(dsh); VulkanTexture* texture = mResourceAllocator.handle_cast(th); - - // We need to make sure the initial layout transition has been completed before we can write - // the sampler descriptor. We flush and wait until the transition has been completed. - if (!texture->transitionReady()) { - mCommands.flush(); - mCommands.wait(); - } - VkSampler const vksampler = mSamplerCache.getSampler(params); mDescriptorSetManager.updateSampler(set, binding, texture, vksampler); } @@ -1360,8 +1352,6 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP VulkanTexture* texture = attachment.texture; if (texture->samples == 1) { - mRenderPassFboInfo.hasColorResolve = true; - auto const& range = attachment.getSubresourceRange(); attachment.texture->transitionLayout(&commands, range, VulkanLayout::COLOR_ATTACHMENT); diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index a3a9456a82b..7dd95209342 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -152,7 +152,6 @@ class VulkanDriver final : public DriverBase { struct { using AttachmentArray = CappedArray; AttachmentArray attachments; - bool hasColorResolve = false; } mRenderPassFboInfo = {}; bool const mIsSRGBSwapChainSupported; diff --git a/filament/backend/src/vulkan/VulkanImageUtility.h b/filament/backend/src/vulkan/VulkanImageUtility.h index 82ff436c163..f8027d96975 100644 --- a/filament/backend/src/vulkan/VulkanImageUtility.h +++ b/filament/backend/src/vulkan/VulkanImageUtility.h @@ -76,36 +76,6 @@ inline VkImageViewType getViewType(SamplerType target) { } } -inline VulkanLayout getDefaultLayout(TextureUsage usage) { - if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) { - if (any(usage & TextureUsage::SAMPLEABLE)) { - return VulkanLayout::DEPTH_SAMPLER; - } else { - return VulkanLayout::DEPTH_ATTACHMENT; - } - } - - if (any(usage & TextureUsage::COLOR_ATTACHMENT)) { - return VulkanLayout::COLOR_ATTACHMENT; - } - // Finally, the layout for an immutable texture is optimal read-only. - return VulkanLayout::READ_ONLY; -} - -inline VulkanLayout getDefaultLayout(VkImageUsageFlags vkusage) { - TextureUsage usage{}; - if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { - usage = usage | TextureUsage::DEPTH_ATTACHMENT; - } - if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) { - usage = usage | TextureUsage::COLOR_ATTACHMENT; - } - if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) { - usage = usage | TextureUsage::SAMPLEABLE; - } - return getDefaultLayout(usage); -} - constexpr inline VkImageLayout getVkLayout(VulkanLayout layout) { switch (layout) { case VulkanLayout::UNDEFINED: diff --git a/filament/backend/src/vulkan/VulkanReadPixels.cpp b/filament/backend/src/vulkan/VulkanReadPixels.cpp index bef08811c6d..418f6771961 100644 --- a/filament/backend/src/vulkan/VulkanReadPixels.cpp +++ b/filament/backend/src/vulkan/VulkanReadPixels.cpp @@ -232,7 +232,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint VulkanAttachment const srcAttachment = srcTarget->getColor(0); VkImageSubresourceRange const srcRange = srcAttachment.getSubresourceRange(); - srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::TRANSFER_SRC); + srcTexture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC); VkImageCopy const imageCopyRegion = { .srcSubresource = { @@ -268,7 +268,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint imgutil::getVkLayout(VulkanLayout::TRANSFER_DST), 1, &imageCopyRegion); // Restore the source image layout. - srcTexture->transitionLayout(cmdbuffer, {}, srcRange, VulkanLayout::COLOR_ATTACHMENT); + srcTexture->transitionLayout(cmdbuffer, srcRange, srcTexture->getDefaultLayout()); vkEndCommandBuffer(cmdbuffer); diff --git a/filament/backend/src/vulkan/VulkanTexture.cpp b/filament/backend/src/vulkan/VulkanTexture.cpp index e33fd6faa00..2358d6ea0de 100644 --- a/filament/backend/src/vulkan/VulkanTexture.cpp +++ b/filament/backend/src/vulkan/VulkanTexture.cpp @@ -113,25 +113,52 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap }; } -} // anonymous namespace +inline VulkanLayout getDefaultLayoutImpl(TextureUsage usage) { + if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) { + if (any(usage & TextureUsage::SAMPLEABLE)) { + return VulkanLayout::DEPTH_SAMPLER; + } else { + return VulkanLayout::DEPTH_ATTACHMENT; + } + } -VulkanTextureState::VulkanTextureState( - VkDevice device, VmaAllocator allocator, VulkanCommands* commands, - VulkanStagePool& stagePool, - VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount) - : VulkanResource(VulkanResourceType::HEAP_ALLOCATED), - mVkFormat(format), - mViewType(viewType), - mFullViewRange { - filament::backend::getImageAspect(format), 0, levels, 0, layerCount - }, - mStagePool(stagePool), - mDevice(device), - mAllocator(allocator), - mCommands(commands), - mIsTransientAttachment(false) { + if (any(usage & TextureUsage::COLOR_ATTACHMENT)) { + return VulkanLayout::COLOR_ATTACHMENT; + } + // Finally, the layout for an immutable texture is optimal read-only. + return VulkanLayout::READ_ONLY; +} + +inline VulkanLayout getDefaultLayoutImpl(VkImageUsageFlags vkusage) { + TextureUsage usage{}; + if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { + usage = usage | TextureUsage::DEPTH_ATTACHMENT; + } + if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) { + usage = usage | TextureUsage::COLOR_ATTACHMENT; + } + if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) { + usage = usage | TextureUsage::SAMPLEABLE; + } + return getDefaultLayoutImpl(usage); } +} // anonymous namespace + +VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator, + VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format, + VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout) + : VulkanResource(VulkanResourceType::HEAP_ALLOCATED), + mVkFormat(format), + mViewType(viewType), + mFullViewRange{filament::backend::getImageAspect(format), 0, levels, 0, layerCount}, + mDefaultLayout(defaultLayout), + mStagePool(stagePool), + mDevice(device), + mAllocator(allocator), + mCommands(commands), + mIsTransientAttachment(false) {} + VulkanTextureState* VulkanTexture::getSharedState() { VulkanTextureState* state = mAllocator->handle_cast(mState); return state; @@ -142,6 +169,7 @@ VulkanTextureState const* VulkanTexture::getSharedState() const { return state; } +// Constructor for internally passed VkImage VulkanTexture::VulkanTexture( VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, @@ -154,12 +182,14 @@ VulkanTexture::VulkanTexture( mAllocator(handleAllocator), mState(handleAllocator->initHandle( device, allocator, commands, stagePool, - format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1)) { + format, imgutil::getViewType(SamplerType::SAMPLER_2D), 1, 1, + getDefaultLayoutImpl(tusage))) { auto* const state = getSharedState(); state->mTextureImage = image; mPrimaryViewRange = state->mFullViewRange; } +// Constructor for user facing texture VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, VulkanResourceAllocator* handleAllocator, SamplerType target, uint8_t levels, @@ -171,7 +201,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, mAllocator(handleAllocator), mState(handleAllocator->initHandle(device, allocator, commands, stagePool, backend::getVkFormat(tformat), imgutil::getViewType(target), levels, - getLayerCount(target, depth))) { + getLayerCount(target, depth), VulkanLayout::UNDEFINED)) { auto* const state = getSharedState(); // Create an appropriately-sized device-only VkImage, but do not fill it yet. @@ -326,18 +356,20 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanCommandBuffer& commandsBuf = state->mCommands->get(); commandsBuf.acquire(this); - transitionLayout(&commandsBuf, mPrimaryViewRange, imgutil::getDefaultLayout(imageInfo.usage)); + + auto const defaultLayout = state->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage); + transitionLayout(&commandsBuf, mPrimaryViewRange, defaultLayout); } +// Constructor for creating a texture view VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, - VulkanResourceAllocator* handleAllocator, - VulkanTexture const* src, uint8_t baseLevel, uint8_t levelCount) - : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, - src->format, src->usage), - VulkanResource(VulkanResourceType::TEXTURE), - mAllocator(handleAllocator) -{ + VulkanResourceAllocator* handleAllocator, VulkanTexture const* src, uint8_t baseLevel, + uint8_t levelCount) + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + VulkanResource(VulkanResourceType::TEXTURE), + mAllocator(handleAllocator) { mState = src->mState; auto* state = getSharedState(); @@ -347,14 +379,15 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, mPrimaryViewRange.levelCount = levelCount; } +// Constructor for creating a texture view with swizzle VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands, - VulkanResourceAllocator* handleAllocator, - VulkanTexture const* src, VkComponentMapping swizzle) - : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, - src->format, src->usage), - VulkanResource(VulkanResourceType::TEXTURE), - mAllocator(handleAllocator) { + VulkanResourceAllocator* handleAllocator, VulkanTexture const* src, + VkComponentMapping swizzle) + : HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth, + src->format, src->usage), + VulkanResource(VulkanResourceType::TEXTURE), + mAllocator(handleAllocator) { mState = src->mState; auto* state = getSharedState(); state->refs++; @@ -457,7 +490,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt VkImageLayout const newVkLayout = imgutil::getVkLayout(newLayout); if (nextLayout == VulkanLayout::UNDEFINED) { - nextLayout = imgutil::getDefaultLayout(this->usage); + nextLayout = getDefaultLayout(); } transitionLayout(&commands, transitionRange, newLayout); @@ -508,6 +541,11 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u transitionLayout(&commands, range, oldLayout); } +VulkanLayout VulkanTexture::getDefaultLayout() const { + auto* const state = getSharedState(); + return state->mDefaultLayout; +} + VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) { range.levelCount = 1; range.layerCount = 1; @@ -553,13 +591,11 @@ VkImageAspectFlags VulkanTexture::getImageAspect() const { } bool VulkanTexture::transitionLayout(VulkanCommandBuffer* commands, - const VkImageSubresourceRange& range, VulkanLayout newLayout) { - return transitionLayout(commands->buffer(), commands->fence, range, newLayout); + VkImageSubresourceRange const& range, VulkanLayout newLayout) { + return transitionLayout(commands->buffer(), range, newLayout); } -bool VulkanTexture::transitionLayout( - VkCommandBuffer cmdbuf, std::shared_ptr fence, - const VkImageSubresourceRange& range, +bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range, VulkanLayout newLayout) { auto* const state = getSharedState(); VulkanLayout const oldLayout = getLayout(range.baseArrayLayer, range.baseMipLevel); @@ -619,8 +655,6 @@ bool VulkanTexture::transitionLayout( setLayout(range, newLayout); if (hasTransitions) { - state->mTransitionFence = fence; - #if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION) FVK_LOGD << "transition texture=" << state->mTextureImage << " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << "," diff --git a/filament/backend/src/vulkan/VulkanTexture.h b/filament/backend/src/vulkan/VulkanTexture.h index 4b76eb5329e..acc58846709 100644 --- a/filament/backend/src/vulkan/VulkanTexture.h +++ b/filament/backend/src/vulkan/VulkanTexture.h @@ -34,8 +34,8 @@ class VulkanResourceAllocator; struct VulkanTextureState : public VulkanResource { VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, - VulkanStagePool& stagePool, - VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount); + VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels, + uint8_t layerCount, VulkanLayout defaultLayout); struct ImageViewKey { VkImageSubresourceRange range; // 4 * 5 bytes @@ -67,8 +67,8 @@ struct VulkanTextureState : public VulkanResource { VkFormat const mVkFormat; VkImageViewType const mViewType; VkImageSubresourceRange const mFullViewRange; - VkImage mTextureImage = VK_NULL_HANDLE; + VulkanLayout mDefaultLayout; // Track the image layout of each subresource using a sparse range map. utils::RangeMap mSubresourceLayouts; @@ -78,7 +78,6 @@ struct VulkanTextureState : public VulkanResource { VkDevice mDevice; VmaAllocator mAllocator; VulkanCommands* mCommands; - std::shared_ptr mTransitionFence; bool mIsTransientAttachment; }; @@ -135,6 +134,10 @@ struct VulkanTexture : public HwTexture, VulkanResource { return getLayout(mPrimaryViewRange.baseArrayLayer, mPrimaryViewRange.baseMipLevel); } + // Returns the layout for the intended use of this texture (and not the expected layout at the + // time of the call. + VulkanLayout getDefaultLayout() const; + // Gets or creates a cached VkImageView for a single subresource that can be used as a render // target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D // and the identity swizzle. @@ -176,11 +179,11 @@ struct VulkanTexture : public HwTexture, VulkanResource { return state->mIsTransientAttachment; } - bool transitionLayout(VulkanCommandBuffer* commands, const VkImageSubresourceRange& range, + bool transitionLayout(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range, VulkanLayout newLayout); - bool transitionLayout(VkCommandBuffer cmdbuf, std::shared_ptr fence, - VkImageSubresourceRange const& range, VulkanLayout newLayout); + bool transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceRange const& range, + VulkanLayout newLayout); void attachmentToSamplerBarrier(VulkanCommandBuffer* commands, VkImageSubresourceRange const& range); @@ -196,13 +199,6 @@ struct VulkanTexture : public HwTexture, VulkanResource { // manually (outside of calls to transitionLayout). void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout); - bool transitionReady() { - VulkanTextureState* state = getSharedState(); - auto res = !state->mTransitionFence || state->mTransitionFence->getStatus() == VK_SUCCESS; - state->mTransitionFence.reset(); - return res; - } - #if FVK_ENABLED(FVK_DEBUG_TEXTURE) void print() const; #endif diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp index 660001083e8..6ffaa8ae07d 100644 --- a/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSetManager.cpp @@ -514,7 +514,7 @@ void VulkanDescriptorSetManager::updateSampler(VulkanDescriptorSet* set, uint8_t } else { info.imageView = texture->getViewForType(range, expectedType); } - info.imageLayout = imgutil::getVkLayout(texture->getPrimaryImageLayout()); + info.imageLayout = imgutil::getVkLayout(texture->getDefaultLayout()); VkWriteDescriptorSet const descriptorWrite = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = nullptr, From d784ce311f839d3288f716a8ddbf2f963bc61010 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 8 Oct 2024 20:39:14 -0700 Subject: [PATCH 29/36] gles: add a flag to enable the validation of the nativewindow We are seeing a cluster of crashes that could be due to using an EGLSurface whose ANativeWindow has become invalid. This could happen if we continued to use (i.e. draw with) an EGLSurface after SurfaceHolder::onSurfaceDestroyed() has returned. This new flag enables an assertion that the native window is valid at the time of makeCurrent(), which happens early in the frame. BUG=[330392256] --- .../filament-android/src/main/cpp/Engine.cpp | 3 +- .../com/google/android/filament/Engine.java | 21 ++++-- filament/backend/include/backend/Platform.h | 7 ++ .../backend/platforms/PlatformEGLAndroid.h | 9 +++ .../src/opengl/platforms/PlatformEGL.cpp | 2 + .../opengl/platforms/PlatformEGLAndroid.cpp | 65 +++++++++++++++++-- filament/include/filament/Engine.h | 8 ++- filament/src/details/Engine.cpp | 4 +- 8 files changed, 105 insertions(+), 14 deletions(-) diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 314472557fd..7de25a58ba2 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -525,7 +525,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge, jboolean disableHandleUseAfterFreeCheck, jint preferredShaderLanguage, - jboolean forceGLES2Context) { + jboolean forceGLES2Context, jboolean assertNativeWindowIsValid) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; Engine::Config config = { .commandBufferSizeMB = (uint32_t) commandBufferSizeMB, @@ -542,6 +542,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu .disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck, .preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage, .forceGLES2Context = (bool) forceGLES2Context, + .assertNativeWindowIsValid = (bool) assertNativeWindowIsValid, }; builder->config(&config); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 3b869c779d0..a433a55d8c0 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -229,7 +229,7 @@ public Builder config(Config config) { config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge, config.disableHandleUseAfterFreeCheck, config.preferredShaderLanguage.ordinal(), - config.forceGLES2Context); + config.forceGLES2Context, config.assertNativeWindowIsValid); return this; } @@ -418,12 +418,12 @@ public static class Config { */ public long stereoscopicEyeCount = 2; - /* + /** * @Deprecated This value is no longer used. */ public long resourceAllocatorCacheSizeMB = 64; - /* + /** * This value determines how many frames texture entries are kept for in the cache. This * is a soft limit, meaning some texture older than this are allowed to stay in the cache. * Typically only one texture is evicted per frame. @@ -431,12 +431,12 @@ public static class Config { */ public long resourceAllocatorCacheMaxAge = 1; - /* + /** * Disable backend handles use-after-free checks. */ public boolean disableHandleUseAfterFreeCheck = false; - /* + /** * Sets a preferred shader language for Filament to use. * * The Metal backend supports two shader languages: MSL (Metal Shading Language) and @@ -458,12 +458,19 @@ public enum ShaderLanguage { }; public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT; - /* + /** * When the OpenGL ES backend is used, setting this value to true will force a GLES2.0 * context if supported by the Platform, or if not, will have the backend pretend * it's a GLES2 context. Ignored on other backends. */ public boolean forceGLES2Context = false; + + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + */ + public boolean assertNativeWindowIsValid = false; } private Engine(long nativeEngine, Config config) { @@ -1409,7 +1416,7 @@ private static native void nSetBuilderConfig(long nativeBuilder, long commandBuf long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge, boolean disableHandleUseAfterFreeCheck, int preferredShaderLanguage, - boolean forceGLES2Context); + boolean forceGLES2Context, boolean assertNativeWindowIsValid); private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal); private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext); private static native void nSetBuilderPaused(long nativeBuilder, boolean paused); diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index af18b1488a4..f9adc51a623 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -91,6 +91,13 @@ class UTILS_PUBLIC Platform { * Sets the technique for stereoscopic rendering. */ StereoscopicType stereoscopicType = StereoscopicType::NONE; + + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + */ + bool assertNativeWindowIsValid = false; }; Platform() noexcept; diff --git a/filament/backend/include/backend/platforms/PlatformEGLAndroid.h b/filament/backend/include/backend/platforms/PlatformEGLAndroid.h index c3cc7da89d8..bc7d0c1f2f2 100644 --- a/filament/backend/include/backend/platforms/PlatformEGLAndroid.h +++ b/filament/backend/include/backend/platforms/PlatformEGLAndroid.h @@ -89,6 +89,11 @@ class PlatformEGLAndroid : public PlatformEGL { */ AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override; +protected: + bool makeCurrent(ContextType type, + SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept override; + private: struct InitializeJvmForPerformanceManagerIfNeeded { InitializeJvmForPerformanceManagerIfNeeded(); @@ -102,6 +107,10 @@ class PlatformEGLAndroid : public PlatformEGL { using clock = std::chrono::high_resolution_clock; clock::time_point mStartTimeOfActualWork; + + void* mNativeWindowLib = nullptr; + int32_t (*ANativeWindow_getBuffersDefaultDataSpace)(ANativeWindow* window) = nullptr; + bool mAssertNativeWindowIsValid = false; }; } // namespace filament::backend diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index 0a8f638a73e..38b8b9d5049 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -553,6 +553,8 @@ void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { if (swapChain) { SwapChainEGL const* const sc = static_cast(swapChain); if (sc->sur != EGL_NO_SURFACE) { + // - if EGL_KHR_surfaceless_context is supported, mEGLDummySurface is EGL_NO_SURFACE. + // - this is actually a bit too aggressive, but it is a rare operation. egl.makeCurrent(mEGLDummySurface, mEGLDummySurface); eglDestroySurface(mEGLDisplay, sc->sur); delete sc; diff --git a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp index 577b66fb315..f7dce97903c 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -25,12 +25,14 @@ #include "ExternalStreamManagerAndroid.h" #include +#include #include #include #include #include +#include #include #include @@ -42,7 +44,9 @@ #include #include +#include +#include #include #include @@ -80,8 +84,6 @@ UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID = } using namespace glext; -using EGLStream = Platform::Stream; - // --------------------------------------------------------------------------------------------- PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() { @@ -112,6 +114,13 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept mOSVersion = length >= 0 ? atoi(scratch) : 1; } + mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW); + if (mNativeWindowLib) { + ANativeWindow_getBuffersDefaultDataSpace = + (int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib, + "ANativeWindow_getBuffersDefaultDataSpace"); + } + // This disables an ANGLE optimization on ARM, which turns out to be more costly for us // see b/229017581 // We need to do this before we create the GL context. @@ -125,14 +134,60 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept setenv("ANGLE_FEATURE_OVERRIDES_DISABLED", "preferSubmitAtFBOBoundary", false); } -PlatformEGLAndroid::~PlatformEGLAndroid() noexcept = default; - +PlatformEGLAndroid::~PlatformEGLAndroid() noexcept { + if (mNativeWindowLib) { + dlclose(mNativeWindowLib); + } +} void PlatformEGLAndroid::terminate() noexcept { ExternalStreamManagerAndroid::destroy(&mExternalStreamManager); PlatformEGL::terminate(); } +static constexpr const std::string_view kNativeWindowInvalidMsg = + "ANativeWindow is invalid. It probably has been destroyed. EGL surface = "; + +bool PlatformEGLAndroid::makeCurrent(ContextType type, + SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { + + // fast & safe path + if (UTILS_LIKELY(!mAssertNativeWindowIsValid)) { + return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain); + } + + SwapChainEGL const* const dsc = static_cast(drawSwapChain); + if (ANativeWindow_getBuffersDefaultDataSpace) { + // anw can be nullptr if we're using a pbuffer surface + if (UTILS_LIKELY(dsc->nativeWindow)) { + // this a proxy of is_valid() + auto result = ANativeWindow_getBuffersDefaultDataSpace(dsc->nativeWindow); + FILAMENT_CHECK_POSTCONDITION(result >= 0) << kNativeWindowInvalidMsg << dsc->sur; + } + } else { + // If we don't have ANativeWindow_getBuffersDefaultDataSpace, we revert to using the + // private query() call. + // Shadow version if the real ANativeWindow, so we can access the query() hook. Query + // has existed since forever, probably Android 1.0. + struct NativeWindow { + // is valid query enum value + enum { IS_VALID = 17 }; + uint64_t pad[18]; + int (* query)(ANativeWindow const*, int, int*); + } const* pWindow = reinterpret_cast(dsc->nativeWindow); + int isValid = 0; + if (UTILS_LIKELY(pWindow->query)) { // just in case it's nullptr + int const err = pWindow->query(dsc->nativeWindow, NativeWindow::IS_VALID, &isValid); + if (UTILS_LIKELY(err >= 0)) { // in case the IS_VALID enum is not recognized + // query call succeeded + FILAMENT_CHECK_POSTCONDITION(isValid) << kNativeWindowInvalidMsg << dsc->sur; + } + } + } + return PlatformEGL::makeCurrent(type, drawSwapChain, readSwapChain); +} + void PlatformEGLAndroid::beginFrame( int64_t monotonic_clock_ns, int64_t refreshIntervalNs, @@ -189,6 +244,8 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext, "eglGetFrameTimestampsANDROID"); } + mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid; + return driver; } diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 2686f25ea1c..311cf2e5a0c 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -380,8 +380,14 @@ class UTILS_PUBLIC Engine { * it's a GLES2 context. Ignored on other backends. */ bool forceGLES2Context = false; - }; + /** + * Assert the native window associated to a SwapChain is valid when calling makeCurrent(). + * This is only supported for: + * - PlatformEGLAndroid + */ + bool assertNativeWindowIsValid = false; + }; #if UTILS_HAS_THREADING using CreateCallback = void(void* UTILS_NULLABLE user, void* UTILS_NONNULL token); diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index b1aaa5c9f5f..e62a94d0307 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -107,7 +107,8 @@ Engine* FEngine::create(Engine::Builder const& builder) { .disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile, .disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck, .forceGLES2Context = instance->getConfig().forceGLES2Context, - .stereoscopicType = instance->getConfig().stereoscopicType, + .stereoscopicType = instance->getConfig().stereoscopicType, + .assertNativeWindowIsValid = instance->getConfig().assertNativeWindowIsValid, }; instance->mDriver = platform->createDriver(sharedContext, driverConfig); @@ -705,6 +706,7 @@ int FEngine::loop() { .disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck, .forceGLES2Context = mConfig.forceGLES2Context, .stereoscopicType = mConfig.stereoscopicType, + .assertNativeWindowIsValid = mConfig.assertNativeWindowIsValid, }; mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig); From 1eb1df2cf2969098041c7f58a64aca1c8835b13a Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 11 Oct 2024 13:28:18 -0700 Subject: [PATCH 30/36] Check for use-after-free for heap handles (#8182) --- filament/backend/CMakeLists.txt | 1 + .../include/private/backend/HandleAllocator.h | 28 ++++- filament/backend/test/test_Handles.cpp | 115 ++++++++++++++++++ 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 filament/backend/test/test_Handles.cpp diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 793486d8923..29a580924bb 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -442,6 +442,7 @@ if (APPLE OR LINUX) test/test_StencilBuffer.cpp test/test_Scissor.cpp test/test_MipLevels.cpp + test/test_Handles.cpp ) set(BACKEND_TEST_LIBS backend diff --git a/filament/backend/include/private/backend/HandleAllocator.h b/filament/backend/include/private/backend/HandleAllocator.h index 8eaa9481d62..93333363022 100644 --- a/filament/backend/include/private/backend/HandleAllocator.h +++ b/filament/backend/include/private/backend/HandleAllocator.h @@ -169,7 +169,7 @@ class HandleAllocator { auto [p, tag] = handleToPointer(handle.getId()); if (isPoolHandle(handle.getId())) { - // check for use after free + // check for pool handle use-after-free if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) { uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT; auto const pNode = static_cast(p); @@ -179,6 +179,22 @@ class HandleAllocator { << "use-after-free of Handle with id=" << handle.getId() << ", tag=" << getHandleTag(handle.getId()).c_str_safe(); } + } else { + // check for heap handle use-after-free + if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) { + uint8_t const index = (handle.getId() & HANDLE_INDEX_MASK); + // if we've already handed out this handle index before, it's definitely a + // use-after-free, otherwise it's probably just a corrupted handle + if (index < mId) { + FILAMENT_CHECK_POSTCONDITION(p != nullptr) + << "use-after-free of heap Handle with id=" << handle.getId() + << ", tag=" << getHandleTag(handle.getId()).c_str_safe(); + } else { + FILAMENT_CHECK_POSTCONDITION(p != nullptr) + << "corrupted heap Handle with id=" << handle.getId() + << ", tag=" << getHandleTag(handle.getId()).c_str_safe(); + } + } } return static_cast(p); @@ -186,14 +202,18 @@ class HandleAllocator { template bool is_valid(Handle
& handle) { - if (handle && isPoolHandle(handle.getId())) { - auto [p, tag] = handleToPointer(handle.getId()); + if (!handle) { + // null handles are invalid + return false; + } + auto [p, tag] = handleToPointer(handle.getId()); + if (isPoolHandle(handle.getId())) { uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT; auto const pNode = static_cast(p); uint8_t const expectedAge = pNode[-1].age; return expectedAge == age; } - return true; + return p != nullptr; } template diff --git a/filament/backend/test/test_Handles.cpp b/filament/backend/test/test_Handles.cpp new file mode 100644 index 00000000000..22390b7acb7 --- /dev/null +++ b/filament/backend/test/test_Handles.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include "utils/Panic.h" + +using namespace filament::backend; + +// FIXME: consider making this constant non-private so we can use it in tests. +static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; +static constexpr size_t POOL_SIZE_BYTES = 8 * 1024U * 1024U; +// NOTE: actual count may be lower due to alignment requirements +constexpr size_t const POOL_HANDLE_COUNT = POOL_SIZE_BYTES / (32 + 96 + 136); // 31775 + +// This must match HandleAllocatorGL, so its implementation is present on all platforms. +#define HandleAllocatorTest HandleAllocator<32, 96, 136> // ~4520 / pool / MiB + +struct MyHandle { +}; + +struct Concrete : public MyHandle { + uint8_t data[32]; +}; + +#if GTEST_HAS_EXCEPTIONS +#define EXPECT_THROW_IF_ENABLED EXPECT_THROW +#else +#define EXPECT_THROW_IF_ENABLED(args...) +#endif + +TEST(HandlesTest, useAfterFreePool) { + HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false); + + Handle handleA = allocator.allocate(); + allocator.deallocate(handleA); + + Handle handleB = allocator.allocate(); + + EXPECT_THROW_IF_ENABLED({ + Concrete* actualHandleA = allocator.handle_cast(handleA); + }, utils::PostconditionPanic); +} + +TEST(HandlesTest, useAfterFreeHeap) { + HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false); + + // Use up all the non-heap handles. + for (int i = 0; i < POOL_HANDLE_COUNT; i++) { + Handle handle = allocator.allocate(); + } + + // This one is guaranteed to be a heap handle. + Handle handleA = allocator.allocate(); + EXPECT_TRUE(handleA.getId() & HANDLE_HEAP_FLAG); + allocator.deallocate(handleA); + + Handle handleB = allocator.allocate(); + + // This should assert: + EXPECT_THROW_IF_ENABLED({ + Concrete* actualHandleA = allocator.handle_cast(handleA); + }, utils::PostconditionPanic); + + // simulate a "corrupted" handle + Handle corruptedHandle { HANDLE_HEAP_FLAG | 0x123456 }; + + EXPECT_THROW_IF_ENABLED({ + Concrete* actualHandleA = allocator.handle_cast(corruptedHandle); + }, utils::PostconditionPanic); +} + +TEST(HandlesTest, isValid) { + HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES, false); + Handle poolHandle = allocator.allocate(); + EXPECT_TRUE((poolHandle.getId() & HANDLE_HEAP_FLAG) == 0u); + + // Use up all the non-heap handles. + for (int i = 0; i < POOL_HANDLE_COUNT; i++) { + Handle handle = allocator.allocate(); + } + + // This one is guaranteed to be a heap handle. + Handle heapHandle = allocator.allocate(); + EXPECT_TRUE(heapHandle.getId() & HANDLE_HEAP_FLAG); + + EXPECT_TRUE(allocator.is_valid(poolHandle)); + EXPECT_TRUE(allocator.is_valid(heapHandle)); + + { + // null handles are invalid + Handle handle; + EXPECT_FALSE(allocator.is_valid(handle)); + } + { + Handle handle { HANDLE_HEAP_FLAG | 0x123456 }; + EXPECT_FALSE(allocator.is_valid(handle)); + } +} From ccdb58e93c45c03acf657fa113a2c936c4ec631e Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 11 Oct 2024 13:52:54 -0700 Subject: [PATCH 31/36] don't set ANGLE features in filament (#8192) ANGLE features should be set by apps, the system or developers but it's not a good idea to set them in a library as it might conflict with other libs etc. we did it because it improved performance, but that should be fixed at the angle level instead. --- .../opengl/platforms/PlatformEGLAndroid.cpp | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp index f7dce97903c..07e4b10d36d 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -103,15 +103,9 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept mExternalStreamManager(ExternalStreamManagerAndroid::create()), mInitializeJvmForPerformanceManagerIfNeeded(), mPerformanceHintManager() { - - char scratch[PROP_VALUE_MAX + 1]; - int length = __system_property_get("ro.build.version.release", scratch); - int const androidVersion = length >= 0 ? atoi(scratch) : 1; - if (!androidVersion) { - mOSVersion = 1000; // if androidVersion is 0, it means "future" - } else { - length = __system_property_get("ro.build.version.sdk", scratch); - mOSVersion = length >= 0 ? atoi(scratch) : 1; + mOSVersion = android_get_device_api_level(); + if (mOSVersion < 0) { + mOSVersion = __ANDROID_API_FUTURE__; } mNativeWindowLib = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW); @@ -120,18 +114,6 @@ PlatformEGLAndroid::PlatformEGLAndroid() noexcept (int32_t(*)(ANativeWindow*))dlsym(mNativeWindowLib, "ANativeWindow_getBuffersDefaultDataSpace"); } - - // This disables an ANGLE optimization on ARM, which turns out to be more costly for us - // see b/229017581 - // We need to do this before we create the GL context. - // An alternative solution is use a system property: - // __system_property_set( - // "debug.angle.feature_overrides_disabled", - // "preferSubmitAtFBOBoundary"); - // but that would outlive this process, so the environment variable is better. - // We also make sure to not update the variable if it already exists. - // There is no harm setting this if we're not on ANGLE or ARM. - setenv("ANGLE_FEATURE_OVERRIDES_DISABLED", "preferSubmitAtFBOBoundary", false); } PlatformEGLAndroid::~PlatformEGLAndroid() noexcept { From fd2f9555f187b43b7552892118779d7ba2218d94 Mon Sep 17 00:00:00 2001 From: Evan Mezeske Date: Fri, 11 Oct 2024 16:09:32 -0700 Subject: [PATCH 32/36] Fix tiny typo in VulkanPlatformSwapChainImpl.cpp logging (#8196) --- .../backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp index cfec76e3bc9..d8cace97a62 100644 --- a/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp +++ b/filament/backend/src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp @@ -239,7 +239,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() { }; VkResult result = vkCreateSwapchainKHR(mDevice, &createInfo, VKALLOC, &mSwapchain); FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) - << "vkGetPhysicalDeviceSurfaceFormatsKHR error: " << static_cast(result); + << "vkCreateSwapchainKHR error: " << static_cast(result); mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain); mSwapChainBundle.colorFormat = surfaceFormat.format; From 4ee33cd591e953d6ad85b6d22e4121af3e4d3dc2 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Mon, 14 Oct 2024 06:07:33 +0000 Subject: [PATCH 33/36] Release Filament 1.55.0 --- README.md | 4 ++-- RELEASE_NOTES.md | 3 +++ android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6d1f5f7e1c9..1daff364f40 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.54.5' + implementation 'com.google.android.filament:filament-android:1.55.0' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.54.5' +pod 'Filament', '~> 1.55.0' ``` ## Documentation diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e6727d791a7..b5522d40a21 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.55.1 + + ## v1.55.0 - Add descriptor sets to describe shader resources. [⚠️ **New Material Version**] diff --git a/android/gradle.properties b/android/gradle.properties index 85a92d8700c..19e07a87034 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.54.5 +VERSION_NAME=1.55.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index a4f0521f66a..cbddfbcb0c2 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.54.5" + spec.version = "1.55.0" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.54.5/filament-v1.54.5-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.0/filament-v1.55.0-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 9860fd38434..58c8ffdb202 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.54.5", + "version": "1.55.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 22bb67e0b01ffb68c16022ecaaf21561161863a8 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Mon, 14 Oct 2024 06:07:43 +0000 Subject: [PATCH 34/36] Bump version to 1.55.1 --- README.md | 4 ++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1daff364f40..15741c613ff 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.55.0' + implementation 'com.google.android.filament:filament-android:1.55.1' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.55.0' +pod 'Filament', '~> 1.55.1' ``` ## Documentation diff --git a/android/gradle.properties b/android/gradle.properties index 19e07a87034..9b015765b8f 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.55.0 +VERSION_NAME=1.55.1 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index cbddfbcb0c2..c9eab0abb15 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.55.0" + spec.version = "1.55.1" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.0/filament-v1.55.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.55.1/filament-v1.55.1-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 58c8ffdb202..97cfa9933a2 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.55.0", + "version": "1.55.1", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 9e1ee2f29055204d07dd54a2b8f92ca16215095e Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 18 Oct 2024 16:42:14 -0400 Subject: [PATCH 35/36] Fix potential crash when a descriptor set is destroyed but not unbound (#8215) --- filament/backend/CMakeLists.txt | 2 ++ filament/backend/src/metal/MetalState.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 29a580924bb..3878723f434 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -540,6 +540,8 @@ if (APPLE AND NOT IOS) add_executable(metal_utils_test test/MetalTest.mm) +target_compile_options(metal_utils_test PRIVATE "-fobjc-arc") + target_link_libraries(metal_utils_test PRIVATE backend getopt diff --git a/filament/backend/src/metal/MetalState.h b/filament/backend/src/metal/MetalState.h index 1e14cddc258..3948c78aec9 100644 --- a/filament/backend/src/metal/MetalState.h +++ b/filament/backend/src/metal/MetalState.h @@ -434,7 +434,7 @@ class MetalBufferBindings { private: static_assert(N <= 8); - std::array<__unsafe_unretained id, N> mBuffers = { nil }; + std::array<__weak id, N> mBuffers = { nil }; std::array mOffsets = { 0 }; utils::bitset8 mDirtyBuffers; utils::bitset8 mDirtyOffsets; From 66abb75bc49402799a963405cf965dd5a5a81836 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Mon, 28 Oct 2024 15:54:09 -0700 Subject: [PATCH 36/36] vk: remove incorrect assert --- filament/backend/src/vulkan/VulkanDriver.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 18f6bf53596..d71a91d014d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1293,8 +1293,6 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP rpkey.colorFormat[i] = info.getFormat(); if (rpkey.samples > 1) { const VulkanTexture* sidecar = info.texture->getSidecar(); - assert_invariant(sidecar); - assert_invariant(sidecar->samples > 1); if (sidecar && sidecar->isTransientAttachment()) { rpkey.usesLazilyAllocatedMemory |= (1 << i); }