From af8f38d83c5ea83a29551c7257bae830d442c49f Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Tue, 27 Feb 2024 16:11:33 -0800 Subject: [PATCH 01/22] Keep supporting API level 19 (#7609) This is a partial rollback from d83b3858b3dbc97b2a5e039ae9fb5d170dd95907. Keep supporting API level 19 for some of our clients. --- .../backend/src/opengl/platforms/PlatformEGLAndroid.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp index c7050622383..b61bbeab9a7 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -29,9 +29,11 @@ #include -// We require filament to be built with a API 21 toolchain, before that, OpenGLES 3.1 didn't exist -#if __ANDROID_API__ < 21 -# error "__ANDROID_API__ must be at least 21" +// We require filament to be built with an API 19 toolchain, before that, OpenGLES 3.0 didn't exist +// Actually, OpenGL ES 3.0 was added to API 18, but API 19 is the better target and +// the minimum for Jetpack at the time of this comment. +#if __ANDROID_API__ < 19 +# error "__ANDROID_API__ must be at least 19" #endif using namespace utils; From 57fff3a63695e5fb2f73eefe2ffc7381511d2f5e Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Wed, 28 Feb 2024 14:14:44 -0800 Subject: [PATCH 02/22] Add a new material param, stereoscopicType (#7613) * Add a new material param, stereoscopicType This new parameter allows us to specify which implementation of stereoscopic rendering Filament uses for the material. This change just includes material parameter addition and shader code changes, so it doesn't affect the current rendering behavior. These changes will follow as separate commits. - render pipeline changes - material parameter override via matc parameter - material document update --- libs/filamat/include/filamat/MaterialBuilder.h | 5 +++++ libs/filamat/src/MaterialBuilder.cpp | 6 ++++++ libs/filamat/src/shaders/CodeGenerator.cpp | 9 +++++++++ libs/filamat/src/shaders/MaterialInfo.h | 1 + shaders/src/common_getters.glsl | 4 ++++ shaders/src/main.vs | 6 +++--- shaders/src/varyings.glsl | 2 +- tools/matc/src/matc/ParametersProcessor.cpp | 15 +++++++++++++++ 8 files changed, 44 insertions(+), 4 deletions(-) diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index bd586fe92ee..f3e2080df6a 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -243,6 +243,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using Precision = filament::backend::Precision; using CullingMode = filament::backend::CullingMode; using FeatureLevel = filament::backend::FeatureLevel; + using StereoscopicType = filament::backend::StereoscopicType; enum class VariableQualifier : uint8_t { OUT @@ -521,6 +522,9 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Specifies how transparent objects should be rendered (default is DEFAULT). MaterialBuilder& transparencyMode(TransparencyMode mode) noexcept; + //! Specify the stereoscopic type (default is INSTANCED) + MaterialBuilder& stereoscopicType(StereoscopicType stereoscopicType) noexcept; + /** * Enable / disable custom surface shading. Custom surface shading requires the LIT * shading model. In addition, the following function must be defined in the fragment @@ -829,6 +833,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { Interpolation mInterpolation = Interpolation::SMOOTH; VertexDomain mVertexDomain = VertexDomain::OBJECT; TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; + StereoscopicType mStereoscopicType = StereoscopicType::INSTANCED; filament::AttributeBitset mRequiredAttributes; diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 492c898c693..f022c0a983e 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -498,6 +498,11 @@ MaterialBuilder& MaterialBuilder::transparencyMode(TransparencyMode mode) noexce return *this; } +MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType stereoscopicType) noexcept { + mStereoscopicType = stereoscopicType; + return *this; +} + MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept { mReflectionMode = mode; return *this; @@ -632,6 +637,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { info.vertexDomainDeviceJittered = mVertexDomainDeviceJittered; info.featureLevel = mFeatureLevel; info.groupSize = mGroupSize; + info.stereoscopicType = mStereoscopicType; // This is determined via static analysis of the glsl after prepareToBuild(). info.userMaterialHasCustomDepth = false; diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index a739e726e94..f713411e8b5 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -181,6 +181,15 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade } generateDefine(out, "FILAMENT_EFFECTIVE_VERSION", effective_version); + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + generateDefine(out, "FILAMENT_STEREO_INSTANCED", true); + break; + case StereoscopicType::MULTIVIEW: + generateDefine(out, "FILAMENT_STEREO_MULTIVIEW", true); + break; + } + if (stage == ShaderStage::VERTEX) { CodeGenerator::generateDefine(out, "FLIP_UV_ATTRIBUTE", material.flipUV); CodeGenerator::generateDefine(out, "LEGACY_MORPHING", material.useLegacyMorphing); diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h index b12863cede4..5ede9f8c1ff 100644 --- a/libs/filamat/src/shaders/MaterialInfo.h +++ b/libs/filamat/src/shaders/MaterialInfo.h @@ -68,6 +68,7 @@ struct UTILS_PUBLIC MaterialInfo { filament::SamplerBindingMap samplerBindings; filament::ShaderQuality quality; filament::backend::FeatureLevel featureLevel; + filament::backend::StereoscopicType stereoscopicType; filament::math::uint3 groupSize; using BufferContainer = utils::FixedCapacityVector; diff --git a/shaders/src/common_getters.glsl b/shaders/src/common_getters.glsl index eaa2521d855..a2bbebe4f8b 100644 --- a/shaders/src/common_getters.glsl +++ b/shaders/src/common_getters.glsl @@ -25,8 +25,12 @@ highp mat4 getViewFromClipMatrix() { /** @public-api */ highp mat4 getClipFromWorldMatrix() { #if defined(VARIANT_HAS_STEREO) +#if defined(FILAMENT_STEREO_INSTANCED) int eye = instance_index % CONFIG_STEREO_EYE_COUNT; return frameUniforms.clipFromWorldMatrix[eye]; +#elif defined(FILAMENT_STEREO_MULTIVIEW) + return frameUniforms.clipFromWorldMatrix[gl_ViewID_OVR]; +#endif #else return frameUniforms.clipFromWorldMatrix[0]; #endif diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 5ef0ebbe208..bbb9230fc8d 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -23,7 +23,7 @@ void main() { logical_instance_index = instance_index; #endif -#if defined(VARIANT_HAS_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) #if !defined(FILAMENT_HAS_FEATURE_INSTANCING) #error Instanced stereo not supported at this feature level #endif @@ -215,7 +215,7 @@ void main() { // this must happen before we compensate for vulkan below vertex_position = position; -#if defined(VARIANT_HAS_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) // We're transforming a vertex whose x coordinate is within the range (-w to w). // To move it to the correct portion of the viewport, we need to modify the x coordinate. // It's important to do this after computing vertex_position. @@ -253,7 +253,7 @@ void main() { // some PowerVR drivers crash when gl_Position is written more than once gl_Position = position; -#if defined(VARIANT_HAS_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) // Fragment shaders filter out the stereo variant, so we need to set instance_index here. instance_index = logical_instance_index; #endif diff --git a/shaders/src/varyings.glsl b/shaders/src/varyings.glsl index a7a48a302f9..a0478838ded 100644 --- a/shaders/src/varyings.glsl +++ b/shaders/src/varyings.glsl @@ -34,7 +34,7 @@ LAYOUT_LOCATION(11) VARYING highp vec4 vertex_lightSpacePosition; // Note that fragColor is an output and is not declared here; see main.fs and depth_main.fs -#if defined(VARIANT_HAS_STEREO) +#if defined(VARIANT_HAS_STEREO) && defined(FILAMENT_STEREO_INSTANCED) #if defined(GL_ES) && defined(FILAMENT_GLSLANG) // On ES, gl_ClipDistance is not a built-in, so we have to rely on EXT_clip_cull_distance // However, this extension is not supported by glslang, so we instead write to diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index 50d3ab74202..eacf7f24aa8 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -796,6 +796,20 @@ static bool processGroupSizes(MaterialBuilder& builder, const JsonishValue& v) { return true; } +static bool processStereoscopicType(MaterialBuilder& builder, const JsonishValue& value) { + static const std::unordered_map strToEnum{ + { "instanced", MaterialBuilder::StereoscopicType::INSTANCED }, + { "multiview", MaterialBuilder::StereoscopicType::MULTIVIEW }, + }; + auto jsonString = value.toJsonString(); + if (!isStringValidEnum(strToEnum, jsonString->getString())) { + return logEnumIssue("stereoscopicType", *jsonString, strToEnum); + } + + builder.stereoscopicType(stringToEnum(strToEnum, jsonString->getString())); + return true; +} + static bool processOutput(MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept { const JsonishValue* nameValue = jsonObject.getValue("name"); @@ -1214,6 +1228,7 @@ ParametersProcessor::ParametersProcessor() { mParameters["customSurfaceShading"] = { &processCustomSurfaceShading, Type::BOOL }; mParameters["featureLevel"] = { &processFeatureLevel, Type::NUMBER }; mParameters["groupSize"] = { &processGroupSizes, Type::ARRAY }; + mParameters["stereoscopicType"] = { &processStereoscopicType, Type::STRING }; } bool ParametersProcessor::process(MaterialBuilder& builder, const JsonishObject& jsonObject) { From 43f6c4507e9ff6b23aa48d1c57555110effd7ccc Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Thu, 29 Feb 2024 10:32:22 -0800 Subject: [PATCH 03/22] Make glFramebufferTextureMultiviewOVR available for Android (#7615) This OpenGL function is going to be used for multiview on Android. Make it available. --- filament/backend/src/opengl/gl_headers.cpp | 2 ++ filament/backend/src/opengl/gl_headers.h | 1 + 2 files changed, 3 insertions(+) diff --git a/filament/backend/src/opengl/gl_headers.cpp b/filament/backend/src/opengl/gl_headers.cpp index d9007d0ff5f..5e6c46b3411 100644 --- a/filament/backend/src/opengl/gl_headers.cpp +++ b/filament/backend/src/opengl/gl_headers.cpp @@ -72,6 +72,7 @@ PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR; // On Android, If we want to support a build system less than ANDROID_API 21, we need to // use getProcAddress for ES3.1 and above entry points. PFNGLDISPATCHCOMPUTEPROC glDispatchCompute; +PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR; #endif static std::once_flag sGlExtInitialized; #endif // __EMSCRIPTEN__ @@ -119,6 +120,7 @@ void importGLESExtensionsEntryPoints() { #endif #if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) getProcAddress(glDispatchCompute, "glDispatchCompute"); + getProcAddress(glFramebufferTextureMultiviewOVR, "glFramebufferTextureMultiviewOVR"); #endif }); #endif // __EMSCRIPTEN__ diff --git a/filament/backend/src/opengl/gl_headers.h b/filament/backend/src/opengl/gl_headers.h index ca387eb7137..1909c876667 100644 --- a/filament/backend/src/opengl/gl_headers.h +++ b/filament/backend/src/opengl/gl_headers.h @@ -153,6 +153,7 @@ extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR; #endif #if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute; +extern PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR; #endif #endif // __EMSCRIPTEN__ } // namespace glext From 9c0c56d6d0037c61a456e6e2a750cf053f753a1d Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 28 Feb 2024 12:12:29 -0800 Subject: [PATCH 04/22] add support for protected textures This is currently only implemented in the GLES backend and simply exposes GL_EXT_protected_textures. There is not much that can be done with this yet. Protected textures can't be read nor written at the moment. --- .../backend/include/backend/DriverEnums.h | 21 ++++++++-------- .../include/private/backend/DriverAPI.inc | 1 + filament/backend/src/DriverBase.h | 2 ++ filament/backend/src/metal/MetalDriver.mm | 4 +++ filament/backend/src/noop/NoopDriver.cpp | 4 +++ filament/backend/src/opengl/OpenGLContext.cpp | 1 + filament/backend/src/opengl/OpenGLContext.h | 1 + filament/backend/src/opengl/OpenGLDriver.cpp | 25 +++++++++++++++++-- filament/backend/src/opengl/OpenGLDriver.h | 4 +-- filament/backend/src/vulkan/VulkanDriver.cpp | 4 +++ filament/include/filament/Texture.h | 3 +++ filament/src/Texture.cpp | 4 +++ filament/src/details/Texture.cpp | 11 ++++++++ filament/src/details/Texture.h | 7 ++++-- 14 files changed, 76 insertions(+), 16 deletions(-) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index c41d1b83049..035179486ac 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -659,16 +659,17 @@ enum class TextureFormat : uint16_t { }; //! Bitmask describing the intended Texture Usage -enum class TextureUsage : uint8_t { - NONE = 0x00, - COLOR_ATTACHMENT = 0x01, //!< Texture can be used as a color attachment - DEPTH_ATTACHMENT = 0x02, //!< Texture can be used as a depth attachment - STENCIL_ATTACHMENT = 0x04, //!< Texture can be used as a stencil attachment - UPLOADABLE = 0x08, //!< Data can be uploaded into this texture (default) - SAMPLEABLE = 0x10, //!< Texture can be sampled (default) - SUBPASS_INPUT = 0x20, //!< Texture can be used as a subpass input - BLIT_SRC = 0x40, //!< Texture can be used the source of a blit() - BLIT_DST = 0x80, //!< Texture can be used the destination of a blit() +enum class TextureUsage : uint16_t { + NONE = 0x0000, + COLOR_ATTACHMENT = 0x0001, //!< Texture can be used as a color attachment + DEPTH_ATTACHMENT = 0x0002, //!< Texture can be used as a depth attachment + STENCIL_ATTACHMENT = 0x0004, //!< Texture can be used as a stencil attachment + UPLOADABLE = 0x0008, //!< Data can be uploaded into this texture (default) + SAMPLEABLE = 0x0010, //!< Texture can be sampled (default) + SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input + BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit() + BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit() + PROTECTED = 0x0100, //!< Texture can be used the destination of a blit() DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage }; diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index d3f9dd43cae..b04a71059c8 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -301,6 +301,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported) DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported) +DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported) DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers) DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize) DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams) diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 43cd3ef90dd..24acfe60e1b 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -113,7 +113,9 @@ struct HwTexture : public HwBase { uint8_t levels : 4; // This allows up to 15 levels (max texture size of 32768 x 32768) uint8_t samples : 4; // Sample count per pixel (should always be a power of 2) TextureFormat format{}; + uint8_t reserved0 = 0; TextureUsage usage{}; + uint16_t reserved1 = 0; HwStream* hwStream = nullptr; HwTexture() noexcept : levels{}, samples{} {} diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 1bf3fc93374..f36fbfb0da3 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -788,6 +788,10 @@ return false; } +bool MetalDriver::isProtectedTexturesSupported() { + return false; +} + bool MetalDriver::isWorkaroundNeeded(Workaround workaround) { switch (workaround) { case Workaround::SPLIT_EASU: diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 797a1a61b01..6c291297894 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -189,6 +189,10 @@ bool NoopDriver::isDepthStencilResolveSupported() { return true; } +bool NoopDriver::isProtectedTexturesSupported() { + return true; +} + bool NoopDriver::isWorkaroundNeeded(Workaround) { return false; } diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 8d7a6a4b223..7f0559486ca 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -599,6 +599,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv); ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv); ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv); + ext->EXT_protected_textures = exts.has("GL_EXT_protected_textures"sv); #endif ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv); #ifndef __EMSCRIPTEN__ diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index 315f9490313..415c5a014de 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -183,6 +183,7 @@ class OpenGLContext { bool EXT_discard_framebuffer; bool EXT_multisampled_render_to_texture2; bool EXT_multisampled_render_to_texture; + bool EXT_protected_textures; bool EXT_shader_framebuffer_fetch; bool EXT_texture_compression_bptc; bool EXT_texture_compression_etc2; diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 98031d6ff8a..9682fc2ce1c 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -580,13 +580,22 @@ void OpenGLDriver::createSamplerGroupR(Handle sbh, uint32_t size UTILS_NOINLINE void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t, - uint32_t width, uint32_t height, uint32_t depth) noexcept { + uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept { auto& gl = mContext; bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t); gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING); +#ifdef GL_EXT_protected_textures +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + if (UTILS_UNLIKELY(useProtectedMemory)) { + assert_invariant(gl.ext.EXT_protected_textures); + glTexParameteri(t->gl.target, GL_TEXTURE_PROTECTED_EXT, 1); + } +#endif +#endif + switch (t->gl.target) { case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: @@ -677,6 +686,12 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint GLenum internalFormat = getInternalFormat(format); assert_invariant(internalFormat); + if (UTILS_UNLIKELY(usage & TextureUsage::PROTECTED)) { + // renderbuffers don't have a protected mode, so we need to use a texture + // because protected textures are only supported on GLES 3.2, MSAA will be available. + usage |= TextureUsage::SAMPLEABLE; + } + auto& gl = mContext; samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples)); GLTexture* t = construct(th, target, levels, samples, w, h, depth, format, usage); @@ -746,7 +761,8 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint } #endif } - textureStorage(t, w, h, depth); + + textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED)); } } else { assert_invariant(any(usage & ( @@ -755,6 +771,7 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint TextureUsage::STENCIL_ATTACHMENT))); assert_invariant(levels == 1); assert_invariant(target == SamplerType::SAMPLER_2D); + assert_invariant(none(usage & TextureUsage::PROTECTED)); t->gl.internalFormat = internalFormat; t->gl.target = GL_RENDERBUFFER; glGenRenderbuffers(1, &t->gl.id); @@ -1945,6 +1962,10 @@ bool OpenGLDriver::isDepthStencilResolveSupported() { return true; } +bool OpenGLDriver::isProtectedTexturesSupported() { + return getContext().ext.EXT_protected_textures; +} + bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) { switch (workaround) { case Workaround::SPLIT_EASU: diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index 5365811b01c..c895f16c6f4 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -325,8 +325,8 @@ class OpenGLDriver final : public DriverBase { void renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width, uint32_t height, uint8_t samples) const noexcept; - void textureStorage(GLTexture* t, - uint32_t width, uint32_t height, uint32_t depth) noexcept; + void textureStorage(OpenGLDriver::GLTexture* t, uint32_t width, uint32_t height, + uint32_t depth, bool useProtectedMemory) noexcept; /* State tracking GL wrappers... */ diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 8e2d0ed4607..7c8b19a992a 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -887,6 +887,10 @@ bool VulkanDriver::isDepthStencilResolveSupported() { return false; } +bool VulkanDriver::isProtectedTexturesSupported() { + return false; +} + bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) { switch (workaround) { case Workaround::SPLIT_EASU: { diff --git a/filament/include/filament/Texture.h b/filament/include/filament/Texture.h index 1b7c39ea5e9..8a27f831c2b 100644 --- a/filament/include/filament/Texture.h +++ b/filament/include/filament/Texture.h @@ -88,6 +88,9 @@ class UTILS_PUBLIC Texture : public FilamentAPI { /** @return whether a backend supports a particular format. */ static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept; + /** @return whether this backend supports protected textures. */ + static bool isProtectedTexturesSupported(Engine& engine) noexcept; + /** @return whether a backend supports texture swizzling. */ static bool isTextureSwizzleSupported(Engine& engine) noexcept; diff --git a/filament/src/Texture.cpp b/filament/src/Texture.cpp index 3ed5687b47b..f4c381927a7 100644 --- a/filament/src/Texture.cpp +++ b/filament/src/Texture.cpp @@ -78,6 +78,10 @@ bool Texture::isTextureFormatSupported(Engine& engine, InternalFormat format) no return FTexture::isTextureFormatSupported(downcast(engine), format); } +bool Texture::isProtectedTexturesSupported(Engine& engine) noexcept { + return FTexture::isProtectedTexturesSupported(downcast(engine)); +} + bool Texture::isTextureSwizzleSupported(Engine& engine) noexcept { return FTexture::isTextureSwizzleSupported(downcast(engine)); } diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index 16eaabac530..7a0754ac609 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -125,6 +125,13 @@ Texture* Texture::Builder::build(Engine& engine) { ASSERT_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat), "Texture format %u not supported on this platform", mImpl->mFormat); + const bool isProtectedTexturesSupported = + downcast(engine).getDriverApi().isProtectedTexturesSupported(); + const bool useProtectedMemory = bool(mImpl->mUsage & TextureUsage::PROTECTED); + + ASSERT_PRECONDITION((isProtectedTexturesSupported && useProtectedMemory) || !useProtectedMemory, + "Texture is PROTECTED but protected textures are not supported"); + uint8_t maxLevelCount; switch (mImpl->mTarget) { case SamplerType::SAMPLER_2D: @@ -424,6 +431,10 @@ bool FTexture::isTextureFormatSupported(FEngine& engine, InternalFormat format) return engine.getDriverApi().isTextureFormatSupported(format); } +bool FTexture::isProtectedTexturesSupported(FEngine& engine) noexcept { + return engine.getDriverApi().isProtectedTexturesSupported(); +} + bool FTexture::isTextureSwizzleSupported(FEngine& engine) noexcept { return engine.getDriverApi().isTextureSwizzleSupported(); } diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index bf8d7e435db..0e0fe9320ee 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -79,10 +79,13 @@ class FTexture : public Texture { * Utilities */ - // synchronous call to the backend. returns whether a backend supports a particular format. + // Synchronous call to the backend. Returns whether a backend supports a particular format. static bool isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept; - // synchronous call to the backend. returns whether a backend supports texture swizzling. + // Synchronous call to the backend. Returns whether a backend supports protected textures. + static bool isProtectedTexturesSupported(FEngine& engine) noexcept; + + // Synchronous call to the backend. Returns whether a backend supports texture swizzling. static bool isTextureSwizzleSupported(FEngine& engine) noexcept; // storage needed on the CPU side for texture data uploads From b921d78fe770bf6ddc4f6c5b8b985d7260a55431 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 23 Feb 2024 10:09:13 -0800 Subject: [PATCH 05/22] Add support for protected SwapChain. This is supported only by the PlatformEGL currently. There is not much that can be done with it either at this point. A protected swapchain is one that can only be written by a protected context, however, there is currently no way to create such context. --- .../src/main/cpp/SwapChain.cpp | 12 +++++- .../google/android/filament/SwapChain.java | 18 +++++++++ .../backend/include/backend/DriverEnums.h | 9 ++++- .../backend/platforms/OpenGLPlatform.h | 24 ++++++++---- .../include/backend/platforms/PlatformEGL.h | 4 ++ .../include/private/backend/DriverAPI.inc | 1 + filament/backend/src/metal/MetalDriver.mm | 5 +++ filament/backend/src/noop/NoopDriver.cpp | 4 ++ filament/backend/src/opengl/OpenGLDriver.cpp | 4 ++ .../backend/src/opengl/OpenGLPlatform.cpp | 4 ++ .../src/opengl/platforms/PlatformEGL.cpp | 38 +++++++++++++++++-- filament/backend/src/vulkan/VulkanDriver.cpp | 5 +++ filament/include/filament/SwapChain.h | 21 ++++++++-- filament/src/SwapChain.cpp | 8 ++++ filament/src/details/SwapChain.cpp | 10 +++++ filament/src/details/SwapChain.h | 2 + 16 files changed, 152 insertions(+), 17 deletions(-) diff --git a/android/filament-android/src/main/cpp/SwapChain.cpp b/android/filament-android/src/main/cpp/SwapChain.cpp index 27e006ae87a..e1424595b29 100644 --- a/android/filament-android/src/main/cpp/SwapChain.cpp +++ b/android/filament-android/src/main/cpp/SwapChain.cpp @@ -34,7 +34,15 @@ Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* en } extern "C" JNIEXPORT jboolean JNICALL -Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported(JNIEnv *, jclass, jlong nativeEngine) { +Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported( + JNIEnv *, jclass, jlong nativeEngine) { Engine* engine = (Engine*) nativeEngine; - return (bool)SwapChain::isSRGBSwapChainSupported(*engine); + return (jboolean)SwapChain::isSRGBSwapChainSupported(*engine); +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_SwapChain_nIsProtectedContentSupported( + JNIEnv *, jclass, jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jboolean)SwapChain::isProtectedContentSupported(*engine); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java index 429af3e140d..e3d1cf43aad 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java @@ -128,11 +128,28 @@ public class SwapChain { */ public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20; + /** + * The SwapChain contains protected content. Only supported when isProtectedContentSupported() + * is true. + */ + public static final long CONFIG_PROTECTED_CONTENT = 0x40; + SwapChain(long nativeSwapChain, Object surface) { mNativeObject = nativeSwapChain; mSurface = surface; } + /** + * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag. + * The default implementation returns false. + * + * @param engine A reference to the filament Engine + * @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise. + */ + public static boolean isProtectedContentSupported(@NonNull Engine engine) { + return nIsProtectedContentSupported(engine.getNativeObject()); + } + /** * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. * The default implementation returns false. @@ -186,4 +203,5 @@ void clearNativeObject() { private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback); private static native boolean nIsSRGBSwapChainSupported(long nativeEngine); + private static native boolean nIsProtectedContentSupported(long nativeEngine); } diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 035179486ac..61a8b0ad70b 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -81,7 +81,14 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_SRGB_COLORSPACE = 0x10; /** * Indicates that the SwapChain should also contain a stencil component. */ -static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = 0x20; +static constexpr uint64_t SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER = 0x20; +static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER; + +/** + * The SwapChain contains protected content. Currently only supported by OpenGLPlatform and + * only when OpenGLPlatform::isProtectedContextSupported() is true. + */ +static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40; static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES. diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index 12deb801303..63b346fe46d 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -62,6 +62,22 @@ class OpenGLPlatform : public Platform { */ virtual void terminate() noexcept = 0; + /** + * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. + * The default implementation returns false. + * + * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. + */ + virtual bool isSRGBSwapChainSupported() const noexcept; + + /** + * Return whether protected contexts are supported by this backend. + * If protected context are supported, the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag can be + * used when creating a SwapChain. + * The default implementation returns false. + */ + virtual bool isProtectedContextSupported() const noexcept; + /** * Called by the driver to create a SwapChain for this driver. * @@ -74,14 +90,6 @@ class OpenGLPlatform : public Platform { virtual SwapChain* UTILS_NONNULL createSwapChain( void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0; - /** - * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. - * The default implementation returns false. - * - * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. - */ - virtual bool isSRGBSwapChainSupported() const noexcept; - /** * Called by the driver create a headless SwapChain. * diff --git a/filament/backend/include/backend/platforms/PlatformEGL.h b/filament/backend/include/backend/platforms/PlatformEGL.h index 61b7ec8c45a..c1be11e137c 100644 --- a/filament/backend/include/backend/platforms/PlatformEGL.h +++ b/filament/backend/include/backend/platforms/PlatformEGL.h @@ -91,6 +91,8 @@ class PlatformEGL : public OpenGLPlatform { void terminate() noexcept override; + bool isProtectedContextSupported() const noexcept override; + bool isSRGBSwapChainSupported() const noexcept override; SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; @@ -148,6 +150,7 @@ class PlatformEGL : public OpenGLPlatform { bool KHR_gl_colorspace = false; bool KHR_no_config_context = false; bool KHR_surfaceless_context = false; + bool EXT_protected_content = false; } egl; } ext; @@ -156,6 +159,7 @@ class PlatformEGL : public OpenGLPlatform { Config attribs{}; EGLNativeWindowType nativeWindow{}; EGLConfig config{}; + uint64_t flags{}; }; void initializeGlExtensions() noexcept; diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index b04a71059c8..09a302c9d76 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -298,6 +298,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported) +DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported) DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported) diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index f36fbfb0da3..4761b33e2f1 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -770,6 +770,11 @@ return false; } +bool MetalDriver::isProtectedContentSupported() { + // the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported + return false; +} + bool MetalDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) { switch (stereoscopicType) { case backend::StereoscopicType::INSTANCED: diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 6c291297894..f6de98ceae0 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -177,6 +177,10 @@ bool NoopDriver::isSRGBSwapChainSupported() { return false; } +bool NoopDriver::isProtectedContentSupported() { + return false; +} + bool NoopDriver::isStereoSupported(backend::StereoscopicType) { return false; } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 9682fc2ce1c..d496fea911d 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1938,6 +1938,10 @@ bool OpenGLDriver::isSRGBSwapChainSupported() { return mPlatform.isSRGBSwapChainSupported(); } +bool OpenGLDriver::isProtectedContentSupported() { + return mPlatform.isProtectedContextSupported(); +} + bool OpenGLDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) { // Instanced-stereo requires instancing and EXT_clip_cull_distance. // Multiview-stereo requires ES 3.0 and OVR_multiview2. diff --git a/filament/backend/src/opengl/OpenGLPlatform.cpp b/filament/backend/src/opengl/OpenGLPlatform.cpp index ccffdfa6d42..d3aa2e6f99b 100644 --- a/filament/backend/src/opengl/OpenGLPlatform.cpp +++ b/filament/backend/src/opengl/OpenGLPlatform.cpp @@ -35,6 +35,10 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform, OpenGLPlatform::~OpenGLPlatform() noexcept = default; +bool OpenGLPlatform::isProtectedContextSupported() const noexcept { + return false; +} + bool OpenGLPlatform::isSRGBSwapChainSupported() const noexcept { return false; } diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index c05fa097865..ab01df11223 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -152,6 +152,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context"); ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context"); ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context"); + ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content"); if (ext.egl.KHR_create_context) { // KHR_create_context implies KHR_surfaceless_context for ES3.x contexts ext.egl.KHR_surfaceless_context = true; @@ -318,6 +319,10 @@ bool PlatformEGL::isExtraContextSupported() const noexcept { return ext.egl.KHR_surfaceless_context; } +bool PlatformEGL::isProtectedContextSupported() const noexcept { + return ext.egl.EXT_protected_content; +} + void PlatformEGL::createContext(bool shared) { EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig; @@ -438,6 +443,8 @@ EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbu return config; } +// ----------------------------------------------------------------------------------------------- + bool PlatformEGL::isSRGBSwapChainSupported() const noexcept { return ext.egl.KHR_gl_colorspace; } @@ -462,6 +469,16 @@ Platform::SwapChain* PlatformEGL::createSwapChain( if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) { attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR; } + } else { + flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE; + } + + if (ext.egl.EXT_protected_content) { + if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) { + attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + } + } else { + flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; } EGLSurface sur = eglCreateWindowSurface(mEGLDisplay, config, @@ -479,7 +496,8 @@ Platform::SwapChain* PlatformEGL::createSwapChain( .sur = sur, .attribs = std::move(attribs), .nativeWindow = (EGLNativeWindowType)nativeWindow, - .config = config + .config = config, + .flags = flags }); return sc; } @@ -507,6 +525,16 @@ Platform::SwapChain* PlatformEGL::createSwapChain( if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) { attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR; } + } else { + flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE; + } + + if (ext.egl.EXT_protected_content) { + if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) { + attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + } + } else { + flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; } EGLSurface sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data()); @@ -554,6 +582,8 @@ void PlatformEGL::commit(Platform::SwapChain* swapChain) noexcept { } } +// ----------------------------------------------------------------------------------------------- + bool PlatformEGL::canCreateFence() noexcept { return true; } @@ -592,6 +622,8 @@ FenceStatus PlatformEGL::waitFence( return FenceStatus::ERROR; } +// ----------------------------------------------------------------------------------------------- + OpenGLPlatform::ExternalTexture* PlatformEGL::createExternalImageTexture() noexcept { ExternalTexture* outTexture = new(std::nothrow) ExternalTexture{}; glGenTextures(1, &outTexture->id); @@ -622,6 +654,8 @@ bool PlatformEGL::setExternalImage(void* externalImage, return true; } +// ----------------------------------------------------------------------------------------------- + void PlatformEGL::initializeGlExtensions() noexcept { // We're guaranteed to be on an ES platform, since we're using EGL GLUtils::unordered_string_set glExtensions; @@ -667,5 +701,3 @@ void PlatformEGL::Config::erase(EGLint name) noexcept { } } // namespace filament::backend - -// --------------------------------------------------------------------------------------------- diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 7c8b19a992a..c5f0cdb90ab 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -868,6 +868,11 @@ bool VulkanDriver::isSRGBSwapChainSupported() { return mIsSRGBSwapChainSupported; } +bool VulkanDriver::isProtectedContentSupported() { + // the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported + return false; +} + bool VulkanDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) { switch (stereoscopicType) { case backend::StereoscopicType::INSTANCED: diff --git a/filament/include/filament/SwapChain.h b/filament/include/filament/SwapChain.h index 8db477ade08..404ab0c1db0 100644 --- a/filament/include/filament/SwapChain.h +++ b/filament/include/filament/SwapChain.h @@ -225,14 +225,29 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * @see View.setStencilBufferEnabled * @see View.setPostProcessingEnabled */ - static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_HAS_STENCIL_BUFFER; + static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER; /** - * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. + * The SwapChain contains protected content. Only supported when isProtectedContentSupported() + * is true. + */ + static constexpr uint64_t CONFIG_PROTECTED_CONTENT = backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; + + /** + * Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag. + * The default implementation returns false. + * + * @param engine A pointer to the filament Engine + * @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise. + */ + static bool isProtectedContentSupported(Engine& engine) noexcept; + + /** + * Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag. * The default implementation returns false. * * @param engine A pointer to the filament Engine - * @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise. + * @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise. */ static bool isSRGBSwapChainSupported(Engine& engine) noexcept; diff --git a/filament/src/SwapChain.cpp b/filament/src/SwapChain.cpp index c30bce69416..1800056887e 100644 --- a/filament/src/SwapChain.cpp +++ b/filament/src/SwapChain.cpp @@ -18,6 +18,10 @@ #include "details/Engine.h" +#include + +#include + namespace filament { void* SwapChain::getNativeWindow() const noexcept { @@ -37,4 +41,8 @@ bool SwapChain::isSRGBSwapChainSupported(Engine& engine) noexcept { return FSwapChain::isSRGBSwapChainSupported(downcast(engine)); } +bool SwapChain::isProtectedContentSupported(Engine& engine) noexcept { + return FSwapChain::isProtectedContentSupported(downcast(engine)); +} + } // namespace filament diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp index d9cb80911d9..956fa0660f6 100644 --- a/filament/src/details/SwapChain.cpp +++ b/filament/src/details/SwapChain.cpp @@ -18,6 +18,12 @@ #include "details/Engine.h" + +#include +#include + +#include + namespace filament { FSwapChain::FSwapChain(FEngine& engine, void* nativeWindow, uint64_t flags) @@ -62,4 +68,8 @@ bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept { return engine.getDriverApi().isSRGBSwapChainSupported(); } +bool FSwapChain::isProtectedContentSupported(FEngine& engine) noexcept { + return engine.getDriverApi().isProtectedContentSupported(); +} + } // namespace filament diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index 3c820b9799f..6bee6994463 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -73,6 +73,8 @@ class FSwapChain : public SwapChain { static bool isSRGBSwapChainSupported(FEngine& engine) noexcept; + static bool isProtectedContentSupported(FEngine& engine) noexcept; + private: FEngine& mEngine; backend::Handle mSwapChain; From 2022be928e8c95424e602e7e21dc9310eb828d37 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 28 Feb 2024 13:42:04 -0800 Subject: [PATCH 06/22] Timer queries can now return an error. --- .../backend/include/backend/DriverEnums.h | 6 ++++++ .../include/private/backend/DriverAPI.inc | 2 +- filament/backend/src/metal/MetalDriver.mm | 5 +++-- filament/backend/src/noop/NoopDriver.cpp | 4 ++-- filament/backend/src/opengl/OpenGLDriver.cpp | 2 +- .../backend/src/opengl/OpenGLTimerQuery.cpp | 20 +++++++++++-------- .../backend/src/opengl/OpenGLTimerQuery.h | 2 +- filament/backend/src/vulkan/VulkanDriver.cpp | 8 ++++---- filament/src/FrameInfo.cpp | 17 ++++++++++++---- 9 files changed, 43 insertions(+), 23 deletions(-) diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 61a8b0ad70b..c2deaf547c7 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -136,6 +136,12 @@ enum class Backend : uint8_t { NOOP = 4, //!< Selects the no-op driver for testing purposes. }; +enum class TimerQueryResult : int8_t { + ERROR = -1, // an error occurred, result won't be available + NOT_READY = 0, // result to ready yet + AVAILABLE = 1, // result is available +}; + static constexpr const char* backendToString(Backend backend) { switch (backend) { case Backend::NOOP: diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index 09a302c9d76..05170ac8d42 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -307,7 +307,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers) DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize) DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams) DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image) -DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime) +DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime) DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround) DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel) diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 4761b33e2f1..e291ab60fd2 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -923,9 +923,10 @@ void MetalDriver::setExternalStream(Handle th, Handle sh) { } -bool MetalDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { +TimerQueryResult MetalDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { auto* tq = handle_cast(tqh); - return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime); + return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime) ? + TimerQueryResult::AVAILABLE : TimerQueryResult::NOT_READY; } void MetalDriver::generateMipmaps(Handle th) { diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index f6de98ceae0..9f5a7b644d9 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -252,8 +252,8 @@ void NoopDriver::update3DImage(Handle th, void NoopDriver::setupExternalImage(void* image) { } -bool NoopDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { - return false; +TimerQueryResult NoopDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { + return TimerQueryResult::ERROR; } void NoopDriver::setExternalImage(Handle th, void* image) { diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index d496fea911d..bd0e5ccb4e3 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -2694,7 +2694,7 @@ void OpenGLDriver::endTimerQuery(Handle tqh) { mTimerQueryImpl->endTimeElapsedQuery(tq); } -bool OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { +TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { GLTimerQuery* tq = handle_cast(tqh); return OpenGLTimerQueryInterface::getTimerQueryValue(tq, elapsedTime); } diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.cpp b/filament/backend/src/opengl/OpenGLTimerQuery.cpp index 6acb9322111..66592266d31 100644 --- a/filament/backend/src/opengl/OpenGLTimerQuery.cpp +++ b/filament/backend/src/opengl/OpenGLTimerQuery.cpp @@ -69,16 +69,17 @@ OpenGLTimerQueryInterface* OpenGLTimerQueryFactory::init( OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default; // This is a backend synchronous call -bool OpenGLTimerQueryInterface::getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept { +TimerQueryResult OpenGLTimerQueryInterface::getTimerQueryValue( + GLTimerQuery* tq, uint64_t* elapsedTime) noexcept { if (UTILS_LIKELY(tq->state)) { int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed); - bool const available = elapsed > 0; - if (available) { + if (elapsed > 0) { *elapsedTime = elapsed; + return TimerQueryResult::AVAILABLE; } - return available; + return TimerQueryResult(elapsed); } - return false; + return TimerQueryResult::ERROR; } // ------------------------------------------------------------------------------------------------ @@ -107,19 +108,20 @@ void TimerQueryNative::destroyTimerQuery(GLTimerQuery* tq) { void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - tq->state->elapsed.store(0); + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY)); mDriver.getContext().procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) } void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - mDriver.getContext().procs.endQuery(GL_TIME_ELAPSED); + auto& context = mDriver.getContext(); + context.procs.endQuery(GL_TIME_ELAPSED); CHECK_GL_ERROR(utils::slog.e) std::weak_ptr const weak = tq->state; - mDriver.runEveryNowAndThen([context = mDriver.getContext(), weak]() -> bool { + mDriver.runEveryNowAndThen([&context, weak]() -> bool { auto state = weak.lock(); if (state) { GLuint available = 0; @@ -133,6 +135,8 @@ void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { // we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime); state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed); + } else { + state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed); } return true; }); diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.h b/filament/backend/src/opengl/OpenGLTimerQuery.h index 5a42c290822..55bb1960afa 100644 --- a/filament/backend/src/opengl/OpenGLTimerQuery.h +++ b/filament/backend/src/opengl/OpenGLTimerQuery.h @@ -62,7 +62,7 @@ class OpenGLTimerQueryInterface { virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0; virtual void endTimeElapsedQuery(GLTimerQuery* query) = 0; - static bool getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept; + static TimerQueryResult getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept; }; #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index c5f0cdb90ab..4b479eb36db 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1042,10 +1042,10 @@ void VulkanDriver::update3DImage(Handle th, uint32_t level, uint32_t void VulkanDriver::setupExternalImage(void* image) { } -bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { +TimerQueryResult VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { VulkanTimerQuery* vtq = mResourceAllocator.handle_cast(tqh); if (!vtq->isCompleted()) { - return false; + return TimerQueryResult::NOT_READY; } auto results = mTimestamps->getResult(vtq); @@ -1055,7 +1055,7 @@ bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapse uint64_t available1 = results[3]; if (available0 == 0 || available1 == 0) { - return false; + return TimerQueryResult::NOT_READY; } ASSERT_POSTCONDITION(timestamp1 >= timestamp0, "Timestamps are not monotonically increasing."); @@ -1067,7 +1067,7 @@ bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapse float const period = mContext.getPhysicalDeviceLimits().timestampPeriod; uint64_t delta = uint64_t(float(timestamp1 - timestamp0) * period); *elapsedTime = delta; - return true; + return TimerQueryResult::AVAILABLE; } void VulkanDriver::setExternalImage(Handle th, void* image) { diff --git a/filament/src/FrameInfo.cpp b/filament/src/FrameInfo.cpp index 12923e69d37..060eb97ba29 100644 --- a/filament/src/FrameInfo.cpp +++ b/filament/src/FrameInfo.cpp @@ -54,10 +54,19 @@ void FrameInfoManager::terminate(DriverApi& driver) noexcept { void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32_t) noexcept { driver.beginTimerQuery(mQueries[mIndex]); uint64_t elapsed = 0; - if (driver.getTimerQueryValue(mQueries[mLast], &elapsed)) { - mLast = (mLast + 1) % POOL_COUNT; - // conversion to our duration happens here - mFrameTime = std::chrono::duration(elapsed); + TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed); + switch (result) { + case TimerQueryResult::NOT_READY: + // nothing to do + break; + case TimerQueryResult::ERROR: + mLast = (mLast + 1) % POOL_COUNT; + break; + case TimerQueryResult::AVAILABLE: + mLast = (mLast + 1) % POOL_COUNT; + // conversion to our duration happens here + mFrameTime = std::chrono::duration(elapsed); + break; } update(config, mFrameTime); } From 167ec626676f3088bd807bf3e37e1d5ede79ab2b Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 28 Feb 2024 16:38:17 -0800 Subject: [PATCH 07/22] Add protected mode to the FrameGraph. In protected mode, the FrameGraph will automatically add the PROTECTED usage bit to texture resources. --- filament/src/fg/FrameGraph.cpp | 20 ++++++++++++++++++-- filament/src/fg/FrameGraph.h | 9 ++++++++- filament/src/fg/FrameGraphTexture.cpp | 7 ++++++- filament/src/fg/FrameGraphTexture.h | 5 +++-- filament/src/fg/details/Resource.h | 10 ++++++---- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index 209ed4f99e8..00deb7dc7b8 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -16,17 +16,31 @@ #include "fg/FrameGraph.h" #include "fg/details/PassNode.h" +#include "fg/details/Resource.h" #include "fg/details/ResourceNode.h" #include "fg/details/DependencyGraph.h" +#include "FrameGraphId.h" +#include "FrameGraphPass.h" +#include "FrameGraphRenderPass.h" +#include "FrameGraphTexture.h" + #include "details/Engine.h" #include #include +#include +#include +#include #include #include +#include +#include + +#include + namespace filament { inline FrameGraph::Builder::Builder(FrameGraph& fg, PassNode* passNode) noexcept @@ -59,9 +73,10 @@ FrameGraphId FrameGraph::Builder::declareRenderPass( // ------------------------------------------------------------------------------------------------ -FrameGraph::FrameGraph(ResourceAllocatorInterface& resourceAllocator) +FrameGraph::FrameGraph(ResourceAllocatorInterface& resourceAllocator, Mode mode) : mResourceAllocator(resourceAllocator), mArena("FrameGraph Arena", 262144), + mMode(mode), mResourceSlots(mArena), mResources(mArena), mResourceNodes(mArena), @@ -181,6 +196,7 @@ void FrameGraph::execute(backend::DriverApi& driver) noexcept { SYSTRACE_CALL(); + bool const useProtectedMemory = mMode == Mode::PROTECTED; auto const& passNodes = mPassNodes; auto& resourceAllocator = mResourceAllocator; @@ -200,7 +216,7 @@ void FrameGraph::execute(backend::DriverApi& driver) noexcept { // devirtualize resourcesList for (VirtualResource* resource : node->devirtualize) { assert_invariant(resource->first == node); - resource->devirtualize(resourceAllocator); + resource->devirtualize(resourceAllocator, useProtectedMemory); } // call execute diff --git a/filament/src/fg/FrameGraph.h b/filament/src/fg/FrameGraph.h index 803e5c8088c..0e01379a014 100644 --- a/filament/src/fg/FrameGraph.h +++ b/filament/src/fg/FrameGraph.h @@ -218,7 +218,13 @@ class FrameGraph { // -------------------------------------------------------------------------------------------- - explicit FrameGraph(ResourceAllocatorInterface& resourceAllocator); + enum class Mode { + UNPROTECTED, + PROTECTED, + }; + + explicit FrameGraph(ResourceAllocatorInterface& resourceAllocator, + Mode mode = Mode::UNPROTECTED); FrameGraph(FrameGraph const&) = delete; FrameGraph& operator=(FrameGraph const&) = delete; ~FrameGraph() noexcept; @@ -517,6 +523,7 @@ class FrameGraph { ResourceAllocatorInterface& mResourceAllocator; LinearAllocatorArena mArena; DependencyGraph mGraph; + const Mode mMode; Vector mResourceSlots; Vector mResources; diff --git a/filament/src/fg/FrameGraphTexture.cpp b/filament/src/fg/FrameGraphTexture.cpp index 2212b78c56e..4785867a43c 100644 --- a/filament/src/fg/FrameGraphTexture.cpp +++ b/filament/src/fg/FrameGraphTexture.cpp @@ -23,7 +23,12 @@ namespace filament { void FrameGraphTexture::create(ResourceAllocatorInterface& resourceAllocator, const char* name, - FrameGraphTexture::Descriptor const& descriptor, FrameGraphTexture::Usage usage) noexcept { + FrameGraphTexture::Descriptor const& descriptor, FrameGraphTexture::Usage usage, + bool useProtectedMemory) noexcept { + if (useProtectedMemory) { + // FIXME: I think we should restrict this to attachments and blit destinations only + usage |= FrameGraphTexture::Usage::PROTECTED; + } std::array swizzle = { descriptor.swizzle.r, descriptor.swizzle.g, diff --git a/filament/src/fg/FrameGraphTexture.h b/filament/src/fg/FrameGraphTexture.h index 99a86d87e38..cb785bb7b6b 100644 --- a/filament/src/fg/FrameGraphTexture.h +++ b/filament/src/fg/FrameGraphTexture.h @@ -34,7 +34,8 @@ namespace filament { * struct SubResourceDescriptor; * a Usage bitmask * And declares and define: - * void create(ResourceAllocatorInterface&, const char* name, Descriptor const&, Usage) noexcept; + * void create(ResourceAllocatorInterface&, const char* name, Descriptor const&, Usage, + * bool useProtectedMemory) noexcept; * void destroy(ResourceAllocatorInterface&) noexcept; */ struct FrameGraphTexture { @@ -78,7 +79,7 @@ struct FrameGraphTexture { * @param descriptor Descriptor to the resource */ void create(ResourceAllocatorInterface& resourceAllocator, const char* name, - Descriptor const& descriptor, Usage usage) noexcept; + Descriptor const& descriptor, Usage usage, bool useProtectedMemory) noexcept; /** * Destroy the concrete resource diff --git a/filament/src/fg/details/Resource.h b/filament/src/fg/details/Resource.h index 6c3bffaf581..0b9a89954de 100644 --- a/filament/src/fg/details/Resource.h +++ b/filament/src/fg/details/Resource.h @@ -84,7 +84,8 @@ class VirtualResource { ResourceEdgeBase const* writer) noexcept = 0; /* Instantiate the concrete resource */ - virtual void devirtualize(ResourceAllocatorInterface& resourceAllocator) noexcept = 0; + virtual void devirtualize(ResourceAllocatorInterface& resourceAllocator, + bool useProtectedMemory) noexcept = 0; /* Destroy the concrete resource */ virtual void destroy(ResourceAllocatorInterface& resourceAllocator) noexcept = 0; @@ -229,9 +230,10 @@ class Resource : public VirtualResource { delete static_cast(edge); } - void devirtualize(ResourceAllocatorInterface& resourceAllocator) noexcept override { + void devirtualize(ResourceAllocatorInterface& resourceAllocator, + bool useProtectedMemory) noexcept override { if (!isSubResource()) { - resource.create(resourceAllocator, name, descriptor, usage); + resource.create(resourceAllocator, name, descriptor, usage, useProtectedMemory); } else { // resource is guaranteed to be initialized before we are by construction resource = static_cast(parent)->resource; @@ -268,7 +270,7 @@ class ImportedResource : public Resource { } protected: - void devirtualize(ResourceAllocatorInterface&) noexcept override { + void devirtualize(ResourceAllocatorInterface&, bool) noexcept override { // imported resources don't need to devirtualize } void destroy(ResourceAllocatorInterface&) noexcept override { From d21613a5e666e5e28280d8f19b9a191d23453083 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Mon, 4 Mar 2024 09:20:10 -0800 Subject: [PATCH 08/22] Add SwapChain::getFrameScheduledCallback (#7599) --- NEW_RELEASE_NOTES.md | 2 ++ filament/include/filament/SwapChain.h | 14 ++++++++++++++ filament/src/SwapChain.cpp | 6 +++++- filament/src/details/SwapChain.cpp | 10 +++++++++- filament/src/details/SwapChain.h | 3 +++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7e..5445b36ffd0 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,3 +7,5 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut + +- Add new API `SwapChain::getFrameScheduledCallback` diff --git a/filament/include/filament/SwapChain.h b/filament/include/filament/SwapChain.h index 404ab0c1db0..0af01afc966 100644 --- a/filament/include/filament/SwapChain.h +++ b/filament/include/filament/SwapChain.h @@ -268,6 +268,10 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { * automatically schedule itself for presentation. Instead, the application must call the * PresentCallable passed to the FrameScheduledCallback. * + * There may be only one FrameScheduledCallback set per SwapChain. A call to + * SwapChain::setFrameScheduledCallback will overwrite any previous FrameScheduledCallbacks set + * on the same SwapChain. + * * If your application delays the call to the PresentCallable by, for example, calling it on a * separate thread, you must ensure all PresentCallables have been called before shutting down * the Filament Engine. You can do this by issuing an Engine::flushAndWait before calling @@ -287,6 +291,16 @@ class UTILS_PUBLIC SwapChain : public FilamentAPI { void setFrameScheduledCallback(FrameScheduledCallback UTILS_NULLABLE callback, void* UTILS_NULLABLE user = nullptr); + /** + * Returns the SwapChain::FrameScheduledCallback that was previously set with + * SwapChain::setFrameScheduledCallback, or nullptr if one is not set. + * + * @return the previously-set FrameScheduledCallback, or nullptr + * + * @see SwapChain::setFrameCompletedCallback + */ + UTILS_NULLABLE FrameScheduledCallback getFrameScheduledCallback() const noexcept; + /** * FrameCompletedCallback is a callback function that notifies an application when a frame's * contents have completed rendering on the GPU. diff --git a/filament/src/SwapChain.cpp b/filament/src/SwapChain.cpp index 1800056887e..a242ef06ccb 100644 --- a/filament/src/SwapChain.cpp +++ b/filament/src/SwapChain.cpp @@ -29,7 +29,11 @@ void* SwapChain::getNativeWindow() const noexcept { } void SwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) { - return downcast(this)->setFrameScheduledCallback(callback, user); + downcast(this)->setFrameScheduledCallback(callback, user); +} + +SwapChain::FrameScheduledCallback SwapChain::getFrameScheduledCallback() const noexcept { + return downcast(this)->getFrameScheduledCallback(); } void SwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp index 956fa0660f6..6a7107cc8ed 100644 --- a/filament/src/details/SwapChain.cpp +++ b/filament/src/details/SwapChain.cpp @@ -27,7 +27,10 @@ namespace filament { FSwapChain::FSwapChain(FEngine& engine, void* nativeWindow, uint64_t flags) - : mEngine(engine), mNativeWindow(nativeWindow), mConfigFlags(flags) { + : mEngine(engine), + mFrameScheduledCallback(nullptr), + mNativeWindow(nativeWindow), + mConfigFlags(flags) { mSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags); } @@ -41,9 +44,14 @@ void FSwapChain::terminate(FEngine& engine) noexcept { } void FSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) { + mFrameScheduledCallback = callback; mEngine.getDriverApi().setFrameScheduledCallback(mSwapChain, callback, user); } +SwapChain::FrameScheduledCallback FSwapChain::getFrameScheduledCallback() const noexcept { + return mFrameScheduledCallback; +} + void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, utils::Invocable&& callback) noexcept { struct Callback { diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index 6bee6994463..23e987e3314 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -68,6 +68,8 @@ class FSwapChain : public SwapChain { void setFrameScheduledCallback(FrameScheduledCallback callback, void* user); + FrameScheduledCallback getFrameScheduledCallback() const noexcept; + void setFrameCompletedCallback(backend::CallbackHandler* handler, utils::Invocable&& callback) noexcept; @@ -78,6 +80,7 @@ class FSwapChain : public SwapChain { private: FEngine& mEngine; backend::Handle mSwapChain; + FrameScheduledCallback mFrameScheduledCallback; void* mNativeWindow = nullptr; uint64_t mConfigFlags = 0; }; From 9ce7f3247048a0b9e8adfb88a12abb7b066a1a0c Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 4 Mar 2024 10:13:44 -0800 Subject: [PATCH 09/22] Small improvements and cleanup to SwapChain For debugging we add a way to recreate the SwapChain with different flags. --- .../include/backend/platforms/PlatformEGL.h | 8 ++-- filament/src/details/SwapChain.cpp | 48 ++++++++++++++----- filament/src/details/SwapChain.h | 41 +++++++++++----- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/filament/backend/include/backend/platforms/PlatformEGL.h b/filament/backend/include/backend/platforms/PlatformEGL.h index c1be11e137c..2f26f7341a5 100644 --- a/filament/backend/include/backend/platforms/PlatformEGL.h +++ b/filament/backend/include/backend/platforms/PlatformEGL.h @@ -41,15 +41,11 @@ class PlatformEGL : public OpenGLPlatform { public: PlatformEGL() noexcept; - bool isExtraContextSupported() const noexcept override; - void createContext(bool shared) override; - void releaseContext() noexcept override; // Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default. virtual bool isOpenGL() const noexcept; protected: - // -------------------------------------------------------------------------------------------- // Helper for EGL configs and attributes parameters @@ -89,6 +85,10 @@ class PlatformEGL : public OpenGLPlatform { // -------------------------------------------------------------------------------------------- // OpenGLPlatform Interface + bool isExtraContextSupported() const noexcept override; + void createContext(bool shared) override; + void releaseContext() noexcept override; + void terminate() noexcept override; bool isProtectedContextSupported() const noexcept override; diff --git a/filament/src/details/SwapChain.cpp b/filament/src/details/SwapChain.cpp index 6a7107cc8ed..ef4eb4fabd4 100644 --- a/filament/src/details/SwapChain.cpp +++ b/filament/src/details/SwapChain.cpp @@ -18,6 +18,11 @@ #include "details/Engine.h" +#include + +#include + +#include #include #include @@ -27,25 +32,46 @@ namespace filament { FSwapChain::FSwapChain(FEngine& engine, void* nativeWindow, uint64_t flags) - : mEngine(engine), - mFrameScheduledCallback(nullptr), - mNativeWindow(nativeWindow), - mConfigFlags(flags) { - mSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags); + : mEngine(engine), mNativeWindow(nativeWindow), mConfigFlags(initFlags(engine, flags)) { + mHwSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags); } FSwapChain::FSwapChain(FEngine& engine, uint32_t width, uint32_t height, uint64_t flags) - : mEngine(engine), mConfigFlags(flags) { - mSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, flags); + : mEngine(engine), mWidth(width), mHeight(height), mConfigFlags(initFlags(engine, flags)) { + mHwSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, flags); +} + +void FSwapChain::recreateWithNewFlags(FEngine& engine, uint64_t flags) noexcept { + flags = initFlags(engine, flags); + if (flags != mConfigFlags) { + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.destroySwapChain(mHwSwapChain); + mConfigFlags = flags; + if (mNativeWindow) { + mHwSwapChain = driver.createSwapChain(mNativeWindow, flags); + } else { + mHwSwapChain = driver.createSwapChainHeadless(mWidth, mHeight, flags); + } + } +} + +uint64_t FSwapChain::initFlags(FEngine& engine, uint64_t flags) noexcept { + if (!isSRGBSwapChainSupported(engine)) { + flags &= ~CONFIG_SRGB_COLORSPACE; + } + if (!isProtectedContentSupported(engine)) { + flags &= ~CONFIG_PROTECTED_CONTENT; + } + return flags; } void FSwapChain::terminate(FEngine& engine) noexcept { - engine.getDriverApi().destroySwapChain(mSwapChain); + engine.getDriverApi().destroySwapChain(mHwSwapChain); } void FSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) { mFrameScheduledCallback = callback; - mEngine.getDriverApi().setFrameScheduledCallback(mSwapChain, callback, user); + mEngine.getDriverApi().setFrameScheduledCallback(mHwSwapChain, callback, user); } SwapChain::FrameScheduledCallback FSwapChain::getFrameScheduledCallback() const noexcept { @@ -66,9 +92,9 @@ void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler, if (callback) { auto* const user = new(std::nothrow) Callback{ std::move(callback), this }; mEngine.getDriverApi().setFrameCompletedCallback( - mSwapChain, handler, &Callback::func, static_cast(user)); + mHwSwapChain, handler, &Callback::func, static_cast(user)); } else { - mEngine.getDriverApi().setFrameCompletedCallback(mSwapChain, nullptr, nullptr, nullptr); + mEngine.getDriverApi().setFrameCompletedCallback(mHwSwapChain, nullptr, nullptr, nullptr); } } diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index 23e987e3314..7a97727e832 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -24,9 +24,12 @@ #include #include +#include +#include #include -#include + +#include namespace filament { @@ -39,31 +42,40 @@ class FSwapChain : public SwapChain { void terminate(FEngine& engine) noexcept; void makeCurrent(backend::DriverApi& driverApi) noexcept { - driverApi.makeCurrent(mSwapChain, mSwapChain); + driverApi.makeCurrent(mHwSwapChain, mHwSwapChain); } void commit(backend::DriverApi& driverApi) noexcept { - driverApi.commit(mSwapChain); + driverApi.commit(mHwSwapChain); } void* getNativeWindow() const noexcept { return mNativeWindow; } - constexpr bool isTransparent() const noexcept { + bool isTransparent() const noexcept { return (mConfigFlags & CONFIG_TRANSPARENT) != 0; } - constexpr bool isReadable() const noexcept { + bool isReadable() const noexcept { return (mConfigFlags & CONFIG_READABLE) != 0; } - constexpr bool hasStencilBuffer() const noexcept { + bool hasStencilBuffer() const noexcept { return (mConfigFlags & CONFIG_HAS_STENCIL_BUFFER) != 0; } + bool isProtected() const noexcept { + return (mConfigFlags & CONFIG_PROTECTED_CONTENT) != 0; + } + + // This returns the effective flags. Unsupported flags are cleared automatically. + uint64_t getFlags() const noexcept { + return mConfigFlags; + } + backend::Handle getHwHandle() const noexcept { - return mSwapChain; + return mHwSwapChain; } void setFrameScheduledCallback(FrameScheduledCallback callback, void* user); @@ -77,12 +89,19 @@ class FSwapChain : public SwapChain { static bool isProtectedContentSupported(FEngine& engine) noexcept; + // This is currently only used for debugging. This allows to recreate the HwSwapChain with + // new flags. + void recreateWithNewFlags(FEngine& engine, uint64_t flags) noexcept; + private: FEngine& mEngine; - backend::Handle mSwapChain; - FrameScheduledCallback mFrameScheduledCallback; - void* mNativeWindow = nullptr; - uint64_t mConfigFlags = 0; + backend::Handle mHwSwapChain; + FrameScheduledCallback mFrameScheduledCallback{}; + void* mNativeWindow{}; + uint32_t mWidth{}; + uint32_t mHeight{}; + uint64_t mConfigFlags{}; + static uint64_t initFlags(FEngine& engine, uint64_t flags) noexcept; }; FILAMENT_DOWNCAST(SwapChain) From 7115bd2a3465c556960991c1d5407cd087ea54b7 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 4 Mar 2024 14:20:59 -0800 Subject: [PATCH 10/22] more cleanup-up of timer queries - better naming - TimerQueryFactory doesn't depend on OpenGLDriver anymore, only a OpenGLContext. - timer query factory is now owned by and accessed through OpenGLDriver - we can't temporarily store a negative number in the query shared state, because it now indicates an error. --- filament/backend/src/opengl/OpenGLContext.cpp | 25 ++- filament/backend/src/opengl/OpenGLContext.h | 18 ++- filament/backend/src/opengl/OpenGLDriver.cpp | 18 +-- filament/backend/src/opengl/OpenGLDriver.h | 24 ++- .../backend/src/opengl/OpenGLTimerQuery.cpp | 144 ++++++++++-------- .../backend/src/opengl/OpenGLTimerQuery.h | 78 ++++++---- 6 files changed, 183 insertions(+), 124 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 7f0559486ca..c710d53bb88 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -48,7 +48,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept { #endif } -OpenGLContext::OpenGLContext() noexcept { +OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept { state.vao.p = &mDefaultVAO; @@ -231,6 +231,12 @@ OpenGLContext::OpenGLContext() noexcept { glDebugMessageCallback(cb, nullptr); } #endif + + mTimerQueryFactory = TimerQueryFactory::init(platform, *this); +} + +OpenGLContext::~OpenGLContext() noexcept { + delete mTimerQueryFactory; } void OpenGLContext::setDefaultState() noexcept { @@ -1009,7 +1015,22 @@ void OpenGLContext::resetState() noexcept { state.window.viewport.w ); glDepthRangef(state.window.depthRange.x, state.window.depthRange.y); - +} + +void OpenGLContext::createTimerQuery(GLTimerQuery* query) { + mTimerQueryFactory->createTimerQuery(query); +} + +void OpenGLContext::destroyTimerQuery(GLTimerQuery* query) { + mTimerQueryFactory->destroyTimerQuery(query); +} + +void OpenGLContext::beginTimeElapsedQuery(GLTimerQuery* query) { + mTimerQueryFactory->beginTimeElapsedQuery(query); +} + +void OpenGLContext::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) { + mTimerQueryFactory->endTimeElapsedQuery(driver, query); } } // namesapce filament diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index 415c5a014de..94b64ba2212 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -19,6 +19,8 @@ #include +#include "OpenGLTimerQuery.h" + #include #include @@ -35,7 +37,7 @@ namespace filament::backend { class OpenGLPlatform; -class OpenGLContext { +class OpenGLContext final : public TimerQueryFactoryInterface { public: static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT; static constexpr const size_t DUMMY_TEXTURE_BINDING = 7; // highest binding guaranteed to work with ES2 @@ -67,7 +69,18 @@ class OpenGLContext { static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept; - OpenGLContext() noexcept; + explicit OpenGLContext(OpenGLPlatform& platform) noexcept; + ~OpenGLContext() noexcept; + + // TimerQueryInterface ------------------------------------------------------------------------ + + // note: OpenGLContext being final ensures (clang) these are not called through the vtable + void createTimerQuery(GLTimerQuery* query) override; + void destroyTimerQuery(GLTimerQuery* query) override; + void beginTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; + + // -------------------------------------------------------------------------------------------- template inline bool isAtLeastGL() const noexcept { @@ -420,6 +433,7 @@ class OpenGLContext { private: ShaderModel mShaderModel = ShaderModel::MOBILE; FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; + TimerQueryFactoryInterface* mTimerQueryFactory = nullptr; const std::array, sizeof(bugs)> mBugDatabase{{ { bugs.disable_glFlush, diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index bd0e5ccb4e3..a177f0d05bc 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -170,7 +170,7 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept { OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept : mPlatform(*platform), - mContext(), + mContext(mPlatform), mShaderCompilerService(*this), mHandleAllocator("Handles", driverConfig.handleArenaSize), mSamplerMap(32), @@ -194,8 +194,6 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi assert_invariant(mContext.ext.EXT_disjoint_timer_query); #endif - mTimerQueryImpl = OpenGLTimerQueryFactory::init(mPlatform, *this); - mShaderCompilerService.init(); } @@ -240,8 +238,6 @@ void OpenGLDriver::terminate() { } #endif - delete mTimerQueryImpl; - mPlatform.terminate(); } @@ -1434,7 +1430,7 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, void OpenGLDriver::createTimerQueryR(Handle tqh, int) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->createTimerQuery(tq); + mContext.createTimerQuery(tq); } // ------------------------------------------------------------------------------------------------ @@ -1629,7 +1625,7 @@ void OpenGLDriver::destroyTimerQuery(Handle tqh) { if (tqh) { GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->destroyTimerQuery(tq); + mContext.destroyTimerQuery(tq); destruct(tqh, tq); } } @@ -1920,7 +1916,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() { } bool OpenGLDriver::isFrameTimeSupported() { - return OpenGLTimerQueryFactory::isGpuTimeSupported(); + return TimerQueryFactory::isGpuTimeSupported(); } bool OpenGLDriver::isAutoDepthResolveSupported() { @@ -2685,18 +2681,18 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce void OpenGLDriver::beginTimerQuery(Handle tqh) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->beginTimeElapsedQuery(tq); + mContext.beginTimeElapsedQuery(tq); } void OpenGLDriver::endTimerQuery(Handle tqh) { DEBUG_MARKER() GLTimerQuery* tq = handle_cast(tqh); - mTimerQueryImpl->endTimeElapsedQuery(tq); + mContext.endTimeElapsedQuery(*this, tq); } TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { GLTimerQuery* tq = handle_cast(tqh); - return OpenGLTimerQueryInterface::getTimerQueryValue(tq, elapsedTime); + return TimerQueryFactoryInterface::getTimerQueryValue(tq, elapsedTime); } void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority, diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index c895f16c6f4..7ef5bc4f204 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -20,6 +20,7 @@ #include "DriverBase.h" #include "GLUtils.h" #include "OpenGLContext.h" +#include "OpenGLTimerQuery.h" #include "ShaderCompilerService.h" #include "private/backend/Driver.h" @@ -38,8 +39,12 @@ #include +#include +#include #include +#include + #ifndef FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB # define FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB 4 #endif @@ -51,7 +56,7 @@ class PixelBufferDescriptor; struct TargetBufferInfo; class OpenGLProgram; -class OpenGLTimerQueryInterface; +class TimerQueryFactoryInterface; class OpenGLDriver final : public DriverBase { inline explicit OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept; @@ -161,15 +166,7 @@ class OpenGLDriver final : public DriverBase { OpenGLPlatform::ExternalTexture* externalTexture = nullptr; }; - struct GLTimerQuery : public HwTimerQuery { - struct State { - struct { - GLuint query; - } gl; - std::atomic elapsed{}; - }; - std::shared_ptr state; - }; + using GLTimerQuery = filament::backend::GLTimerQuery; struct GLStream : public HwStream { using HwStream::HwStream; @@ -223,8 +220,8 @@ class OpenGLDriver final : public DriverBase { OpenGLContext mContext; ShaderCompilerService mShaderCompilerService; - friend class OpenGLTimerQueryFactory; - friend class TimerQueryNative; + friend class TimerQueryFactory; + friend class TimerQueryNativeFactory; OpenGLContext& getContext() noexcept { return mContext; } ShaderCompilerService& getShaderCompilerService() noexcept { @@ -411,9 +408,6 @@ class OpenGLDriver final : public DriverBase { void executeEveryNowAndThenOps() noexcept; std::vector> mEveryNowAndThenOps; - // timer query implementation - OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr; - const Platform::DriverConfig mDriverConfig; Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; } diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.cpp b/filament/backend/src/opengl/OpenGLTimerQuery.cpp index 66592266d31..83a9d834c1e 100644 --- a/filament/backend/src/opengl/OpenGLTimerQuery.cpp +++ b/filament/backend/src/opengl/OpenGLTimerQuery.cpp @@ -16,60 +16,76 @@ #include "OpenGLTimerQuery.h" +#include "GLUtils.h" +#include "OpenGLDriver.h" + +#include #include +#include #include +#include #include #include +#include #include -#include + +#include +#include +#include +#include +#include + +#include namespace filament::backend { using namespace backend; using namespace GLUtils; +class OpenGLDriver; + // ------------------------------------------------------------------------------------------------ -bool OpenGLTimerQueryFactory::mGpuTimeSupported = false; +bool TimerQueryFactory::mGpuTimeSupported = false; -OpenGLTimerQueryInterface* OpenGLTimerQueryFactory::init( - OpenGLPlatform& platform, OpenGLDriver& driver) noexcept { - (void)driver; +TimerQueryFactoryInterface* TimerQueryFactory::init( + OpenGLPlatform& platform, OpenGLContext& context) noexcept { + (void)context; - OpenGLTimerQueryInterface* impl; + TimerQueryFactoryInterface* impl = nullptr; #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) - auto& context = driver.getContext(); if (context.ext.EXT_disjoint_timer_query) { // timer queries are available if (context.bugs.dont_use_timer_query && platform.canCreateFence()) { // however, they don't work well, revert to using fences if we can. - impl = new(std::nothrow) OpenGLTimerQueryFence(platform); + impl = new(std::nothrow) TimerQueryFenceFactory(platform); } else { - impl = new(std::nothrow) TimerQueryNative(driver); + impl = new(std::nothrow) TimerQueryNativeFactory(context); } mGpuTimeSupported = true; } else #endif if (platform.canCreateFence()) { // no timer queries, but we can use fences - impl = new(std::nothrow) OpenGLTimerQueryFence(platform); + impl = new(std::nothrow) TimerQueryFenceFactory(platform); mGpuTimeSupported = true; } else { // no queries, no fences -- that's a problem - impl = new(std::nothrow) TimerQueryFallback(); + impl = new(std::nothrow) TimerQueryFallbackFactory(); mGpuTimeSupported = false; } + assert_invariant(impl); return impl; } // ------------------------------------------------------------------------------------------------ -OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default; +TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default; // This is a backend synchronous call -TimerQueryResult OpenGLTimerQueryInterface::getTimerQueryValue( +TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue( GLTimerQuery* tq, uint64_t* elapsedTime) noexcept { if (UTILS_LIKELY(tq->state)) { int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed); @@ -86,42 +102,46 @@ TimerQueryResult OpenGLTimerQueryInterface::getTimerQueryValue( #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) -TimerQueryNative::TimerQueryNative(OpenGLDriver& driver) - : mDriver(driver) { +TimerQueryNativeFactory::TimerQueryNativeFactory(OpenGLContext& context) + : mContext(context) { } -TimerQueryNative::~TimerQueryNative() = default; +TimerQueryNativeFactory::~TimerQueryNativeFactory() = default; -void TimerQueryNative::createTimerQuery(GLTimerQuery* tq) { - if (UTILS_UNLIKELY(!tq->state)) { - tq->state = std::make_shared(); - } - mDriver.getContext().procs.genQueries(1u, &tq->state->gl.query); +void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) { + assert_invariant(!tq->state); + + tq->state = std::make_shared(); + mContext.procs.genQueries(1u, &tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) } -void TimerQueryNative::destroyTimerQuery(GLTimerQuery* tq) { +void TimerQueryNativeFactory::destroyTimerQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - mDriver.getContext().procs.deleteQueries(1u, &tq->state->gl.query); + + mContext.procs.deleteQueries(1u, &tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) + + tq->state.reset(); } -void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryNativeFactory::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY)); - mDriver.getContext().procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query); + + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed); + mContext.procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query); CHECK_GL_ERROR(utils::slog.e) } -void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) { assert_invariant(tq->state); - auto& context = mDriver.getContext(); - context.procs.endQuery(GL_TIME_ELAPSED); + + mContext.procs.endQuery(GL_TIME_ELAPSED); CHECK_GL_ERROR(utils::slog.e) std::weak_ptr const weak = tq->state; - mDriver.runEveryNowAndThen([&context, weak]() -> bool { + driver.runEveryNowAndThen([&context = mContext, weak]() -> bool { auto state = weak.lock(); if (state) { GLuint available = 0; @@ -146,7 +166,7 @@ void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) { // ------------------------------------------------------------------------------------------------ -OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform) +TimerQueryFenceFactory::TimerQueryFenceFactory(OpenGLPlatform& platform) : mPlatform(platform) { mQueue.reserve(2); mThread = std::thread([this]() { @@ -170,7 +190,8 @@ OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform) }); } -OpenGLTimerQueryFence::~OpenGLTimerQueryFence() { +TimerQueryFenceFactory::~TimerQueryFenceFactory() { + assert_invariant(mQueue.empty()); if (mThread.joinable()) { std::unique_lock lock(mLock); mExitRequested = true; @@ -182,27 +203,26 @@ OpenGLTimerQueryFence::~OpenGLTimerQueryFence() { } } -void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) { +void TimerQueryFenceFactory::push(TimerQueryFenceFactory::Job&& job) { std::unique_lock const lock(mLock); - mQueue.push_back(std::forward(job)); + mQueue.push_back(std::move(job)); mCondition.notify_one(); } -void OpenGLTimerQueryFence::createTimerQuery(GLTimerQuery* tq) { - if (UTILS_UNLIKELY(!tq->state)) { - tq->state = std::make_shared(); - } +void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) { + assert_invariant(!tq->state); + tq->state = std::make_shared(); } -void OpenGLTimerQueryFence::destroyTimerQuery(GLTimerQuery* tq) { +void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) { assert_invariant(tq->state); + tq->state.reset(); } -void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryFenceFactory::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); - tq->state->elapsed.store(0); + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed); - Platform::Fence* fence = mPlatform.createFence(); std::weak_ptr const weak = tq->state; // FIXME: this implementation of beginTimeElapsedQuery is usually wrong; it ends up @@ -211,12 +231,11 @@ void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) { // on a dummy target for instance, or somehow latch the begin time at the next renderpass // start. - push([&platform = mPlatform, fence, weak]() { + push([&platform = mPlatform, fence = mPlatform.createFence(), weak]() { auto state = weak.lock(); if (state) { platform.waitFence(fence, FENCE_WAIT_FOR_EVER); - int64_t const then = clock::now().time_since_epoch().count(); - state->elapsed.store(-then, std::memory_order_relaxed); + state->then = clock::now().time_since_epoch().count(); SYSTRACE_CONTEXT(); SYSTRACE_ASYNC_BEGIN("OpenGLTimerQueryFence", intptr_t(state.get())); } @@ -224,19 +243,16 @@ void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) { }); } -void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) { +void TimerQueryFenceFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq) { assert_invariant(tq->state); - Platform::Fence* fence = mPlatform.createFence(); std::weak_ptr const weak = tq->state; - push([&platform = mPlatform, fence, weak]() { + push([&platform = mPlatform, fence = mPlatform.createFence(), weak]() { auto state = weak.lock(); if (state) { platform.waitFence(fence, FENCE_WAIT_FOR_EVER); int64_t const now = clock::now().time_since_epoch().count(); - int64_t const then = state->elapsed.load(std::memory_order_relaxed); - assert_invariant(then < 0); - state->elapsed.store(now + then, std::memory_order_relaxed); + state->elapsed.store(now - state->then, std::memory_order_relaxed); SYSTRACE_CONTEXT(); SYSTRACE_ASYNC_END("OpenGLTimerQueryFence", intptr_t(state.get())); } @@ -246,34 +262,32 @@ void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) { // ------------------------------------------------------------------------------------------------ -TimerQueryFallback::TimerQueryFallback() = default; +TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default; -TimerQueryFallback::~TimerQueryFallback() = default; +TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default; -void TimerQueryFallback::createTimerQuery(GLTimerQuery* tq) { - if (UTILS_UNLIKELY(!tq->state)) { - tq->state = std::make_shared(); - } +void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) { + assert_invariant(!tq->state); + tq->state = std::make_shared(); } -void TimerQueryFallback::destroyTimerQuery(GLTimerQuery* tq) { +void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) { assert_invariant(tq->state); + tq->state.reset(); } -void TimerQueryFallback::beginTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) { +void TimerQueryFallbackFactory::beginTimeElapsedQuery(GLTimerQuery* tq) { assert_invariant(tq->state); // this implementation measures the CPU time, but we have no h/w support - int64_t const then = clock::now().time_since_epoch().count(); - tq->state->elapsed.store(-then, std::memory_order_relaxed); + tq->state->then = clock::now().time_since_epoch().count(); + tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed); } -void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) { +void TimerQueryFallbackFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq) { assert_invariant(tq->state); // this implementation measures the CPU time, but we have no h/w support int64_t const now = clock::now().time_since_epoch().count(); - int64_t const then = tq->state->elapsed.load(std::memory_order_relaxed); - assert_invariant(then < 0); - tq->state->elapsed.store(now + then, std::memory_order_relaxed); + tq->state->elapsed.store(now - tq->state->then, std::memory_order_relaxed); } } // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.h b/filament/backend/src/opengl/OpenGLTimerQuery.h index 55bb1960afa..5941c87d2b6 100644 --- a/filament/backend/src/opengl/OpenGLTimerQuery.h +++ b/filament/backend/src/opengl/OpenGLTimerQuery.h @@ -17,18 +17,41 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGL_TIMERQUERY_H #define TNT_FILAMENT_BACKEND_OPENGL_TIMERQUERY_H -#include "OpenGLDriver.h" +#include + +#include "DriverBase.h" #include #include +#include "gl_headers.h" + +#include +#include +#include +#include #include #include +#include + namespace filament::backend { class OpenGLPlatform; -class OpenGLTimerQueryInterface; +class OpenGLContext; +class OpenGLDriver; +class TimerQueryFactoryInterface; + +struct GLTimerQuery : public HwTimerQuery { + struct State { + struct { + GLuint query; + } gl; + int64_t then{}; + std::atomic elapsed{}; + }; + std::shared_ptr state; +}; /* * We need two implementation of timer queries (only elapsed time), because @@ -38,83 +61,80 @@ class OpenGLTimerQueryInterface; * These classes implement the various strategies... */ - -class OpenGLTimerQueryFactory { +class TimerQueryFactory { static bool mGpuTimeSupported; public: - static OpenGLTimerQueryInterface* init( - OpenGLPlatform& platform, OpenGLDriver& driver) noexcept; + static TimerQueryFactoryInterface* init( + OpenGLPlatform& platform, OpenGLContext& context) noexcept; static bool isGpuTimeSupported() noexcept { return mGpuTimeSupported; } }; -class OpenGLTimerQueryInterface { +class TimerQueryFactoryInterface { protected: - using GLTimerQuery = OpenGLDriver::GLTimerQuery; + using GLTimerQuery = filament::backend::GLTimerQuery; using clock = std::chrono::steady_clock; public: - virtual ~OpenGLTimerQueryInterface(); + virtual ~TimerQueryFactoryInterface(); virtual void createTimerQuery(GLTimerQuery* query) = 0; virtual void destroyTimerQuery(GLTimerQuery* query) = 0; virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0; - virtual void endTimeElapsedQuery(GLTimerQuery* query) = 0; + virtual void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) = 0; static TimerQueryResult getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept; }; #if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query) -class TimerQueryNative : public OpenGLTimerQueryInterface { +class TimerQueryNativeFactory final : public TimerQueryFactoryInterface { public: - explicit TimerQueryNative(OpenGLDriver& driver); - ~TimerQueryNative() override; + explicit TimerQueryNativeFactory(OpenGLContext& context); + ~TimerQueryNativeFactory() override; private: void createTimerQuery(GLTimerQuery* query) override; void destroyTimerQuery(GLTimerQuery* query) override; void beginTimeElapsedQuery(GLTimerQuery* query) override; - void endTimeElapsedQuery(GLTimerQuery* query) override; - OpenGLDriver& mDriver; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; + OpenGLContext& mContext; }; #endif -class OpenGLTimerQueryFence : public OpenGLTimerQueryInterface { +class TimerQueryFenceFactory final : public TimerQueryFactoryInterface { public: - explicit OpenGLTimerQueryFence(OpenGLPlatform& platform); - ~OpenGLTimerQueryFence() override; + explicit TimerQueryFenceFactory(OpenGLPlatform& platform); + ~TimerQueryFenceFactory() override; private: using Job = std::function; + using Container = std::vector; + void createTimerQuery(GLTimerQuery* query) override; void destroyTimerQuery(GLTimerQuery* query) override; void beginTimeElapsedQuery(GLTimerQuery* tq) override; - void endTimeElapsedQuery(GLTimerQuery* tq) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) override; - void enqueue(Job&& job); - template - void push(CALLABLE&& func, ARGS&& ... args) { - enqueue(Job(std::bind(std::forward(func), std::forward(args)...))); - } + void push(Job&& job); OpenGLPlatform& mPlatform; std::thread mThread; mutable utils::Mutex mLock; mutable utils::Condition mCondition; - std::vector mQueue; + Container mQueue; bool mExitRequested = false; }; -class TimerQueryFallback : public OpenGLTimerQueryInterface { +class TimerQueryFallbackFactory final : public TimerQueryFactoryInterface { public: - explicit TimerQueryFallback(); - ~TimerQueryFallback() override; + explicit TimerQueryFallbackFactory(); + ~TimerQueryFallbackFactory() override; private: void createTimerQuery(GLTimerQuery* query) override; void destroyTimerQuery(GLTimerQuery* query) override; void beginTimeElapsedQuery(GLTimerQuery* query) override; - void endTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override; }; } // namespace filament::backend From b9a069be05dd0ca41fda8a7e7ac9fe44ba734f81 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 5 Mar 2024 11:05:57 -0800 Subject: [PATCH 11/22] vk: Initial draft for descriptor set refactoring (#7620) - Add cache for ds layouts and ds - Abstract descriptors API into VulkanDescriptorSetManager - Note that this is just a draft and not hooked into the current implementation. --- filament/backend/CMakeLists.txt | 2 + filament/backend/src/vulkan/VulkanDriver.cpp | 2 +- .../src/vulkan/VulkanPipelineCache.cpp | 8 +- .../backend/src/vulkan/VulkanPipelineCache.h | 2 - .../backend/src/vulkan/VulkanResources.cpp | 6 +- filament/backend/src/vulkan/VulkanResources.h | 60 +- filament/backend/src/vulkan/VulkanUtility.h | 94 ++- .../vulkan/caching/VulkanDescriptorSet.cpp | 647 ++++++++++++++++++ .../src/vulkan/caching/VulkanDescriptorSet.h | 111 +++ 9 files changed, 866 insertions(+), 66 deletions(-) create mode 100644 filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp create mode 100644 filament/backend/src/vulkan/caching/VulkanDescriptorSet.h diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 4edf04af14e..fc985aa8191 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -166,6 +166,8 @@ endif() if (FILAMENT_SUPPORTS_VULKAN) list(APPEND SRCS include/backend/platforms/VulkanPlatform.h + src/vulkan/caching/VulkanDescriptorSet.cpp + src/vulkan/caching/VulkanDescriptorSet.h src/vulkan/platform/VulkanPlatform.cpp src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp src/vulkan/platform/VulkanPlatformSwapChainImpl.h diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 4b479eb36db..876007a627c 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -1757,7 +1757,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr}; auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex(); - VulkanPipelineCache::UsageFlags usage = program->getUsage(); + VulkanPipelineCache::UsageFlags usage = program->getUsage(); UTILS_NOUNROLL for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) { diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index 659de0092c5..8c19a12ad1a 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -581,12 +581,8 @@ void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept { void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { - bindUniformBuffer(bindingIndex, bufferObject->buffer.getGpuBuffer(), offset, size); - mPipelineBoundResources.acquire(bufferObject); -} + VkBuffer buffer = bufferObject->buffer.getGpuBuffer(); -void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer, - VkDeviceSize offset, VkDeviceSize size) noexcept { ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT, "Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex, UBUFFER_BINDING_COUNT); @@ -602,6 +598,8 @@ void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer buff key.uniformBufferOffsets[bindingIndex] = offset; key.uniformBufferSizes[bindingIndex] = size; + + mPipelineBoundResources.acquire(bufferObject); } void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT], diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index 26f17b07510..de271c4b7af 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -153,8 +153,6 @@ class VulkanPipelineCache : public CommandBufferObserver { void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept; void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject, VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept; - void bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer, - VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept; void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT], VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept; void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc, diff --git a/filament/backend/src/vulkan/VulkanResources.cpp b/filament/backend/src/vulkan/VulkanResources.cpp index 4cd88f2bb01..bbc84722397 100644 --- a/filament/backend/src/vulkan/VulkanResources.cpp +++ b/filament/backend/src/vulkan/VulkanResources.cpp @@ -14,10 +14,10 @@ * limitations under the License. */ - #include "VulkanResources.h" #include "VulkanHandles.h" #include "VulkanResourceAllocator.h" +#include "caching/VulkanDescriptorSet.h" namespace filament::backend { @@ -62,6 +62,10 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t case VulkanResourceType::RENDER_PRIMITIVE: allocator->destruct(Handle(id)); break; + case VulkanResourceType::DESCRIPTOR_SET: + allocator->destruct(Handle(id)); + break; + // If the resource is heap allocated, then the resource manager just skip refcounted // destruction. case VulkanResourceType::FENCE: diff --git a/filament/backend/src/vulkan/VulkanResources.h b/filament/backend/src/vulkan/VulkanResources.h index c5b7440ffce..8364c232b01 100644 --- a/filament/backend/src/vulkan/VulkanResources.h +++ b/filament/backend/src/vulkan/VulkanResources.h @@ -17,6 +17,8 @@ #ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCES_H #define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H +#include "VulkanUtility.h" + #include #include @@ -44,6 +46,7 @@ enum class VulkanResourceType : uint8_t { TIMER_QUERY, VERTEX_BUFFER, VERTEX_BUFFER_INFO, + DESCRIPTOR_SET, // Below are resources that are managed manually (i.e. not ref counted). FENCE, @@ -162,62 +165,7 @@ namespace { // When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive), // we just use a std::array to back the set. template -class FixedCapacityResourceSet { -private: - using FixedSizeArray = std::array; - -public: - using const_iterator = typename FixedSizeArray::const_iterator; - - inline ~FixedCapacityResourceSet() { - clear(); - } - - inline const_iterator begin() { - if (mInd == 0) { - return mArray.cend(); - } - return mArray.cbegin(); - } - - inline const_iterator end() { - if (mInd == 0) { - return mArray.cend(); - } - if (mInd < SIZE) { - return mArray.begin() + mInd; - } - return mArray.cend(); - } - - inline const_iterator find(VulkanResource* resource) { - return std::find(begin(), end(), resource); - } - - inline void insert(VulkanResource* resource) { - assert_invariant(mInd < SIZE); - mArray[mInd++] = resource; - } - - inline void erase(VulkanResource* resource) { - assert_invariant(false && "FixedCapacityResourceSet::erase should not be called"); - } - - inline void clear() { - if (mInd == 0) { - return; - } - mInd = 0; - } - - inline size_t size() { - return mInd; - } - -private: - FixedSizeArray mArray{nullptr}; - size_t mInd = 0; -}; +using FixedCapacityResourceSet = CappedArray; // robin_set/map are useful for sets that are acquire only and the set will be iterated when the set // is cleared. diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index 3ba35c75e69..1b2ddc2e265 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -23,6 +23,8 @@ #include +#include + namespace filament::backend { VkFormat getVkFormat(ElementType type, bool normalized, bool integer); @@ -88,10 +90,100 @@ utils::FixedCapacityVector enumerate( #undef EXPAND_ENUM_NO_ARGS #undef EXPAND_ENUM_ARGS - // Useful shorthands using VkFormatList = utils::FixedCapacityVector; +// An Array that will be fixed 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; + + CappedArray() = default; + + // Delete copy constructor/assignment. + CappedArray(CappedArray const& rhs) = delete; + CappedArray& operator=(CappedArray& rhs) = delete; + + CappedArray(CappedArray&& rhs) noexcept { + this->swap(rhs); + } + + CappedArray& operator=(CappedArray&& rhs) noexcept { + this->swap(rhs); + return *this; + } + + inline ~CappedArray() { + clear(); + } + + inline const_iterator begin() { + if (mInd == 0) { + return mArray.cend(); + } + return mArray.cbegin(); + } + + inline const_iterator end() { + if (mInd > 0 && mInd < CAPACITY) { + return mArray.begin() + mInd; + } + return mArray.cend(); + } + + inline T back() { + assert_invariant(mInd > 0); + return *(mArray.begin() + mInd); + } + + inline void pop_back() { + assert_invariant(mInd > 0); + mInd--; + } + + inline const_iterator find(T item) { + return std::find(begin(), end(), item); + } + + inline void insert(T item) { + assert_invariant(mInd < CAPACITY); + mArray[mInd++] = item; + } + + inline void erase(T item) { + PANIC_PRECONDITION("CappedArray::erase should not be called"); + } + + inline void clear() { + if (mInd == 0) { + return; + } + mInd = 0; + } + + inline T& operator[](uint16_t ind) { + return mArray[ind]; + } + + inline size_t size() { + return mInd; + } + +private: + void swap(CappedArray& rhs) { + std::swap(mArray, rhs.mArray); + std::swap(mInd, rhs.mInd); + } + + FixedSizeArray mArray; + size_t mInd = 0; +}; + } // namespace filament::backend #endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp new file mode 100644 index 00000000000..78ed4ae46e8 --- /dev/null +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.cpp @@ -0,0 +1,647 @@ +/* + * 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 "VulkanDescriptorSet.h" + +#include +#include + +#include + +namespace filament::backend { + +namespace { + +template +using LayoutMap = tsl::robin_map; +using LayoutArray = VulkanDescriptorSetManager::LayoutArray; +using UniformBufferBitmask = VulkanDescriptorSetManager::UniformBufferBitmask; +using SamplerBitmask = VulkanDescriptorSetManager::SamplerBitmask; + +constexpr uint8_t EXPECTED_IN_FLIGHT_FRAMES = 3; // Asssume triple buffering + +#define PORT_CONSTANT(NameSpace, K) constexpr decltype(NameSpace::K) K = NameSpace::K; + +PORT_CONSTANT(VulkanDescriptorSetManager, MAX_SUPPORTED_SHADER_STAGE) +PORT_CONSTANT(VulkanDescriptorSetManager, VERTEX_STAGE) +PORT_CONSTANT(VulkanDescriptorSetManager, FRAGMENT_STAGE) + +PORT_CONSTANT(Program, UNIFORM_BINDING_COUNT) +PORT_CONSTANT(Program, SAMPLER_BINDING_COUNT) + +#undef PORT_CONSTANT + +// 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]++; + } + } + } + } + uint8_t data[256]; +} BitCountImpl; + +inline uint8_t countBits(uint32_t num) { + static constexpr uint8_t const* BIT_COUNT = BitCountImpl.data; + return BIT_COUNT[num & 0xFF] + BIT_COUNT[(num >> 8) & 0xFF] + BIT_COUNT[(num >> 16) & 0xFF] + + BIT_COUNT[(num >> 24) & 0xFF]; +} + +struct SamplerSet { + using Mask = UniformBufferBitmask; + struct Key { + Mask mask; + static_assert(sizeof(Mask) == 4); + uint32_t padding;// We need a padding to ensure 8-byte alignement. + VkDescriptorSetLayout layout; + VkSampler sampler[SAMPLER_BINDING_COUNT]; + VkImageView imageView[SAMPLER_BINDING_COUNT]; + VkImageLayout imageLayout[SAMPLER_BINDING_COUNT]; + }; + + struct Equal { + bool operator()(Key const& k1, Key const& k2) const { + if (k1.mask != k2.mask) return false; + if (k1.layout != k2.layout) return false; + + for (uint8_t i = 0, bitCount = countBits(k1.mask); i < bitCount; ++i) { + if (k1.sampler[i] != k2.sampler[i] || k1.imageView[i] != k2.imageView[i] || + k1.imageLayout[i] != k2.imageLayout[i]) { + return false; + } + } + return true; + } + }; + using HashFn = utils::hash::MurmurHashFn; + using Cache = std::unordered_map; +}; + +struct UBOSet { + using Mask = SamplerBitmask; + + struct Key { + Mask mask; + static_assert(sizeof(Mask) == 4); + uint32_t padding;// We need a padding to ensure 8-byte alignement. + VkDescriptorSetLayout layout; + VkBuffer buffers[UNIFORM_BINDING_COUNT]; // 80 0 + VkDeviceSize offsets[UNIFORM_BINDING_COUNT];// 40 1592 + VkDeviceSize sizes[UNIFORM_BINDING_COUNT]; // 40 1632 + }; + + struct Equal { + bool operator()(Key const& k1, Key const& k2) const { + if (k1.mask != k2.mask) return false; + if (k1.layout != k2.layout) return false; + + for (uint8_t i = 0, bitCount = countBits(k1.mask); i < bitCount; ++i) { + if (k1.buffers[i] != k2.buffers[i] || k1.offsets[i] != k2.offsets[i] || + k1.sizes[i] != k2.sizes[i]) { + return false; + } + } + return true; + } + }; + using HashFn = utils::hash::MurmurHashFn; + using Cache = std::unordered_map; +}; + +constexpr uint8_t MAX_BINDINGS = UNIFORM_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE; +static_assert(MAX_BINDINGS >= SAMPLER_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE); + +template +class DescriptorPool { +private: + static constexpr uint32_t COUNT_MULTIPLIER = + MAX_SUPPORTED_SHADER_STAGE * EXPECTED_IN_FLIGHT_FRAMES; + static constexpr uint32_t CAPACITY = + std::is_same_v ? Program::UNIFORM_BINDING_COUNT * COUNT_MULTIPLIER + : Program::SAMPLER_BINDING_COUNT * COUNT_MULTIPLIER; + +public: + DescriptorPool() + : mDevice(VK_NULL_HANDLE), + mResourceAllocator(nullptr) {} + + DescriptorPool(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mResourceAllocator(allocator) { + VkDescriptorPoolSize sizes[1] = { + { + .type = std::is_same_v ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = CAPACITY, + }, + }; + VkDescriptorPoolCreateInfo info{ + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = EXPECTED_IN_FLIGHT_FRAMES, + .poolSizeCount = 1, + .pPoolSizes = sizes, + }; + vkCreateDescriptorPool(mDevice, &info, VKALLOC, &mPool); + } + + DescriptorPool(DescriptorPool const&) = delete; + DescriptorPool& operator=(DescriptorPool const&) = delete; + + ~DescriptorPool() { + vkDestroyDescriptorPool(mDevice, mPool, VKALLOC); + } + + VulkanDescriptorSet* obtainSet(VkDescriptorSetLayout layout) { + if (mCount == CAPACITY) { + return nullptr; + } + + if (auto unused = mUnused.find(layout); + unused != mUnused.end() && !unused->second.empty()) { + auto& unusedList = unused->second; + auto set = unusedList.back(); + unusedList.pop_back(); + mCount++; + return set; + } + + // Creating a new set + VkDescriptorSetLayout layouts[1] = {layout}; + VkDescriptorSetAllocateInfo allocInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = mPool, + .descriptorSetCount = 1, + .pSetLayouts = layouts, + }; + VkDescriptorSet vkSet; + UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet); + ASSERT_POSTCONDITION(result == VK_SUCCESS, "Cannot allocate descriptor set. error=%d", + (int) result); + + mCount++; + return createSet(layout, vkSet); + } + +private: + inline VulkanDescriptorSet* createSet(VkDescriptorSetLayout layout, VkDescriptorSet vkSet) { + return VulkanDescriptorSet::create(mResourceAllocator, vkSet, layout, + [this](VulkanDescriptorSet* set) { + auto const layout = set->layout; + auto const vkSet = set->vkSet; + auto listIter = mUnused.find(layout); + assert_invariant(listIter != mUnused.end()); + auto& unusedList = listIter->second; + + // At this point, we know that the pointer will be stale, and we need to remove + // any non-ref-counted references to it. + // TODO: this will be inefficient + auto iter = std::find(unusedList.begin(), unusedList.end(), set); + unusedList.erase(iter); + + // We are recycling - release the vk resource 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. + unusedList.push_back(createSet(layout, vkSet)); + mCount--; + }); + } + + VkDevice mDevice; + VulkanResourceAllocator* mResourceAllocator; + VkDescriptorPool mPool; + + uint32_t mCount = 0; + std::unordered_map> mUnused; +}; + +template +class DescriptorInfinitePool { +public: + DescriptorInfinitePool(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mResourceAllocator(allocator) {} + + ~DescriptorInfinitePool() { + for (auto pool: mPools) { + delete pool; + } + } + + VulkanDescriptorSet* obtainSet(VkDescriptorSetLayout layout) { + for (auto pool: mPools) { + auto set = pool->obtainSet(layout); + if (set) { + return set; + } + } + // We need to increase the number of pools + mPools.push_back(new DescriptorPool{mDevice, mResourceAllocator}); + auto pool = mPools.back(); + return pool->obtainSet(layout); + } + +private: + VkDevice mDevice; + VulkanResourceAllocator* mResourceAllocator; + std::vector*> mPools; +}; + +// This holds a cache of descriptor sets of a TYPE (UBO or Sampler). The Key is defined as the +// layout and the content (for example specific samplers). +template +class DescriptorSetCache { +public: + DescriptorSetCache(VkDevice device, VulkanResourceAllocator* allocator) + : mPool(device, allocator), + mResourceManager(allocator) {} + + std::pair obtainSet(Key const& key) { + mAge++; + if (auto result = mCache.find(key); result != mCache.end()) { + auto set = result->second; + auto const oldAge = mReverseHistory[set]; + mHistory.erase(oldAge); + mHistory[mAge] = set; + mReverseHistory[set] = mAge; + return {set, true}; + } + + VulkanDescriptorSet* set = mPool.obtainSet(key.layout); + mCache[key] = set; + mReverseCache[set] = key; + mHistory[mAge] = set; + mReverseHistory[set] = mAge; + mResourceManager.acquire(set); + return {set, false}; + } + + // We need to periodically purge the descriptor sets so that we're not holding on to resources + // unnecessarily. The strategy for purging needs to be examined more. + void gc() noexcept { + constexpr uint32_t ALLOWED_ENTRIES = EXPECTED_IN_FLIGHT_FRAMES * 3; + int32_t const toCutCount = mHistory.size() - ALLOWED_ENTRIES; + + if (toCutCount <= 0) { + return; + } + + std::vector remove; + for (auto entry = mHistory.begin(); entry != mHistory.end(); entry++) { + auto const& set = entry->second; + Key const& key = mReverseCache[set]; + mCache.erase(key); + mReverseCache.erase(set); + mReverseHistory.erase(set); + + remove.push_back(entry->first); + mResourceManager.release(set); + } + for (auto removeAge: remove) { + mHistory.erase(removeAge); + } + } + +private: + DescriptorInfinitePool mPool; + + // TODO: combine some of these data structures + Cache mCache; + std::unordered_map mReverseCache; + + // Use the ordering for purging if needed; + std::map mHistory; + std::unordered_map mReverseHistory; + uint64_t mAge; + + // Note that instead of owning the resources (i.e. descriptor set) in the pools, we keep them + // here since all the allocated sets have to pass through this class, and this class has better + // knowledge to make decisions about gc. + VulkanResourceManager mResourceManager; +}; + +using UBOSetCache = DescriptorSetCache; +using SamplerSetCache = DescriptorSetCache; + +template +class LayoutCache { +public: + explicit LayoutCache(VkDevice device) + : mDevice(device) {} + + ~LayoutCache() { + for (auto [mask, layout]: mLayouts) { + vkDestroyDescriptorSetLayout(mDevice, layout, VKALLOC); + } + mLayouts.clear(); + } + + VkDescriptorSetLayout getLayout(MaskType stageMask) noexcept { + if (stageMask == 0) { + return VK_NULL_HANDLE; + } + if (auto iter = mLayouts.find(stageMask); iter != mLayouts.end()) { + return iter->second; + } + VkDescriptorType descriptorType; + if constexpr (std::is_same_v) { + descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + } else if constexpr (std::is_same_v) { + descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + } + + VkDescriptorSetLayoutBinding toBind[MAX_BINDINGS]; + uint32_t count = 0; + for (uint8_t i = 0, maxBindings = sizeof(stageMask) * 4; i < maxBindings; i++) { + VkShaderStageFlags stages = 0; + if ((stageMask & (VERTEX_STAGE << i)) != 0) { + stages |= VK_SHADER_STAGE_VERTEX_BIT; + } + if ((stageMask & (FRAGMENT_STAGE << i)) == 0) { + stages |= VK_SHADER_STAGE_FRAGMENT_BIT; + } + if (stages == 0) { + continue; + } + + auto& bindInfo = toBind[count++]; + bindInfo = { + .binding = i, + .descriptorType = descriptorType, + .descriptorCount = 1, + .stageFlags = stages, + }; + } + VkDescriptorSetLayoutCreateInfo dlinfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = count, + .pBindings = toBind, + }; + + VkDescriptorSetLayout handle; + vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &handle); + mLayouts[stageMask] = handle; + + return handle; + } + +private: + VkDevice mDevice; + LayoutMap mLayouts; +}; + +using UBOSetLayoutCache = LayoutCache; +using SamplerSetLayoutCache = LayoutCache; + +} // anonymous namespace + +VulkanDescriptorSet::VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, + VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn) + : VulkanResource(VulkanResourceType::DESCRIPTOR_SET), + resources(allocator), + vkSet(rawSet), + layout(layout), + mOnRecycleFn(std::move(onRecycleFn)) {} + +VulkanDescriptorSet::~VulkanDescriptorSet() { + if (mOnRecycleFn) { + mOnRecycleFn(this); + } +} + +VulkanDescriptorSet* VulkanDescriptorSet::create(VulkanResourceAllocator* allocator, + VkDescriptorSet rawSet, VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn) { + auto handle = allocator->allocHandle(); + auto set = allocator->construct(handle, allocator, rawSet, layout, + std::move(onRecycleFn)); + return set; +} + +class VulkanDescriptorSetManager::Impl { +public: + Impl(VkDevice device, VulkanResourceAllocator* allocator) + : mDevice(device), + mUBOLayoutCache(device), + mUBOCache(device, allocator), + mSamplerLayoutCache(device), + mSamplerCache(device, allocator), + mResources(allocator) {} + + void setUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject, + VkDeviceSize offset, VkDeviceSize size) noexcept { + mUbos.push_back({bindingIndex, bufferObject, offset, size}); + + // Between "set" and "commit", we need to ref the buffer object to avoid it being collected. + mResources.acquire(bufferObject); + } + + void setSamplers(SamplerArray&& samplers) { + mSamplers = std::move(samplers); + for (auto const& sampler: mSamplers) { + mResources.acquire(sampler.texture); + } + } + + void gc() noexcept { + mUBOCache.gc(); + mSamplerCache.gc(); + } + + // This will write/update all of the descriptor set (and create a set if a one of the same + // layout is not available). + void bind(VulkanCommandBuffer* commands, GetPipelineLayoutFunction& getPipelineLayoutFn) { + LayoutArray layouts = {VK_NULL_HANDLE, VK_NULL_HANDLE}; + std::array descSets = {VK_NULL_HANDLE, VK_NULL_HANDLE}; + uint8_t descSetCount = 0; + + VkWriteDescriptorSet descriptorWrites[UNIFORM_BINDING_COUNT + SAMPLER_BINDING_COUNT]; + VkDescriptorBufferInfo uboWrite[UNIFORM_BINDING_COUNT]; + VkDescriptorImageInfo samplerWrite[SAMPLER_BINDING_COUNT]; + uint32_t nwrites = 0; + + UBOSet::Key uboKey = {}; + auto& uboMask = uboKey.mask; + uint8_t uboCount = 0; + for (auto const& ubo: mUbos) { + auto const& binding = std::get<0>(ubo); + + uboKey.buffers[uboCount] = std::get<1>(ubo)->buffer.getGpuBuffer(); + uboKey.offsets[uboCount] = std::get<2>(ubo); + uboKey.sizes[uboCount] = std::get<3>(ubo); + uboCount++; + + // Currently we let ubo be visible in all stages. + uboMask |= (VERTEX_STAGE << binding); + uboMask |= (FRAGMENT_STAGE << binding); + } + + if (uboMask) { + layouts[UBO_SET_INDEX] = uboKey.layout = mUBOLayoutCache.getLayout(uboMask); + + auto [descriptorSet, cached] = mUBOCache.obtainSet(uboKey); + descSets[descSetCount++] = descriptorSet->vkSet; + + // We need to write to the descriptor set since it wasn't cached. + if (!cached) { + // If it wasn't cached before, we need to ref the resources that it touches. + for (auto const& ubo: mUbos) { + auto const buffer = std::get<1>(ubo); + descriptorSet->resources.acquire(buffer); + } + + for (uint8_t i = 0; i < uboCount; ++i) { + auto const& binding = std::get<0>(mUbos[i]); + VkWriteDescriptorSet& descriptorWrite = descriptorWrites[nwrites++]; + auto& writeInfo = uboWrite[i]; + writeInfo = { + .buffer = uboKey.buffers[i], + .offset = uboKey.offsets[i], + .range = uboKey.sizes[i], + }; + descriptorWrite = { + .dstSet = descriptorSet->vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pBufferInfo = &writeInfo, + }; + } + } + commands->acquire(descriptorSet); + } + + SamplerSet::Key samplerKey = {}; + auto& samplerMask = samplerKey.mask; + uint8_t samplerCount = 0; + for (auto const sampler: mSamplers) { + samplerMask |= (sampler.stage << sampler.binding); + + samplerKey.sampler[samplerCount] = sampler.info.sampler; + samplerKey.imageView[samplerCount] = sampler.info.imageView; + samplerKey.imageLayout[samplerCount] = sampler.info.imageLayout; + samplerCount++; + } + + if (samplerMask) { + layouts[SAMPLER_SET_INDEX] = samplerKey.layout = + mSamplerLayoutCache.getLayout(samplerMask); + + auto [descriptorSet, cached] = mSamplerCache.obtainSet(samplerKey); + descSets[descSetCount++] = descriptorSet->vkSet; + + if (!cached) { + for (auto const& sampler: mSamplers) { + descriptorSet->resources.acquire(sampler.texture); + } + + for (uint8_t i = 0; i < samplerCount; ++i) { + auto const& binding = mSamplers[i].binding; + VkWriteDescriptorSet& descriptorWrite = descriptorWrites[nwrites++]; + auto& writeInfo = samplerWrite[i]; + writeInfo = mSamplers[i].info; + descriptorWrite = { + .dstSet = descriptorSet->vkSet, + .dstBinding = binding, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &writeInfo, + }; + } + } + commands->acquire(descriptorSet); + } + if (nwrites) { + vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr); + } + + VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(layouts); + VkCommandBuffer const cmdbuffer = commands->buffer(); + + BoundState state { + .cmdbuf = cmdbuffer, + .pipelineLayout = pipelineLayout, + }; + + if (state == mPreviousBoundState) { + return; + } + + vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, + descSetCount, descSets.data(), 0, nullptr); + + mPreviousBoundState = state; + + // Once bound, the resources are now ref'd in the descriptor set and the references in this + // class can be released and the descriptor set is ref'd by the command buffer. + mResources.clear(); + } + +private: + struct BoundState { + VkCommandBuffer cmdbuf; + VkPipelineLayout pipelineLayout; + + bool operator==(BoundState const& b) const { + return cmdbuf == b.cmdbuf && pipelineLayout == b.pipelineLayout; + } + }; + + VkDevice mDevice; + + UBOSetLayoutCache mUBOLayoutCache; + UBOSetCache mUBOCache; + + SamplerSetLayoutCache mSamplerLayoutCache; + SamplerSetCache mSamplerCache; + + std::vector> mUbos; + SamplerArray mSamplers; + VulkanAcquireOnlyResourceManager mResources; + + BoundState mPreviousBoundState; +}; + +VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device, + VulkanResourceAllocator* resourceAllocator) + : mImpl(new Impl(device, resourceAllocator)) {} + +void VulkanDescriptorSetManager::terminate() noexcept { + assert_invariant(mImpl); + delete mImpl; + mImpl = nullptr; +} + +void VulkanDescriptorSetManager::gc() noexcept { mImpl->gc(); } + +void VulkanDescriptorSetManager::bind(VulkanCommandBuffer* commands, + GetPipelineLayoutFunction& getPipelineLayoutFn) { + mImpl->bind(commands, getPipelineLayoutFn); +} + +void VulkanDescriptorSetManager::setUniformBufferObject(uint32_t bindingIndex, + VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept { + mImpl->setUniformBufferObject(bindingIndex, bufferObject, offset, size); +} + +void VulkanDescriptorSetManager::setSamplers(SamplerArray&& samplers) { + mImpl->setSamplers(std::move(samplers)); +} + +} // namespace filament::backend diff --git a/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h new file mode 100644 index 00000000000..f19812ad7b7 --- /dev/null +++ b/filament/backend/src/vulkan/caching/VulkanDescriptorSet.h @@ -0,0 +1,111 @@ +/* + * 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_CACHING_VULKANDESCRIPTORSET_H +#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +namespace filament::backend { + +// We need to make this class public to enable allocation on the HandleAllocator. +struct VulkanDescriptorSet : public VulkanResourceBase { +public: + // Because we need to recycle descriptor set not used, we allow for a callback that the "Pool" + // can use to repackage the vk handle. + using OnRecycle = std::function; + + VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, + VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn); + + ~VulkanDescriptorSet(); + + static VulkanDescriptorSet* create(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet, + VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn); + + // TODO: maybe change to fixed size for performance. + VulkanAcquireOnlyResourceManager resources; + + VkDescriptorSet const vkSet; + VkDescriptorSetLayout const layout; + +private: + OnRecycle mOnRecycleFn; +}; + +// Abstraction over the pool and the layout cache. +class VulkanDescriptorSetManager { +public: + using StageBitMask = uint32_t; + using UniformBufferBitmask = StageBitMask; + using SamplerBitmask = StageBitMask; + + static constexpr uint8_t CONCURRENT_DESCRIPTOR_SET_COUNT = 2;// UBO and samplers + static constexpr uint8_t MAX_SUPPORTED_SHADER_STAGE = 2; // Vertex and fragment. + + static_assert(sizeof(UniformBufferBitmask) * 8 >= + (Program::UNIFORM_BINDING_COUNT) *MAX_SUPPORTED_SHADER_STAGE); + static_assert(sizeof(SamplerBitmask) * 8 >= + Program::SAMPLER_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE); + + static constexpr StageBitMask VERTEX_STAGE = 0x1; + static constexpr StageBitMask FRAGMENT_STAGE = (0x1 << (sizeof(StageBitMask) / 4)); + static constexpr uint8_t UBO_SET_INDEX = 0; + static constexpr uint8_t SAMPLER_SET_INDEX = 1; + + using LayoutArray = std::array; + struct SamplerBundle { + VkDescriptorImageInfo info = {}; + VulkanTexture* texture = nullptr; + uint8_t binding = 0; + SamplerBitmask stage = 0; + }; + using SamplerArray = CappedArray; + using GetPipelineLayoutFunction = std::function; + + VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator); + + void terminate() noexcept; + + void gc() noexcept; + + // This will write/update all of the descriptor set. + void bind(VulkanCommandBuffer* commands, GetPipelineLayoutFunction& getPipelineFn); + + void setUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject, + VkDeviceSize offset, VkDeviceSize size) noexcept; + + void setSamplers(SamplerArray&& samplers); + +private: + class Impl; + Impl* mImpl; +}; + +} // namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H From 89d8f8ebbff39adb0c1692911a4416b50411d1e4 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 5 Mar 2024 12:09:16 -0800 Subject: [PATCH 12/22] engine: avoid leaking vertex buffer (#7628) Previous commit [1] changed the semantic of the index to mBufferObjects. Here we just make sure that if a buffer has been allocated, we don't allocate another (otherwise, we'd leak). Also cleaned up `updateBoneIndicesAndWeights` indexing [1]: a3131a64b6cef2306965eec35a2f9a348601fc00 --- filament/src/details/VertexBuffer.cpp | 40 +++++++++++++++------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/filament/src/details/VertexBuffer.cpp b/filament/src/details/VertexBuffer.cpp index 868ca60e92c..d718bbe0a85 100644 --- a/filament/src/details/VertexBuffer.cpp +++ b/filament/src/details/VertexBuffer.cpp @@ -284,10 +284,12 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& build size_t const i = mAttributes[index].buffer; if (i != Attribute::BUFFER_UNUSED) { assert_invariant(bufferSizes[i] > 0); - BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], - backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); - driver.setVertexBufferObject(mHandle, i, bo); - mBufferObjects[i] = bo; + if (!mBufferObjects[i]) { + BufferObjectHandle bo = driver.createBufferObject(bufferSizes[i], + backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); + driver.setVertexBufferObject(mHandle, i, bo); + mBufferObjects[i] = bo; + } } } } else { @@ -298,10 +300,12 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& build size_t const i = mAttributes[index].buffer; assert_invariant(i != Attribute::BUFFER_UNUSED); assert_invariant(bufferSizes[i] > 0); - BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], - backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); - driver.setVertexBufferObject(mHandle, i, bo); - mBufferObjects[i] = bo; + if (!mBufferObjects[i]) { + BufferObjectHandle const bo = driver.createBufferObject(bufferSizes[i], + backend::BufferObjectBinding::VERTEX, backend::BufferUsage::STATIC); + driver.setVertexBufferObject(mHandle, i, bo); + mBufferObjects[i] = bo; + } } } } @@ -355,18 +359,18 @@ void FVertexBuffer::updateBoneIndicesAndWeights(FEngine& engine, std::unique_ptr skinWeights) { ASSERT_PRECONDITION(mAdvancedSkinningEnabled, "No advanced skinning enabled"); auto jointsData = skinJoints.release(); - engine.getDriverApi().updateBufferObject(mBufferObjects[mBufferCount - 2], { - jointsData, mVertexCount * 8, - [](void* buffer, size_t, void*) { - delete[] static_cast(buffer); - }}, 0); + uint8_t const indicesIndex = mAttributes[VertexAttribute::BONE_INDICES].buffer; + engine.getDriverApi().updateBufferObject(mBufferObjects[indicesIndex], + {jointsData, mVertexCount * 8, + [](void* buffer, size_t, void*) { delete[] static_cast(buffer); }}, + 0); auto weightsData = skinWeights.release(); - engine.getDriverApi().updateBufferObject(mBufferObjects[mBufferCount - 1], { - weightsData, mVertexCount * 16, - [](void* buffer, size_t, void*) { - delete[] static_cast(buffer); - }}, 0); + uint8_t const weightsIndex = mAttributes[VertexAttribute::BONE_WEIGHTS].buffer; + engine.getDriverApi().updateBufferObject(mBufferObjects[weightsIndex], + {weightsData, mVertexCount * 16, + [](void* buffer, size_t, void*) { delete[] static_cast(buffer); }}, + 0); } } // namespace filament From 21d2847a6bf93d30f6bf80f8b88c870e0ba05d48 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Tue, 5 Mar 2024 13:40:31 -0800 Subject: [PATCH 13/22] Update code generator for multiview (#7616) It generates shader code for multiview based on parameters. --- .../filamat/include/filamat/MaterialBuilder.h | 4 ++ libs/filamat/src/GLSLPostProcessor.cpp | 14 ++++++ libs/filamat/src/MaterialBuilder.cpp | 6 +++ libs/filamat/src/shaders/CodeGenerator.cpp | 44 +++++++++++++++---- libs/filamat/src/shaders/MaterialInfo.h | 1 + libs/gltfio/src/JitShaderProvider.cpp | 3 +- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index f3e2080df6a..541153112cf 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -525,6 +525,9 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { //! Specify the stereoscopic type (default is INSTANCED) MaterialBuilder& stereoscopicType(StereoscopicType stereoscopicType) noexcept; + //! Specify the number of eyes for stereoscopic rendering + MaterialBuilder& stereoscopicEyeCount(uint8_t eyeCount) noexcept; + /** * Enable / disable custom surface shading. Custom surface shading requires the LIT * shading model. In addition, the following function must be defined in the fragment @@ -834,6 +837,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { VertexDomain mVertexDomain = VertexDomain::OBJECT; TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT; StereoscopicType mStereoscopicType = StereoscopicType::INSTANCED; + uint8_t mStereoscopicEyeCount = 2; filament::AttributeBitset mRequiredAttributes; diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 7256b3c9323..8062dfefd28 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -536,6 +536,20 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader, glslOptions.emit_uniform_buffer_as_plain_uniforms = true; } + if (config.variant.hasStereo() && config.shaderType == ShaderStage::VERTEX) { + switch (config.materialInfo->stereoscopicType) { + case StereoscopicType::INSTANCED: + // Nothing to generate + break; + case StereoscopicType::MULTIVIEW: + // For stereo variants using multiview feature, this generates the shader code below. + // #extension GL_OVR_multiview2 : require + // layout(num_views = 2) in; + glslOptions.ovr_multiview_view_count = config.materialInfo->stereoscopicEyeCount; + break; + } + } + CompilerGLSL glslCompiler(std::move(spirv)); glslCompiler.set_common_options(glslOptions); diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index f022c0a983e..2ab0a8d2db2 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -503,6 +503,11 @@ MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType stereoscopic return *this; } +MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t eyeCount) noexcept { + mStereoscopicEyeCount = eyeCount; + return *this; +} + MaterialBuilder& MaterialBuilder::reflectionMode(ReflectionMode mode) noexcept { mReflectionMode = mode; return *this; @@ -638,6 +643,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept { info.featureLevel = mFeatureLevel; info.groupSize = mGroupSize; info.stereoscopicType = mStereoscopicType; + info.stereoscopicEyeCount = mStereoscopicEyeCount; // This is determined via static analysis of the glsl after prepareToBuild(). info.userMaterialHasCustomDepth = false; diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index f713411e8b5..c242eee6d5b 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -64,13 +64,20 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade } } if (v.hasStereo() && stage == ShaderStage::VERTEX) { - // If we're not processing the shader through glslang (in the case of unoptimized - // OpenGL shaders), then we need to add the #extension string ourselves. - // If we ARE running the shader through glslang, then we must not include it, - // otherwise glslang will complain. - out << "#ifndef FILAMENT_GLSLANG\n"; - out << "#extension GL_EXT_clip_cull_distance : require\n"; - out << "#endif\n\n"; + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + // If we're not processing the shader through glslang (in the case of unoptimized + // OpenGL shaders), then we need to add the #extension string ourselves. + // If we ARE running the shader through glslang, then we must not include it, + // otherwise glslang will complain. + out << "#ifndef FILAMENT_GLSLANG\n"; + out << "#extension GL_EXT_clip_cull_distance : require\n"; + out << "#endif\n\n"; + break; + case StereoscopicType::MULTIVIEW: + out << "#extension GL_OVR_multiview2 : require\n"; + break; + } } break; case ShaderModel::DESKTOP: @@ -83,6 +90,16 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade out << "#version 410 core\n\n"; out << "#extension GL_ARB_shading_language_packing : enable\n\n"; } + if (v.hasStereo() && stage == ShaderStage::VERTEX) { + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + // Nothing to generate + break; + case StereoscopicType::MULTIVIEW: + out << "#extension GL_OVR_multiview2 : require\n"; + break; + } + } break; } @@ -94,6 +111,17 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade // #included code. This way, glslang reports errors more accurately. out << "#extension GL_GOOGLE_cpp_style_line_directive : enable\n\n"; + if (v.hasStereo() && stage == ShaderStage::VERTEX) { + switch (material.stereoscopicType) { + case StereoscopicType::INSTANCED: + // Nothing to generate + break; + case StereoscopicType::MULTIVIEW: + out << "layout(num_views = " << material.stereoscopicEyeCount << ") in;\n"; + break; + } + } + if (stage == ShaderStage::COMPUTE) { out << "layout(local_size_x = " << material.groupSize.x << ", local_size_y = " << material.groupSize.y @@ -293,7 +321,7 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade +ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, false); generateSpecializationConstant(out, "CONFIG_STEREO_EYE_COUNT", - +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, 2); + +ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, material.stereoscopicEyeCount); // CONFIG_MAX_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to // be explicitly, statically defined (as in #define). Otherwise (using const int for diff --git a/libs/filamat/src/shaders/MaterialInfo.h b/libs/filamat/src/shaders/MaterialInfo.h index 5ede9f8c1ff..4979bed5c3c 100644 --- a/libs/filamat/src/shaders/MaterialInfo.h +++ b/libs/filamat/src/shaders/MaterialInfo.h @@ -54,6 +54,7 @@ struct UTILS_PUBLIC MaterialInfo { bool instanced; bool vertexDomainDeviceJittered; bool userMaterialHasCustomDepth; + int stereoscopicEyeCount; filament::SpecularAmbientOcclusion specularAO; filament::RefractionMode refractionMode; filament::RefractionType refractionType; diff --git a/libs/gltfio/src/JitShaderProvider.cpp b/libs/gltfio/src/JitShaderProvider.cpp index 7855d8b9056..22fca3dfe14 100644 --- a/libs/gltfio/src/JitShaderProvider.cpp +++ b/libs/gltfio/src/JitShaderProvider.cpp @@ -330,7 +330,8 @@ Material* createMaterial(Engine* engine, const MaterialKey& config, const UvMap& MaterialBuilder::TransparencyMode::TWO_PASSES_TWO_SIDES : MaterialBuilder::TransparencyMode::DEFAULT) .reflectionMode(MaterialBuilder::ReflectionMode::SCREEN_SPACE) - .targetApi(filamat::targetApiFromBackend(engine->getBackend())); + .targetApi(filamat::targetApiFromBackend(engine->getBackend())) + .stereoscopicEyeCount(engine->getConfig().stereoscopicEyeCount); if (!optimizeShaders) { builder.optimization(MaterialBuilder::Optimization::NONE); From 9f33a2d06212f82bfa68701c6494f83b1da9c528 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 5 Mar 2024 17:01:20 -0800 Subject: [PATCH 14/22] Revert "vk: remove subpasses to simplify descriptor set refactor (#7592)" (#7630) This reverts commit a9793b3cf619394dc738e3483222a7cff6b81dcf. Due to change in output for swiftshader --- filament/backend/src/vulkan/VulkanConstants.h | 12 +-- filament/backend/src/vulkan/VulkanContext.h | 1 + filament/backend/src/vulkan/VulkanDriver.cpp | 34 +++++++- .../backend/src/vulkan/VulkanFboCache.cpp | 65 ++++++++++++-- filament/backend/src/vulkan/VulkanHandles.cpp | 5 +- .../src/vulkan/VulkanPipelineCache.cpp | 85 ++++++++++++++++--- .../backend/src/vulkan/VulkanPipelineCache.h | 47 +++++----- 7 files changed, 199 insertions(+), 50 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanConstants.h b/filament/backend/src/vulkan/VulkanConstants.h index b0a05d61005..a2b62bd34c8 100644 --- a/filament/backend/src/vulkan/VulkanConstants.h +++ b/filament/backend/src/vulkan/VulkanConstants.h @@ -66,7 +66,8 @@ // Usefaul default combinations #define FVK_DEBUG_EVERYTHING 0xFFFFFFFF #define FVK_DEBUG_PERFORMANCE \ - FVK_DEBUG_SYSTRACE + FVK_DEBUG_SYSTRACE | \ + FVK_DEBUG_GROUP_MARKERS #define FVK_DEBUG_CORRECTNESS \ FVK_DEBUG_VALIDATION | \ @@ -79,17 +80,18 @@ FVK_DEBUG_PRINT_GROUP_MARKERS #ifndef NDEBUG -#define FVK_DEBUG_FLAGS FVK_DEBUG_PERFORMANCE +#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_DEBUG_UTILS | FVK_DEBUG_VALIDATION) #else #define FVK_DEBUG_FLAGS 0 #endif -#define FVK_ENABLED(flags) (((FVK_DEBUG_FLAGS) & (flags)) == (flags)) -#define FVK_ENABLED_BOOL(flags) FVK_ENABLED(flags) +#define FVK_ENABLED(flags) ((FVK_DEBUG_FLAGS) & (flags)) +#define FVK_ENABLED_BOOL(flags) ((bool) FVK_ENABLED(flags)) + // Group marker only works only if validation or debug utils is enabled since it uses // vkCmd(Begin/End)DebugUtilsLabelEXT or vkCmdDebugMarker(Begin/End)EXT -#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) +#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS) static_assert(FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_VALIDATION)); #endif diff --git a/filament/backend/src/vulkan/VulkanContext.h b/filament/backend/src/vulkan/VulkanContext.h index 88b9904276d..c9a4fa2c4e7 100644 --- a/filament/backend/src/vulkan/VulkanContext.h +++ b/filament/backend/src/vulkan/VulkanContext.h @@ -83,6 +83,7 @@ struct VulkanRenderPass { VulkanRenderTarget* renderTarget; VkRenderPass renderPass; RenderPassParams params; + int currentSubpass; }; // This is a collection of immutable data about the vulkan context. This actual handles to the diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 876007a627c..c1e8f4c0dee 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -848,7 +848,7 @@ bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) { } bool VulkanDriver::isFrameBufferFetchSupported() { - return false; + return true; } bool VulkanDriver::isFrameBufferFetchMultiSampleSupported() { @@ -1256,7 +1256,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP } VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey); - mPipelineCache.bindRenderPass(renderPass); + mPipelineCache.bindRenderPass(renderPass, 0); // Create the VkFramebuffer or fetch it from cache. VulkanFboCache::FboKey fbkey { @@ -1381,6 +1381,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP .renderTarget = rt, .renderPass = renderPassInfo.renderPass, .params = params, + .currentSubpass = 0, }; FVK_SYSTRACE_END(); } @@ -1423,13 +1424,40 @@ void VulkanDriver::endRenderPass(int) { 0, 1, &barrier, 0, nullptr, 0, nullptr); } + if (mCurrentRenderPass.currentSubpass > 0) { + for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) { + mPipelineCache.bindInputAttachment(i, {}); + } + mCurrentRenderPass.currentSubpass = 0; + } mCurrentRenderPass.renderTarget = nullptr; mCurrentRenderPass.renderPass = VK_NULL_HANDLE; FVK_SYSTRACE_END(); } void VulkanDriver::nextSubpass(int) { - PANIC_POSTCONDITION("Subpasses are unsupported"); + ASSERT_PRECONDITION(mCurrentRenderPass.currentSubpass == 0, + "Only two subpasses are currently supported."); + + VulkanRenderTarget* renderTarget = mCurrentRenderPass.renderTarget; + assert_invariant(renderTarget); + assert_invariant(mCurrentRenderPass.params.subpassMask); + + vkCmdNextSubpass(mCommands->get().buffer(), VK_SUBPASS_CONTENTS_INLINE); + + mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass, + ++mCurrentRenderPass.currentSubpass); + + for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) { + if ((1 << i) & mCurrentRenderPass.params.subpassMask) { + VulkanAttachment subpassInput = renderTarget->getColor(i); + VkDescriptorImageInfo info = { + .imageView = subpassInput.getImageView(VK_IMAGE_ASPECT_COLOR_BIT), + .imageLayout = ImgUtil::getVkLayout(subpassInput.getLayout()), + }; + mPipelineCache.bindInputAttachment(i, info); + } + } } void VulkanDriver::setRenderPrimitiveBuffer(Handle rph, PrimitiveType pt, diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 200ea23e025..0546ffd04fb 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -129,6 +129,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { iter.value().timestamp = mCurrentTime; return iter->second.handle; } + const bool hasSubpasses = config.subpassMask != 0; // Set up some const aliases for terseness. const VkAttachmentLoadOp kClear = VK_ATTACHMENT_LOAD_OP_CLEAR; @@ -140,18 +141,26 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { // In Vulkan, the subpass desc specifies the layout to transition to at the start of the render // pass, and the attachment description specifies the layout to transition to at the end. + VkAttachmentReference inputAttachmentRef[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; VkAttachmentReference colorAttachmentRefs[2][MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; VkAttachmentReference resolveAttachmentRef[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; VkAttachmentReference depthAttachmentRef = {}; const bool hasDepth = config.depthFormat != VK_FORMAT_UNDEFINED; - VkSubpassDescription subpasses[1] = {{ + VkSubpassDescription subpasses[2] = {{ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .pInputAttachments = nullptr, .pColorAttachments = colorAttachmentRefs[0], .pResolveAttachments = resolveAttachmentRef, .pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr + }, + { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pInputAttachments = inputAttachmentRef, + .pColorAttachments = colorAttachmentRefs[1], + .pResolveAttachments = resolveAttachmentRef, + .pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr }}; // The attachment list contains: Color Attachments, Resolve Attachments, and Depth Attachment. @@ -159,20 +168,29 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { // Note that this needs to have the same ordering as the corollary array in getFramebuffer. VkAttachmentDescription attachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {}; + // We support 2 subpasses, which means we need to supply 1 dependency struct. + VkSubpassDependency dependencies[1] = {{ + .srcSubpass = 0, + .dstSubpass = 1, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, + }}; + VkRenderPassCreateInfo renderPassInfo { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 0u, .pAttachments = attachments, - .subpassCount = 1u, + .subpassCount = hasSubpasses ? 2u : 1u, .pSubpasses = subpasses, - .dependencyCount = 0u, - .pDependencies = nullptr, + .dependencyCount = hasSubpasses ? 1u : 0u, + .pDependencies = dependencies }; int attachmentIndex = 0; - assert_invariant(config.subpassMask == 0 && "Subpass is not supported"); - // Populate the Color Attachments. for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) { @@ -181,9 +199,36 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { const VkImageLayout subpassLayout = ImgUtil::getVkLayout(VulkanLayout::COLOR_ATTACHMENT); uint32_t index; - index = subpasses[0].colorAttachmentCount++; - colorAttachmentRefs[0][index].layout = subpassLayout; - colorAttachmentRefs[0][index].attachment = attachmentIndex; + if (!hasSubpasses) { + index = subpasses[0].colorAttachmentCount++; + colorAttachmentRefs[0][index].layout = subpassLayout; + colorAttachmentRefs[0][index].attachment = attachmentIndex; + } else { + + // The Driver API consolidates all color attachments from the first and second subpasses + // into a single list, and uses a bitmask to mark attachments that belong only to the + // second subpass and should be available as inputs. All color attachments in the first + // subpass are automatically made available to the second subpass. + + // If there are subpasses, we require the input attachment to be the first attachment. + // Breaking this assumption would likely require enhancements to the Driver API in order + // to supply Vulkan with all the information needed. + assert_invariant(config.subpassMask == 1); + + if (config.subpassMask & (1 << i)) { + index = subpasses[0].colorAttachmentCount++; + colorAttachmentRefs[0][index].layout = subpassLayout; + colorAttachmentRefs[0][index].attachment = attachmentIndex; + + index = subpasses[1].inputAttachmentCount++; + inputAttachmentRef[index].layout = subpassLayout; + inputAttachmentRef[index].attachment = attachmentIndex; + } + + index = subpasses[1].colorAttachmentCount++; + colorAttachmentRefs[1][index].layout = subpassLayout; + colorAttachmentRefs[1][index].attachment = attachmentIndex; + } const TargetBufferFlags flag = TargetBufferFlags(int(TargetBufferFlags::COLOR0) << i); const bool clear = any(config.clear & flag); @@ -207,6 +252,8 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { if (subpasses[0].colorAttachmentCount == 0) { subpasses[0].pColorAttachments = nullptr; subpasses[0].pResolveAttachments = nullptr; + subpasses[1].pColorAttachments = nullptr; + subpasses[1].pResolveAttachments = nullptr; } // Populate the Resolve Attachments. diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index c3ed89aa416..77121150824 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -273,7 +273,10 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co if (!mColor[i].texture) { continue; } - count++; + // NOTE: This must be consistent with VkRenderPass construction (see VulkanFboCache). + if (!(pass.params.subpassMask & (1 << i)) || pass.currentSubpass == 1) { + count++; + } } return count; } diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.cpp b/filament/backend/src/vulkan/VulkanPipelineCache.cpp index 8c19a12ad1a..5b96344c0c8 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.cpp +++ b/filament/backend/src/vulkan/VulkanPipelineCache.cpp @@ -81,6 +81,16 @@ VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator) mDummyBufferWriteInfo.pImageInfo = nullptr; mDummyBufferWriteInfo.pBufferInfo = &mDummyBufferInfo; mDummyBufferWriteInfo.pTexelBufferView = nullptr; + + mDummyTargetInfo.imageLayout = VulkanImageUtility::getVkLayout(VulkanLayout::READ_ONLY); + mDummyTargetWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + mDummyTargetWriteInfo.pNext = nullptr; + mDummyTargetWriteInfo.dstArrayElement = 0; + mDummyTargetWriteInfo.descriptorCount = 1; + mDummyTargetWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + mDummyTargetWriteInfo.pImageInfo = &mDummyTargetInfo; + mDummyTargetWriteInfo.pBufferInfo = nullptr; + mDummyTargetWriteInfo.pTexelBufferView = nullptr; } VulkanPipelineCache::~VulkanPipelineCache() { @@ -246,7 +256,9 @@ VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptor // Rewrite every binding in the new descriptor sets. VkDescriptorBufferInfo descriptorBuffers[UBUFFER_BINDING_COUNT]; VkDescriptorImageInfo descriptorSamplers[SAMPLER_BINDING_COUNT]; - VkWriteDescriptorSet descriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT]; + VkDescriptorImageInfo descriptorInputAttachments[INPUT_ATTACHMENT_COUNT]; + VkWriteDescriptorSet descriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT + + INPUT_ATTACHMENT_COUNT]; uint32_t nwrites = 0; VkWriteDescriptorSet* writes = descriptorWrites; nwrites = 0; @@ -296,6 +308,23 @@ VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptor writeInfo.dstBinding = binding; } } + for (uint32_t binding = 0; binding < INPUT_ATTACHMENT_COUNT; binding++) { + if (mDescriptorRequirements.inputAttachments[binding].imageView) { + VkWriteDescriptorSet& writeInfo = writes[nwrites++]; + VkDescriptorImageInfo& imageInfo = descriptorInputAttachments[binding]; + imageInfo = mDescriptorRequirements.inputAttachments[binding]; + writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeInfo.pNext = nullptr; + writeInfo.dstArrayElement = 0; + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + writeInfo.pImageInfo = &imageInfo; + writeInfo.pBufferInfo = nullptr; + writeInfo.pTexelBufferView = nullptr; + writeInfo.dstSet = descriptorCacheEntry.handles[2]; + writeInfo.dstBinding = binding; + } + } vkUpdateDescriptorSets(mDevice, nwrites, writes, 0, nullptr); @@ -373,15 +402,15 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n const bool hasFragmentShader = shaderStages[1].module != VK_NULL_HANDLE; - VkGraphicsPipelineCreateInfo pipelineCreateInfo = { - .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, - .stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1, - .pStages = shaderStages, - .pVertexInputState = &vertexInputState, - .pInputAssemblyState = &inputAssemblyState, - .layout = layout->handle, - .renderPass = mPipelineRequirements.renderPass, - }; + VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; + pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineCreateInfo.layout = layout->handle; + pipelineCreateInfo.renderPass = mPipelineRequirements.renderPass; + pipelineCreateInfo.subpass = mPipelineRequirements.subpassIndex; + pipelineCreateInfo.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1; + pipelineCreateInfo.pStages = shaderStages; + pipelineCreateInfo.pVertexInputState = &vertexInputState; + pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; VkPipelineRasterizationStateCreateInfo vkRaster = {}; vkRaster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; @@ -506,6 +535,18 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP dlinfo.pBindings = sbindings; vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[1]); + // Next create the descriptor set layout for input attachments. + VkDescriptorSetLayoutBinding tbindings[INPUT_ATTACHMENT_COUNT]; + binding.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) { + binding.binding = i; + tbindings[i] = binding; + } + dlinfo.bindingCount = INPUT_ATTACHMENT_COUNT; + dlinfo.pBindings = tbindings; + vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[2]); + // Create VkPipelineLayout based on how to resources are bounded. VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {}; pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; @@ -528,8 +569,9 @@ void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexce mPipelineRequirements.rasterState = rasterState; } -void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass) noexcept { +void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept { mPipelineRequirements.renderPass = renderPass; + mPipelineRequirements.subpassIndex = subpassIndex; } void VulkanPipelineCache::bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept { @@ -577,6 +619,11 @@ void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept { sampler = {}; } } + for (auto& target : mDescriptorRequirements.inputAttachments) { + if (target.imageView == imageView) { + target = {}; + } + } } void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex, @@ -613,6 +660,14 @@ void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BI mPipelineRequirements.layout = flags; } +void VulkanPipelineCache::bindInputAttachment(uint32_t bindingIndex, + VkDescriptorImageInfo targetInfo) noexcept { + ASSERT_POSTCONDITION(bindingIndex < INPUT_ATTACHMENT_COUNT, + "Input attachment bindings overflow: index = %d, capacity = %d.", + bindingIndex, INPUT_ATTACHMENT_COUNT); + mDescriptorRequirements.inputAttachments[bindingIndex] = targetInfo; +} + void VulkanPipelineCache::terminate() noexcept { // Symmetric to createLayoutsAndDescriptors. destroyLayoutsAndDescriptors(); @@ -736,6 +791,8 @@ VkDescriptorPool VulkanPipelineCache::createDescriptorPool(uint32_t size) const poolSizes[0].descriptorCount = poolInfo.maxSets * UBUFFER_BINDING_COUNT; poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[1].descriptorCount = poolInfo.maxSets * SAMPLER_BINDING_COUNT; + poolSizes[2].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT; + poolSizes[2].descriptorCount = poolInfo.maxSets * INPUT_ATTACHMENT_COUNT; VkDescriptorPool pool; const UTILS_UNUSED VkResult result = vkCreateDescriptorPool(mDevice, &poolInfo, VKALLOC, &pool); @@ -846,6 +903,12 @@ bool VulkanPipelineCache::DescEqual::operator()(const DescriptorKey& k1, return false; } } + for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) { + if (k1.inputAttachments[i].imageView != k2.inputAttachments[i].imageView || + k1.inputAttachments[i].imageLayout != k2.inputAttachments[i].imageLayout) { + return false; + } + } return true; } diff --git a/filament/backend/src/vulkan/VulkanPipelineCache.h b/filament/backend/src/vulkan/VulkanPipelineCache.h index de271c4b7af..b2acb37ec27 100644 --- a/filament/backend/src/vulkan/VulkanPipelineCache.h +++ b/filament/backend/src/vulkan/VulkanPipelineCache.h @@ -64,11 +64,15 @@ class VulkanPipelineCache : public CommandBufferObserver { static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT; static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT; + // We assume only one possible input attachment between two subpasses. See also the subpasses + // definition in VulkanFboCache. + static constexpr uint32_t INPUT_ATTACHMENT_COUNT = 1; + static constexpr uint32_t SHADER_MODULE_COUNT = 2; static constexpr uint32_t VERTEX_ATTRIBUTE_COUNT = MAX_VERTEX_ATTRIBUTE_COUNT; - // Three descriptor set layouts: uniforms, and combined image samplers. - static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 2; + // Three descriptor set layouts: uniforms, combined image samplers, and input attachments. + static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3; static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512; // The VertexArray POD is an array of buffer targets and an array of attributes that refer to @@ -149,12 +153,13 @@ class VulkanPipelineCache : public CommandBufferObserver { // Each of the following methods are fast and do not make Vulkan calls. void bindProgram(VulkanProgram* program) noexcept; void bindRasterState(const RasterState& rasterState) noexcept; - void bindRenderPass(VkRenderPass renderPass) noexcept; + void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept; void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept; void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject, VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept; void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT], VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept; + void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept; void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc, VkVertexInputBindingDescription const* bufferDesc, uint8_t count); @@ -251,17 +256,16 @@ class VulkanPipelineCache : public CommandBufferObserver { // The pipeline key is a POD that represents all currently bound states that form the immutable // VkPipeline object. The size:offset comments below are expressed in bytes. - struct PipelineKey { // size : offset - VkShaderModule shaders[SHADER_MODULE_COUNT]; // 16 : 0 - VkRenderPass renderPass; // 8 : 16 - uint16_t topology; // 2 : 24 - uint16_t padding0; // 2 : 26 - VertexInputAttributeDescription vertexAttributes[VERTEX_ATTRIBUTE_COUNT]; // 128 : 28 - VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156 - RasterState rasterState; // 16 : 284 - uint32_t padding1; // 4 : 300 - PipelineLayoutKey layout; // 16 : 304 - // total : 320 + struct PipelineKey { // size : offset + VkShaderModule shaders[SHADER_MODULE_COUNT]; // 16 : 0 + VkRenderPass renderPass; // 8 : 16 + uint16_t topology; // 2 : 24 + uint16_t subpassIndex; // 2 : 26 + VertexInputAttributeDescription vertexAttributes[VERTEX_ATTRIBUTE_COUNT]; // 128 : 28 + VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156 + RasterState rasterState; // 16 : 284 + uint32_t padding; // 4 : 300 + PipelineLayoutKey layout; // 16 : 304 }; static_assert(sizeof(PipelineKey) == 320, "PipelineKey must not have implicit padding."); @@ -302,15 +306,15 @@ class VulkanPipelineCache : public CommandBufferObserver { struct DescriptorKey { VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 80 0 DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 1488 80 - uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1568 - uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1608 - //total 1648 + DescriptorImageInfo inputAttachments[INPUT_ATTACHMENT_COUNT]; // 24 1568 + uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1592 + uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1632 }; - static_assert(offsetof(DescriptorKey, samplers) == 80); - static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1568); - static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1608); - static_assert(sizeof(DescriptorKey) == 1648, "DescriptorKey must not have implicit padding."); + static_assert(offsetof(DescriptorKey, inputAttachments) == 1568); + static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1592); + static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1632); + static_assert(sizeof(DescriptorKey) == 1672, "DescriptorKey must not have implicit padding."); using DescHashFn = utils::hash::MurmurHashFn; @@ -433,6 +437,7 @@ class VulkanPipelineCache : public CommandBufferObserver { VkDescriptorBufferInfo mDummyBufferInfo = {}; VkWriteDescriptorSet mDummyBufferWriteInfo = {}; VkDescriptorImageInfo mDummyTargetInfo = {}; + VkWriteDescriptorSet mDummyTargetWriteInfo = {}; VkBuffer mDummyBuffer; VmaAllocation mDummyMemory; From c84f80be7c0dc37a0357770e5bc016c3c65e6c87 Mon Sep 17 00:00:00 2001 From: mdagois Date: Wed, 6 Mar 2024 10:27:21 +0900 Subject: [PATCH 15/22] Fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779 (#7626) The validation error triggers on hellotriangle using AMD (desktop), QCOM (mobile) and Mali (mobile) GPU. Before this MR, only a single semaphore object was used to synchronize all the calls to vkAcquireNextImage (signal) and vkQueueSubmit (wait). The issue is that by the time vkQueueSubmit returns, the semaphore is not necessarily reset. When multiple frames are in flight, the next call to vkAcquireNextImage might try to reuse the semaphore while it is still in the wait status. The semaphore is reset at a driver/hardware-dependent timing that's likely to be linked to the GPU queue execution. The solution proposed by this MR is to use a pool of semaphores big enough to cover all possible queue submissions. --- NEW_RELEASE_NOTES.md | 1 + .../backend/src/vulkan/VulkanSwapChain.cpp | 31 ++++++++++++------- filament/backend/src/vulkan/VulkanSwapChain.h | 5 ++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 5445b36ffd0..51e62f407df 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -9,3 +9,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut - Add new API `SwapChain::getFrameScheduledCallback` +- vulkan: fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779 diff --git a/filament/backend/src/vulkan/VulkanSwapChain.cpp b/filament/backend/src/vulkan/VulkanSwapChain.cpp index af18e36fcc6..108cceca5c2 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.cpp +++ b/filament/backend/src/vulkan/VulkanSwapChain.cpp @@ -35,7 +35,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& mStagePool(stagePool), mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow), mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize), - mImageReady(VK_NULL_HANDLE), + mCurrentImageReadyIndex(0), mAcquired(false), mIsFirstRenderPass(true) { swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent); @@ -46,10 +46,15 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& }; // No need to wait on this semaphore before drawing when in Headless mode. - if (!mHeadless) { - VkResult result = - vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, &mImageReady); - ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore"); + if (mHeadless) { + // Set all sempahores to VK_NULL_HANDLE + memset(mImageReady, 0, sizeof(mImageReady[0]) * IMAGE_READY_SEMAPHORE_COUNT); + } else { + for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) { + VkResult result = + vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, mImageReady + i); + ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore"); + } } update(); @@ -62,9 +67,11 @@ VulkanSwapChain::~VulkanSwapChain() { mCommands->wait(); mPlatform->destroy(swapChain); - if (mImageReady != VK_NULL_HANDLE) { - vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC); - } + for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) { + if (mImageReady[i] != VK_NULL_HANDLE) { + vkDestroySemaphore(mPlatform->getDevice(), mImageReady[i], VKALLOC); + } + } } void VulkanSwapChain::update() { @@ -131,11 +138,13 @@ void VulkanSwapChain::acquire(bool& resized) { update(); } - VkResult const result = mPlatform->acquire(swapChain, mImageReady, &mCurrentSwapIndex); + mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT; + const VkSemaphore imageReady = mImageReady[mCurrentImageReadyIndex]; + VkResult const result = mPlatform->acquire(swapChain, imageReady, &mCurrentSwapIndex); ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR, "Cannot acquire in swapchain."); - if (mImageReady != VK_NULL_HANDLE) { - mCommands->injectDependency(mImageReady); + if (imageReady != VK_NULL_HANDLE) { + mCommands->injectDependency(imageReady); } mAcquired = true; } diff --git a/filament/backend/src/vulkan/VulkanSwapChain.h b/filament/backend/src/vulkan/VulkanSwapChain.h index 345dd2650d3..f1894da1d2e 100644 --- a/filament/backend/src/vulkan/VulkanSwapChain.h +++ b/filament/backend/src/vulkan/VulkanSwapChain.h @@ -69,6 +69,8 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource { } private: + static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS; + void update(); VulkanPlatform* mPlatform; @@ -83,7 +85,8 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource { utils::FixedCapacityVector> mColors; std::unique_ptr mDepth; VkExtent2D mExtent; - VkSemaphore mImageReady; + VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT]; + uint32_t mCurrentImageReadyIndex; uint32_t mCurrentSwapIndex; bool mAcquired; bool mIsFirstRenderPass; From 21d938a59f1a58585bb9031e7378fe1c47e6d02a Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 5 Mar 2024 16:35:36 -0800 Subject: [PATCH 16/22] simplify the ResourceAllocator cache eviction strategy Previously the cache would try to keep its size below a user-settable value. This was not effective because when that value was too small, it would cause a lot of churn every frame without actually keeping the memory usage below the specified value. We now evict buffer aggressively after they've not been used (for two frames by default), but we don't cap the size of the cache. The cache will naturally settle at the size it needs. When dynamic resolution is used, it might be needed to increase resources maximum age, which is a user-settable value still. This improves performance on mobile on many scenes because the 64MB default value was too low, causing the crash to thrash. --- .../com/google/android/filament/Engine.java | 6 +-- filament/include/filament/Engine.h | 6 +-- filament/src/ResourceAllocator.cpp | 46 +++---------------- filament/src/ResourceAllocator.h | 3 +- 4 files changed, 11 insertions(+), 50 deletions(-) diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 06f0a2b8ae9..a858330eb4a 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -400,16 +400,14 @@ public static class Config { public long stereoscopicEyeCount = 2; /* - * Size in MiB of the frame graph texture cache. This should be adjusted based on the - * size of used render targets (typically the screen). + * @Deprecated This value is no longer used. */ public long resourceAllocatorCacheSizeMB = 64; /* * This value determines for how many frames are texture entries kept in the cache. - * The default value of 30 corresponds to about half a second at 60 fps. */ - public long resourceAllocatorCacheMaxAge = 30; + public long resourceAllocatorCacheMaxAge = 2; } private Engine(long nativeEngine, Config config) { diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 0ca5f3209da..6eb1916f7ab 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -327,16 +327,14 @@ class UTILS_PUBLIC Engine { uint8_t stereoscopicEyeCount = 2; /* - * Size in MiB of the frame graph texture cache. This should be adjusted based on the - * size of used render targets (typically the screen). + * @deprecated This value is no longer used. */ uint32_t resourceAllocatorCacheSizeMB = 64; /* * This value determines for how many frames are texture entries kept in the cache. - * The default value of 30 corresponds to about half a second at 60 fps. */ - uint32_t resourceAllocatorCacheMaxAge = 30; + uint32_t resourceAllocatorCacheMaxAge = 2; }; diff --git a/filament/src/ResourceAllocator.cpp b/filament/src/ResourceAllocator.cpp index b8d1e6698e5..4eee32aaa10 100644 --- a/filament/src/ResourceAllocator.cpp +++ b/filament/src/ResourceAllocator.cpp @@ -109,8 +109,7 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept { } ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept - : mCacheCapacity(config.resourceAllocatorCacheSizeMB << 20), - mCacheMaxAge(config.resourceAllocatorCacheMaxAge), + : mCacheMaxAge(config.resourceAllocatorCacheMaxAge), mBackend(driverApi) { } @@ -218,51 +217,18 @@ void ResourceAllocator::gc() noexcept { // Purging strategy: // - remove entries that are older than a certain age // - remove only one entry per gc(), - // - unless we're at capacity - // - remove LRU entries until we're below capacity auto& textureCache = mTextureCache; for (auto it = textureCache.begin(); it != textureCache.end();) { const size_t ageDiff = age - it->second.age; if (ageDiff >= mCacheMaxAge) { - it = purge(it); - if (mCacheSize < mCacheCapacity) { - // if we're not at capacity, only purge a single entry per gc, trying to - // avoid a burst of work. - break; - } + purge(it); + // only purge a single entry per gc + break; } else { ++it; } } - - if (UTILS_UNLIKELY(mCacheSize >= mCacheCapacity)) { - // make a copy of our CacheContainer to a vector - using Vector = FixedCapacityVector>; - auto cache = Vector::with_capacity(textureCache.size()); - std::copy(textureCache.begin(), textureCache.end(), std::back_insert_iterator(cache)); - - // sort by least recently used - std::sort(cache.begin(), cache.end(), [](auto const& lhs, auto const& rhs) { - return lhs.second.age < rhs.second.age; - }); - - // now remove entries until we're at capacity - auto curr = cache.begin(); - while (mCacheSize >= mCacheCapacity) { - // by construction this entry must exist - purge(textureCache.find(curr->first)); - ++curr; - } - - // Since we're sorted already, reset the oldestAge of the whole system - size_t const oldestAge = cache.front().second.age; - for (auto& it : textureCache) { - it.second.age -= oldestAge; - } - mAge -= oldestAge; - } - //if (mAge % 60 == 0) dump(); } UTILS_NOINLINE @@ -280,12 +246,12 @@ void ResourceAllocator::dump(bool brief) const noexcept { } } -ResourceAllocator::CacheContainer::iterator ResourceAllocator::purge( +void ResourceAllocator::purge( ResourceAllocator::CacheContainer::iterator const& pos) { //slog.d << "purging " << pos->second.handle.getId() << ", age=" << pos->second.age << io::endl; mBackend.destroyTexture(pos->second.handle); mCacheSize -= pos->second.size; - return mTextureCache.erase(pos); + mTextureCache.erase(pos); } } // namespace filament diff --git a/filament/src/ResourceAllocator.h b/filament/src/ResourceAllocator.h index 220b6f6fbff..6d854946a9a 100644 --- a/filament/src/ResourceAllocator.h +++ b/filament/src/ResourceAllocator.h @@ -94,7 +94,6 @@ class ResourceAllocator final : public ResourceAllocatorInterface { void gc() noexcept; private: - size_t const mCacheCapacity; size_t const mCacheMaxAge; struct TextureKey { @@ -194,7 +193,7 @@ class ResourceAllocator final : public ResourceAllocatorInterface { using CacheContainer = AssociativeContainer; using InUseContainer = AssociativeContainer; - CacheContainer::iterator purge(CacheContainer::iterator const& pos); + void purge(ResourceAllocator::CacheContainer::iterator const& pos); backend::DriverApi& mBackend; CacheContainer mTextureCache; From 6601c7c2b5a05c5e41920fa86de74c65ae430ba8 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Wed, 6 Mar 2024 12:06:30 -0800 Subject: [PATCH 17/22] Add new parameter -P for matc (#7632) * Add new parameter -P for matc This new matc parameter `-P` or `--material-parameter` allows users to set material properties to the specified value. Values passed through this matc parameters have the highest priorities. I.e., they overwrite material properties specified in the material file. --- tools/matc/src/matc/CommandlineConfig.cpp | 11 +++++- tools/matc/src/matc/Config.h | 7 +++- tools/matc/src/matc/MaterialCompiler.cpp | 15 ++++++++ tools/matc/src/matc/MaterialCompiler.h | 2 ++ tools/matc/src/matc/ParametersProcessor.cpp | 38 ++++++++++++++++++++- tools/matc/src/matc/ParametersProcessor.h | 2 ++ 6 files changed, 72 insertions(+), 3 deletions(-) diff --git a/tools/matc/src/matc/CommandlineConfig.cpp b/tools/matc/src/matc/CommandlineConfig.cpp index 30d6114e99d..64850c436ee 100644 --- a/tools/matc/src/matc/CommandlineConfig.cpp +++ b/tools/matc/src/matc/CommandlineConfig.cpp @@ -74,6 +74,11 @@ static void usage(char* name) { " Unlike --define, this applies to the material specification, not GLSL.\n" " Can be repeated to specify multiple macros:\n" " MATC -TBLENDING=fade -TDOUBLESIDED=false ...\n\n" + " --material-parameter =, -P=\n" + " Set the material property pointed to by to \n" + " This overwrites the value configured in the material file.\n" + " Material property of array type is not supported.\n" + " MATC -PflipUV=false -PshadingModel=lit -Pname=myMat ...\n\n" " --reflect, -r\n" " Reflect the specified metadata as JSON: parameters\n\n" " --variant-filter=, -V \n" @@ -174,7 +179,7 @@ static void parseDefine(std::string defineString, Config::StringReplacementMap& } bool CommandlineConfig::parse() { - static constexpr const char* OPTSTR = "hLxo:f:dm:a:l:p:D:T:OSEr:vV:gtwF1"; + static constexpr const char* OPTSTR = "hLxo:f:dm:a:l:p:D:T:P:OSEr:vV:gtwF1"; static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' }, { "license", no_argument, nullptr, 'L' }, @@ -193,6 +198,7 @@ bool CommandlineConfig::parse() { { "no-essl1", no_argument, nullptr, '1' }, { "define", required_argument, nullptr, 'D' }, { "template", required_argument, nullptr, 'T' }, + { "material-parameter",required_argument, nullptr, 'P' }, { "reflect", required_argument, nullptr, 'r' }, { "print", no_argument, nullptr, 't' }, { "version", no_argument, nullptr, 'v' }, @@ -283,6 +289,9 @@ bool CommandlineConfig::parse() { case 'T': parseDefine(arg, mTemplateMap); break; + case 'P': + parseDefine(arg, mMaterialParameters); + break; case 'v': // Similar to --help, the --version command does an early exit in order to avoid // subsequent error spew such as "Missing input filename" etc. diff --git a/tools/matc/src/matc/Config.h b/tools/matc/src/matc/Config.h index 79c6786541a..275799e0814 100644 --- a/tools/matc/src/matc/Config.h +++ b/tools/matc/src/matc/Config.h @@ -41,7 +41,7 @@ class Config { using TargetApi = filamat::MaterialBuilder::TargetApi; using Optimization = filamat::MaterialBuilder::Optimization; - // For defines and template args, we use an ordered map with a transparent comparator. + // For defines, template, and material parameters, we use an ordered map with a transparent comparator. // Even though the key is stored using std::string, this allows you to make lookups using // std::string_view. There is no need to construct a std::string object just to make a lookup. using StringReplacementMap = std::map>; @@ -135,6 +135,10 @@ class Config { return mTemplateMap; } + const StringReplacementMap& getMaterialParameters() const noexcept { + return mMaterialParameters; + } + filament::backend::FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } @@ -153,6 +157,7 @@ class Config { filament::backend::FeatureLevel mFeatureLevel = filament::backend::FeatureLevel::FEATURE_LEVEL_3; StringReplacementMap mDefines; StringReplacementMap mTemplateMap; + StringReplacementMap mMaterialParameters; filament::UserVariantFilterMask mVariantFilter = 0; bool mIncludeEssl1 = true; }; diff --git a/tools/matc/src/matc/MaterialCompiler.cpp b/tools/matc/src/matc/MaterialCompiler.cpp index 667560d0ce2..c528326aee3 100644 --- a/tools/matc/src/matc/MaterialCompiler.cpp +++ b/tools/matc/src/matc/MaterialCompiler.cpp @@ -415,6 +415,11 @@ bool MaterialCompiler::run(const Config& config) { builder.shaderDefine(define.first.c_str(), define.second.c_str()); } + if (!processMaterialParameters(builder, config)) { + std::cerr << "Error while processing material parameters." << std::endl; + return false; + } + JobSystem js; js.adopt(); @@ -620,4 +625,14 @@ bool MaterialCompiler::compileRawShader(const char* glsl, size_t size, bool isDe return true; } +bool MaterialCompiler::processMaterialParameters(filamat::MaterialBuilder& builder, + const Config& config) const { + ParametersProcessor parametersProcessor; + bool ok = true; + for (const auto& param : config.getMaterialParameters()) { + ok &= parametersProcessor.process(builder, param.first, param.second); + } + return ok; +} + } // namespace matc diff --git a/tools/matc/src/matc/MaterialCompiler.h b/tools/matc/src/matc/MaterialCompiler.h index 36147376e9a..583f6cce28a 100644 --- a/tools/matc/src/matc/MaterialCompiler.h +++ b/tools/matc/src/matc/MaterialCompiler.h @@ -70,6 +70,8 @@ class MaterialCompiler final: public Compiler { bool compileRawShader(const char* glsl, size_t size, bool isDebug, Config::Output* output, const char* ext) const noexcept; + bool processMaterialParameters(filamat::MaterialBuilder& builder, const Config& config) const; + // Member function pointer type, this is used to implement a Command design // pattern. using MaterialConfigProcessor = bool (MaterialCompiler::*) diff --git a/tools/matc/src/matc/ParametersProcessor.cpp b/tools/matc/src/matc/ParametersProcessor.cpp index eacf7f24aa8..498ea75a35c 100644 --- a/tools/matc/src/matc/ParametersProcessor.cpp +++ b/tools/matc/src/matc/ParametersProcessor.cpp @@ -1251,11 +1251,47 @@ bool ParametersProcessor::process(MaterialBuilder& builder, const JsonishObject& auto fPointer = mParameters[key].callback; bool ok = fPointer(builder, *field); if (!ok) { - std::cerr << "Error while processing material key:\"" << key << "\"" << std::endl; + std::cerr << "Error while processing material json, key:\"" << key << "\"" << std::endl; return false; } } return true; } +bool ParametersProcessor::process(filamat::MaterialBuilder& builder, const std::string& key, const std::string& value) { + if (mParameters.find(key) == mParameters.end()) { + std::cerr << "Ignoring config entry (unknown key): \"" << key << "\"" << std::endl; + return false; + } + + std::unique_ptr var; + switch (mParameters.at(key).rootAssert) { + case JsonishValue::Type::BOOL: { + std::string lower; + std::transform(value.begin(), value.end(), std::back_inserter(lower), ::tolower); + if (lower.empty() || lower == "false" || lower == "f" || lower == "0") { + var = std::make_unique(false); + } + else { + var = std::make_unique(true); + } + break; + } + case JsonishValue::Type::NUMBER: + var = std::make_unique(std::stof(value)); + break; + case JsonishValue::Type::STRING: + var = std::make_unique(value); + break; + } + + auto fPointer = mParameters[key].callback; + bool ok = fPointer(builder, *var); + if (!ok) { + std::cerr << "Error while processing material param, key:\"" << key << "\"" << std::endl; + return false; + } + return true; +} + } // namespace matc diff --git a/tools/matc/src/matc/ParametersProcessor.h b/tools/matc/src/matc/ParametersProcessor.h index ba983c9e75e..a3aa3117d35 100644 --- a/tools/matc/src/matc/ParametersProcessor.h +++ b/tools/matc/src/matc/ParametersProcessor.h @@ -19,6 +19,7 @@ #include #include +#include #include "JsonishLexeme.h" #include "JsonishParser.h" @@ -33,6 +34,7 @@ class ParametersProcessor { ParametersProcessor(); ~ParametersProcessor() = default; bool process(filamat::MaterialBuilder& builder, const JsonishObject& jsonObject); + bool process(filamat::MaterialBuilder& builder, const std::string& key, const std::string& value); private: From 11d2ac1019614b555f4b7577938b83860a988772 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 23 Feb 2024 10:09:13 -0800 Subject: [PATCH 18/22] Add support for protected contexts Protected contexts are now supported by the OpenGLPlatform interface and implemented in EGLPlatform. Protected contexts can read from regular and protected resources but can only write to protected resources (e.g. protected swap chains or textures backed by protected memory. These can be created on Android via AHardwareBuffer and EGLImage for instance). The underlaying EGL implementation must support protected contexts. Switching to a protected context is achieved by using a protected-content SwapChain in Renderer::beginFrame(). A protected-content SwapChain can be created using the new CONFIG_PROTECTED_CONTENT flag at creation time. The OpenGL backend implementation will then use a protected context for rendering until an unprotected SwapChain is used again. The crux of this implementation is to use different VAOs in the different contexts, because those can't be shared between contexts. We also need to synchronize the state with our state cache and ensure VAOs objects are destructed properly in the right context. --- NEW_RELEASE_NOTES.md | 1 + .../backend/platforms/OpenGLPlatform.h | 22 ++++ .../include/backend/platforms/PlatformEGL.h | 19 +++- filament/backend/src/opengl/OpenGLContext.cpp | 57 ++++++++-- filament/backend/src/opengl/OpenGLContext.h | 87 +++++++++++---- filament/backend/src/opengl/OpenGLDriver.cpp | 67 +++++++++--- .../backend/src/opengl/OpenGLPlatform.cpp | 8 ++ .../src/opengl/platforms/PlatformEGL.cpp | 102 ++++++++++++++++-- filament/src/details/Renderer.cpp | 25 ++++- 9 files changed, 328 insertions(+), 60 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 51e62f407df..eae436ad6b7 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -10,3 +10,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). - Add new API `SwapChain::getFrameScheduledCallback` - vulkan: fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779 +- opengl: Add support for protected content swapchains and contexts diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index 63b346fe46d..c89684aa708 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -22,7 +22,9 @@ #include #include +#include +#include #include namespace filament::backend { @@ -139,6 +141,26 @@ class OpenGLPlatform : public Platform { SwapChain* UTILS_NONNULL drawSwapChain, SwapChain* UTILS_NONNULL readSwapChain) noexcept = 0; + /** + * Called by the driver to make the OpenGL context active on the calling thread and bind + * the drawSwapChain to the default render target (FBO) created with createDefaultRenderTarget. + * The context used is either the default context or the protected context. When a context + * change is necessary, the preContextChange and postContextChange callbacks are called, + * before and after the context change respectively. postContextChange is given the index + * of the new context (0 for default and 1 for protected). + * The default implementation just calls makeCurrent(SwapChain*, SwapChain*). + * + * @param drawSwapChain SwapChain to draw to. It must be bound to the default FBO. + * @param readSwapChain SwapChain to read from (for operation like `glBlitFramebuffer`) + * @param preContextChange called before the context changes + * @param postContextChange called after the context changes + */ + virtual void makeCurrent( + SwapChain* UTILS_NONNULL drawSwapChain, + SwapChain* UTILS_NONNULL readSwapChain, + utils::Invocable preContextChange, + utils::Invocable postContextChange) noexcept; + /** * Called by the driver once the current frame finishes drawing. Typically, this should present * the drawSwapChain. This is for example where `eglMakeCurrent()` would be called. diff --git a/filament/backend/include/backend/platforms/PlatformEGL.h b/filament/backend/include/backend/platforms/PlatformEGL.h index 2f26f7341a5..4c88fa3fa44 100644 --- a/filament/backend/include/backend/platforms/PlatformEGL.h +++ b/filament/backend/include/backend/platforms/PlatformEGL.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include #include @@ -97,7 +99,11 @@ class PlatformEGL : public OpenGLPlatform { SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override; SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; + void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; + void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain, + utils::Invocable preContextChange, + utils::Invocable postContextChange) noexcept override; void commit(SwapChain* swapChain) noexcept override; bool canCreateFence() noexcept override; @@ -124,15 +130,22 @@ class PlatformEGL : public OpenGLPlatform { static void clearGlError() noexcept; /** - * Always use this instead of eglMakeCurrent(). + * Always use this instead of eglMakeCurrent(), as it tracks some state. */ EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept; + /** + * Returns true if the swapchain is protected + */ + static bool isSwapChainProtected(Platform::SwapChain const* swapChain) noexcept; + // TODO: this should probably use getters instead. EGLDisplay mEGLDisplay = EGL_NO_DISPLAY; EGLContext mEGLContext = EGL_NO_CONTEXT; + EGLContext mEGLContextProtected = EGL_NO_CONTEXT; EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE; EGLSurface mCurrentReadSurface = EGL_NO_SURFACE; + EGLContext mCurrentContext = EGL_NO_CONTEXT; EGLSurface mEGLDummySurface = EGL_NO_SURFACE; // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; @@ -166,6 +179,10 @@ class PlatformEGL : public OpenGLPlatform { protected: EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; + +private: + EGLBoolean makeCurrent(EGLContext context, + EGLSurface drawSurface, EGLSurface readSurface) noexcept; }; } // namespace filament::backend diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index c710d53bb88..2e6193744a1 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -18,6 +18,7 @@ #include +#include #include // change to true to display all GL extensions in the console on start-up @@ -239,6 +240,40 @@ OpenGLContext::~OpenGLContext() noexcept { delete mTimerQueryFactory; } +void OpenGLContext::destroyWithContext( + size_t index, std::function const& closure) noexcept { + if (index == 0) { + // Note: we only need to delay the destruction of objects on the unprotected context + // (index 0) because the protected context is always immediately destroyed and all its + // active objects and bindings are then automatically destroyed. + // TODO: this is only guaranteed for EGLPlatform, but that's the only one we care about. + mDestroyWithNormalContext.push_back(closure); + } +} + +void OpenGLContext::unbindEverything() noexcept { + // TODO: we're supposed to unbind everything here so that resources don't get + // stuck in this context (contextIndex) when destroyed in the other context. + // However, because EGLPlatform always immediately destroys the protected context (1), + // the bindings will automatically be severed when we switch back to the default context. + // Since bindings now only exist in one context, we don't have a ref-counting issue to + // worry about. +} + +void OpenGLContext::synchronizeStateAndCache(size_t index) noexcept { + + // if we're just switching back to context 0, run all the pending destructors + if (index == 0) { + auto list = std::move(mDestroyWithNormalContext); + for (auto&& fn: list) { + fn(*this); + } + } + + contextIndex = index; + resetState(); +} + void OpenGLContext::setDefaultState() noexcept { // We need to make sure our internal state matches the GL state when we start. // (some of these calls may be unneeded as they might be the gl defaults) @@ -826,19 +861,22 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe #endif } -void OpenGLContext::deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept { - procs.deleteVertexArrays(n, arrays); - // if one of the destroyed VAO is bound, clear the binding. - for (GLsizei i = 0; i < n; ++i) { - if (state.vao.p->vao == arrays[i]) { +void OpenGLContext::deleteVertexArray(GLuint vao) noexcept { + if (UTILS_LIKELY(vao)) { + procs.deleteVertexArrays(1, &vao); + // if the destroyed VAO is bound, clear the binding. + if (state.vao.p->vao[contextIndex] == vao) { bindVertexArray(nullptr); - break; } } } void OpenGLContext::resetState() noexcept { // Force GL state to match the Filament state + + // increase the state version so other parts of the state know to reset + state.age++; + if (state.major > 2) { #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo); @@ -855,11 +893,8 @@ void OpenGLContext::resetState() noexcept { glUseProgram(state.program.use); // state.vao - if (state.vao.p) { - procs.bindVertexArray(state.vao.p->vao); - } else { - bindVertexArray(nullptr); - } + state.vao.p = nullptr; + bindVertexArray(nullptr); // state.raster glFrontFace(state.raster.frontFace); diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index 94b64ba2212..c0f237f34b3 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -29,9 +29,11 @@ #include "GLUtils.h" #include +#include #include #include #include +#include namespace filament::backend { @@ -48,19 +50,29 @@ class OpenGLContext final : public TimerQueryFactoryInterface { struct RenderPrimitive { static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16); - GLuint vao = 0; // 4 + GLuint vao[2] = {}; // 4 GLuint elementArray = 0; // 4 - utils::bitset vertexAttribArray; // 2 + mutable utils::bitset vertexAttribArray; // 2 - // If this version number does not match vertexBufferWithObjects->bufferObjectsVersion, - // then the VAO needs to be updated. + // if this differs from vertexBufferWithObjects->bufferObjectsVersion, this VAO needs to + // be updated (see OpenGLDriver::updateVertexArrayObject()) uint8_t vertexBufferVersion = 0; // 1 + + // if this differs from OpenGLContext::state.age, this VAO needs to + // be updated (see OpenGLDriver::updateVertexArrayObject()) + uint8_t stateVersion = 0; // 1 + + // If this differs from OpenGLContext::state.age, this VAO's name needs to be updated. + // 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 + Handle vertexBufferWithObjects; // 4 GLenum getIndicesType() const noexcept { return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; @@ -138,8 +150,8 @@ class OpenGLContext final : public TimerQueryFactoryInterface { inline void bindFramebuffer(GLenum target, GLuint buffer) noexcept; - inline void enableVertexAttribArray(GLuint index) noexcept; - inline void disableVertexAttribArray(GLuint index) noexcept; + inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; + inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; inline void enable(GLenum cap) noexcept; inline void disable(GLenum cap) noexcept; inline void frontFace(GLenum mode) noexcept; @@ -161,7 +173,9 @@ class OpenGLContext final : public TimerQueryFactoryInterface { inline void depthRange(GLclampf near, GLclampf far) noexcept; void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept; - void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept; + void deleteVertexArray(GLuint vao) noexcept; + + void destroyWithContext(size_t index, std::function const& closure) noexcept; // glGet*() values struct Gets { @@ -305,8 +319,19 @@ class OpenGLContext final : public TimerQueryFactoryInterface { FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; } + // This is the index of the context in use. Must be 0 or 1. This is used to manange the + // OpenGL name of ContainerObjects within each context. + uint32_t contextIndex = 0; + // Try to keep the State structure sorted by data-access patterns struct State { + State() noexcept = default; + // make sure we don't copy this state by accident + State(State const& rhs) = delete; + State(State&& rhs) noexcept = delete; + State& operator=(State const& rhs) = delete; + State& operator=(State&& rhs) noexcept = delete; + GLint major = 0; GLint minor = 0; @@ -411,6 +436,7 @@ class OpenGLContext final : public TimerQueryFactoryInterface { vec4gli viewport { 0 }; vec2glf depthRange { 0.0f, 1.0f }; } window; + uint8_t age = 0; } state; struct Procs { @@ -430,10 +456,14 @@ class OpenGLContext final : public TimerQueryFactoryInterface { void (* maxShaderCompilerThreadsKHR)(GLuint count); } procs{}; + void unbindEverything() noexcept; + void synchronizeStateAndCache(size_t index) noexcept; + private: ShaderModel mShaderModel = ShaderModel::MOBILE; FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; TimerQueryFactoryInterface* mTimerQueryFactory = nullptr; + std::vector> mDestroyWithNormalContext; const std::array, sizeof(bugs)> mBugDatabase{{ { bugs.disable_glFlush, @@ -645,11 +675,26 @@ void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept { void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept { RenderPrimitive* vao = p ? const_cast(p) : &mDefaultVAO; update_state(state.vao.p, vao, [&]() { - procs.bindVertexArray(vao->vao); + + // See if we need to create a name for this VAO on the fly, this would happen if: + // - we're not the default VAO, because its name is always 0 + // - our name is 0, this could happen if this VAO was created in the "other" context + // - the nameVersion is out of date *and* we're on the protected context, in this case: + // - the name must be stale from a previous use of this context because we always + // destroy the protected context when we're done with it. + bool const recreateVaoName = p != &mDefaultVAO && + ((vao->vao[contextIndex] == 0) || + (vao->nameVersion != state.age && contextIndex == 1)); + if (UTILS_UNLIKELY(recreateVaoName)) { + vao->nameVersion = state.age; + procs.genVertexArrays(1, &vao->vao[contextIndex]); + } + + procs.bindVertexArray(vao->vao[contextIndex]); // update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER); state.buffers.genericBinding[targetIndex] = vao->elementArray; - if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding)) { + if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) { // This shouldn't be needed, but it looks like some drivers don't do the implicit // glBindBuffer(). glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray); @@ -733,20 +778,22 @@ void OpenGLContext::useProgram(GLuint program) noexcept { }); } -void OpenGLContext::enableVertexAttribArray(GLuint index) noexcept { - assert_invariant(state.vao.p); - assert_invariant(index < state.vao.p->vertexAttribArray.size()); - if (UTILS_UNLIKELY(!state.vao.p->vertexAttribArray[index])) { - state.vao.p->vertexAttribArray.set(index); +void OpenGLContext::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { + assert_invariant(rp); + assert_invariant(index < rp->vertexAttribArray.size()); + bool const force = rp->stateVersion != state.age; + if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) { + rp->vertexAttribArray.set(index); glEnableVertexAttribArray(index); } } -void OpenGLContext::disableVertexAttribArray(GLuint index) noexcept { - assert_invariant(state.vao.p); - assert_invariant(index < state.vao.p->vertexAttribArray.size()); - if (UTILS_UNLIKELY(state.vao.p->vertexAttribArray[index])) { - state.vao.p->vertexAttribArray.unset(index); +void OpenGLContext::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept { + assert_invariant(rp); + assert_invariant(index < rp->vertexAttribArray.size()); + bool const force = rp->stateVersion != state.age; + if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) { + rp->vertexAttribArray.unset(index); glDisableVertexAttribArray(index); } } diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index a177f0d05bc..a4a0ea2b3f0 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -523,8 +523,13 @@ void OpenGLDriver::createRenderPrimitiveR(Handle rph, rp->gl.vertexBufferWithObjects = vbh; rp->type = pt; - gl.procs.genVertexArrays(1, &rp->gl.vao); + // create a name for this VAO in the current context + gl.procs.genVertexArrays(1, &rp->gl.vao[gl.contextIndex]); + // this implies our name is up-to-date + rp->gl.nameVersion = gl.state.age; + + // binding the VAO will actually create it gl.bindVertexArray(&rp->gl); // Note: we don't update the vertex buffer bindings in the VAO just yet, we will do that @@ -864,22 +869,20 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t id, } void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) { + // NOTE: this is called from draw() and must be as efficient as possible. auto& gl = mContext; - // NOTE: this is called from draw() and must be as efficient as possible. - if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) { // The VAO for the given render primitive must already be bound. -#ifndef NDEBUG GLint vaoBinding; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding); - assert_invariant(vaoBinding == (GLint)rp->gl.vao); -#endif - rp->gl.vertexBufferVersion = vb->bufferObjectsVersion; - } else { - // if we don't have OES_vertex_array_object, we never update the buffer version so - // that it's always reset in draw + assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]); + } + + if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion && + rp->gl.stateVersion == gl.state.age)) { + return; } GLVertexBufferInfo const* const vbi = handle_cast(vb->vbih); @@ -914,7 +917,7 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer glVertexAttribPointer(index, size, type, normalized, stride, pointer); } - gl.enableVertexAttribArray(GLuint(i)); + gl.enableVertexAttribArray(&rp->gl, GLuint(i)); } else { // In some OpenGL implementations, we must supply a properly-typed placeholder for // every integer input that is declared in the vertex shader. @@ -937,9 +940,17 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer glVertexAttrib4f(GLuint(i), 0, 0, 0, 0); } - gl.disableVertexAttribArray(GLuint(i)); + gl.disableVertexAttribArray(&rp->gl, GLuint(i)); } } + + rp->gl.stateVersion = gl.state.age; + if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) { + rp->gl.vertexBufferVersion = vb->bufferObjectsVersion; + } else { + // if we don't have OES_vertex_array_object, we never update the buffer version so + // that it's always reset in draw + } } void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, @@ -1484,7 +1495,20 @@ void OpenGLDriver::destroyRenderPrimitive(Handle rph) { if (rph) { auto& gl = mContext; GLRenderPrimitive const* rp = handle_cast(rph); - gl.deleteVertexArrays(1, &rp->gl.vao); + gl.deleteVertexArray(rp->gl.vao[gl.contextIndex]); + + // If we have a name in the "other" context, we need to schedule the destroy for + // later, because it can't be done here. VAOs are "container objects" and are not + // shared between contexts. + size_t const otherContextIndex = 1 - gl.contextIndex; + GLuint const nameInOtherContext = rp->gl.vao[otherContextIndex]; + if (UTILS_UNLIKELY(nameInOtherContext)) { + gl.destroyWithContext(otherContextIndex, + [name = nameInOtherContext](OpenGLContext& gl) { + gl.deleteVertexArray(name); + }); + } + destruct(rph, rp); } } @@ -2033,7 +2057,18 @@ void OpenGLDriver::makeCurrent(Handle schDraw, Handle GLSwapChain* scDraw = handle_cast(schDraw); GLSwapChain* scRead = handle_cast(schRead); - mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain); + + mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain, + [this]() { + // OpenGL context is about to change, unbind everything + mContext.unbindEverything(); + }, + [this](size_t index) { + // OpenGL context has changed, resynchronize the state with the cache + mContext.synchronizeStateAndCache(index); + slog.d << "*** OpenGL context change : " << (index ? "protected" : "default") << io::endl; + }); + mCurrentDrawSwapChain = scDraw; // From the GL spec for glViewport and glScissor: @@ -3776,9 +3811,7 @@ void OpenGLDriver::draw(PipelineState state, Handle rph, // If necessary, mutate the bindings in the VAO. GLVertexBuffer const* const glvb = handle_cast(vb); - if (UTILS_UNLIKELY(rp->gl.vertexBufferVersion != glvb->bufferObjectsVersion)) { - updateVertexArrayObject(rp, glvb); - } + updateVertexArrayObject(rp, glvb); setRasterState(state.rasterState); setStencilState(state.stencilState); diff --git a/filament/backend/src/opengl/OpenGLPlatform.cpp b/filament/backend/src/opengl/OpenGLPlatform.cpp index d3aa2e6f99b..9fdc216d28e 100644 --- a/filament/backend/src/opengl/OpenGLPlatform.cpp +++ b/filament/backend/src/opengl/OpenGLPlatform.cpp @@ -24,7 +24,10 @@ #include +#include +#include #include +#include namespace filament::backend { @@ -35,6 +38,11 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform, OpenGLPlatform::~OpenGLPlatform() noexcept = default; +void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain, + utils::Invocable, utils::Invocable) noexcept { + makeCurrent(drawSwapChain, readSwapChain); +} + bool OpenGLPlatform::isProtectedContextSupported() const noexcept { return false; } diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index ab01df11223..e20dcdc500a 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -281,7 +282,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon } } - if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) { + if (UTILS_UNLIKELY(makeCurrent(mEGLContext, mEGLDummySurface, mEGLDummySurface) == EGL_FALSE)) { // eglMakeCurrent failed logEglError("eglMakeCurrent"); goto error; @@ -305,9 +306,13 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon if (mEGLContext) { eglDestroyContext(mEGLDisplay, mEGLContext); } + if (mEGLContextProtected) { + eglDestroyContext(mEGLDisplay, mEGLContextProtected); + } mEGLDummySurface = EGL_NO_SURFACE; mEGLContext = EGL_NO_CONTEXT; + mEGLContextProtected = EGL_NO_CONTEXT; eglTerminate(mEGLDisplay); eglReleaseThread(); @@ -358,10 +363,21 @@ void PlatformEGL::releaseContext() noexcept { } EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { - if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) { - mCurrentDrawSurface = drawSurface; - mCurrentReadSurface = readSurface; - return eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext); + return makeCurrent(mCurrentContext, drawSurface, readSurface); +} + +EGLBoolean PlatformEGL::makeCurrent(EGLContext context, + EGLSurface drawSurface, EGLSurface readSurface) noexcept { + if (UTILS_UNLIKELY((mCurrentContext != context || + drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) { + EGLBoolean const success = eglMakeCurrent( + mEGLDisplay, drawSurface, readSurface, context); + if (success) { + mCurrentDrawSurface = drawSurface; + mCurrentReadSurface = readSurface; + mCurrentContext = context; + } + return success; } return EGL_TRUE; } @@ -373,6 +389,9 @@ void PlatformEGL::terminate() noexcept { eglDestroySurface(mEGLDisplay, mEGLDummySurface); } eglDestroyContext(mEGLDisplay, mEGLContext); + if (mEGLContextProtected != EGL_NO_CONTEXT) { + eglDestroyContext(mEGLDisplay, mEGLContextProtected); + } for (auto context : mAdditionalContexts) { eglDestroyContext(mEGLDisplay, context); } @@ -449,6 +468,14 @@ bool PlatformEGL::isSRGBSwapChainSupported() const noexcept { return ext.egl.KHR_gl_colorspace; } +bool PlatformEGL::isSwapChainProtected(Platform::SwapChain const* swapChain) noexcept { + if (swapChain) { + SwapChainEGL const* const sc = static_cast(swapChain); + return bool(sc->flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT); + } + return false; +} + Platform::SwapChain* PlatformEGL::createSwapChain( void* nativeWindow, uint64_t flags) noexcept { @@ -556,7 +583,7 @@ void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { if (swapChain) { SwapChainEGL const* const sc = static_cast(swapChain); if (sc->sur != EGL_NO_SURFACE) { - makeCurrent(mEGLDummySurface, mEGLDummySurface); + makeCurrent(mCurrentContext, mEGLDummySurface, mEGLDummySurface); eglDestroySurface(mEGLDisplay, sc->sur); delete sc; } @@ -564,13 +591,68 @@ void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { } void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain, - Platform::SwapChain* readSwapChain) noexcept { + Platform::SwapChain* readSwapChain) noexcept { SwapChainEGL const* const dsc = static_cast(drawSwapChain); SwapChainEGL const* const rsc = static_cast(readSwapChain); - if (UTILS_UNLIKELY(dsc->sur == EGL_NO_SURFACE && rsc->sur == EGL_NO_SURFACE)) { - return; + makeCurrent(mEGLContext, dsc->sur, rsc->sur); +} + +void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain, + Platform::SwapChain* readSwapChain, + utils::Invocable preContextChange, + utils::Invocable postContextChange) noexcept { + SwapChainEGL const* const dsc = static_cast(drawSwapChain); + SwapChainEGL const* const rsc = static_cast(readSwapChain); + EGLContext context = mEGLContext; + if (ext.egl.EXT_protected_content) { + bool const swapChainProtected = PlatformEGL::isSwapChainProtected(dsc); + if (UTILS_UNLIKELY(swapChainProtected)) { + // we need a protected context + if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) { + // we don't have one, create it! + EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig; + Config protectedContextAttribs{ mContextAttribs }; + protectedContextAttribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE; + mEGLContextProtected = eglCreateContext(mEGLDisplay, config, mEGLContext, + protectedContextAttribs.data()); + if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) { + // couldn't create the protected context + logEglError("eglCreateContext[EGL_PROTECTED_CONTENT_EXT]"); + ext.egl.EXT_protected_content = false; + goto error; + } + } + context = mEGLContextProtected; + error: ; + } + + bool const contextChange = context != mCurrentContext; + if (UTILS_UNLIKELY(contextChange)) { + preContextChange(); + EGLBoolean const success = makeCurrent(context, dsc->sur, rsc->sur); + if (UTILS_UNLIKELY(!success)) { + logEglError("PlatformEGL::makeCurrent"); + if (mEGLContextProtected != EGL_NO_CONTEXT) { + eglDestroyContext(mEGLDisplay, mEGLContextProtected); + mEGLContextProtected = EGL_NO_CONTEXT; + } + context = mEGLContext; + } + if (UTILS_LIKELY(!swapChainProtected && mEGLContextProtected != EGL_NO_CONTEXT)) { + // We don't need the protected context anymore, unbind and destroy right away. + eglDestroyContext(mEGLDisplay, mEGLContextProtected); + mEGLContextProtected = EGL_NO_CONTEXT; + } + size_t const contextIndex = (context == mEGLContext) ? 0 : 1; + postContextChange(contextIndex); + return; + } + } + + EGLBoolean const success = makeCurrent(context, dsc->sur, rsc->sur); + if (UTILS_UNLIKELY(!success)) { + logEglError("PlatformEGL::makeCurrent"); } - makeCurrent(dsc->sur, rsc->sur); } void PlatformEGL::commit(Platform::SwapChain* swapChain) noexcept { diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 65f2bbc48de..41b43450f43 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -65,6 +65,10 @@ #include #include +#ifdef __ANDROID__ +#include +#endif + // this helps visualize what dynamic-scaling is doing #define DEBUG_DYNAMIC_SCALING false @@ -229,6 +233,22 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN SYSTRACE_CALL(); + +#if defined(__ANDROID__) + char scratch[PROP_VALUE_MAX + 1]; + int length = __system_property_get("debug.filament.protected", scratch); + if (swapChain && length > 0) { + uint64_t flags = swapChain->getFlags(); + bool value = bool(atoi(scratch)); + if (value) { + flags |= SwapChain::CONFIG_PROTECTED_CONTENT; + } else { + flags &= ~SwapChain::CONFIG_PROTECTED_CONTENT; + } + swapChain->recreateWithNewFlags(mEngine, flags); + } +#endif + // get the timestamp as soon as possible using namespace std::chrono; const steady_clock::time_point now{ steady_clock::now() }; @@ -549,6 +569,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { const bool needsAlphaChannel = (mSwapChain && mSwapChain->isTransparent()) || blendModeTranslucent; + const bool isProtectedContent = mSwapChain && mSwapChain->isProtected(); + // asSubpass is disabled with TAA (although it's supported) because performance was degraded // on qualcomm hardware -- we might need a backend dependent toggle at some point const PostProcessManager::ColorGradingConfig colorGradingConfig{ @@ -695,7 +717,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { * Frame graph */ - FrameGraph fg(engine.getResourceAllocator()); + FrameGraph fg(engine.getResourceAllocator(), + isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED); auto& blackboard = fg.getBlackboard(); /* From 7eb3b2aaf56cbd849cea266302de25ef0ab1ba56 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 6 Mar 2024 13:01:09 -0800 Subject: [PATCH 19/22] Release Filament 1.50.5 --- NEW_RELEASE_NOTES.md | 4 ---- README.md | 4 ++-- RELEASE_NOTES.md | 6 ++++++ android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index eae436ad6b7..4a1a9c7fa7e 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -7,7 +7,3 @@ for next branch cut* header. appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut - -- Add new API `SwapChain::getFrameScheduledCallback` -- vulkan: fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779 -- opengl: Add support for protected content swapchains and contexts diff --git a/README.md b/README.md index 66f0976391a..38792b39368 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.50.4' + implementation 'com.google.android.filament:filament-android:1.50.5' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.50.4' +pod 'Filament', '~> 1.50.5' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f538299884e..64ab3f47697 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,12 @@ 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.50.6 + +- Add new API `SwapChain::getFrameScheduledCallback` +- vulkan: fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779 +- opengl: Add support for protected content swapchains and contexts + ## v1.50.5 - android: NDK 26.1.10909125 is used by default diff --git a/android/gradle.properties b/android/gradle.properties index 34340a20b8a..0c82646753c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.50.4 +VERSION_NAME=1.50.5 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index df0911beb7d..29df33ce0f0 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.50.4" + spec.version = "1.50.5" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.50.4/filament-v1.50.4-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.50.5/filament-v1.50.5-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 52b661bd10e..ff16aeadc9d 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.50.4", + "version": "1.50.5", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 4a465450f18e00df333f56314aa35b2144559009 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 6 Mar 2024 13:01:20 -0800 Subject: [PATCH 20/22] Bump version to 1.50.6 --- README.md | 4 ++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 38792b39368..3bff570f9c5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.50.5' + implementation 'com.google.android.filament:filament-android:1.50.6' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.50.5' +pod 'Filament', '~> 1.50.6' ``` ### Snapshots diff --git a/android/gradle.properties b/android/gradle.properties index 0c82646753c..5dd1904abe2 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.50.5 +VERSION_NAME=1.50.6 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 29df33ce0f0..e92cedb1b49 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.50.5" + spec.version = "1.50.6" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.50.5/filament-v1.50.5-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.50.6/filament-v1.50.6-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index ff16aeadc9d..582f75eec43 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.50.5", + "version": "1.50.6", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 86d2e118016715fba45ddbf6653cb8e762664e85 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 6 Mar 2024 17:09:04 -0800 Subject: [PATCH 21/22] Try fixing windows artifact output again (#7637) The [previous] change assumed that the shell is powershell, but the shell is actually commands (cmd). The [previous] change assumed we're in the root directory. This assumption is probably correct [ref]. So we keep that change. [ref]: https://github.com/google/filament/blob/main/build/windows/build-github.bat#L134 [previous]: 373c5710b11bec4ee27e094b255e4eb4c4ed9915 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf7211a1039..2312795605b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -205,7 +205,7 @@ jobs: TAG: ${{ steps.git_ref.outputs.tag }} run: | build\windows\build-github.bat release - move out\filament-windows.tgz out\filament-$Env:TAG-windows.tgz + move out\filament-windows.tgz out\filament-%TAG%-windows.tgz shell: cmd - uses: actions/github-script@v6 env: From ca27bb58bf836a7b2a4c3ca323a484d3ab224e90 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 8 Mar 2024 10:32:38 -0800 Subject: [PATCH 22/22] Metal: change shader compilation pool size to 1 (#7639) --- filament/backend/src/metal/MetalShaderCompiler.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index 7af9c65944e..713133b1db4 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -77,7 +77,7 @@ bool isReady() const noexcept { } void MetalShaderCompiler::init() noexcept { - const uint32_t poolSize = 2; + const uint32_t poolSize = 1; mCompilerThreadPool.init(poolSize, []() {}, []() {}); }