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 6a7f66cc2fd..be00e2b07b3 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 @@ -495,21 +500,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 295229809a6..20297b61792 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -200,20 +200,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, @@ -225,9 +238,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, @@ -261,25 +271,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 @@ -346,15 +384,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, @@ -369,10 +398,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, @@ -419,25 +450,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 0269c049c10..3c4b28d8596 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -41,6 +41,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 { @@ -57,7 +67,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 @@ -71,7 +80,6 @@ << "\nMetalVertexBuffer: " << sizeof(MetalVertexBuffer) << "\nMetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo) << "\nMetalIndexBuffer: " << sizeof(MetalIndexBuffer) - << "\nMetalSamplerGroup: " << sizeof(MetalSamplerGroup) << "\nMetalRenderPrimitive: " << sizeof(MetalRenderPrimitive) << "\nMetalTexture: " << sizeof(MetalTexture) << "\nMetalTimerQuery: " << sizeof(MetalTimerQuery) @@ -112,6 +120,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; @@ -222,10 +233,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 @@ -237,6 +251,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)); } } @@ -262,6 +278,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); @@ -294,6 +311,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."; @@ -305,9 +385,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, @@ -363,22 +447,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, @@ -403,11 +511,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) { @@ -416,6 +519,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)); } @@ -444,7 +552,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 }; } @@ -455,7 +562,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 }; } @@ -466,7 +572,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 }; } @@ -483,6 +588,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); @@ -498,6 +606,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(); } @@ -518,16 +692,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 { @@ -566,6 +748,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); @@ -588,22 +778,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); } @@ -619,22 +793,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; } @@ -661,7 +821,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); + } } } @@ -675,6 +844,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()) { @@ -686,8 +870,6 @@ // This must be done before calling bufferPool->reset() to ensure no buffers are in flight. finish(); - executeTickOps(); - mContext->bufferPool->reset(); mContext->commandQueue = nil; @@ -945,11 +1127,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, @@ -959,30 +1136,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) { } @@ -998,96 +1171,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, @@ -1099,6 +1184,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"; @@ -1140,7 +1226,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(); @@ -1150,6 +1256,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 @@ -1189,109 +1296,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)) @@ -1535,7 +1539,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, @@ -1591,102 +1599,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); @@ -1831,37 +1747,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) { @@ -1901,23 +1786,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++) { @@ -1931,8 +1858,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) @@ -1984,31 +1910,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); @@ -2066,32 +1967,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) { } @@ -2111,6 +1986,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 4fba4724eac..b7c61e4fa94 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 { @@ -84,6 +85,8 @@ class MetalSwapChain : public HwSwapChain { NSUInteger getSurfaceWidth() const; NSUInteger getSurfaceHeight() const; + bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; } + private: enum class SwapChainType { @@ -93,7 +96,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(); @@ -138,12 +140,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; }; @@ -200,12 +196,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; @@ -227,43 +221,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 @@ -302,97 +295,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: @@ -547,6 +459,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 938d4569d36..71be41842bd 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()); @@ -474,11 +470,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); } @@ -498,10 +489,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) @@ -587,16 +577,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. @@ -610,44 +612,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) { @@ -766,15 +762,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, @@ -898,98 +891,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) : @@ -1335,5 +1236,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 cd3029d64bc..8a718288f52 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 c753125d399..adac7f881e6 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 587e4c823bc..debb6213359 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/PerShadowMapUniforms.cpp b/filament/src/PerShadowMapUniforms.cpp index 458b15db836..ae6aacaaede 100644 --- a/filament/src/PerShadowMapUniforms.cpp +++ b/filament/src/PerShadowMapUniforms.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/PerShadowMapUniforms.h index 05fcdd82e5f..c1f55c5c89d 100644 --- a/filament/src/PerShadowMapUniforms.h +++ b/filament/src/PerShadowMapUniforms.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/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index 35867ea855b..18d3f495d6c 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.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/PerViewUniforms.h index c8df0661903..79086753d75 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/PerViewUniforms.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/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/ds/ColorPassDescriptorSet.cpp b/filament/src/ds/ColorPassDescriptorSet.cpp new file mode 100644 index 00000000000..18d3f495d6c --- /dev/null +++ b/filament/src/ds/ColorPassDescriptorSet.cpp @@ -0,0 +1,565 @@ +/* + * 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. + */ + +#include "ColorPassDescriptorSet.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 + +namespace filament { + +using namespace backend; +using namespace math; + +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; +} + + +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); + 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 ColorPassDescriptorSet::terminate(HwDescriptorSetLayoutFactory& factory, DriverApi& driver) { + for (auto&& entry : mDescriptorSet) { + entry.terminate(driver); + } + for (auto&& entry : mDescriptorSetLayout) { + entry.terminate(factory, driver); + } +} + +void ColorPassDescriptorSet::prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept { + mat4f const& viewFromWorld = camera.view; + mat4f const& worldFromView = camera.model; + mat4f const& clipFromView = camera.projection; + + const mat4f viewFromClip{ inverse((mat4)camera.projection) }; + const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) }; + + auto& s = mUniforms.edit(); + s.viewFromWorldMatrix = viewFromWorld; // view + s.worldFromViewMatrix = worldFromView; // model + s.clipFromViewMatrix = clipFromView; // projection + s.viewFromClipMatrix = viewFromClip; // 1/projection + s.worldFromClipMatrix = worldFromClip; // 1/(projection * view) + s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform)); + s.clipTransform = camera.clipTransform; + s.cameraFar = camera.zf; + s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn); + s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn); + + mat4f const& headFromWorld = camera.view; + Engine::Config const& config = engine.getConfig(); + for (int i = 0; i < config.stereoscopicEyeCount; i++) { + mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering + mat4f const& clipFromEye = camera.eyeProjection[i]; + // clipFromEye * eyeFromHead * headFromWorld + s.clipFromWorldMatrix[i] = highPrecisionMultiply( + clipFromEye, highPrecisionMultiply(eyeFromHead, headFromWorld)); + } + + // with a clip-space of [-w, w] ==> z' = -z + // with a clip-space of [0, w] ==> z' = (w - z)/2 + s.clipControl = engine.getDriverApi().getClipSpaceParams(); +} + +void ColorPassDescriptorSet::prepareLodBias(float bias, float2 derivativesScale) noexcept { + auto& s = mUniforms.edit(); + s.lodBias = bias; + s.derivativesScale = derivativesScale; +} + +void ColorPassDescriptorSet::prepareExposure(float ev100) noexcept { + const float exposure = Exposure::exposure(ev100); + auto& s = mUniforms.edit(); + s.exposure = exposure; + s.ev100 = ev100; +} + +void ColorPassDescriptorSet::prepareViewport( + const filament::Viewport& physicalViewport, + const filament::Viewport& logicalViewport) noexcept { + float4 const physical{ physicalViewport.left, physicalViewport.bottom, + physicalViewport.width, physicalViewport.height }; + float4 const logical{ logicalViewport.left, logicalViewport.bottom, + logicalViewport.width, logicalViewport.height }; + auto& s = mUniforms.edit(); + s.resolution = { physical.zw, 1.0f / physical.zw }; + s.logicalViewportScale = physical.zw / logical.zw; + s.logicalViewportOffset = -logical.xy / logical.zw; +} + +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); + s.time = fraction; + s.userTime = userTime; +} + +void ColorPassDescriptorSet::prepareTemporalNoise(FEngine& engine, + TemporalAntiAliasingOptions const& options) noexcept { + std::uniform_real_distribution uniformDistribution{ 0.0f, 1.0f }; + auto& s = mUniforms.edit(); + const float temporalNoise = uniformDistribution(engine.getRandomEngine()); + s.temporalNoise = options.enabled ? temporalNoise : 0.0f; +} + +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 { + short2 s; + memcpy(&s[0], &v[0], sizeof(s)); + return s.y << 16 | s.x; + }; + + // Fog should be calculated in the "user's world coordinates" so that it's not + // affected by the IBL rotation. + // fogFromWorldMatrix below is only used to transform the view vector in the shader, which is + // why we store the cofactor matrix. + + mat4f const viewFromWorld = cameraInfo.view; + mat4 const worldFromUserWorld = cameraInfo.worldTransform; + mat4 const worldFromFog = worldFromUserWorld * userWorldFromFog; + mat4 const viewFromFog = viewFromWorld * worldFromFog; + + mat4 const fogFromView = inverse(viewFromFog); + mat3 const fogFromWorld = inverse(worldFromFog.upperLeft()); + + // camera position relative to the fog's origin + auto const userCameraPosition = fogFromView[3].xyz; + + const float heightFalloff = std::max(0.0f, options.heightFalloff); + + // precalculate the constant part of density integral + const float density = -float(heightFalloff * (userCameraPosition.y - options.height)); + + auto& s = mUniforms.edit(); + + // note: this code is written so that near/far/minLod/maxLod could be user settable + // currently they're inferred. + Handle fogColorTextureHandle; + if (options.skyColor) { + 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); + s.fogNearOverFarMinusNear = cameraInfo.zn / (cameraInfo.zf - cameraInfo.zn); + } + if (!fogColorTextureHandle && options.fogColorFromIbl) { + if (ibl) { + // When using the IBL, because we don't have mip levels, we don't have a mop to + // select based on the distance. However, we can cheat a little and use + // mip_roughnessOne-1 as the horizon base color and mip_roughnessOne as the near + // camera base color. This will give a distant fog that's a bit too sharp, but it + // improves the effect overall. + fogColorTextureHandle = ibl->getReflectionHwHandle(); + float const levelCount = float(ibl->getLevelCount()); + math::half2 const minMaxMip{ levelCount - 2.0f, levelCount - 1.0f }; + s.fogMinMaxMip = packHalf2x16(minMaxMip); + s.fogOneOverFarMinusNear = 1.0f / (cameraInfo.zf - cameraInfo.zn); + s.fogNearOverFarMinusNear = cameraInfo.zn / (cameraInfo.zf - cameraInfo.zn); + } + } + + setSampler(+PerViewBindingPoints::FOG, + fogColorTextureHandle ? + fogColorTextureHandle : engine.getDummyCubemap()->getHwHandleForSampling(), { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR + }); + + s.fogStart = options.distance; + s.fogMaxOpacity = options.maximumOpacity; + s.fogHeightFalloff = heightFalloff; + s.fogCutOffDistance = options.cutOffDistance; + s.fogColor = options.color; + s.fogDensity = { options.density, density, options.density * std::exp(density) }; + s.fogInscatteringStart = options.inScatteringStart; + s.fogInscatteringSize = options.inScatteringSize; + s.fogColorFromIbl = fogColorTextureHandle ? 1.0f : 0.0f; + s.fogFromWorldMatrix = mat3f{ cof(fogFromWorld) }; +} + +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. + const bool highQualitySampling = options.upsampling >= QualityLevel::HIGH + && options.resolution < 1.0f; + + // LINEAR filtering is only needed when AO is enabled and low-quality upsampling is used. + setSampler(+PerViewBindingPoints::SSAO, ssao, { + .filterMag = options.enabled && !highQualitySampling ? + SamplerMagFilter::LINEAR : SamplerMagFilter::NEAREST + }); + + const float edgeDistance = 1.0f / options.bilateralThreshold; + auto& s = mUniforms.edit(); + s.aoSamplingQualityAndEdgeDistance = + options.enabled ? (highQualitySampling ? edgeDistance : 0.0f) : -1.0f; + s.aoBentNormals = options.enabled && options.bentNormals ? 1.0f : 0.0f; +} + +void ColorPassDescriptorSet::prepareBlending(bool needsAlphaChannel) noexcept { + mUniforms.edit().needsAlphaChannel = needsAlphaChannel ? 1.0f : 0.0f; +} + +void ColorPassDescriptorSet::prepareMaterialGlobals( + std::array const& materialGlobals) noexcept { + mUniforms.edit().custom[0] = materialGlobals[0]; + mUniforms.edit().custom[1] = materialGlobals[1]; + mUniforms.edit().custom[2] = materialGlobals[2]; + mUniforms.edit().custom[3] = materialGlobals[3]; +} + +void ColorPassDescriptorSet::prepareSSR(Handle ssr, + bool disableSSR, + float refractionLodOffset, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { + + 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 ColorPassDescriptorSet::prepareHistorySSR(Handle ssr, + math::mat4f const& historyProjection, + math::mat4f const& uvFromViewMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept { + + setSampler(+PerViewBindingPoints::SSR, ssr, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR + }); + + 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 ColorPassDescriptorSet::prepareStructure(Handle structure) noexcept { + // sampler must be NEAREST + setSampler(+PerViewBindingPoints::STRUCTURE, structure, {}); +} + +void ColorPassDescriptorSet::prepareDirectionalLight(FEngine& engine, + float exposure, + float3 const& sceneSpaceDirection, + ColorPassDescriptorSet::LightManagerInstance directionalLight) noexcept { + FLightManager const& lcm = engine.getLightManager(); + auto& s = mUniforms.edit(); + + float const shadowFar = lcm.getShadowFar(directionalLight); + s.shadowFarAttenuationParams = shadowFar > 0.0f ? + 0.5f * float2{ 10.0f, 10.0f / (shadowFar * shadowFar) } : float2{ 1.0f, 0.0f }; + + const float3 l = -sceneSpaceDirection; // guaranteed normalized + + if (directionalLight.isValid()) { + const float4 colorIntensity = { + lcm.getColor(directionalLight), lcm.getIntensity(directionalLight) * exposure }; + + s.lightDirection = l; + s.lightColorIntensity = colorIntensity; + s.lightChannels = lcm.getLightChannels(directionalLight); + + const bool isSun = lcm.isSunLight(directionalLight); + // The last parameter must be < 0.0f for regular directional lights + float4 sun{ 0.0f, 0.0f, 0.0f, -1.0f }; + if (UTILS_UNLIKELY(isSun && colorIntensity.w > 0.0f)) { + // Currently we have only a single directional light, so it's probably likely that it's + // also the Sun. However, conceptually, most directional lights won't be sun lights. + float const radius = lcm.getSunAngularRadius(directionalLight); + float const haloSize = lcm.getSunHaloSize(directionalLight); + float const haloFalloff = lcm.getSunHaloFalloff(directionalLight); + sun.x = std::cos(radius); + sun.y = std::sin(radius); + sun.z = 1.0f / (std::cos(radius * haloSize) - sun.x); + sun.w = haloFalloff; + } + s.sun = sun; + } else { + // Disable the sun if there's no directional light + s.sun = float4{ 0.0f, 0.0f, 0.0f, -1.0f }; + } +} + +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 = float(ibl.getLevelCount() - 1); + s.iblRoughnessOneLevel = iblRoughnessOneLevel; + s.iblLuminance = intensity * exposure; + std::transform(ibl.getSH(), ibl.getSH() + 9, s.iblSH, [](float3 v) { + return float4(v, 0.0f); + }); + + // We always sample from the reflection texture, so provide a dummy texture if necessary. + Handle reflection = ibl.getReflectionHwHandle(); + if (!reflection) { + reflection = engine.getDummyCubemap()->getHwHandle(); + } + setSampler(+PerViewBindingPoints::IBL_SPECULAR, + reflection, { + .filterMag = SamplerMagFilter::LINEAR, + .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR + }); +} + +void ColorPassDescriptorSet::prepareDynamicLights(Froxelizer& froxelizer) noexcept { + auto& s = mUniforms.edit(); + froxelizer.updateUniforms(s); + float const f = froxelizer.getLightFar(); + // TODO: make the falloff rate a parameter + s.lightFarAttenuationParams = 0.5f * float2{ 10.0f, 10.0f / (f * f) }; +} + +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 ColorPassDescriptorSet::prepareShadowSampling(PerViewUib& uniforms, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept { + uniforms.cascadeSplits = shadowMappingUniforms.cascadeSplits; + uniforms.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance; + uniforms.directionalShadows = int32_t(shadowMappingUniforms.directionalShadows); + uniforms.cascades = int32_t(shadowMappingUniforms.cascades); +} + +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; + constexpr float high = 42.0f; // ~ std::log(std::numeric_limits::max()) * 0.5f; + SamplerMinFilter filterMin = SamplerMinFilter::LINEAR; + if (options.anisotropy > 0 || options.mipmapping) { + filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR; + } + 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; + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); +} + +void ColorPassDescriptorSet::prepareShadowPCF(Handle texture, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept { + 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; + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); +} + +void ColorPassDescriptorSet::prepareShadowDPCF(Handle texture, + ShadowMappingUniforms const& shadowMappingUniforms, + SoftShadowOptions const& options) noexcept { + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, {}); + auto& s = mUniforms.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_DPCF; + s.shadowPenumbraRatioScale = options.penumbraRatioScale; + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); +} + +void ColorPassDescriptorSet::prepareShadowPCSS(Handle texture, + ShadowMappingUniforms const& shadowMappingUniforms, + SoftShadowOptions const& options) noexcept { + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, {}); + auto& s = mUniforms.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCSS; + s.shadowPenumbraRatioScale = options.penumbraRatioScale; + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); +} + +void ColorPassDescriptorSet::prepareShadowPCFDebug(Handle texture, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept { + setSampler(+PerViewBindingPoints::SHADOW_MAP, texture, { + .filterMag = SamplerMagFilter::NEAREST, + .filterMin = SamplerMinFilter::NEAREST + }); + auto& s = mUniforms.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCF; + ColorPassDescriptorSet::prepareShadowSampling(s, shadowMappingUniforms); +} + +void ColorPassDescriptorSet::commit(backend::DriverApi& driver) noexcept { + if (mUniforms.isDirty()) { + driver.updateBufferObject(mUniforms.getUboHandle(), + mUniforms.toBufferDescriptor(driver), 0); + } + for (size_t i = 0; i < DESCRIPTOR_LAYOUT_COUNT; i++) { + mDescriptorSet[i].commit(mDescriptorSetLayout[i], driver); + } +} + +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 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/ds/ColorPassDescriptorSet.h b/filament/src/ds/ColorPassDescriptorSet.h new file mode 100644 index 00000000000..79086753d75 --- /dev/null +++ b/filament/src/ds/ColorPassDescriptorSet.h @@ -0,0 +1,184 @@ +/* + * 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_PERVIEWUNIFORMS_H +#define TNT_FILAMENT_PERVIEWUNIFORMS_H + +#include + +#include "DescriptorSet.h" + +#include "TypedUniformBuffer.h" + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include + +namespace filament { + +class DescriptorSetLayout; +class HwDescriptorSetLayoutFactory; + +struct AmbientOcclusionOptions; +struct DynamicResolutionOptions; +struct FogOptions; +struct ScreenSpaceReflectionsOptions; +struct SoftShadowOptions; +struct TemporalAntiAliasingOptions; +struct VsmShadowOptions; + +struct CameraInfo; +struct ShadowMappingUniforms; + +class FEngine; +class FIndirectLight; +class Froxelizer; +class LightManager; + +/* + * PerViewUniforms manages the UBO and samplers needed to render the color passes. Internally it + * 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 ColorPassDescriptorSet { + + using LightManagerInstance = utils::EntityInstance; + using TextureHandle = backend::Handle; + + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCF = 0u; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_EVSM = 1u; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_DPCF = 2u; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCSS = 3u; + +public: + + 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; + + /* + * @param viewport viewport (should be same as RenderPassParams::viewport) + * @param xoffset horizontal rendering offset *within* the viewport. + * Non-zero when we have guard bands. + * @param yoffset vertical rendering offset *within* the viewport. + * Non-zero when we have guard bands. + */ + void prepareViewport( + const filament::Viewport& physicalViewport, + const filament::Viewport& logicalViewport) noexcept; + + void prepareTime(FEngine& engine, math::float4 const& userTime) noexcept; + void prepareTemporalNoise(FEngine& engine, TemporalAntiAliasingOptions const& options) noexcept; + void prepareExposure(float ev100) noexcept; + void prepareFog(FEngine& engine, const CameraInfo& cameraInfo, + math::mat4 const& fogTransform, FogOptions const& options, + FIndirectLight const* ibl) noexcept; + void prepareStructure(TextureHandle structure) noexcept; + void prepareSSAO(TextureHandle ssao, AmbientOcclusionOptions const& options) noexcept; + void prepareBlending(bool needsAlphaChannel) noexcept; + void prepareMaterialGlobals(std::array const& materialGlobals) noexcept; + + // screen-space reflection and/or refraction (SSR) + void prepareSSR(TextureHandle ssr, + bool disableSSR, + float refractionLodOffset, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept; + + void prepareHistorySSR(TextureHandle ssr, + math::mat4f const& historyProjection, + math::mat4f const& uvFromViewMatrix, + ScreenSpaceReflectionsOptions const& ssrOptions) noexcept; + + void prepareShadowMapping(backend::BufferObjectHandle shadowUniforms, bool highPrecision) noexcept; + + void prepareDirectionalLight(FEngine& engine, float exposure, + math::float3 const& sceneSpaceDirection, LightManagerInstance instance) noexcept; + + void prepareAmbientLight(FEngine& engine, + FIndirectLight const& ibl, float intensity, float exposure) noexcept; + + void prepareDynamicLights(Froxelizer& froxelizer) noexcept; + + void prepareShadowVSM(TextureHandle texture, + ShadowMappingUniforms const& shadowMappingUniforms, + VsmShadowOptions const& options) noexcept; + + void prepareShadowPCF(TextureHandle texture, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept; + + void prepareShadowDPCF(TextureHandle texture, + ShadowMappingUniforms const& shadowMappingUniforms, + SoftShadowOptions const& options) noexcept; + + void prepareShadowPCSS(TextureHandle texture, + ShadowMappingUniforms const& shadowMappingUniforms, + SoftShadowOptions const& options) noexcept; + + void prepareShadowPCFDebug(TextureHandle texture, + ShadowMappingUniforms const& shadowMappingUniforms) noexcept; + + // update local data into GPU UBO + void commit(backend::DriverApi& driver) noexcept; + + // bind this UBO + void bind(backend::DriverApi& driver, uint8_t index) const noexcept { + mDescriptorSet[index].bind(driver, DescriptorSetBindingPoints::PER_VIEW); + } + +private: + 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; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_PERVIEWUNIFORMS_H 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/ds/ShadowMapDescriptorSet.cpp b/filament/src/ds/ShadowMapDescriptorSet.cpp new file mode 100644 index 00000000000..ae6aacaaede --- /dev/null +++ b/filament/src/ds/ShadowMapDescriptorSet.cpp @@ -0,0 +1,143 @@ +/* + * 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 "ShadowMapDescriptorSet.h" + +#include "details/Camera.h" +#include "details/Engine.h" + +#include +#include +#include + +#include + +#include + +#include + +#include + +namespace filament { + +using namespace backend; +using namespace math; + +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 ShadowMapDescriptorSet::terminate(DriverApi& driver) { + mDescriptorSet.terminate(driver); + driver.destroyBufferObject(mUniformBufferHandle); +} + +PerViewUib& ShadowMapDescriptorSet::edit(Transaction const& transaction) noexcept { + assert_invariant(transaction.uniforms); + return *transaction.uniforms; +} + +void ShadowMapDescriptorSet::prepareCamera(Transaction const& transaction, + FEngine& engine, const CameraInfo& camera) noexcept { + mat4f const& viewFromWorld = camera.view; + mat4f const& worldFromView = camera.model; + mat4f const& clipFromView = camera.projection; + + const mat4f viewFromClip{ inverse((mat4)camera.projection) }; + const mat4f clipFromWorld{ highPrecisionMultiply(clipFromView, viewFromWorld) }; + const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) }; + + auto& s = edit(transaction); + s.viewFromWorldMatrix = viewFromWorld; // view + s.worldFromViewMatrix = worldFromView; // model + s.clipFromViewMatrix = clipFromView; // projection + s.viewFromClipMatrix = viewFromClip; // 1/projection + s.clipFromWorldMatrix[0] = clipFromWorld; // projection * view + s.worldFromClipMatrix = worldFromClip; // 1/(projection * view) + s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform)); + s.clipTransform = camera.clipTransform; + s.cameraFar = camera.zf; + s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn); + s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn); + + // with a clip-space of [-w, w] ==> z' = -z + // with a clip-space of [0, w] ==> z' = (w - z)/2 + s.clipControl = engine.getDriverApi().getClipSpaceParams(); +} + +void ShadowMapDescriptorSet::prepareLodBias(Transaction const& transaction, float bias) noexcept { + auto& s = edit(transaction); + s.lodBias = bias; +} + +void ShadowMapDescriptorSet::prepareViewport(Transaction const& transaction, + backend::Viewport const& viewport) noexcept { + float2 const dimensions{ viewport.width, viewport.height }; + auto& s = edit(transaction); + s.resolution = { dimensions, 1.0f / dimensions }; + s.logicalViewportScale = 1.0f; + s.logicalViewportOffset = 0.0f; +} + +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; + const float fraction = float(double(oneSecondRemainder) / 1'000'000'000.0); + s.time = fraction; + s.userTime = userTime; +} + +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; + constexpr float high = 42.0f; // ~ std::log(std::numeric_limits::max()) * 0.5f; + s.vsmExponent = highPrecision ? high : low; +} + +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); + assert_invariant(transaction.uniforms); + return transaction; +} + +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 ShadowMapDescriptorSet::bind(backend::DriverApi& driver) noexcept { + mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_VIEW); +} + +} // namespace filament + diff --git a/filament/src/ds/ShadowMapDescriptorSet.h b/filament/src/ds/ShadowMapDescriptorSet.h new file mode 100644 index 00000000000..c1f55c5c89d --- /dev/null +++ b/filament/src/ds/ShadowMapDescriptorSet.h @@ -0,0 +1,89 @@ +/* + * 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_SHADOWMAPDESCRIPTORSET_H +#define TNT_FILAMENT_SHADOWMAPDESCRIPTORSET_H + +#include "DescriptorSet.h" + +#include "DescriptorSetLayout.h" + +#include "private/filament/UibStructs.h" + +#include +#include +#include + +#include + +namespace filament { + +struct CameraInfo; + +class FEngine; +class LightManager; + +/* + * PerShadowMapUniforms manages the UBO needed to generate our shadow maps. Internally it just + * holds onto a `PerViewUniform` UBO handle, but doesn't keep any shadow copy of it, instead it + * writes the data directly into the CommandStream, for this reason partial update of the data + * is not possible. + */ +class ShadowMapDescriptorSet { + +public: + class Transaction { + friend ShadowMapDescriptorSet; + PerViewUib* uniforms = nullptr; + Transaction() = default; // disallow creation by the caller + }; + + explicit ShadowMapDescriptorSet(FEngine& engine) noexcept; + + void terminate(backend::DriverApi& driver); + + static void prepareCamera(Transaction const& transaction, + FEngine& engine, const CameraInfo& camera) noexcept; + + static void prepareLodBias(Transaction const& transaction, + float bias) noexcept; + + static void prepareViewport(Transaction const& transaction, + backend::Viewport const& viewport) noexcept; + + static void prepareTime(Transaction const& transaction, + FEngine& engine, math::float4 const& userTime) noexcept; + + static void prepareShadowMapping(Transaction const& transaction, + bool highPrecision) noexcept; + + static Transaction open(backend::DriverApi& driver) noexcept; + + // update local data into GPU UBO + void commit(Transaction& transaction, FEngine& engine, backend::DriverApi& driver) noexcept; + + // bind this UBO + void bind(backend::DriverApi& driver) noexcept; + +private: + static PerViewUib& edit(Transaction const& transaction) noexcept; + backend::Handle mUniformBufferHandle; + DescriptorSet mDescriptorSet; +}; + +} // namespace filament + +#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;