From dea345d28e96f810827f8a4ff0a9846a70726983 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Mon, 11 Mar 2024 15:27:46 -0700 Subject: [PATCH 01/29] geometry: fix mikktspace wrapper (#7651) - Fix missing attributes in TangentSpaceMesh - Fix missing reference in MikktspaceImpl.cpp --- libs/geometry/src/MikktspaceImpl.cpp | 78 +++++++++++++++----- libs/geometry/src/MikktspaceImpl.h | 18 ++++- libs/geometry/src/TangentSpaceMesh.cpp | 19 +++-- libs/geometry/src/TangentSpaceMeshInternal.h | 6 +- 4 files changed, 93 insertions(+), 28 deletions(-) diff --git a/libs/geometry/src/MikktspaceImpl.cpp b/libs/geometry/src/MikktspaceImpl.cpp index 19942ada3bd..5c5a1b582b1 100644 --- a/libs/geometry/src/MikktspaceImpl.cpp +++ b/libs/geometry/src/MikktspaceImpl.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -82,7 +83,7 @@ void MikktspaceImpl::setTSpaceBasic(SMikkTSpaceContext const* context, float con // TODO: packTangentFrame actually changes the orientation of b. quatf const quat = mat3f::packTangentFrame({t, b, n}, sizeof(int32_t)); - auto output = wrapper->mOutputData; + auto& output = wrapper->mOutputData; auto const& EMPTY_ELEMENT = wrapper->EMPTY_ELEMENT; size_t const outputCurSize = output.size(); @@ -92,15 +93,15 @@ void MikktspaceImpl::setTSpaceBasic(SMikkTSpaceContext const* context, float con uint8_t* cursor = output.data() + outputCurSize; - *((float3*) (cursor)) = pos; - *((float2*) (cursor + 12)) = uv; - *((quatf*) (cursor + 20)) = quat; + *((float3*) (cursor + POS_OFFSET)) = pos; + *((float2*) (cursor + UV_OFFSET)) = uv; + *((quatf*) (cursor + TBN_OFFSET)) = quat; - cursor += 36; - for (auto [attribArray, attribStride, attribSize]: wrapper->mInputAttribArrays) { - uint8_t const* input = pointerAdd(attribArray, vertInd, attribStride); - memcpy(cursor, input, attribSize); - cursor += attribSize; + cursor += BASE_OUTPUT_SIZE; + for (auto const& inputAttrib: wrapper->mInputAttribArrays) { + uint8_t const* input = pointerAdd(inputAttrib.data, vertInd, inputAttrib.stride); + memcpy(cursor, input, inputAttrib.size); + cursor += inputAttrib.size; } } @@ -125,7 +126,12 @@ MikktspaceImpl::MikktspaceImpl(const TangentSpaceMeshInput* input) noexcept for (auto attrib: input->getAuxAttributes()) { size_t const attribSize =input->attributeSize(attrib); mOutputElementSize += attribSize; - mInputAttribArrays.push_back({input->raw(attrib), input->stride(attrib), attribSize}); + mInputAttribArrays.push_back({ + .attrib = attrib, + .data = input->raw(attrib), + .stride = input->stride(attrib), + .size = attribSize, + }); } mOutputData.reserve(mFaceCount * 3 * mOutputElementSize); @@ -156,12 +162,12 @@ void MikktspaceImpl::run(TangentSpaceMeshOutput* output) noexcept { genTangSpaceDefault(&context); size_t oVertexCount = mOutputData.size() / mOutputElementSize; - - std::vector remap(oVertexCount); + std::vector remap; + remap.resize(oVertexCount); size_t vertexCount = meshopt_generateVertexRemap(remap.data(), NULL, remap.size(), mOutputData.data(), oVertexCount, mOutputElementSize); - std::vector newVertices(vertexCount); + std::vector newVertices(vertexCount * mOutputElementSize); meshopt_remapVertexBuffer((void*) newVertices.data(), mOutputData.data(), oVertexCount, mOutputElementSize, remap.data()); @@ -172,10 +178,48 @@ void MikktspaceImpl::run(TangentSpaceMeshOutput* output) noexcept { float2* outUVs = output->uvs().allocate(vertexCount); quatf* outQuats = output->tspace().allocate(vertexCount); - for (size_t i = 0; i < vertexCount; ++i) { - outPositions[i] = newVertices[i].position; - outUVs[i] = newVertices[i].uv; - outQuats[i] = newVertices[i].tangentSpace; + uint8_t* const verts = newVertices.data(); + + std::vector> attributes; + + for (auto const& inputAttrib: mInputAttribArrays) { + auto const attrib = inputAttrib.attrib; + switch(attrib) { + case AttributeImpl::UV1: + attributes.push_back( + {attrib, output->data(attrib).allocate(vertexCount), + inputAttrib.size}); + break; + case AttributeImpl::COLORS: + attributes.push_back( + {attrib, output->data(attrib).allocate(vertexCount), + inputAttrib.size}); + break; + case AttributeImpl::JOINTS: + attributes.push_back( + {attrib, output->data(attrib).allocate(vertexCount), + inputAttrib.size}); + break; + case AttributeImpl::WEIGHTS: + attributes.push_back( + {attrib, output->data(attrib).allocate(vertexCount), + inputAttrib.size}); + break; + default: + PANIC_POSTCONDITION("Unexpected attribute=%d", (int) inputAttrib.attrib); + } + } + + for (size_t i = 0, vi=0; i < vertexCount; ++i, vi+=mOutputElementSize) { + outPositions[i] = *((float3*) (verts + vi + POS_OFFSET)); + outUVs[i] = *((float2*) (verts + vi + UV_OFFSET)); + outQuats[i] = *((quatf*) (verts + vi + TBN_OFFSET)); + + uint8_t* cursor = verts + vi + BASE_OUTPUT_SIZE; + for (auto const [attrib, outdata, size] : attributes) { + memcpy((uint8_t*) outdata + (i * size), cursor, size); + cursor += size; + } } output->vertexCount = vertexCount; diff --git a/libs/geometry/src/MikktspaceImpl.h b/libs/geometry/src/MikktspaceImpl.h index 84de57f0032..6add3027faf 100644 --- a/libs/geometry/src/MikktspaceImpl.h +++ b/libs/geometry/src/MikktspaceImpl.h @@ -48,7 +48,14 @@ class MikktspaceImpl { private: // sizeof(float3 + float2 + quatf) (pos, uv, tangent) - static constexpr size_t const BASE_OUTPUT_SIZE = 36; + static constexpr size_t const FLOAT3_SIZE = sizeof(float3); + static constexpr size_t const FLOAT2_SIZE = sizeof(float2); + static constexpr size_t const QUATF_SIZE = sizeof(quatf); + + static constexpr size_t const POS_OFFSET = 0; + static constexpr size_t const UV_OFFSET = FLOAT3_SIZE; + static constexpr size_t const TBN_OFFSET = FLOAT3_SIZE + FLOAT2_SIZE; + static constexpr size_t const BASE_OUTPUT_SIZE = FLOAT3_SIZE + FLOAT2_SIZE + QUATF_SIZE; static int getNumFaces(SMikkTSpaceContext const* context) noexcept; static int getNumVerticesOfFace(SMikkTSpaceContext const* context, int const iFace) noexcept; @@ -74,7 +81,14 @@ class MikktspaceImpl { size_t const mUVStride; uint8_t const* mTriangles; bool mIsTriangle16; - std::vector> mInputAttribArrays; + + struct InputAttribute { + AttributeImpl attrib; + uint8_t const* data; + size_t stride; + size_t size; + }; + std::vector mInputAttribArrays; size_t mOutputElementSize; std::vector mOutputData; diff --git a/libs/geometry/src/TangentSpaceMesh.cpp b/libs/geometry/src/TangentSpaceMesh.cpp index 836c9171a64..bb4a74ab105 100644 --- a/libs/geometry/src/TangentSpaceMesh.cpp +++ b/libs/geometry/src/TangentSpaceMesh.cpp @@ -503,6 +503,14 @@ void lengyelMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* o output->triangles16.borrow(triangles16); } +void auxImpl(TangentSpaceMeshInput::AttributeMap& attributeData, AttributeImpl attribute, + InData data, size_t stride) noexcept { + attributeData[attribute] = { + data, + stride ? stride : TangentSpaceMeshInput::attributeSize(attribute), + }; +} + } // anonymous namespace Builder::Builder() noexcept @@ -527,27 +535,27 @@ Builder& Builder::vertexCount(size_t vertexCount) noexcept { } Builder& Builder::normals(float3 const* normals, size_t stride) noexcept { - mMesh->mInput->attributeData[AttributeImpl::NORMALS] = { normals, stride }; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::NORMALS, normals, stride); return *this; } Builder& Builder::uvs(float2 const* uvs, size_t stride) noexcept { - mMesh->mInput->attributeData[AttributeImpl::UV0] = { uvs, stride }; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::UV0, uvs, stride); return *this; } Builder& Builder::positions(float3 const* positions, size_t stride) noexcept { - mMesh->mInput->attributeData[AttributeImpl::POSITIONS] = { positions, stride }; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::POSITIONS, positions, stride); return *this; } Builder& Builder::tangents(float4 const* tangents, size_t stride) noexcept { - mMesh->mInput->attributeData[AttributeImpl::TANGENTS] = { tangents, stride }; + auxImpl(mMesh->mInput->attributeData, AttributeImpl::TANGENTS, tangents, stride); return *this; } Builder& Builder::aux(AuxAttribute attribute, InData data, size_t stride) noexcept { - mMesh->mInput->attributeData[static_cast(attribute)] = { data, stride }; + auxImpl(mMesh->mInput->attributeData, static_cast(attribute), data, stride); return *this; } @@ -646,7 +654,6 @@ size_t TangentSpaceMesh::getVertexCount() const noexcept { void TangentSpaceMesh::getPositions(float3* positions, size_t stride) const { auto inPositions = mInput->positions(); - ASSERT_PRECONDITION(inPositions, "Must provide input positions"); stride = stride ? stride : sizeof(decltype(*positions)); auto const& outPositions = mOutput->positions(); diff --git a/libs/geometry/src/TangentSpaceMeshInternal.h b/libs/geometry/src/TangentSpaceMeshInternal.h index e9f8a2ee4ac..fb83f8eaa97 100644 --- a/libs/geometry/src/TangentSpaceMeshInternal.h +++ b/libs/geometry/src/TangentSpaceMeshInternal.h @@ -150,8 +150,6 @@ class InternalArray { using ArrayType = std::variant, InternalArray, InternalArray, InternalArray, InternalArray, InternalArray>; -using AttributeMap = std::unordered_map; - ArrayType toArray(AttributeImpl attribute) { switch (attribute) { case AttributeImpl::UV1: @@ -178,6 +176,8 @@ ArrayType toArray(AttributeImpl attribute) { } // namespace struct TangentSpaceMeshInput { + using AttributeMap = std::unordered_map; + size_t vertexCount = 0; ushort3 const* triangles16 = nullptr; uint3 const* triangles32 = nullptr; @@ -357,7 +357,7 @@ struct TangentSpaceMeshOutput { return std::get>(attributeData[attrib]); } - void passthrough(AttributeMap const& inAttributeMap, + void passthrough(TangentSpaceMeshInput::AttributeMap const& inAttributeMap, std::vector const& attributes) { auto const borrow = [&inAttributeMap, this](AttributeImpl attrib) { auto ref = inAttributeMap.find(attrib); From 6d7eaf31d347735922d85027e548fbd8c407c9a5 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Mon, 11 Mar 2024 19:50:54 -0700 Subject: [PATCH 02/29] Add multiview filamat for default materials (#7644) Add prebuilt materials for the engine default materials. They'll be selected for multiview stereoscopic implementation. --- filament/CMakeLists.txt | 38 ++++++++++++++++++-------- filament/src/details/Engine.cpp | 14 ++++++++-- filament/src/details/Skybox.cpp | 11 ++++++-- libs/filamat/src/GLSLPostProcessor.cpp | 1 + libs/filamat/src/MaterialBuilder.cpp | 2 +- libs/gltfio/src/JitShaderProvider.cpp | 1 + 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 25318ec0fae..66b78307cc8 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -256,6 +256,11 @@ set(MATERIAL_FL0_SRCS src/materials/skybox.mat ) +set(MATERIAL_MULTIVIEW_SRCS + src/materials/defaultMaterial.mat + src/materials/skybox.mat +) + # Embed the binary resource blob for materials. get_resgen_vars(${RESOURCE_DIR} materials) list(APPEND PRIVATE_HDRS ${RESGEN_HEADER}) @@ -315,30 +320,41 @@ foreach (mat_src ${MATERIAL_SRCS}) get_filename_component(localname "${mat_src}" NAME_WE) get_filename_component(fullname "${mat_src}" ABSOLUTE) set(output_path "${MATERIAL_DIR}/${localname}.filamat") + add_custom_command( + OUTPUT ${output_path} + COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} + MAIN_DEPENDENCY ${fullname} + DEPENDS matc + COMMENT "Compiling material ${fullname} to ${output_path}" + ) + list(APPEND MATERIAL_BINS ${output_path}) list(FIND MATERIAL_FL0_SRCS ${mat_src} index) if (${index} GREATER -1 AND FILAMENT_ENABLE_FEATURE_LEVEL_0) - string(REGEX REPLACE "[.]filamat$" "0.filamat" output_path0 ${output_path}) + string(REGEX REPLACE "[.]filamat$" "_fl0.filamat" output_path_fl0 ${output_path}) add_custom_command( - OUTPUT ${output_path} ${output_path0} - COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} - COMMAND matc ${MATC_BASE_FLAGS} -PfeatureLevel=0 -o ${output_path0} ${fullname} + OUTPUT ${output_path_fl0} + COMMAND matc ${MATC_BASE_FLAGS} -PfeatureLevel=0 -o ${output_path_fl0} ${fullname} MAIN_DEPENDENCY ${fullname} DEPENDS matc - COMMENT "Compiling material ${fullname} to ${output_path} and ${output_path0}" + COMMENT "Compiling material ${fullname} to ${output_path_fl0}" ) - list(APPEND MATERIAL_BINS ${output_path0}) - else () + list(APPEND MATERIAL_BINS ${output_path_fl0}) + endif () + + list(FIND MATERIAL_MULTIVIEW_SRCS ${mat_src} index) + if (${index} GREATER -1) + string(REGEX REPLACE "[.]filamat$" "_multiview.filamat" output_path_multiview ${output_path}) add_custom_command( - OUTPUT ${output_path} - COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} + OUTPUT ${output_path_multiview} + COMMAND matc ${MATC_BASE_FLAGS} -PstereoscopicType=multiview -o ${output_path_multiview} ${fullname} MAIN_DEPENDENCY ${fullname} DEPENDS matc - COMMENT "Compiling material ${fullname} to ${output_path}" + COMMENT "Compiling material ${fullname} to ${output_path_multiview}" ) + list(APPEND MATERIAL_BINS ${output_path_multiview}) endif () - list(APPEND MATERIAL_BINS ${output_path}) endforeach() # Additional dependencies on included files for materials diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index dcf1521055a..97c22b90c10 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -339,7 +339,7 @@ void FEngine::init() { if (UTILS_UNLIKELY(mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0)) { FMaterial::DefaultMaterialBuilder defaultMaterialBuilder; defaultMaterialBuilder.package( - MATERIALS_DEFAULTMATERIAL0_DATA, MATERIALS_DEFAULTMATERIAL0_SIZE); + MATERIALS_DEFAULTMATERIAL_FL0_DATA, MATERIALS_DEFAULTMATERIAL_FL0_SIZE); mDefaultMaterial = downcast(defaultMaterialBuilder.build(*const_cast(this))); } else #endif @@ -347,8 +347,16 @@ void FEngine::init() { mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this)); FMaterial::DefaultMaterialBuilder defaultMaterialBuilder; - defaultMaterialBuilder.package( - MATERIALS_DEFAULTMATERIAL_DATA, MATERIALS_DEFAULTMATERIAL_SIZE); + switch (mConfig.stereoscopicType) { + case StereoscopicType::INSTANCED: + defaultMaterialBuilder.package( + MATERIALS_DEFAULTMATERIAL_DATA, MATERIALS_DEFAULTMATERIAL_SIZE); + break; + case StereoscopicType::MULTIVIEW: + defaultMaterialBuilder.package( + MATERIALS_DEFAULTMATERIAL_MULTIVIEW_DATA, MATERIALS_DEFAULTMATERIAL_MULTIVIEW_SIZE); + break; + } mDefaultMaterial = downcast(defaultMaterialBuilder.build(*const_cast(this))); float3 dummyPositions[1] = {}; diff --git a/filament/src/details/Skybox.cpp b/filament/src/details/Skybox.cpp index bfdb94b3005..7fe6291bc8c 100644 --- a/filament/src/details/Skybox.cpp +++ b/filament/src/details/Skybox.cpp @@ -120,11 +120,18 @@ FMaterial const* FSkybox::createMaterial(FEngine& engine) { Material::Builder builder; #ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0 if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == Engine::FeatureLevel::FEATURE_LEVEL_0)) { - builder.package(MATERIALS_SKYBOX0_DATA, MATERIALS_SKYBOX0_SIZE); + builder.package(MATERIALS_SKYBOX_FL0_DATA, MATERIALS_SKYBOX_FL0_SIZE); } else #endif { - builder.package(MATERIALS_SKYBOX_DATA, MATERIALS_SKYBOX_SIZE); + switch (engine.getConfig().stereoscopicType) { + case Engine::StereoscopicType::INSTANCED: + builder.package(MATERIALS_SKYBOX_DATA, MATERIALS_SKYBOX_SIZE); + break; + case Engine::StereoscopicType::MULTIVIEW: + builder.package(MATERIALS_SKYBOX_MULTIVIEW_DATA, MATERIALS_SKYBOX_MULTIVIEW_SIZE); + break; + } } auto material = builder.build(engine); return downcast(material); diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 8062dfefd28..ce8db716dfc 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -169,6 +169,7 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl, } mslOptions.argument_buffers = true; + mslOptions.ios_support_base_vertex_instance = true; // We're using argument buffers for texture resources, however, we cannot rely on spirv-cross to // generate the argument buffer definitions. diff --git a/libs/filamat/src/MaterialBuilder.cpp b/libs/filamat/src/MaterialBuilder.cpp index 2ab0a8d2db2..746c49caf3c 100644 --- a/libs/filamat/src/MaterialBuilder.cpp +++ b/libs/filamat/src/MaterialBuilder.cpp @@ -906,7 +906,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vectorgetBackend())) + .stereoscopicType(engine->getConfig().stereoscopicType) .stereoscopicEyeCount(engine->getConfig().stereoscopicEyeCount); if (!optimizeShaders) { From 2e581be8fd34ae3c8248b150fdb8eb7b3d8e613b Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Tue, 12 Mar 2024 12:10:43 -0700 Subject: [PATCH 03/29] Move SwapChain flags into separate file (#7654) --- .../com/google/android/filament/Engine.java | 26 ++--- .../google/android/filament/SwapChain.java | 72 +------------- .../android/filament/SwapChainFlags.java | 97 +++++++++++++++++++ .../android/filament/android/UiHelper.java | 4 +- 4 files changed, 117 insertions(+), 82 deletions(-) create mode 100644 android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java 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 a858330eb4a..aee4c2b34aa 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 @@ -654,7 +654,7 @@ public long getMaxStereoscopicEyes() { */ @NonNull public SwapChain createSwapChain(@NonNull Object surface) { - return createSwapChain(surface, SwapChain.CONFIG_DEFAULT); + return createSwapChain(surface, SwapChainFlags.CONFIG_DEFAULT); } /** @@ -662,15 +662,15 @@ public SwapChain createSwapChain(@NonNull Object surface) { * * @param surface on Android, must be an instance of {@link android.view.Surface} * - * @param flags configuration flags, see {@link SwapChain} + * @param flags configuration flags, see {@link SwapChainFlags} * * @return a newly created {@link SwapChain} object * * @exception IllegalStateException can be thrown if the SwapChain couldn't be created * - * @see SwapChain#CONFIG_DEFAULT - * @see SwapChain#CONFIG_TRANSPARENT - * @see SwapChain#CONFIG_READABLE + * @see SwapChainFlags#CONFIG_DEFAULT + * @see SwapChainFlags#CONFIG_TRANSPARENT + * @see SwapChainFlags#CONFIG_READABLE * */ @NonNull @@ -688,21 +688,22 @@ public SwapChain createSwapChain(@NonNull Object surface, long flags) { * * @param width width of the rendering buffer * @param height height of the rendering buffer - * @param flags configuration flags, see {@link SwapChain} + * @param flags configuration flags, see {@link SwapChainFlags} * * @return a newly created {@link SwapChain} object * * @exception IllegalStateException can be thrown if the SwapChain couldn't be created * - * @see SwapChain#CONFIG_DEFAULT - * @see SwapChain#CONFIG_TRANSPARENT - * @see SwapChain#CONFIG_READABLE + * @see SwapChainFlags#CONFIG_DEFAULT + * @see SwapChainFlags#CONFIG_TRANSPARENT + * @see SwapChainFlags#CONFIG_READABLE * */ @NonNull public SwapChain createSwapChain(int width, int height, long flags) { if (width >= 0 && height >= 0) { - long nativeSwapChain = nCreateSwapChainHeadless(getNativeObject(), width, height, flags); + long nativeSwapChain = + nCreateSwapChainHeadless(getNativeObject(), width, height, flags); if (nativeSwapChain == 0) throw new IllegalStateException("Couldn't create SwapChain"); return new SwapChain(nativeSwapChain, null); } @@ -714,11 +715,12 @@ public SwapChain createSwapChain(int width, int height, long flags) { * * @param surface a properly initialized {@link NativeSurface} * - * @param flags configuration flags, see {@link SwapChain} + * @param flags configuration flags, see {@link SwapChainFlags} * * @return a newly created {@link SwapChain} object * - * @exception IllegalStateException can be thrown if the {@link SwapChain} couldn't be created + * @exception IllegalStateException can be thrown if the {@link SwapChainFlags} couldn't be + * created */ @NonNull public SwapChain createSwapChainFromNativeSurface(@NonNull NativeSurface surface, long flags) { 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 e3d1cf43aad..db47d215635 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 @@ -68,72 +68,6 @@ public class SwapChain { private final Object mSurface; private long mNativeObject; - public static final long CONFIG_DEFAULT = 0x0; - - /** - * This flag indicates that the SwapChain must be allocated with an - * alpha-channel. - */ - public static final long CONFIG_TRANSPARENT = 0x1; - - /** - * This flag indicates that the SwapChain may be used as a source surface - * for reading back render results. This config must be set when creating - * any SwapChain that will be used as the source for a blit operation. - * - * @see Renderer#copyFrame - */ - public static final long CONFIG_READABLE = 0x2; - - /** - * Indicates that the native X11 window is an XCB window rather than an XLIB window. - * This is ignored on non-Linux platforms and in builds that support only one X11 API. - */ - public static final long CONFIG_ENABLE_XCB = 0x4; - - /** - * Indicates that the SwapChain must automatically perform linear to sRGB encoding. - * - * This flag is ignored if isSRGBSwapChainSupported() is false. - * - * When using this flag, post-processing should be disabled. - * - * @see SwapChain#isSRGBSwapChainSupported - * @see View#setPostProcessingEnabled - */ - public static final long CONFIG_SRGB_COLORSPACE = 0x10; - - /** - * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer. - * - * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into - * the SwapChain (when post-processing is disabled). - * - * The specific format of the stencil buffer depends on platform support. The following pixel - * formats are tried, in order of preference: - * - * Depth only (without CONFIG_HAS_STENCIL_BUFFER): - * - DEPTH32F - * - DEPTH24 - * - * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER): - * - DEPTH32F_STENCIL8 - * - DEPTH24F_STENCIL8 - * - * Note that enabling the stencil buffer may hinder depth precision and should only be used if - * necessary. - * - * @see View#setStencilBufferEnabled - * @see View#setPostProcessingEnabled - */ - 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; @@ -145,17 +79,19 @@ public class SwapChain { * * @param engine A reference to the filament Engine * @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise. + * @see SwapChainFlags#CONFIG_PROTECTED_CONTENT */ public static boolean isProtectedContentSupported(@NonNull Engine engine) { return nIsProtectedContentSupported(engine.getNativeObject()); } /** - * Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag. + * Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag. * The default implementation returns false. * * @param engine A reference 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. + * @see SwapChainFlags#CONFIG_SRGB_COLORSPACE */ public static boolean isSRGBSwapChainSupported(@NonNull Engine engine) { return nIsSRGBSwapChainSupported(engine.getNativeObject()); diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java new file mode 100644 index 00000000000..daef2dd15f2 --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChainFlags.java @@ -0,0 +1,97 @@ +/* + * 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. + */ + +package com.google.android.filament; + +// Note: SwapChainFlags is kept separate from SwapChain so that UiHelper does not need to depend +// on SwapChain. This allows clients to use UiHelper without requiring all of Filament's Java +// classes. + +/** + * Flags that a SwapChain can be created with to control behavior. + * + * @see Engine#createSwapChain + * @see Engine#createSwapChainFromNativeSurface + */ +public final class SwapChainFlags { + + public static final long CONFIG_DEFAULT = 0x0; + + /** + * This flag indicates that the SwapChain must be allocated with an + * alpha-channel. + */ + public static final long CONFIG_TRANSPARENT = 0x1; + + /** + * This flag indicates that the SwapChain may be used as a source surface + * for reading back render results. This config must be set when creating + * any SwapChain that will be used as the source for a blit operation. + * + * @see Renderer#copyFrame + */ + public static final long CONFIG_READABLE = 0x2; + + /** + * Indicates that the native X11 window is an XCB window rather than an XLIB window. + * This is ignored on non-Linux platforms and in builds that support only one X11 API. + */ + public static final long CONFIG_ENABLE_XCB = 0x4; + + /** + * Indicates that the SwapChain must automatically perform linear to sRGB encoding. + * + * This flag is ignored if isSRGBSwapChainSupported() is false. + * + * When using this flag, post-processing should be disabled. + * + * @see SwapChain#isSRGBSwapChainSupported + * @see View#setPostProcessingEnabled + */ + public static final long CONFIG_SRGB_COLORSPACE = 0x10; + + /** + * Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer. + * + * This flag is necessary when using View::setStencilBufferEnabled and rendering directly into + * the SwapChain (when post-processing is disabled). + * + * The specific format of the stencil buffer depends on platform support. The following pixel + * formats are tried, in order of preference: + * + * Depth only (without CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F + * - DEPTH24 + * + * Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER): + * - DEPTH32F_STENCIL8 + * - DEPTH24F_STENCIL8 + * + * Note that enabling the stencil buffer may hinder depth precision and should only be used if + * necessary. + * + * @see View#setStencilBufferEnabled + * @see View#setPostProcessingEnabled + */ + 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; +} + diff --git a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java index 451c09a33be..4d6f3a140f1 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java +++ b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java @@ -27,7 +27,7 @@ import android.view.SurfaceView; import android.view.TextureView; -import com.google.android.filament.SwapChain; +import com.google.android.filament.SwapChainFlags; /** * UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder @@ -538,7 +538,7 @@ public void setMediaOverlay(boolean overlay) { * the options set on this UiHelper. */ public long getSwapChainFlags() { - return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT; + return isOpaque() ? SwapChainFlags.CONFIG_DEFAULT : SwapChainFlags.CONFIG_TRANSPARENT; } /** From 9aad4df44145922daec659c07751713501ee4955 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Tue, 12 Mar 2024 13:06:43 -0700 Subject: [PATCH 04/29] Metal: respect disableParallelShaderCompile config (#7659) --- filament/backend/include/backend/Platform.h | 2 +- filament/backend/src/metal/MetalDriver.mm | 7 +- .../backend/src/metal/MetalShaderCompiler.h | 16 +++-- .../backend/src/metal/MetalShaderCompiler.mm | 71 ++++++++++--------- filament/include/filament/Engine.h | 2 +- 5 files changed, 57 insertions(+), 41 deletions(-) diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index b89fb98f7fd..1d85568a883 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -56,7 +56,7 @@ class UTILS_PUBLIC Platform { /** * Set to `true` to forcibly disable parallel shader compilation in the backend. - * Currently only honored by the GL backend. + * Currently only honored by the GL and Metal backends. */ bool disableParallelShaderCompile = false; }; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index a47c57a30fa..8dfbb27c51b 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -180,7 +180,10 @@ mContext->eventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:queue]; } - mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this); + const MetalShaderCompiler::Mode compilerMode = driverConfig.disableParallelShaderCompile + ? MetalShaderCompiler::Mode::SYNCHRONOUS + : MetalShaderCompiler::Mode::ASYNCHRONOUS; + mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this, compilerMode); mContext->shaderCompiler->init(); #if defined(FILAMENT_METAL_PROFILING) @@ -788,7 +791,7 @@ } bool MetalDriver::isParallelShaderCompileSupported() { - return true; + return mContext->shaderCompiler->isParallelShaderCompileSupported(); } bool MetalDriver::isDepthStencilResolveSupported() { diff --git a/filament/backend/src/metal/MetalShaderCompiler.h b/filament/backend/src/metal/MetalShaderCompiler.h index 5d967081e49..f0b827ad6ac 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.h +++ b/filament/backend/src/metal/MetalShaderCompiler.h @@ -41,6 +41,11 @@ class MetalShaderCompiler { struct MetalProgramToken; public: + enum class Mode { + SYNCHRONOUS, // synchronous shader compilation + ASYNCHRONOUS // asynchronous shader compilation + }; + class MetalFunctionBundle { public: using Raster = std::tuple, id>; @@ -110,7 +115,7 @@ class MetalShaderCompiler { using program_token_t = std::shared_ptr; - explicit MetalShaderCompiler(id device, MetalDriver& driver); + explicit MetalShaderCompiler(id device, MetalDriver& driver, Mode mode); MetalShaderCompiler(MetalShaderCompiler const& rhs) = delete; MetalShaderCompiler(MetalShaderCompiler&& rhs) = delete; @@ -120,15 +125,15 @@ class MetalShaderCompiler { void init() noexcept; void terminate() noexcept; - // Creates a program asynchronously + bool isParallelShaderCompileSupported() const noexcept; + + // Creates a program, either synchronously or asynchronously, depending on the Mode + // MetalShaderCompiler was constructed with. program_token_t createProgram(utils::CString const& name, Program&& program); // Returns the functions, blocking if necessary. The Token is destroyed and becomes invalid. MetalFunctionBundle getProgram(program_token_t& token); - // Destroys a valid token and all associated resources. Used to "cancel" a program compilation. - static void terminate(program_token_t& token); - void notifyWhenAllProgramsAreReady( CallbackHandler* handler, CallbackHandler::Callback callback, void* user); @@ -138,6 +143,7 @@ class MetalShaderCompiler { CompilerThreadPool mCompilerThreadPool; id mDevice; CallbackManager mCallbackManager; + Mode mMode; }; } // namespace filament::backend diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index 713133b1db4..7c2cffdb103 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -70,22 +70,31 @@ bool isReady() const noexcept { MetalShaderCompiler::MetalProgramToken::~MetalProgramToken() = default; -MetalShaderCompiler::MetalShaderCompiler(id device, MetalDriver& driver) +MetalShaderCompiler::MetalShaderCompiler(id device, MetalDriver& driver, Mode mode) : mDevice(device), - mCallbackManager(driver) { + mCallbackManager(driver), + mMode(mode) { } void MetalShaderCompiler::init() noexcept { const uint32_t poolSize = 1; - mCompilerThreadPool.init(poolSize, []() {}, []() {}); + if (mMode == Mode::ASYNCHRONOUS) { + mCompilerThreadPool.init(poolSize, []() {}, []() {}); + } } void MetalShaderCompiler::terminate() noexcept { - mCompilerThreadPool.terminate(); + if (mMode == Mode::ASYNCHRONOUS) { + mCompilerThreadPool.terminate(); + } mCallbackManager.terminate(); } +bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept { + return mMode == Mode::ASYNCHRONOUS; +} + /* static */ MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::compileProgram( const Program& program, id device) { std::array, Program::SHADER_TYPE_COUNT> functions = { nil }; @@ -180,14 +189,26 @@ bool isReady() const noexcept { token->handle = mCallbackManager.get(); - CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); - mCompilerThreadPool.queue(priorityQueue, token, - [this, name, device = mDevice, program = std::move(program), token]() { - MetalFunctionBundle compiledProgram = compileProgram(program, device); + switch (mMode) { + case Mode::ASYNCHRONOUS: { + CompilerPriorityQueue const priorityQueue = program.getPriorityQueue(); + mCompilerThreadPool.queue(priorityQueue, token, + [this, name, device = mDevice, program = std::move(program), token]() { + MetalFunctionBundle compiledProgram = compileProgram(program, device); + token->set(compiledProgram); + mCallbackManager.put(token->handle); + }); + + break; + } - token->set(compiledProgram); - mCallbackManager.put(token->handle); - }); + case Mode::SYNCHRONOUS: { + MetalFunctionBundle compiledProgram = compileProgram(program, mDevice); + token->set(compiledProgram); + mCallbackManager.put(token->handle); + break; + } + } return token; } @@ -195,35 +216,21 @@ bool isReady() const noexcept { MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::getProgram(program_token_t& token) { assert_invariant(token); - if (!token->isReady()) { - auto job = mCompilerThreadPool.dequeue(token); - if (job) { - job(); + if (mMode == Mode::ASYNCHRONOUS) { + if (!token->isReady()) { + auto job = mCompilerThreadPool.dequeue(token); + if (job) { + job(); + } } } + assert_invariant(token->isReady()); MetalShaderCompiler::MetalFunctionBundle program = token->get(); - token = nullptr; - return program; } -/* static */ void MetalShaderCompiler::terminate(program_token_t& token) { - assert_invariant(token); - - auto job = token->compiler.mCompilerThreadPool.dequeue(token); - if (!job) { - // The job is being executed right now (or has already executed). - token->wait(); - } else { - // The job has not executed yet. - token->compiler.mCallbackManager.put(token->handle); - } - - token.reset(); -} - void MetalShaderCompiler::notifyWhenAllProgramsAreReady( CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { mCallbackManager.setCallback(handler, callback, user); diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 6eb1916f7ab..04d71259b7c 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -300,7 +300,7 @@ class UTILS_PUBLIC Engine { /** * Set to `true` to forcibly disable parallel shader compilation in the backend. - * Currently only honored by the GL backend. + * Currently only honored by the GL and Metal backends. */ bool disableParallelShaderCompile = false; From fa6b4ebd0479f312298fc0421eaff3089bca72e2 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 12 Mar 2024 13:31:19 -0700 Subject: [PATCH 05/29] gltfio: refactor for clarity (#7652) - Pull certain utility functions in a separate header and cpp - Refactor ResourceLoader::loadeResources into smaller methods --- android/gltfio-android/CMakeLists.txt | 2 + libs/gltfio/CMakeLists.txt | 2 + libs/gltfio/include/gltfio/ResourceLoader.h | 1 - libs/gltfio/src/AssetLoader.cpp | 31 +- libs/gltfio/src/FFilamentAsset.h | 1 + libs/gltfio/src/ResourceLoader.cpp | 564 +++++++------------- libs/gltfio/src/Utility.cpp | 265 +++++++++ libs/gltfio/src/Utility.h | 59 ++ 8 files changed, 528 insertions(+), 397 deletions(-) create mode 100644 libs/gltfio/src/Utility.cpp create mode 100644 libs/gltfio/src/Utility.h diff --git a/android/gltfio-android/CMakeLists.txt b/android/gltfio-android/CMakeLists.txt index ffac6b8c610..41b546a92dd 100644 --- a/android/gltfio-android/CMakeLists.txt +++ b/android/gltfio-android/CMakeLists.txt @@ -81,6 +81,8 @@ set(GLTFIO_SRCS ${GLTFIO_DIR}/src/TangentsJob.cpp ${GLTFIO_DIR}/src/TangentsJob.h ${GLTFIO_DIR}/src/UbershaderProvider.cpp + ${GLTFIO_DIR}/src/Utility.cpp + ${GLTFIO_DIR}/src/Utility.h ${GLTFIO_DIR}/src/Wireframe.cpp ${GLTFIO_DIR}/src/Wireframe.h ${GLTFIO_DIR}/src/downcast.h diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index d12f24d2597..3791525e29d 100644 --- a/libs/gltfio/CMakeLists.txt +++ b/libs/gltfio/CMakeLists.txt @@ -47,6 +47,8 @@ set(SRCS src/TangentsJob.cpp src/TangentsJob.h src/UbershaderProvider.cpp + src/Utility.cpp + src/Utility.h src/Wireframe.cpp src/Wireframe.h src/downcast.h diff --git a/libs/gltfio/include/gltfio/ResourceLoader.h b/libs/gltfio/include/gltfio/ResourceLoader.h index 05c8a56d358..acccc9f86fc 100644 --- a/libs/gltfio/include/gltfio/ResourceLoader.h +++ b/libs/gltfio/include/gltfio/ResourceLoader.h @@ -156,7 +156,6 @@ class UTILS_PUBLIC ResourceLoader { private: bool loadResources(FFilamentAsset* asset, bool async); - void normalizeSkinningWeights(FFilamentAsset* asset) const; struct Impl; Impl* pImpl; }; diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 6e3df0af442..fa73eb27417 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -23,6 +23,7 @@ #include "FNodeManager.h" #include "FTrsTransformManager.h" #include "GltfEnums.h" +#include "Utility.h" #include #include @@ -52,7 +53,6 @@ #include -#define CGLTF_IMPLEMENTATION #include #include "downcast.h" @@ -85,21 +85,6 @@ static constexpr cgltf_material kDefaultMat = { }, }; -// Sometimes a glTF bufferview includes unused data at the end (e.g. in skinning.gltf) so we need to -// compute the correct size of the vertex buffer. Filament automatically infers the size of -// driver-level vertex buffers from the attribute data (stride, count, offset) and clients are -// expected to avoid uploading data blobs that exceed this size. Since this information doesn't -// exist in the glTF we need to compute it manually. This is a bit of a cheat, cgltf_calc_size is -// private but its implementation file is available in this cpp file. -uint32_t computeBindingSize(const cgltf_accessor* accessor) { - cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); - return uint32_t(accessor->stride * (accessor->count - 1) + element_size); -} - -uint32_t computeBindingOffset(const cgltf_accessor* accessor) { - return uint32_t(accessor->offset + accessor->buffer_view->offset); -} - static const char* getNodeName(const cgltf_node* node, const char* defaultNodeName) { if (node->name) return node->name; if (node->mesh && node->mesh->name) return node->mesh->name; @@ -617,15 +602,15 @@ void FAssetLoader::recurseEntities(const cgltf_node* node, SceneMask scenes, Ent void FAssetLoader::createPrimitives(const cgltf_node* node, const char* name, FFilamentAsset* fAsset) { - const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy; + cgltf_data* gltf = fAsset->mSourceAsset->hierarchy; const cgltf_mesh* mesh = node->mesh; - assert_invariant(srcAsset != nullptr); + assert_invariant(gltf != nullptr); assert_invariant(mesh != nullptr); // If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer objects // that were already generated (one for each primitive), otherwise allocate a new list of // pointers for the primitives. - FixedCapacityVector& prims = fAsset->mMeshCache[mesh - srcAsset->meshes]; + FixedCapacityVector& prims = fAsset->mMeshCache[mesh - gltf->meshes]; if (prims.empty()) { prims.reserve(mesh->primitives_count); prims.resize(mesh->primitives_count); @@ -791,6 +776,7 @@ void FAssetLoader::createMaterialVariants(const cgltf_mesh* mesh, Entity entity, bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* name, Primitive* outPrim, FFilamentAsset* fAsset) { + Material* material = getMaterial(fAsset->mSourceAsset->hierarchy, inPrim.material, &outPrim->uvmap, primitiveHasVertexColor(inPrim)); AttributeBitset requiredAttributes = material->getRequiredAttributes(); @@ -849,7 +835,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false; uint32_t vertexCount = 0; - const size_t firstSlot = fAsset->mBufferSlots.size(); + const size_t firstSlot = slots->size(); int slot = 0; for (cgltf_size aindex = 0; aindex < inPrim.attributes_count; aindex++) { @@ -891,6 +877,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na utils::slog.e << "Too many joints in " << name << utils::io::endl; continue; } + if (atype == cgltf_attribute_type_texcoord) { if (index >= UvMapSize) { utils::slog.e << "Too many texture coordinate sets in " << name << utils::io::endl; @@ -1065,8 +1052,8 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na fAsset->mPrimitives.push_back({&inPrim, vertices}); fAsset->mVertexBuffers.push_back(vertices); - for (size_t i = firstSlot; i < fAsset->mBufferSlots.size(); ++i) { - fAsset->mBufferSlots[i].vertexBuffer = vertices; + for (size_t i = firstSlot; i < slots->size(); ++i) { + (*slots)[i].vertexBuffer = vertices; } if (targetsCount > 0) { diff --git a/libs/gltfio/src/FFilamentAsset.h b/libs/gltfio/src/FFilamentAsset.h index ba101bc6e1f..a29c016d24e 100644 --- a/libs/gltfio/src/FFilamentAsset.h +++ b/libs/gltfio/src/FFilamentAsset.h @@ -45,6 +45,7 @@ #include "DependencyGraph.h" #include "DracoCache.h" #include "FFilamentInstance.h" +#include "Utility.h" #include diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 9e292ae9c00..135cca7c0e9 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -21,6 +21,7 @@ #include "FFilamentAsset.h" #include "TangentsJob.h" #include "downcast.h" +#include "Utility.h" #include #include @@ -50,6 +51,7 @@ #include #include #include +#include using namespace filament; using namespace filament::math; @@ -63,16 +65,16 @@ namespace filament::gltfio { using BufferTextureCache = tsl::robin_map; using FilepathTextureCache = tsl::robin_map; -using UriDataCache = tsl::robin_map; -using UriDataCacheHandle = std::shared_ptr; using TextureProviderList = tsl::robin_map; +namespace { enum class CacheResult { ERROR, NOT_READY, FOUND, MISS, }; +} // anonymous namespace struct ResourceLoader::Impl { explicit Impl(const ResourceConfiguration& config) : @@ -108,9 +110,7 @@ struct ResourceLoader::Impl { ~Impl(); }; -uint32_t computeBindingSize(const cgltf_accessor* accessor); -uint32_t computeBindingOffset(const cgltf_accessor* accessor); - +namespace { // This little struct holds a shared_ptr that wraps cgltf_data (and, potentially, glb data) while // uploading vertex buffer data to the GPU. struct UploadEvent { @@ -122,174 +122,14 @@ UploadEvent* uploadUserdata(FFilamentAsset* asset, UriDataCacheHandle dataCache) return new UploadEvent({ asset->mSourceAsset, dataCache }); } -static void uploadCallback(void* buffer, size_t size, void* user) { +void uploadCallback(void* buffer, size_t size, void* user) { auto event = (UploadEvent*) user; delete event; } -static void convertBytesToShorts(uint16_t* dst, const uint8_t* src, size_t count) { - for (size_t i = 0; i < count; ++i) { - dst[i] = src[i]; - } -} - -static bool requiresConversion(const cgltf_accessor* accessor) { - if (UTILS_UNLIKELY(accessor->is_sparse)) { - return true; - } - const cgltf_type type = accessor->type; - const cgltf_component_type ctype = accessor->component_type; - filament::VertexBuffer::AttributeType permitted; - filament::VertexBuffer::AttributeType actual; - bool supported = getElementType(type, ctype, &permitted, &actual); - return supported && permitted != actual; -} - -static bool requiresPacking(const cgltf_accessor* accessor) { - if (requiresConversion(accessor)) { - return true; - } - const size_t dim = cgltf_num_components(accessor->type); - switch (accessor->component_type) { - case cgltf_component_type_r_8: - case cgltf_component_type_r_8u: - return accessor->stride != dim; - case cgltf_component_type_r_16: - case cgltf_component_type_r_16u: - return accessor->stride != dim * 2; - case cgltf_component_type_r_32u: - case cgltf_component_type_r_32f: - return accessor->stride != dim * 4; - default: - assert_invariant(false); - return true; - } -} - -static void decodeDracoMeshes(FFilamentAsset* asset) { - DracoCache* dracoCache = &asset->mSourceAsset->dracoCache; - - // For a given primitive and attribute, find the corresponding accessor. - auto findAccessor = [](const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int idx) { - for (cgltf_size i = 0; i < prim->attributes_count; i++) { - const cgltf_attribute& attr = prim->attributes[i]; - if (attr.type == type && attr.index == idx) { - return attr.data; - } - } - return (cgltf_accessor*) nullptr; - }; - - // Go through every primitive and check if it has a Draco mesh. - for (auto& [prim, vertexBuffer] : asset->mPrimitives) { - if (!prim->has_draco_mesh_compression) { - continue; - } - - const cgltf_draco_mesh_compression& draco = prim->draco_mesh_compression; - - // If an error occurs, we can simply set the primitive's associated VertexBuffer to null. - // This does not cause a leak because it is a weak reference. - - // Check if we have already decoded this mesh. - DracoMesh* mesh = dracoCache->findOrCreateMesh(draco.buffer_view); - if (!mesh) { - slog.e << "Cannot decompress mesh, Draco decoding error." << io::endl; - vertexBuffer = nullptr; - continue; - } - - // Copy over the decompressed data, converting the data type if necessary. - if (prim->indices && !mesh->getFaceIndices(prim->indices)) { - vertexBuffer = nullptr; - continue; - } - - // Go through each attribute in the decompressed mesh. - for (cgltf_size i = 0; i < draco.attributes_count; i++) { - - // In cgltf, each Draco attribute's data pointer is an attribute id, not an accessor. - const uint32_t id = draco.attributes[i].data - asset->mSourceAsset->hierarchy->accessors; - - // Find the destination accessor; this contains the desired component type, etc. - const cgltf_attribute_type type = draco.attributes[i].type; - const cgltf_int index = draco.attributes[i].index; - cgltf_accessor* accessor = findAccessor(prim, type, index); - if (!accessor) { - slog.w << "Cannot find matching accessor for Draco id " << id << io::endl; - continue; - } - - // Copy over the decompressed data, converting the data type if necessary. - if (!mesh->getVertexAttributes(id, accessor)) { - vertexBuffer = nullptr; - break; - } - } - } -} - -static void decodeMeshoptCompression(cgltf_data* data) { - for (size_t i = 0; i < data->buffer_views_count; ++i) { - if (!data->buffer_views[i].has_meshopt_compression) { - continue; - } - - cgltf_meshopt_compression* compression = &data->buffer_views[i].meshopt_compression; - const uint8_t* source = (const uint8_t*) compression->buffer->data; - assert_invariant(source); - source += compression->offset; - - // This memory is freed by cgltf. - void* destination = malloc(compression->count * compression->stride); - assert_invariant(destination); - - UTILS_UNUSED_IN_RELEASE int error = 0; - switch (compression->mode) { - case cgltf_meshopt_compression_mode_invalid: - break; - case cgltf_meshopt_compression_mode_attributes: - error = meshopt_decodeVertexBuffer(destination, compression->count, compression->stride, - source, compression->size); - break; - case cgltf_meshopt_compression_mode_triangles: - error = meshopt_decodeIndexBuffer(destination, compression->count, compression->stride, - source, compression->size); - break; - case cgltf_meshopt_compression_mode_indices: - error = meshopt_decodeIndexSequence(destination, compression->count, compression->stride, - source, compression->size); - break; - default: - assert_invariant(false); - break; - } - assert_invariant(!error); - - switch (compression->filter) { - case cgltf_meshopt_compression_filter_none: - break; - case cgltf_meshopt_compression_filter_octahedral: - meshopt_decodeFilterOct(destination, compression->count, compression->stride); - break; - case cgltf_meshopt_compression_filter_quaternion: - meshopt_decodeFilterQuat(destination, compression->count, compression->stride); - break; - case cgltf_meshopt_compression_filter_exponential: - meshopt_decodeFilterExp(destination, compression->count, compression->stride); - break; - default: - assert_invariant(false); - break; - } - - data->buffer_views[i].data = destination; - } -} - // Parses a data URI and returns a blob that gets malloc'd in cgltf, which the caller must free. // (implementation snarfed from meshoptimizer) -static const uint8_t* parseDataUri(const char* uri, std::string* mimeType, size_t* psize) { +uint8_t const* parseDataUri(const char* uri, std::string* mimeType, size_t* psize) { if (strncmp(uri, "data:", 5) != 0) { return nullptr; } @@ -315,6 +155,165 @@ static const uint8_t* parseDataUri(const char* uri, std::string* mimeType, size_ return nullptr; } +inline void normalizeSkinningWeights(cgltf_data const* gltf) { + auto normalize = [](cgltf_accessor* data) { + if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) { + slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl; + return; + } + uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data; + bytes += data->offset + data->buffer_view->offset; + for (cgltf_size i = 0, n = data->count; i < n; ++i, bytes += data->stride) { + float4* weights = (float4*) bytes; + const float sum = weights->x + weights->y + weights->z + weights->w; + *weights /= sum; + } + }; + cgltf_size mcount = gltf->meshes_count; + for (cgltf_size mindex = 0; mindex < mcount; ++mindex) { + const cgltf_mesh& mesh = gltf->meshes[mindex]; + cgltf_size pcount = mesh.primitives_count; + for (cgltf_size pindex = 0; pindex < pcount; ++pindex) { + const cgltf_primitive& prim = mesh.primitives[pindex]; + cgltf_size acount = prim.attributes_count; + for (cgltf_size aindex = 0; aindex < acount; ++aindex) { + const auto& attr = prim.attributes[aindex]; + if (attr.type == cgltf_attribute_type_weights) { + normalize(attr.data); + } + } + } + } +} + +inline void createSkins(cgltf_data const* gltf, bool normalize, + utils::FixedCapacityVector& skins) { + // For each skin, optionally normalize skinning weights and store a copy of the bind matrices. + if (gltf->skins_count == 0) { + return; + } + if (normalize) { + normalizeSkinningWeights(gltf); + } + skins.reserve(gltf->skins_count); + for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) { + const cgltf_skin& srcSkin = gltf->skins[i]; + CString name; + if (srcSkin.name) { + name = CString(srcSkin.name); + } + const cgltf_accessor* srcMatrices = srcSkin.inverse_bind_matrices; + FixedCapacityVector inverseBindMatrices(srcSkin.joints_count); + if (srcMatrices) { + uint8_t* bytes = nullptr; + uint8_t* srcBuffer = nullptr; + if (srcMatrices->buffer_view->has_meshopt_compression) { + bytes = (uint8_t*) srcMatrices->buffer_view->data; + srcBuffer = bytes + srcMatrices->offset; + } else { + bytes = (uint8_t*) srcMatrices->buffer_view->buffer->data; + srcBuffer = bytes + srcMatrices->offset + srcMatrices->buffer_view->offset; + } + assert_invariant(bytes); + memcpy((uint8_t*) inverseBindMatrices.data(), (const void*) srcBuffer, + srcSkin.joints_count * sizeof(mat4f)); + } + FFilamentAsset::Skin skin{ + .name = std::move(name), + .inverseBindMatrices = std::move(inverseBindMatrices), + }; + skins.emplace_back(std::move(skin)); + } +} + +inline void uploadBuffers(FFilamentAsset* asset, Engine& engine, + UriDataCacheHandle uriDataCache) { + // Upload VertexBuffer and IndexBuffer data to the GPU. + auto& slots = asset->mBufferSlots; + for (auto slot: slots) { + const cgltf_accessor* accessor = slot.accessor; + if (!accessor->buffer_view) { + continue; + } + const uint8_t* bufferData = nullptr; + const uint8_t* data = nullptr; + if (accessor->buffer_view->has_meshopt_compression) { + bufferData = (const uint8_t*) accessor->buffer_view->data; + data = bufferData + accessor->offset; + } else { + bufferData = (const uint8_t*) accessor->buffer_view->buffer->data; + data = utility::computeBindingOffset(accessor) + bufferData; + } + assert_invariant(bufferData); + const uint32_t size = utility::computeBindingSize(accessor); + if (slot.vertexBuffer) { + if (utility::requiresConversion(accessor)) { + const size_t floatsCount = accessor->count * cgltf_num_components(accessor->type); + const size_t floatsByteCount = sizeof(float) * floatsCount; + float* floatsData = (float*) malloc(floatsByteCount); + cgltf_accessor_unpack_floats(accessor, floatsData, floatsCount); + BufferObject* bo = BufferObject::Builder().size(floatsByteCount).build(engine); + asset->mBufferObjects.push_back(bo); + bo->setBuffer(engine, BufferDescriptor(floatsData, floatsByteCount, FREE_CALLBACK)); + slot.vertexBuffer->setBufferObjectAt(engine, slot.bufferIndex, bo); + continue; + } + + BufferObject* bo = BufferObject::Builder().size(size).build(engine); + asset->mBufferObjects.push_back(bo); + bo->setBuffer(engine, BufferDescriptor(data, size, uploadCallback, + uploadUserdata(asset, uriDataCache))); + slot.vertexBuffer->setBufferObjectAt(engine, slot.bufferIndex, bo); + continue; + } else if (slot.indexBuffer) { + if (accessor->component_type == cgltf_component_type_r_8u) { + const size_t size16 = size * 2; + uint16_t* data16 = (uint16_t*) malloc(size16); + utility::convertBytesToShorts(data16, data, size); + IndexBuffer::BufferDescriptor bd(data16, size16, FREE_CALLBACK); + + slot.indexBuffer->setBuffer(engine, std::move(bd)); + continue; + } + IndexBuffer::BufferDescriptor bd(data, size, uploadCallback, + uploadUserdata(asset, uriDataCache)); + slot.indexBuffer->setBuffer(engine, std::move(bd)); + continue; + } + + // If the buffer slot does not have an associated VertexBuffer or IndexBuffer, then this + // must be a morph target. + assert(slot.morphTargetBuffer); + + if (utility::requiresPacking(accessor)) { + const size_t floatsCount = accessor->count * cgltf_num_components(accessor->type); + const size_t floatsByteCount = sizeof(float) * floatsCount; + float* floatsData = (float*) malloc(floatsByteCount); + cgltf_accessor_unpack_floats(accessor, floatsData, floatsCount); + if (accessor->type == cgltf_type_vec3) { + slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, + (const float3*) floatsData, slot.morphTargetBuffer->getVertexCount()); + } else { + slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, + (const float4*) data, slot.morphTargetBuffer->getVertexCount()); + } + free(floatsData); + continue; + } + + if (accessor->type == cgltf_type_vec3) { + slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, (const float3*) data, + slot.morphTargetBuffer->getVertexCount()); + } else { + assert_invariant(accessor->type == cgltf_type_vec4); + slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, (const float4*) data, + slot.morphTargetBuffer->getVertexCount()); + } + } +} + +} // anonymous namespace + ResourceLoader::ResourceLoader(const ResourceConfiguration& config) : pImpl(new Impl(config)) { } ResourceLoader::~ResourceLoader() { @@ -388,6 +387,9 @@ void ResourceLoader::evictResourceData() { bool ResourceLoader::loadResources(FilamentAsset* asset) { FFilamentAsset* fasset = downcast(asset); + + // This is a workaround in case of using extended algo, please see description in + // FFilamentAsset.h return loadResources(fasset, false); } @@ -410,191 +412,37 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { pImpl->mBufferTextureCache.clear(); pImpl->mFilepathTextureCache.clear(); - const cgltf_data* gltf = asset->mSourceAsset->hierarchy; - cgltf_options options {}; - - // For emscripten and Android builds we supply a custom file reader callback that looks inside a - // cache of externally-supplied data blobs, rather than loading from the filesystem. + cgltf_data const* gltf = asset->mSourceAsset->hierarchy; - SYSTRACE_NAME_BEGIN("Load buffers"); - #if !GLTFIO_USE_FILESYSTEM - - struct Closure { - Impl* impl; - const cgltf_data* gltf; - }; + utility::loadCgltfBuffers(gltf, pImpl->mGltfPath.c_str(), pImpl->mUriDataCache); - Closure closure = { pImpl, gltf }; - - options.file.user_data = &closure; - - options.file.read = [](const cgltf_memory_options* memoryOpts, - const cgltf_file_options* fileOpts, const char* path, cgltf_size* size, void** data) { - Closure* closure = (Closure*) fileOpts->user_data; - auto& uriDataCache = closure->impl->mUriDataCache; - - if (auto iter = uriDataCache->find(path); iter != uriDataCache->end()) { - *size = iter->second.size; - *data = iter->second.buffer; - } else { - // Even if we don't find the given resource in the cache, we still return a successful - // error code, because we allow downloads to finish after the decoding work starts. - *size = 0; - *data = 0; - } - - return cgltf_result_success; - }; - - #endif - - // Read data from the file system and base64 URIs. - cgltf_result result = cgltf_load_buffers(&options, (cgltf_data*) gltf, pImpl->mGltfPath.c_str()); - if (result != cgltf_result_success) { - slog.e << "Unable to load resources." << io::endl; - return false; - } - - SYSTRACE_NAME_END(); - - #ifndef NDEBUG - if (cgltf_validate((cgltf_data*) gltf) != cgltf_result_success) { - slog.e << "Failed cgltf validation." << io::endl; - return false; - } - #endif - // Decompress Draco meshes early on, which allows us to exploit subsequent processing such as - // tangent generation. - decodeDracoMeshes(asset); - decodeMeshoptCompression((cgltf_data*) gltf); - - // For each skin, optionally normalize skinning weights and store a copy of the bind matrices. - if (gltf->skins_count > 0) { - if (pImpl->mNormalizeSkinningWeights) { - normalizeSkinningWeights(asset); - } - asset->mSkins.reserve(gltf->skins_count); - for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) { - const cgltf_skin& srcSkin = gltf->skins[i]; - CString name; - if (srcSkin.name) { - name = CString(srcSkin.name); - } - const cgltf_accessor* srcMatrices = srcSkin.inverse_bind_matrices; - FixedCapacityVector inverseBindMatrices(srcSkin.joints_count); - if (srcMatrices) { - uint8_t* bytes = nullptr; - uint8_t* srcBuffer = nullptr; - if (srcMatrices->buffer_view->has_meshopt_compression) { - bytes = (uint8_t*) srcMatrices->buffer_view->data; - srcBuffer = bytes + srcMatrices->offset; - } else { - bytes = (uint8_t*) srcMatrices->buffer_view->buffer->data; - srcBuffer = bytes + srcMatrices->offset + srcMatrices->buffer_view->offset; - } - assert_invariant(bytes); - memcpy((uint8_t*) inverseBindMatrices.data(), - (const void*) srcBuffer, srcSkin.joints_count * sizeof(mat4f)); - } - FFilamentAsset::Skin skin { - .name = std::move(name), - .inverseBindMatrices = std::move(inverseBindMatrices), - }; - asset->mSkins.emplace_back(std::move(skin)); - } - } - - Engine& engine = *pImpl->mEngine; - - // Upload VertexBuffer and IndexBuffer data to the GPU. - for (auto slot : asset->mBufferSlots) { - const cgltf_accessor* accessor = slot.accessor; - if (!accessor->buffer_view) { - continue; - } - const uint8_t* bufferData = nullptr; - const uint8_t* data = nullptr; - if (accessor->buffer_view->has_meshopt_compression) { - bufferData = (const uint8_t*) accessor->buffer_view->data; - data = bufferData + accessor->offset; - } else { - bufferData = (const uint8_t*) accessor->buffer_view->buffer->data; - data = computeBindingOffset(accessor) + bufferData; - } - assert_invariant(bufferData); - const uint32_t size = computeBindingSize(accessor); - if (slot.vertexBuffer) { - if (requiresConversion(accessor)) { - const size_t floatsCount = accessor->count * cgltf_num_components(accessor->type); - const size_t floatsByteCount = sizeof(float) * floatsCount; - float* floatsData = (float*) malloc(floatsByteCount); - cgltf_accessor_unpack_floats(accessor, floatsData, floatsCount); - BufferObject* bo = BufferObject::Builder().size(floatsByteCount).build(engine); - asset->mBufferObjects.push_back(bo); - bo->setBuffer(engine, BufferDescriptor(floatsData, floatsByteCount, FREE_CALLBACK)); - slot.vertexBuffer->setBufferObjectAt(engine, slot.bufferIndex, bo); - continue; - } - BufferObject* bo = BufferObject::Builder().size(size).build(engine); - asset->mBufferObjects.push_back(bo); - bo->setBuffer(engine, BufferDescriptor(data, size, - uploadCallback, uploadUserdata(asset, pImpl->mUriDataCache))); - slot.vertexBuffer->setBufferObjectAt(engine, slot.bufferIndex, bo); - continue; - } else if (slot.indexBuffer) { - if (accessor->component_type == cgltf_component_type_r_8u) { - const size_t size16 = size * 2; - uint16_t* data16 = (uint16_t*) malloc(size16); - convertBytesToShorts(data16, data, size); - IndexBuffer::BufferDescriptor bd(data16, size16, FREE_CALLBACK); - slot.indexBuffer->setBuffer(engine, std::move(bd)); - continue; - } - IndexBuffer::BufferDescriptor bd(data, size, uploadCallback, - uploadUserdata(asset, pImpl->mUriDataCache)); - slot.indexBuffer->setBuffer(engine, std::move(bd)); - continue; - } - - // If the buffer slot does not have an associated VertexBuffer or IndexBuffer, then this - // must be a morph target. - assert(slot.morphTargetBuffer); - - if (requiresPacking(accessor)) { - const size_t floatsCount = accessor->count * cgltf_num_components(accessor->type); - const size_t floatsByteCount = sizeof(float) * floatsCount; - float* floatsData = (float*) malloc(floatsByteCount); - cgltf_accessor_unpack_floats(accessor, floatsData, floatsCount); - if (accessor->type == cgltf_type_vec3) { - slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, - (const float3*) floatsData, slot.morphTargetBuffer->getVertexCount()); - } else { - slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, - (const float4*) data, slot.morphTargetBuffer->getVertexCount()); - } - free(floatsData); + // Decompress Draco meshes early on, which allows us to exploit subsequent processing such + // as tangent generation. + DracoCache* dracoCache = &asset->mSourceAsset->dracoCache; + auto& primitives = asset->mPrimitives; + // Go through every primitive and check if it has a Draco mesh. + for (auto& [prim, vertexBuffer]: primitives) { + if (!prim->has_draco_mesh_compression) { continue; } - - if (accessor->type == cgltf_type_vec3) { - slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, - (const float3*) data, slot.morphTargetBuffer->getVertexCount()); - } else { - assert_invariant(accessor->type == cgltf_type_vec4); - slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, - (const float4*) data, slot.morphTargetBuffer->getVertexCount()); - } + utility::decodeDracoMeshes(gltf, prim, dracoCache); } + utility::decodeMeshoptCompression((cgltf_data*) gltf); - // Compute surface orientation quaternions if necessary. This is similar to sparse data in that - // we need to generate the contents of a GPU buffer by processing one or more CPU buffer(s). + uploadBuffers(asset, *pImpl->mEngine, pImpl->mUriDataCache); + + // Compute surface orientation quaternions if necessary. This is similar to sparse data in + // that we need to generate the contents of a GPU buffer by processing one or more CPU + // buffer(s). pImpl->computeTangents(asset); - asset->mBufferSlots = {}; - asset->mPrimitives = {}; + asset->mBufferSlots.clear(); + asset->mPrimitives.clear(); + + createSkins(gltf, pImpl->mNormalizeSkinningWeights, asset->mSkins); // If any decoding jobs are still underway from a previous load, wait for them to finish. - for (const auto& iter : pImpl->mTextureProviders) { + for (const auto& iter: pImpl->mTextureProviders) { iter.second->waitForCompletion(); iter.second->updateQueue(); } @@ -901,36 +749,4 @@ ResourceLoader::Impl::~Impl() { } } -void ResourceLoader::normalizeSkinningWeights(FFilamentAsset* asset) const { - auto normalize = [](cgltf_accessor* data) { - if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) { - slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl; - return; - } - uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data; - bytes += data->offset + data->buffer_view->offset; - for (cgltf_size i = 0, n = data->count; i < n; ++i, bytes += data->stride) { - float4* weights = (float4*) bytes; - const float sum = weights->x + weights->y + weights->z + weights->w; - *weights /= sum; - } - }; - const cgltf_data* gltf = asset->mSourceAsset->hierarchy; - cgltf_size mcount = gltf->meshes_count; - for (cgltf_size mindex = 0; mindex < mcount; ++mindex) { - const cgltf_mesh& mesh = gltf->meshes[mindex]; - cgltf_size pcount = mesh.primitives_count; - for (cgltf_size pindex = 0; pindex < pcount; ++pindex) { - const cgltf_primitive& prim = mesh.primitives[pindex]; - cgltf_size acount = prim.attributes_count; - for (cgltf_size aindex = 0; aindex < acount; ++aindex) { - const auto& attr = prim.attributes[aindex]; - if (attr.type == cgltf_attribute_type_weights) { - normalize(attr.data); - } - } - } - } -} - } // namespace filament::gltfio diff --git a/libs/gltfio/src/Utility.cpp b/libs/gltfio/src/Utility.cpp new file mode 100644 index 00000000000..f875f71a4ea --- /dev/null +++ b/libs/gltfio/src/Utility.cpp @@ -0,0 +1,265 @@ +/* + * 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 "Utility.h" + +#include "DracoCache.h" +#include "FFilamentAsset.h" +#include "GltfEnums.h" + +#include +#include + +#define CGLTF_IMPLEMENTATION +#include +#include + +namespace filament::gltfio::utility { + +using namespace utils; + +void decodeDracoMeshes(cgltf_data const* gltf, cgltf_primitive const* prim, + DracoCache* dracoCache) { + if (!prim->has_draco_mesh_compression) { + return; + } + + // For a given primitive and attribute, find the corresponding accessor. + auto findAccessor = [](const cgltf_primitive* prim, cgltf_attribute_type type, cgltf_int idx) { + for (cgltf_size i = 0; i < prim->attributes_count; i++) { + const cgltf_attribute& attr = prim->attributes[i]; + if (attr.type == type && attr.index == idx) { + return attr.data; + } + } + return (cgltf_accessor*) nullptr; + }; + const cgltf_draco_mesh_compression& draco = prim->draco_mesh_compression; + + // Check if we have already decoded this mesh. + DracoMesh* mesh = dracoCache->findOrCreateMesh(draco.buffer_view); + if (!mesh) { + slog.e << "Cannot decompress mesh, Draco decoding error." << io::endl; + return; + } + + // Copy over the decompressed data, converting the data type if necessary. + if (prim->indices && !mesh->getFaceIndices(prim->indices)) { + return; + } + + // Go through each attribute in the decompressed mesh. + for (cgltf_size i = 0; i < draco.attributes_count; i++) { + + // In cgltf, each Draco attribute's data pointer is an attribute id, not an accessor. + const uint32_t id = draco.attributes[i].data - gltf->accessors; + + // Find the destination accessor; this contains the desired component type, etc. + const cgltf_attribute_type type = draco.attributes[i].type; + const cgltf_int index = draco.attributes[i].index; + cgltf_accessor* accessor = findAccessor(prim, type, index); + if (!accessor) { + slog.w << "Cannot find matching accessor for Draco id " << id << io::endl; + continue; + } + + // Copy over the decompressed data, converting the data type if necessary. + if (!mesh->getVertexAttributes(id, accessor)) { + break; + } + } +} + +void decodeMeshoptCompression(cgltf_data* data) { + for (size_t i = 0; i < data->buffer_views_count; ++i) { + if (!data->buffer_views[i].has_meshopt_compression) { + continue; + } + cgltf_meshopt_compression* compression = &data->buffer_views[i].meshopt_compression; + const uint8_t* source = (const uint8_t*) compression->buffer->data; + assert_invariant(source); + source += compression->offset; + + // This memory is freed by cgltf. + void* destination = malloc(compression->count * compression->stride); + assert_invariant(destination); + + UTILS_UNUSED_IN_RELEASE int error = 0; + switch (compression->mode) { + case cgltf_meshopt_compression_mode_invalid: + break; + case cgltf_meshopt_compression_mode_attributes: + error = meshopt_decodeVertexBuffer(destination, compression->count, + compression->stride, source, compression->size); + break; + case cgltf_meshopt_compression_mode_triangles: + error = meshopt_decodeIndexBuffer(destination, compression->count, + compression->stride, source, compression->size); + break; + case cgltf_meshopt_compression_mode_indices: + error = meshopt_decodeIndexSequence(destination, compression->count, + compression->stride, source, compression->size); + break; + default: + assert_invariant(false); + break; + } + assert_invariant(!error); + + switch (compression->filter) { + case cgltf_meshopt_compression_filter_none: + break; + case cgltf_meshopt_compression_filter_octahedral: + meshopt_decodeFilterOct(destination, compression->count, compression->stride); + break; + case cgltf_meshopt_compression_filter_quaternion: + meshopt_decodeFilterQuat(destination, compression->count, compression->stride); + break; + case cgltf_meshopt_compression_filter_exponential: + meshopt_decodeFilterExp(destination, compression->count, compression->stride); + break; + default: + assert_invariant(false); + break; + } + + data->buffer_views[i].data = destination; + } +} + +bool primitiveHasVertexColor(cgltf_primitive* inPrim) { + for (int slot = 0; slot < inPrim->attributes_count; slot++) { + const cgltf_attribute& inputAttribute = inPrim->attributes[slot]; + if (inputAttribute.type == cgltf_attribute_type_color) { + return true; + } + } + return false; +} + +// Sometimes a glTF bufferview includes unused data at the end (e.g. in skinning.gltf) so we need to +// compute the correct size of the vertex buffer. Filament automatically infers the size of +// driver-level vertex buffers from the attribute data (stride, count, offset) and clients are +// expected to avoid uploading data blobs that exceed this size. Since this information doesn't +// exist in the glTF we need to compute it manually. This is a bit of a cheat, cgltf_calc_size is +// private but its implementation file is available in this cpp file. +uint32_t computeBindingSize(cgltf_accessor const* accessor) { + cgltf_size element_size = cgltf_calc_size(accessor->type, accessor->component_type); + return uint32_t(accessor->stride * (accessor->count - 1) + element_size); +} + +void convertBytesToShorts(uint16_t* dst, uint8_t const* src, size_t count) { + for (size_t i = 0; i < count; ++i) { + dst[i] = src[i]; + } +} + +uint32_t computeBindingOffset(cgltf_accessor const* accessor) { + return uint32_t(accessor->offset + accessor->buffer_view->offset); +} + +bool requiresConversion(cgltf_accessor const* accessor) { + if (UTILS_UNLIKELY(accessor->is_sparse)) { + return true; + } + const cgltf_type type = accessor->type; + const cgltf_component_type ctype = accessor->component_type; + filament::VertexBuffer::AttributeType permitted; + filament::VertexBuffer::AttributeType actual; + UTILS_UNUSED_IN_RELEASE bool supported = getElementType(type, ctype, &permitted, &actual); + assert_invariant(supported && "Unsupported types"); + return permitted != actual; +} + +bool requiresPacking(cgltf_accessor const* accessor) { + if (requiresConversion(accessor)) { + return true; + } + const size_t dim = cgltf_num_components(accessor->type); + switch (accessor->component_type) { + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + return accessor->stride != dim; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + return accessor->stride != dim * 2; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + return accessor->stride != dim * 4; + default: + assert_invariant(false); + return true; + } +} + +bool loadCgltfBuffers(cgltf_data const* gltf, char const* gltfPath, + UriDataCacheHandle uriDataCacheHandle) { + SYSTRACE_CONTEXT(); + SYSTRACE_NAME_BEGIN("Load buffers"); + cgltf_options options{}; + + // For emscripten and Android builds we supply a custom file reader callback that looks inside a + // cache of externally-supplied data blobs, rather than loading from the filesystem. + +#if !GLTFIO_USE_FILESYSTEM + struct Closure { + UriDataCacheHandle uriDataCache; + const cgltf_data* gltf; + }; + + Closure closure = { uriDataCacheHandle, gltf }; + + options.file.user_data = &closure; + + options.file.read = [](const cgltf_memory_options* memoryOpts, + const cgltf_file_options* fileOpts, const char* path, + cgltf_size* size, void** data) { + Closure* closure = (Closure*) fileOpts->user_data; + auto& uriDataCache = closure->uriDataCache; + + if (auto iter = uriDataCache->find(path); iter != uriDataCache->end()) { + *size = iter->second.size; + *data = iter->second.buffer; + } else { + // Even if we don't find the given resource in the cache, we still return a successful + // error code, because we allow downloads to finish after the decoding work starts. + *size = 0; + *data = 0; + } + + return cgltf_result_success; + }; +#endif + + // Read data from the file system and base64 URIs. + cgltf_result result = cgltf_load_buffers(&options, (cgltf_data*) gltf, gltfPath); + if (result != cgltf_result_success) { + slog.e << "Unable to load resources." << io::endl; + return false; + } + + SYSTRACE_NAME_END(); + +#ifndef NDEBUG + if (cgltf_validate((cgltf_data*) gltf) != cgltf_result_success) { + slog.e << "Failed cgltf validation." << io::endl; + return false; + } +#endif + return true; +} + +} // namespace filament::gltfio::utility diff --git a/libs/gltfio/src/Utility.h b/libs/gltfio/src/Utility.h new file mode 100644 index 00000000000..3d2271ec4aa --- /dev/null +++ b/libs/gltfio/src/Utility.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_UTILITY_H +#define GLTFIO_UTILITY_H + +#include + +#include + +#include +#include + +struct FFilamentAsset; +struct cgltf_primitive; +struct cgltf_data; +class DracoCache; + +struct cgltf_accessor; + +namespace filament::gltfio { + +// Referenced in ResourceLoader and AssetLoaderExtended +using BufferDescriptor = filament::backend::BufferDescriptor; +using UriDataCache = tsl::robin_map; +using UriDataCacheHandle = std::shared_ptr; + +} // namespace filament::gltfio::utility + +namespace filament::gltfio::utility { + +// Functions that are shared between the original implementation and the extended implementation. +void decodeDracoMeshes(cgltf_data const* gltf, cgltf_primitive const* prim, DracoCache* dracoCache); +void decodeMeshoptCompression(cgltf_data* data); +bool primitiveHasVertexColor(cgltf_primitive* inPrim); +uint32_t computeBindingSize(cgltf_accessor const* accessor); +void convertBytesToShorts(uint16_t* dst, uint8_t const* src, size_t count); +uint32_t computeBindingOffset(cgltf_accessor const* accessor); +bool requiresConversion(cgltf_accessor const* accessor); +bool requiresPacking(cgltf_accessor const* accessor); +bool loadCgltfBuffers(cgltf_data const* gltf, char const* gltfPath, + UriDataCacheHandle uriDataCacheHandle); + +} // namespace filament::gltfio::utility + +#endif From bb6c8ef1c8aeb71ea3c02173d600ae6762520f4d Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Tue, 12 Mar 2024 13:49:00 -0700 Subject: [PATCH 06/29] Add option FILAMENT_SAMPLES_STEREO_TYPE for samples/gltfio (#7658) This option can be either "instanced" or "multiview", indicating what stereoscopic rendering type shaders in samples/gltfio should be built for. --- CMakeLists.txt | 10 ++++++++++ libs/filamentapp/CMakeLists.txt | 12 +++++++++++- libs/filamentapp/src/FilamentApp.cpp | 5 +++++ libs/gltfio/CMakeLists.txt | 9 ++++++++- samples/CMakeLists.txt | 9 ++++++++- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91ab2922d83..b18c4a5b4ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -531,6 +531,16 @@ else() option(FILAMENT_DISABLE_MATOPT "Disable material optimizations" ON) endif() +# This only affects the prebuilt shader files in gltfio and samples, not filament library. +# The value can be either "instanced" or "multiview". +set(FILAMENT_SAMPLES_STEREO_TYPE "instanced" CACHE STRING + "Stereoscopic type that shader files in gltfio and samples are built for." +) +string(TOLOWER "${FILAMENT_SAMPLES_STEREO_TYPE}" FILAMENT_SAMPLES_STEREO_TYPE) +if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced" AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") + message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\" or \"multiview\" ") +endif () + # ================================================================================================== # Material compilation flags # ================================================================================================== diff --git a/libs/filamentapp/CMakeLists.txt b/libs/filamentapp/CMakeLists.txt index a2c1d0dedbd..2e0e95b7da4 100644 --- a/libs/filamentapp/CMakeLists.txt +++ b/libs/filamentapp/CMakeLists.txt @@ -88,13 +88,23 @@ file(MAKE_DIRECTORY ${MATERIAL_DIR}) file(MAKE_DIRECTORY ${RESOURCE_DIR}) set(RESOURCE_BINS) + +set (MATC_FLAGS ${MATC_BASE_FLAGS}) +if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=instanced) + add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_INSTANCED) +elseif (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=multiview) + add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW) +endif () + foreach (mat_src ${MATERIAL_SRCS}) get_filename_component(localname "${mat_src}" NAME_WE) get_filename_component(fullname "${mat_src}" ABSOLUTE) set(output_path "${MATERIAL_DIR}/${localname}.filamat") add_custom_command( OUTPUT ${output_path} - COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} + COMMAND matc ${MATC_FLAGS} -o ${output_path} ${fullname} MAIN_DEPENDENCY ${mat_src} DEPENDS matc COMMENT "Compiling material ${mat_src} to ${output_path}" diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 7e315b0da47..25f7126f834 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -605,6 +605,11 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, Engine::Config engineConfig = {}; engineConfig.stereoscopicEyeCount = config.stereoscopicEyeCount; +#if defined(FILAMENT_SAMPLES_STEREO_TYPE_INSTANCED) + engineConfig.stereoscopicType = Engine::StereoscopicType::INSTANCED; +#elif defined (FILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW) + engineConfig.stereoscopicType = Engine::StereoscopicType::MULTIVIEW; +#endif if (backend == Engine::Backend::VULKAN) { #if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN) diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index 3791525e29d..2c1532cc163 100644 --- a/libs/gltfio/CMakeLists.txt +++ b/libs/gltfio/CMakeLists.txt @@ -69,6 +69,13 @@ set(TRANSPARENCY default) set(UBERZ_OUTPUT_PATH "${RESOURCE_DIR}/default.uberz") +set (MATC_FLAGS ${MATC_BASE_FLAGS}) +if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=instanced) +elseif (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=multiview) +endif () + function(build_ubershader NAME SRC SHADINGMODEL BLENDING) set(DEST "${RESOURCE_DIR}/${NAME}") configure_file(materials/${SRC}.mat.in "${DEST}.mat" COPYONLY) @@ -85,7 +92,7 @@ function(build_ubershader NAME SRC SHADINGMODEL BLENDING) add_custom_command( OUTPUT "${NAME}.filamat" - COMMAND matc ${MATC_BASE_FLAGS} ${TEMPLATE_ARGS} -o "${NAME}.filamat" "${NAME}.mat" + COMMAND matc ${MATC_FLAGS} ${TEMPLATE_ARGS} -o "${NAME}.filamat" "${NAME}.mat" DEPENDS matc "${DEST}.mat" WORKING_DIRECTORY ${RESOURCE_DIR} COMMENT "Compiling material ${NAME}") diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 9b7804e94f3..027f2f7d6bb 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -47,13 +47,20 @@ endif() file(MAKE_DIRECTORY ${MATERIAL_DIR}) +set (MATC_FLAGS ${MATC_BASE_FLAGS}) +if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=instanced) +elseif (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") + set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=multiview) +endif () + foreach (mat_src ${MATERIAL_SRCS}) get_filename_component(localname "${mat_src}" NAME_WE) get_filename_component(fullname "${mat_src}" ABSOLUTE) set(output_path "${MATERIAL_DIR}/${localname}.filamat") add_custom_command( OUTPUT ${output_path} - COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} + COMMAND matc ${MATC_FLAGS} -o ${output_path} ${fullname} MAIN_DEPENDENCY ${mat_src} DEPENDS matc COMMENT "Compiling material ${mat_src} to ${output_path}" From 348454781fa5fec7f757695672fe26b41e84e21e Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Tue, 12 Mar 2024 14:29:49 -0700 Subject: [PATCH 07/29] Remove erroneous assertion (#7661) --- filament/backend/src/metal/MetalShaderCompiler.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/filament/backend/src/metal/MetalShaderCompiler.mm b/filament/backend/src/metal/MetalShaderCompiler.mm index 7c2cffdb103..7741ae54489 100644 --- a/filament/backend/src/metal/MetalShaderCompiler.mm +++ b/filament/backend/src/metal/MetalShaderCompiler.mm @@ -224,7 +224,9 @@ bool isReady() const noexcept { } } } - assert_invariant(token->isReady()); + + // The job isn't guaranteed to have finished yet. We may have failed to dequeue it above, + // which means it's currently running. In that case get() will block until it finishes. MetalShaderCompiler::MetalFunctionBundle program = token->get(); token = nullptr; From f27f0ef4fc865d14eee1d7f400e7ee8e30e12ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 13 Mar 2024 09:51:53 +0100 Subject: [PATCH 08/29] fix: apply stashCrossFade to correct entity --- libs/gltfio/src/Animator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/gltfio/src/Animator.cpp b/libs/gltfio/src/Animator.cpp index 99a4269d023..e7cdffd6e0f 100644 --- a/libs/gltfio/src/Animator.cpp +++ b/libs/gltfio/src/Animator.cpp @@ -368,7 +368,8 @@ void AnimatorImpl::stashCrossFade() { return index; }; - const Instance root = tm.getInstance(asset->mRoot); + const Entity rootEntity = instance ? instance->getRoot() : asset->mRoot; + const Instance root = tm.getInstance(rootEntity); const size_t count = recursiveCount(root, 0, recursiveCount); crossFade.reserve(count); crossFade.resize(count); From 4ea5872b2644aa757ff85dd96636f4242a24ac12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Wed, 13 Mar 2024 09:54:56 +0100 Subject: [PATCH 09/29] fix: applyCrossFade use correct instance --- libs/gltfio/src/Animator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/gltfio/src/Animator.cpp b/libs/gltfio/src/Animator.cpp index e7cdffd6e0f..6e53b705c60 100644 --- a/libs/gltfio/src/Animator.cpp +++ b/libs/gltfio/src/Animator.cpp @@ -395,7 +395,9 @@ void AnimatorImpl::applyCrossFade(float alpha) { } return index; }; - recursiveFn(tm.getInstance(asset->mRoot), 0, recursiveFn); + const Entity rootEntity = instance ? instance->getRoot() : asset->mRoot; + const Instance root = tm.getInstance(rootEntity); + recursiveFn(root, 0, recursiveFn); } void AnimatorImpl::addChannels(const FixedCapacityVector& nodeMap, From 86479781a5c479595bc3a673825df447eaf0877e Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 11 Mar 2024 13:42:27 -0700 Subject: [PATCH 10/29] assert when swapchain creation fails This is better than risking a null-pointer dereference later. --- filament/backend/src/opengl/OpenGLDriver.cpp | 6 ++++++ filament/backend/src/opengl/platforms/PlatformEGL.cpp | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index cb6958a253a..e2f358fee43 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1418,6 +1418,9 @@ void OpenGLDriver::createSwapChainR(Handle sch, void* nativeWindow, GLSwapChain* sc = handle_cast(sch); sc->swapChain = mPlatform.createSwapChain(nativeWindow, flags); + ASSERT_POSTCONDITION(sc->swapChain, + "createSwapChain(%p, 0x%lx) failed. See logs for details.", + nativeWindow, flags); // See if we need the emulated rec709 output conversion if (UTILS_UNLIKELY(mContext.isES2())) { @@ -1432,6 +1435,9 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, GLSwapChain* sc = handle_cast(sch); sc->swapChain = mPlatform.createSwapChain(width, height, flags); + ASSERT_POSTCONDITION(sc->swapChain, + "createSwapChainHeadless(%u, %u, 0x%lx) failed. See logs for details.", + width, height, flags); // See if we need the emulated rec709 output conversion if (UTILS_UNLIKELY(mContext.isES2())) { diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index e20dcdc500a..b57c118fe67 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -487,6 +487,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain( } if (UTILS_UNLIKELY(config == EGL_NO_CONFIG_KHR)) { + // error already logged return nullptr; } @@ -512,7 +513,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain( (EGLNativeWindowType)nativeWindow, attribs.data()); if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { - logEglError("eglCreateWindowSurface"); + logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface"); return nullptr; } @@ -540,6 +541,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain( } if (UTILS_UNLIKELY(config == EGL_NO_CONFIG_KHR)) { + // error already logged return nullptr; } @@ -567,7 +569,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain( EGLSurface sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data()); if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { - logEglError("eglCreatePbufferSurface"); + logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface"); return nullptr; } From 963e097bdcd6aa6b8be7d7ca793c2228b803a56a Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 11 Mar 2024 15:49:24 -0700 Subject: [PATCH 11/29] handle non-0 default FBO with protected contextes --- .../backend/platforms/OpenGLPlatform.h | 9 +++- filament/backend/src/opengl/OpenGLContext.cpp | 51 ++++++++++++++++++- filament/backend/src/opengl/OpenGLContext.h | 40 ++++----------- filament/backend/src/opengl/OpenGLDriver.cpp | 46 ++++++++--------- filament/backend/src/opengl/OpenGLDriver.h | 4 +- 5 files changed, 91 insertions(+), 59 deletions(-) diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index c89684aa708..8d568bc344f 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -127,7 +127,14 @@ class OpenGLPlatform : public Platform { /** * Called by the driver to establish the default FBO. The default implementation returns 0. - * @return a GLuint casted to a uint32_t that is an OpenGL framebuffer object. + * + * This method can be called either on the regular or protected OpenGL contexts and can return + * a different name. + * + * Despite its misleading name, this method doesn't create a new handle each time it is called, + * as there can only be a single default FBO per context. + * + * @return a GLuint casted to a uint32_t that is an OpenGL framebuffer object. */ virtual uint32_t createDefaultRenderTarget() noexcept; diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 2e6193744a1..bd906451178 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -49,7 +49,8 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept { #endif } -OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept { +OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept + : mPlatform(platform) { state.vao.p = &mDefaultVAO; @@ -270,6 +271,9 @@ void OpenGLContext::synchronizeStateAndCache(size_t index) noexcept { } } + // the default FBO could be invalid + mDefaultFbo[index].reset(); + contextIndex = index; resetState(); } @@ -751,6 +755,51 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) #endif // BACKEND_OPENGL_VERSION_GL + +GLuint OpenGLContext::bindFramebuffer(GLenum target, GLuint buffer) noexcept { + if (UTILS_UNLIKELY(buffer == 0)) { + // we're binding the default frame buffer, resolve it's actual name + auto& defaultFboForThisContext = mDefaultFbo[contextIndex]; + if (UTILS_UNLIKELY(!defaultFboForThisContext.has_value())) { + defaultFboForThisContext = GLuint(mPlatform.createDefaultRenderTarget()); + } + buffer = defaultFboForThisContext.value(); + } + bindFramebufferResolved(target, buffer); + return buffer; +} + +void OpenGLContext::unbindFramebuffer(GLenum target) noexcept { + bindFramebufferResolved(target, 0); +} + +void OpenGLContext::bindFramebufferResolved(GLenum target, GLuint buffer) noexcept { + switch (target) { + case GL_FRAMEBUFFER: + if (state.draw_fbo != buffer || state.read_fbo != buffer) { + state.draw_fbo = state.read_fbo = buffer; + glBindFramebuffer(target, buffer); + } + break; +#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 + case GL_DRAW_FRAMEBUFFER: + if (state.draw_fbo != buffer) { + state.draw_fbo = buffer; + glBindFramebuffer(target, buffer); + } + break; + case GL_READ_FRAMEBUFFER: + if (state.read_fbo != buffer) { + state.read_fbo = buffer; + glBindFramebuffer(target, buffer); + } + break; +#endif + default: + break; + } +} + void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept { if (target == GL_ELEMENT_ARRAY_BUFFER) { constexpr size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER); diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index c0f237f34b3..cce88957698 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -21,6 +21,8 @@ #include "OpenGLTimerQuery.h" +#include + #include #include @@ -30,6 +32,7 @@ #include #include +#include #include #include #include @@ -148,7 +151,8 @@ class OpenGLContext final : public TimerQueryFactoryInterface { inline void bindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) noexcept; - inline void bindFramebuffer(GLenum target, GLuint buffer) noexcept; + GLuint bindFramebuffer(GLenum target, GLuint buffer) noexcept; + void unbindFramebuffer(GLenum target) noexcept; inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept; @@ -460,10 +464,15 @@ class OpenGLContext final : public TimerQueryFactoryInterface { void synchronizeStateAndCache(size_t index) noexcept; private: + OpenGLPlatform& mPlatform; ShaderModel mShaderModel = ShaderModel::MOBILE; FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; TimerQueryFactoryInterface* mTimerQueryFactory = nullptr; std::vector> mDestroyWithNormalContext; + RenderPrimitive mDefaultVAO; + std::optional mDefaultFbo[2]; + + void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept; const std::array, sizeof(bugs)> mBugDatabase{{ { bugs.disable_glFlush, @@ -516,8 +525,6 @@ class OpenGLContext final : public TimerQueryFactoryInterface { ""}, }}; - RenderPrimitive mDefaultVAO; - // this is chosen to minimize code size #if defined(BACKEND_OPENGL_VERSION_GLES) static void initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept; @@ -732,33 +739,6 @@ void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer, #endif } -void OpenGLContext::bindFramebuffer(GLenum target, GLuint buffer) noexcept { - switch (target) { - case GL_FRAMEBUFFER: - if (state.draw_fbo != buffer || state.read_fbo != buffer) { - state.draw_fbo = state.read_fbo = buffer; - glBindFramebuffer(target, buffer); - } - break; -#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - case GL_DRAW_FRAMEBUFFER: - if (state.draw_fbo != buffer) { - state.draw_fbo = buffer; - glBindFramebuffer(target, buffer); - } - break; - case GL_READ_FRAMEBUFFER: - if (state.read_fbo != buffer) { - state.read_fbo = buffer; - glBindFramebuffer(target, buffer); - } - break; -#endif - default: - break; - } -} - void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, size_t targetIndex) noexcept { assert_invariant(targetIndex == getIndexForTextureTarget(target)); assert_invariant(targetIndex < TEXTURE_TARGET_COUNT); diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index e2f358fee43..3aebf795809 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1257,11 +1257,9 @@ void OpenGLDriver::createDefaultRenderTargetR( construct(rth, 0, 0); // FIXME: we don't know the width/height - uint32_t const framebuffer = mPlatform.createDefaultRenderTarget(); - GLRenderTarget* rt = handle_cast(rth); rt->gl.isDefault = true; - rt->gl.fbo = framebuffer; + rt->gl.fbo = 0; // the actual id is resolved at binding time rt->gl.samples = 1; // FIXME: these flags should reflect the actual attachments present rt->targets = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH; @@ -1581,11 +1579,11 @@ void OpenGLDriver::destroyRenderTarget(Handle rth) { GLRenderTarget* rt = handle_cast(rth); if (rt->gl.fbo) { // first unbind this framebuffer if needed - gl.bindFramebuffer(GL_FRAMEBUFFER, 0); + gl.unbindFramebuffer(GL_FRAMEBUFFER); } if (rt->gl.fbo_read) { // first unbind this framebuffer if needed - gl.bindFramebuffer(GL_FRAMEBUFFER, 0); + gl.unbindFramebuffer(GL_FRAMEBUFFER); } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 @@ -2766,13 +2764,13 @@ void OpenGLDriver::beginRenderPass(Handle rth, const TargetBufferFlags clearFlags = params.flags.clear & rt->targets; TargetBufferFlags discardFlags = params.flags.discardStart & rt->targets; - gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); + GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER) if (gl.ext.EXT_discard_framebuffer && !gl.bugs.disable_invalidate_framebuffer) { AttachmentArray attachments; // NOLINT - GLsizei const attachmentCount = getAttachments(attachments, rt, discardFlags); + GLsizei const attachmentCount = getAttachments(attachments, discardFlags, !fbo); if (attachmentCount) { gl.procs.invalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data()); } @@ -2860,9 +2858,9 @@ void OpenGLDriver::endRenderPass(int) { } if (!gl.bugs.disable_invalidate_framebuffer) { // we wouldn't have to bind the framebuffer if we had glInvalidateNamedFramebuffer() - gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); + GLuint const fbo = gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo); AttachmentArray attachments; // NOLINT - GLsizei const attachmentCount = getAttachments(attachments, rt, effectiveDiscardFlags); + GLsizei const attachmentCount = getAttachments(attachments, effectiveDiscardFlags, !fbo); if (attachmentCount) { gl.procs.invalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data()); } @@ -2924,50 +2922,48 @@ void OpenGLDriver::resolvePass(ResolveAction action, GLRenderTarget const* rt, } GLsizei OpenGLDriver::getAttachments(AttachmentArray& attachments, - GLRenderTarget const* rt, TargetBufferFlags buffers) noexcept { - assert_invariant(buffers <= rt->targets); - + TargetBufferFlags buffers, bool isDefaultFramebuffer) noexcept { GLsizei attachmentCount = 0; // the default framebuffer uses different constants!!! - const bool defaultFramebuffer = (rt->gl.fbo == 0); + if (any(buffers & TargetBufferFlags::COLOR0)) { - attachments[attachmentCount++] = defaultFramebuffer ? GL_COLOR : GL_COLOR_ATTACHMENT0; + attachments[attachmentCount++] = isDefaultFramebuffer ? GL_COLOR : GL_COLOR_ATTACHMENT0; } #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (any(buffers & TargetBufferFlags::COLOR1)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT1; } if (any(buffers & TargetBufferFlags::COLOR2)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT2; } if (any(buffers & TargetBufferFlags::COLOR3)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT3; } if (any(buffers & TargetBufferFlags::COLOR4)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT4; } if (any(buffers & TargetBufferFlags::COLOR5)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT5; } if (any(buffers & TargetBufferFlags::COLOR6)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT6; } if (any(buffers & TargetBufferFlags::COLOR7)) { - assert_invariant(!defaultFramebuffer); + assert_invariant(!isDefaultFramebuffer); attachments[attachmentCount++] = GL_COLOR_ATTACHMENT7; } #endif if (any(buffers & TargetBufferFlags::DEPTH)) { - attachments[attachmentCount++] = defaultFramebuffer ? GL_DEPTH : GL_DEPTH_ATTACHMENT; + attachments[attachmentCount++] = isDefaultFramebuffer ? GL_DEPTH : GL_DEPTH_ATTACHMENT; } if (any(buffers & TargetBufferFlags::STENCIL)) { - attachments[attachmentCount++] = defaultFramebuffer ? GL_STENCIL : GL_STENCIL_ATTACHMENT; + attachments[attachmentCount++] = isDefaultFramebuffer ? GL_STENCIL : GL_STENCIL_ATTACHMENT; } return attachmentCount; } @@ -3687,8 +3683,8 @@ void OpenGLDriver::blit( mask, GL_NEAREST); CHECK_GL_ERROR(utils::slog.e) - gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - gl.bindFramebuffer(GL_READ_FRAMEBUFFER, 0); + gl.unbindFramebuffer(GL_DRAW_FRAMEBUFFER); + gl.unbindFramebuffer(GL_READ_FRAMEBUFFER); glDeleteFramebuffers(2, fbo); if (any(d->usage & TextureUsage::SAMPLEABLE)) { diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index ccaddeefbf6..129a34f0f7f 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -357,8 +357,8 @@ class OpenGLDriver final : public DriverBase { } using AttachmentArray = std::array; - static GLsizei getAttachments(AttachmentArray& attachments, - GLRenderTarget const* rt, TargetBufferFlags buffers) noexcept; + static GLsizei getAttachments(AttachmentArray& attachments, TargetBufferFlags buffers, + bool isDefaultFramebuffer) noexcept; // state required to represent the current render pass Handle mRenderPassTarget; From 93a80fd084b2b7b47a4730ffe47b49154e3e2749 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 11 Mar 2024 20:09:09 -0700 Subject: [PATCH 12/29] More Platform improvements for protected contexts - `isSwapChainProtected()` is now virtual - `createDefaultRenderTarget()` is renamed to `getDefaultFramebufferObject()` - new `getCurrentContextType()` returns the current context type - `makeCurrent` now takes an additional `ContextType` - `PlatformEGL::getContextForType()` to retrieve the `EGLContext` for a given type. - `PlatformEGL::makeCurrent` non-virtual utilities to set only the context or swapchains. --- .../backend/platforms/OpenGLPlatform.h | 43 +++++-- .../backend/platforms/PlatformCocoaGL.h | 2 +- .../backend/platforms/PlatformCocoaTouchGL.h | 4 +- .../include/backend/platforms/PlatformEGL.h | 45 ++++++-- .../include/backend/platforms/PlatformGLX.h | 2 +- .../include/backend/platforms/PlatformWGL.h | 2 +- .../include/backend/platforms/PlatformWebGL.h | 2 +- filament/backend/src/opengl/OpenGLContext.cpp | 4 +- .../backend/src/opengl/OpenGLPlatform.cpp | 14 ++- .../src/opengl/platforms/PlatformCocoaGL.mm | 5 +- .../opengl/platforms/PlatformCocoaTouchGL.mm | 6 +- .../src/opengl/platforms/PlatformEGL.cpp | 108 +++++++++++------- .../opengl/platforms/PlatformEGLAndroid.cpp | 5 +- .../src/opengl/platforms/PlatformGLX.cpp | 5 +- .../src/opengl/platforms/PlatformWGL.cpp | 5 +- .../src/opengl/platforms/PlatformWebGL.cpp | 5 +- filament/src/details/Renderer.cpp | 3 +- 17 files changed, 170 insertions(+), 90 deletions(-) diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index 8d568bc344f..565a4641cbc 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -125,37 +125,60 @@ class OpenGLPlatform : public Platform { */ virtual TargetBufferFlags getPreservedFlags(SwapChain* UTILS_NONNULL swapChain) noexcept; + /** + * Returns true if the swapchain is protected + */ + virtual bool isSwapChainProtected(Platform::SwapChain* UTILS_NONNULL swapChain) noexcept; + /** * Called by the driver to establish the default FBO. The default implementation returns 0. * * This method can be called either on the regular or protected OpenGL contexts and can return - * a different name. - * - * Despite its misleading name, this method doesn't create a new handle each time it is called, - * as there can only be a single default FBO per context. + * a different or identical name, since these names exist in different namespaces. * * @return a GLuint casted to a uint32_t that is an OpenGL framebuffer object. */ - virtual uint32_t createDefaultRenderTarget() noexcept; + virtual uint32_t getDefaultFramebufferObject() noexcept; + /** - * 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. + * Type of contexts available + */ + enum class ContextType { + NONE, //!< No current context + UNPROTECTED, //!< current context is unprotected + PROTECTED //!< current context supports protected content + }; + + /** + * Returns the type of the context currently in use. This value is updated by makeCurrent() + * and therefore can be cached between calls. ContextType::PROTECTED can only be returned + * if isProtectedContextSupported() is true. + * @return ContextType + */ + virtual ContextType getCurrentContextType() const noexcept; + + /** + * Binds the requested context to the current thread and drawSwapChain to the default FBO + * returned by getDefaultFramebufferObject(). + * + * @param type type of context to bind to the current thread. * @param drawSwapChain SwapChain to draw to. It must be bound to the default FBO. * @param readSwapChain SwapChain to read from (for operation like `glBlitFramebuffer`) + * @return true on success, false on error. */ - virtual void makeCurrent( + virtual bool makeCurrent(ContextType type, 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 drawSwapChain to the default FBO returned by getDefaultFramebufferObject(). * 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*). + * The default implementation just calls makeCurrent(getCurrentContextType(), 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`) diff --git a/filament/backend/include/backend/platforms/PlatformCocoaGL.h b/filament/backend/include/backend/platforms/PlatformCocoaGL.h index 72a12185d3d..97c9c3ce892 100644 --- a/filament/backend/include/backend/platforms/PlatformCocoaGL.h +++ b/filament/backend/include/backend/platforms/PlatformCocoaGL.h @@ -57,7 +57,7 @@ class PlatformCocoaGL : 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; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; void destroyExternalImage(ExternalTexture* texture) noexcept override; diff --git a/filament/backend/include/backend/platforms/PlatformCocoaTouchGL.h b/filament/backend/include/backend/platforms/PlatformCocoaTouchGL.h index a60a6369561..e7f1d1ffe44 100644 --- a/filament/backend/include/backend/platforms/PlatformCocoaTouchGL.h +++ b/filament/backend/include/backend/platforms/PlatformCocoaTouchGL.h @@ -45,7 +45,7 @@ class PlatformCocoaTouchGL : public OpenGLPlatform { void terminate() noexcept override; - uint32_t createDefaultRenderTarget() noexcept override; + uint32_t getDefaultFramebufferObject() noexcept override; bool isExtraContextSupported() const noexcept override; void createContext(bool shared) override; @@ -53,7 +53,7 @@ class PlatformCocoaTouchGL : 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; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override; diff --git a/filament/backend/include/backend/platforms/PlatformEGL.h b/filament/backend/include/backend/platforms/PlatformEGL.h index 4c88fa3fa44..ef6876536b1 100644 --- a/filament/backend/include/backend/platforms/PlatformEGL.h +++ b/filament/backend/include/backend/platforms/PlatformEGL.h @@ -99,11 +99,18 @@ 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; + bool isSwapChainProtected(SwapChain* swapChain) noexcept override; + + ContextType getCurrentContextType() const noexcept override; + + bool makeCurrent(ContextType type, + SwapChain* drawSwapChain, + SwapChain* readSwapChain) 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; @@ -132,21 +139,25 @@ class PlatformEGL : public OpenGLPlatform { /** * 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; + EGLContext getContextForType(ContextType type) const noexcept; + + // makes the draw and read surface current without changing the current context + EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { + return egl.makeCurrent(drawSurface, readSurface); + } + + // makes context current and set draw and read surfaces to EGL_NO_SURFACE + EGLBoolean makeCurrent(EGLContext context) noexcept { + return egl.makeCurrent(context, mEGLDummySurface, mEGLDummySurface); + } // 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; + ContextType mCurrentContextType = ContextType::NONE; // mEGLConfig is valid only if ext.egl.KHR_no_config_context is false EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR; Config mContextAttribs; @@ -181,8 +192,20 @@ class PlatformEGL : public OpenGLPlatform { EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const; private: - EGLBoolean makeCurrent(EGLContext context, - EGLSurface drawSurface, EGLSurface readSurface) noexcept; + class EGL { + EGLDisplay& mEGLDisplay; + EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE; + EGLSurface mCurrentReadSurface = EGL_NO_SURFACE; + EGLContext mCurrentContext = EGL_NO_CONTEXT; + public: + explicit EGL(EGLDisplay& dpy) : mEGLDisplay(dpy) {} + EGLBoolean makeCurrent(EGLContext context, + EGLSurface drawSurface, EGLSurface readSurface) noexcept; + + EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { + return makeCurrent(mCurrentContext, drawSurface, readSurface); + } + } egl{ mEGLDisplay }; }; } // namespace filament::backend diff --git a/filament/backend/include/backend/platforms/PlatformGLX.h b/filament/backend/include/backend/platforms/PlatformGLX.h index b2be5e40628..796e27a1118 100644 --- a/filament/backend/include/backend/platforms/PlatformGLX.h +++ b/filament/backend/include/backend/platforms/PlatformGLX.h @@ -51,7 +51,7 @@ class PlatformGLX : 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; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; private: diff --git a/filament/backend/include/backend/platforms/PlatformWGL.h b/filament/backend/include/backend/platforms/PlatformWGL.h index 6c16c30518b..e0003156b86 100644 --- a/filament/backend/include/backend/platforms/PlatformWGL.h +++ b/filament/backend/include/backend/platforms/PlatformWGL.h @@ -53,7 +53,7 @@ class PlatformWGL : 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; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; protected: diff --git a/filament/backend/include/backend/platforms/PlatformWebGL.h b/filament/backend/include/backend/platforms/PlatformWebGL.h index 92bff0c4e8c..0d83fbb979e 100644 --- a/filament/backend/include/backend/platforms/PlatformWebGL.h +++ b/filament/backend/include/backend/platforms/PlatformWebGL.h @@ -46,7 +46,7 @@ class PlatformWebGL : 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; + bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; }; diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index bd906451178..79231ab3f68 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -758,10 +758,10 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) GLuint OpenGLContext::bindFramebuffer(GLenum target, GLuint buffer) noexcept { if (UTILS_UNLIKELY(buffer == 0)) { - // we're binding the default frame buffer, resolve it's actual name + // we're binding the default frame buffer, resolve its actual name auto& defaultFboForThisContext = mDefaultFbo[contextIndex]; if (UTILS_UNLIKELY(!defaultFboForThisContext.has_value())) { - defaultFboForThisContext = GLuint(mPlatform.createDefaultRenderTarget()); + defaultFboForThisContext = GLuint(mPlatform.getDefaultFramebufferObject()); } buffer = defaultFboForThisContext.value(); } diff --git a/filament/backend/src/opengl/OpenGLPlatform.cpp b/filament/backend/src/opengl/OpenGLPlatform.cpp index 9fdc216d28e..17359e85f7b 100644 --- a/filament/backend/src/opengl/OpenGLPlatform.cpp +++ b/filament/backend/src/opengl/OpenGLPlatform.cpp @@ -40,7 +40,7 @@ OpenGLPlatform::~OpenGLPlatform() noexcept = default; void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain, utils::Invocable, utils::Invocable) noexcept { - makeCurrent(drawSwapChain, readSwapChain); + makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain); } bool OpenGLPlatform::isProtectedContextSupported() const noexcept { @@ -51,10 +51,14 @@ bool OpenGLPlatform::isSRGBSwapChainSupported() const noexcept { return false; } -uint32_t OpenGLPlatform::createDefaultRenderTarget() noexcept { +uint32_t OpenGLPlatform::getDefaultFramebufferObject() noexcept { return 0; } +OpenGLPlatform::ContextType OpenGLPlatform::getCurrentContextType() const noexcept { + return ContextType::UNPROTECTED; +} + void OpenGLPlatform::setPresentationTime( UTILS_UNUSED int64_t presentationTimeInNanosecond) noexcept { } @@ -125,10 +129,14 @@ AcquiredImage OpenGLPlatform::transformAcquiredImage(AcquiredImage source) noexc return source; } -TargetBufferFlags OpenGLPlatform::getPreservedFlags(UTILS_UNUSED SwapChain* swapChain) noexcept { +TargetBufferFlags OpenGLPlatform::getPreservedFlags(UTILS_UNUSED SwapChain*) noexcept { return TargetBufferFlags::NONE; } +bool OpenGLPlatform::isSwapChainProtected(UTILS_UNUSED SwapChain*) noexcept { + return false; +} + bool OpenGLPlatform::isExtraContextSupported() const noexcept { return false; } diff --git a/filament/backend/src/opengl/platforms/PlatformCocoaGL.mm b/filament/backend/src/opengl/platforms/PlatformCocoaGL.mm index b19ec4cbbf4..95645f02b51 100644 --- a/filament/backend/src/opengl/platforms/PlatformCocoaGL.mm +++ b/filament/backend/src/opengl/platforms/PlatformCocoaGL.mm @@ -247,8 +247,8 @@ delete cocoaSwapChain; } -void PlatformCocoaGL::makeCurrent(Platform::SwapChain* drawSwapChain, - Platform::SwapChain* readSwapChain) noexcept { +bool PlatformCocoaGL::makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain, "ContextManagerCocoa does not support using distinct draw/read swap chains."); CocoaGLSwapChain* swapChain = (CocoaGLSwapChain*)drawSwapChain; @@ -287,6 +287,7 @@ swapChain->previousBounds = currentBounds; swapChain->previousWindowFrame = currentWindowFrame; + return true; } void PlatformCocoaGL::commit(Platform::SwapChain* swapChain) noexcept { diff --git a/filament/backend/src/opengl/platforms/PlatformCocoaTouchGL.mm b/filament/backend/src/opengl/platforms/PlatformCocoaTouchGL.mm index 0d375df103b..91fb5cb88a2 100644 --- a/filament/backend/src/opengl/platforms/PlatformCocoaTouchGL.mm +++ b/filament/backend/src/opengl/platforms/PlatformCocoaTouchGL.mm @@ -143,11 +143,12 @@ } } -uint32_t PlatformCocoaTouchGL::createDefaultRenderTarget() noexcept { +uint32_t PlatformCocoaTouchGL::getDefaultFramebufferObject() noexcept { return pImpl->mDefaultFramebuffer; } -void PlatformCocoaTouchGL::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept { +bool PlatformCocoaTouchGL::makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain, "PlatformCocoaTouchGL does not support using distinct draw/read swap chains."); CAEAGLLayer* const glLayer = (__bridge CAEAGLLayer*) drawSwapChain; @@ -182,6 +183,7 @@ ASSERT_POSTCONDITION(status == GL_FRAMEBUFFER_COMPLETE, "Incomplete framebuffer."); glBindFramebuffer(GL_FRAMEBUFFER, oldFramebuffer); } + return true; } void PlatformCocoaTouchGL::commit(Platform::SwapChain* swapChain) noexcept { diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index b57c118fe67..fcf4b9c4922 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -25,8 +25,9 @@ #include #include -#if defined(__ANDROID__) +#include +#if defined(__ANDROID__) #include #endif #include @@ -282,12 +283,14 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon } } - if (UTILS_UNLIKELY(makeCurrent(mEGLContext, mEGLDummySurface, mEGLDummySurface) == EGL_FALSE)) { + if (UTILS_UNLIKELY( + egl.makeCurrent(mEGLContext, mEGLDummySurface, mEGLDummySurface) == EGL_FALSE)) { // eglMakeCurrent failed logEglError("eglMakeCurrent"); goto error; } + mCurrentContextType = ContextType::UNPROTECTED; mContextAttribs = std::move(contextAttribs); initializeGlExtensions(); @@ -362,26 +365,6 @@ void PlatformEGL::releaseContext() noexcept { eglReleaseThread(); } -EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept { - 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; -} - void PlatformEGL::terminate() noexcept { // it's always allowed to use EGL_NO_SURFACE, EGL_NO_CONTEXT eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); @@ -468,14 +451,6 @@ 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 { @@ -585,29 +560,42 @@ void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { if (swapChain) { SwapChainEGL const* const sc = static_cast(swapChain); if (sc->sur != EGL_NO_SURFACE) { - makeCurrent(mCurrentContext, mEGLDummySurface, mEGLDummySurface); + egl.makeCurrent(mEGLDummySurface, mEGLDummySurface); eglDestroySurface(mEGLDisplay, sc->sur); delete sc; } } } -void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain, - Platform::SwapChain* readSwapChain) noexcept { +bool PlatformEGL::isSwapChainProtected(Platform::SwapChain* swapChain) noexcept { + if (swapChain) { + SwapChainEGL const* const sc = static_cast(swapChain); + return bool(sc->flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT); + } + return false; +} + +OpenGLPlatform::ContextType PlatformEGL::getCurrentContextType() const noexcept { + return mCurrentContextType; +} + +bool PlatformEGL::makeCurrent(ContextType type, + SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept { SwapChainEGL const* const dsc = static_cast(drawSwapChain); SwapChainEGL const* const rsc = static_cast(readSwapChain); - makeCurrent(mEGLContext, dsc->sur, rsc->sur); + EGLContext context = getContextForType(type); + EGLBoolean const success = egl.makeCurrent(context, dsc->sur, rsc->sur); + return success == EGL_TRUE ? true : false; } 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; + + ContextType type = ContextType::UNPROTECTED; if (ext.egl.EXT_protected_content) { - bool const swapChainProtected = PlatformEGL::isSwapChainProtected(dsc); + bool const swapChainProtected = isSwapChainProtected(drawSwapChain); if (UTILS_UNLIKELY(swapChainProtected)) { // we need a protected context if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) { @@ -624,34 +612,36 @@ void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain, goto error; } } - context = mEGLContextProtected; + type = ContextType::PROTECTED; error: ; } - bool const contextChange = context != mCurrentContext; + bool const contextChange = type != mCurrentContextType; + mCurrentContextType = type; + if (UTILS_UNLIKELY(contextChange)) { preContextChange(); - EGLBoolean const success = makeCurrent(context, dsc->sur, rsc->sur); + bool const success = makeCurrent(mCurrentContextType, drawSwapChain, readSwapChain); if (UTILS_UNLIKELY(!success)) { logEglError("PlatformEGL::makeCurrent"); if (mEGLContextProtected != EGL_NO_CONTEXT) { eglDestroyContext(mEGLDisplay, mEGLContextProtected); mEGLContextProtected = EGL_NO_CONTEXT; } - context = mEGLContext; + mCurrentContextType = ContextType::UNPROTECTED; } 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; + size_t const contextIndex = (mCurrentContextType == ContextType::PROTECTED) ? 1 : 0; postContextChange(contextIndex); return; } } - EGLBoolean const success = makeCurrent(context, dsc->sur, rsc->sur); + bool const success = makeCurrent(mCurrentContextType, drawSwapChain, readSwapChain); if (UTILS_UNLIKELY(!success)) { logEglError("PlatformEGL::makeCurrent"); } @@ -748,6 +738,17 @@ void PlatformEGL::initializeGlExtensions() noexcept { ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3"); } +EGLContext PlatformEGL::getContextForType(OpenGLPlatform::ContextType type) const noexcept { + switch (type) { + case ContextType::NONE: + return EGL_NO_CONTEXT; + case ContextType::UNPROTECTED: + return mEGLContext; + case ContextType::PROTECTED: + return mEGLContextProtected; + } +} + // --------------------------------------------------------------------------------------------- PlatformEGL::Config::Config() = default; @@ -784,4 +785,23 @@ void PlatformEGL::Config::erase(EGLint name) noexcept { } } +// ------------------------------------------------------------------------------------------------ + +EGLBoolean PlatformEGL::EGL::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; +} + } // namespace filament::backend diff --git a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp index 81d390397f2..addc5e02719 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGLAndroid.cpp @@ -142,11 +142,12 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext, } void PlatformEGLAndroid::setPresentationTime(int64_t presentationTimeInNanosecond) noexcept { - if (mCurrentDrawSurface != EGL_NO_SURFACE) { + EGLSurface currentDrawSurface = eglGetCurrentSurface(EGL_DRAW); + if (currentDrawSurface != EGL_NO_SURFACE) { if (eglPresentationTimeANDROID) { eglPresentationTimeANDROID( mEGLDisplay, - mCurrentDrawSurface, + currentDrawSurface, presentationTimeInNanosecond); } } diff --git a/filament/backend/src/opengl/platforms/PlatformGLX.cpp b/filament/backend/src/opengl/platforms/PlatformGLX.cpp index 7a11f9f3f2a..b2f7ba3432b 100644 --- a/filament/backend/src/opengl/platforms/PlatformGLX.cpp +++ b/filament/backend/src/opengl/platforms/PlatformGLX.cpp @@ -265,10 +265,11 @@ void PlatformGLX::destroySwapChain(Platform::SwapChain* swapChain) noexcept { } } -void PlatformGLX::makeCurrent( - Platform::SwapChain* drawSwapChain, Platform::SwapChain* readSwapChain) noexcept { +bool PlatformGLX::makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { g_glx.setCurrentContext(mGLXDisplay, (GLXDrawable)drawSwapChain, (GLXDrawable)readSwapChain, mGLXContext); + return true; } void PlatformGLX::commit(Platform::SwapChain* swapChain) noexcept { diff --git a/filament/backend/src/opengl/platforms/PlatformWGL.cpp b/filament/backend/src/opengl/platforms/PlatformWGL.cpp index 7247f4d0df7..26746b708e6 100644 --- a/filament/backend/src/opengl/platforms/PlatformWGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformWGL.cpp @@ -255,8 +255,8 @@ void PlatformWGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { wglMakeCurrent(mWhdc, mContext); } -void PlatformWGL::makeCurrent(Platform::SwapChain* drawSwapChain, - Platform::SwapChain* readSwapChain) noexcept { +bool PlatformWGL::makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain, "PlatformWGL does not support distinct draw/read swap chains."); @@ -269,6 +269,7 @@ void PlatformWGL::makeCurrent(Platform::SwapChain* drawSwapChain, wglMakeCurrent(0, NULL); } } + return true; } void PlatformWGL::commit(Platform::SwapChain* swapChain) noexcept { diff --git a/filament/backend/src/opengl/platforms/PlatformWebGL.cpp b/filament/backend/src/opengl/platforms/PlatformWebGL.cpp index e4017da9454..a6d4c5c5a20 100644 --- a/filament/backend/src/opengl/platforms/PlatformWebGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformWebGL.cpp @@ -46,8 +46,9 @@ Platform::SwapChain* PlatformWebGL::createSwapChain( void PlatformWebGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { } -void PlatformWebGL::makeCurrent(Platform::SwapChain* drawSwapChain, - Platform::SwapChain* readSwapChain) noexcept { +bool PlatformWebGL::makeCurrent(ContextType type, SwapChain* drawSwapChain, + SwapChain* readSwapChain) noexcept { + return true; } void PlatformWebGL::commit(Platform::SwapChain* swapChain) noexcept { diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 41b43450f43..329c3841b3a 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -233,8 +233,7 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN SYSTRACE_CALL(); - -#if defined(__ANDROID__) +#if 0 && defined(__ANDROID__) char scratch[PROP_VALUE_MAX + 1]; int length = __system_property_get("debug.filament.protected", scratch); if (swapChain && length > 0) { From 3c25dab22fe6209c36b7734fc30b7cad0ad268e4 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Wed, 13 Mar 2024 16:51:22 -0700 Subject: [PATCH 13/29] vk: clean-up initialization of classes (#7667) Instead of holding pointers to class instances in VulkanDriver, we standardize by making the relevant classes have proper constructors and initialize in VulkanDriver's constructor initializer list. --- filament/backend/src/vulkan/VulkanBlitter.cpp | 12 +-- filament/backend/src/vulkan/VulkanBlitter.h | 7 +- .../backend/src/vulkan/VulkanCommands.cpp | 2 +- filament/backend/src/vulkan/VulkanCommands.h | 97 ++++++++++--------- filament/backend/src/vulkan/VulkanDriver.cpp | 93 +++++++++--------- filament/backend/src/vulkan/VulkanDriver.h | 5 +- .../backend/src/vulkan/VulkanFboCache.cpp | 3 +- filament/backend/src/vulkan/VulkanFboCache.h | 3 +- .../backend/src/vulkan/VulkanSamplerCache.cpp | 3 +- .../backend/src/vulkan/VulkanSamplerCache.h | 2 +- .../backend/src/vulkan/VulkanStagePool.cpp | 7 +- filament/backend/src/vulkan/VulkanStagePool.h | 2 +- 12 files changed, 114 insertions(+), 122 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanBlitter.cpp b/filament/backend/src/vulkan/VulkanBlitter.cpp index fafdd3f8434..c4316cf1ec8 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.cpp +++ b/filament/backend/src/vulkan/VulkanBlitter.cpp @@ -154,15 +154,9 @@ struct BlitterUniforms { }// anonymous namespace -VulkanBlitter::VulkanBlitter() noexcept = default; - -void VulkanBlitter::initialize(VkPhysicalDevice physicalDevice, VkDevice device, - VmaAllocator allocator, VulkanCommands* commands) noexcept { - mPhysicalDevice = physicalDevice; - mDevice = device; - mAllocator = allocator; - mCommands = commands; -} +VulkanBlitter::VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept + : mPhysicalDevice(physicalDevice), + mCommands(commands) {} void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) { diff --git a/filament/backend/src/vulkan/VulkanBlitter.h b/filament/backend/src/vulkan/VulkanBlitter.h index d8b13a07680..3eb93130f25 100644 --- a/filament/backend/src/vulkan/VulkanBlitter.h +++ b/filament/backend/src/vulkan/VulkanBlitter.h @@ -32,10 +32,7 @@ struct VulkanProgram; class VulkanBlitter { public: - VulkanBlitter() noexcept; - - void initialize(VkPhysicalDevice physicalDevice, VkDevice device, - VmaAllocator allocator, VulkanCommands* commands) noexcept; + VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept; void blit(VkFilter filter, VulkanAttachment dst, const VkOffset3D* dstRectPair, @@ -47,8 +44,6 @@ class VulkanBlitter { private: UTILS_UNUSED VkPhysicalDevice mPhysicalDevice; - VkDevice mDevice; - VmaAllocator mAllocator; VulkanCommands* mCommands; }; diff --git a/filament/backend/src/vulkan/VulkanCommands.cpp b/filament/backend/src/vulkan/VulkanCommands.cpp index 21d5944cbd9..4f9957753d3 100644 --- a/filament/backend/src/vulkan/VulkanCommands.cpp +++ b/filament/backend/src/vulkan/VulkanCommands.cpp @@ -155,7 +155,7 @@ VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFam #endif } -VulkanCommands::~VulkanCommands() { +void VulkanCommands::terminate() { wait(); gc(); vkDestroyCommandPool(mDevice, mPool, VKALLOC); diff --git a/filament/backend/src/vulkan/VulkanCommands.h b/filament/backend/src/vulkan/VulkanCommands.h index 29de6f772fc..c3e6697b9d4 100644 --- a/filament/backend/src/vulkan/VulkanCommands.h +++ b/filament/backend/src/vulkan/VulkanCommands.h @@ -139,71 +139,72 @@ class CommandBufferObserver { // - We do this because vkGetFenceStatus must be called from the rendering thread. // class VulkanCommands { - public: - VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex, - VulkanContext* context, VulkanResourceAllocator* allocator); - ~VulkanCommands(); +public: + VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex, + VulkanContext* context, VulkanResourceAllocator* allocator); + + void terminate(); - // Creates a "current" command buffer if none exists, otherwise returns the current one. - VulkanCommandBuffer& get(); + // Creates a "current" command buffer if none exists, otherwise returns the current one. + VulkanCommandBuffer& get(); - // Submits the current command buffer if it exists, then sets "current" to null. - // If there are no outstanding commands then nothing happens and this returns false. - bool flush(); + // Submits the current command buffer if it exists, then sets "current" to null. + // If there are no outstanding commands then nothing happens and this returns false. + bool flush(); - // Returns the "rendering finished" semaphore for the most recent flush and removes - // it from the existing dependency chain. This is especially useful for setting up - // vkQueuePresentKHR. - VkSemaphore acquireFinishedSignal(); + // Returns the "rendering finished" semaphore for the most recent flush and removes + // it from the existing dependency chain. This is especially useful for setting up + // vkQueuePresentKHR. + VkSemaphore acquireFinishedSignal(); - // Takes a semaphore that signals when the next flush can occur. Only one injected - // semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR. - void injectDependency(VkSemaphore next); + // Takes a semaphore that signals when the next flush can occur. Only one injected + // semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR. + void injectDependency(VkSemaphore next); - // Destroys all command buffers that are no longer in use. - void gc(); + // Destroys all command buffers that are no longer in use. + void gc(); - // Waits for all outstanding command buffers to finish. - void wait(); + // Waits for all outstanding command buffers to finish. + void wait(); - // Updates the atomic "status" variable in every extant fence. - void updateFences(); + // Updates the atomic "status" variable in every extant fence. + void updateFences(); - // Sets an observer who is notified every time a new command buffer has been made "current". - // The observer's event handler can only be called during get(). - void setObserver(CommandBufferObserver* observer) { mObserver = observer; } + // Sets an observer who is notified every time a new command buffer has been made "current". + // The observer's event handler can only be called during get(). + void setObserver(CommandBufferObserver* observer) { mObserver = observer; } #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {}); + void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {}); - void popGroupMarker(); + void popGroupMarker(); - void insertEventMarker(char const* string, uint32_t len); + void insertEventMarker(char const* string, uint32_t len); - std::string getTopGroupMarker() const; + std::string getTopGroupMarker() const; #endif - private: - static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS; - VkDevice const mDevice; - VkQueue const mQueue; - VkCommandPool const mPool; - VulkanContext const* mContext; - - // int8 only goes up to 127, therefore capacity must be less than that. - static_assert(CAPACITY < 128); - int8_t mCurrentCommandBufferIndex = -1; - VkSemaphore mSubmissionSignal = {}; - VkSemaphore mInjectedSignal = {}; - utils::FixedCapacityVector> mStorage; - VkFence mFences[CAPACITY] = {}; - VkSemaphore mSubmissionSignals[CAPACITY] = {}; - uint8_t mAvailableBufferCount = CAPACITY; - CommandBufferObserver* mObserver = nullptr; +private: + static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS; + VkDevice const mDevice; + VkQueue const mQueue; + VkCommandPool const mPool; + VulkanContext const* mContext; + + // int8 only goes up to 127, therefore capacity must be less than that. + static_assert(CAPACITY < 128); + int8_t mCurrentCommandBufferIndex = -1; + VkSemaphore mSubmissionSignal = {}; + VkSemaphore mInjectedSignal = {}; + utils::FixedCapacityVector> mStorage; + VkFence mFences[CAPACITY] = {}; + VkSemaphore mSubmissionSignals[CAPACITY] = {}; + uint8_t mAvailableBufferCount = CAPACITY; + CommandBufferObserver* mObserver = nullptr; #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - std::unique_ptr mGroupMarkers; - std::unique_ptr mCarriedOverMarkers; + std::unique_ptr mGroupMarkers; + std::unique_ptr mCarriedOverMarkers; #endif }; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 05e85a6d48b..f65d2ada0ea 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -212,7 +212,13 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mResourceAllocator(driverConfig.handleArenaSize), mResourceManager(&mResourceAllocator), mThreadSafeResourceManager(&mResourceAllocator), + mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), + mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator), mPipelineCache(&mResourceAllocator), + mStagePool(mAllocator, &mCommands), + mFramebufferCache(mPlatform->getDevice()), + mSamplerCache(mPlatform->getDevice()), + mBlitter(mPlatform->getPhysicalDevice(), &mCommands), mReadPixels(mPlatform->getDevice()), mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) { @@ -237,23 +243,16 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex #endif mTimestamps = std::make_unique(mPlatform->getDevice()); - mCommands = std::make_unique(mPlatform->getDevice(), - mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext, - &mResourceAllocator); - mCommands->setObserver(&mPipelineCache); + mCommands.setObserver(&mPipelineCache); mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator); - // TOOD: move them all to be initialized by constructor - mStagePool.initialize(mAllocator, mCommands.get()); - mFramebufferCache.initialize(mPlatform->getDevice()); - mSamplerCache.initialize(mPlatform->getDevice()); + mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), + mContext, mAllocator, &mCommands, mStagePool); - mEmptyTexture.reset(createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), - mContext, mAllocator, mCommands.get(), mStagePool)); + // Use resource manager to ref-count placeholder resources. + mResourceManager.acquire(mEmptyTexture); mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView()); - mBlitter.initialize(mPlatform->getPhysicalDevice(), mPlatform->getDevice(), mAllocator, - mCommands.get()); } VulkanDriver::~VulkanDriver() noexcept = default; @@ -316,8 +315,10 @@ ShaderModel VulkanDriver::getShaderModel() const noexcept { void VulkanDriver::terminate() { // Command buffers should come first since it might have commands depending on resources that // are about to be destroyed. - mCommands.reset(); - mEmptyTexture.reset(); + mCommands.terminate(); + + mResourceManager.clear(); + mTimestamps.reset(); mBlitter.terminate(); @@ -346,7 +347,7 @@ void VulkanDriver::terminate() { } void VulkanDriver::tick(int) { - mCommands->updateFences(); + mCommands.updateFences(); } // Garbage collection should not occur too frequently, only about once per frame. Internally, the @@ -358,7 +359,7 @@ void VulkanDriver::collectGarbage() { FVK_SYSTRACE_START("gc"); // Command buffers need to be submitted and completed before other resources can be gc'd. And // its gc() function carrys out the *wait*. - mCommands->gc(); + mCommands.gc(); mStagePool.gc(); mFramebufferCache.gc(); @@ -386,7 +387,7 @@ void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) { void VulkanDriver::endFrame(uint32_t frameId) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("endframe"); - mCommands->flush(); + mCommands.flush(); collectGarbage(); FVK_SYSTRACE_END(); } @@ -394,7 +395,7 @@ void VulkanDriver::endFrame(uint32_t frameId) { void VulkanDriver::flush(int) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("flush"); - mCommands->flush(); + mCommands.flush(); FVK_SYSTRACE_END(); } @@ -402,8 +403,8 @@ void VulkanDriver::finish(int dummy) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("finish"); - mCommands->flush(); - mCommands->wait(); + mCommands.flush(); + mCommands.wait(); mReadPixels.runUntilComplete(); FVK_SYSTRACE_END(); @@ -500,7 +501,7 @@ void VulkanDriver::createTextureR(Handle th, SamplerType target, uint TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) { auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels, + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels, format, samples, w, h, depth, usage, mStagePool); mResourceManager.acquire(vktexture); } @@ -512,7 +513,7 @@ void VulkanDriver::createTextureSwizzledR(Handle th, SamplerType targ TextureSwizzle swizzleArray[] = {r, g, b, a}; const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray); auto vktexture = mResourceAllocator.construct(th, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels, + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels, format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap); mResourceManager.acquire(vktexture); } @@ -607,7 +608,7 @@ void VulkanDriver::createRenderTargetR(Handle rth, assert_invariant(tmin.x >= width && tmin.y >= height); auto renderTarget = mResourceAllocator.construct(rth, mPlatform->getDevice(), - mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), width, height, + mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height, samples, colorTargets, depthStencil, mStagePool); mResourceManager.acquire(renderTarget); } @@ -625,7 +626,7 @@ void VulkanDriver::destroyRenderTarget(Handle rth) { } void VulkanDriver::createFenceR(Handle fh, int) { - VulkanCommandBuffer const& commandBuffer = mCommands->get(); + VulkanCommandBuffer const& commandBuffer = mCommands.get(); mResourceAllocator.construct(fh, commandBuffer.fence); } @@ -636,7 +637,7 @@ void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE); } auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, - mAllocator, mCommands.get(), mStagePool, nativeWindow, flags); + mAllocator, &mCommands, mStagePool, nativeWindow, flags); mResourceManager.acquire(swapChain); } @@ -649,7 +650,7 @@ void VulkanDriver::createSwapChainHeadlessR(Handle sch, uint32_t wi } assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions."); auto swapChain = mResourceAllocator.construct(sch, mPlatform, mContext, - mAllocator, mCommands.get(), mStagePool, nullptr, flags, VkExtent2D{width, height}); + mAllocator, &mCommands, mStagePool, nullptr, flags, VkExtent2D{width, height}); mResourceManager.acquire(swapChain); } @@ -994,7 +995,7 @@ void VulkanDriver::setVertexBufferObject(Handle vbh, uint32_t in void VulkanDriver::updateIndexBuffer(Handle ibh, BufferDescriptor&& p, uint32_t byteOffset) { - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = mCommands.get(); auto ib = mResourceAllocator.handle_cast(ibh); commands.acquire(ib); ib->buffer.loadFromCpu(commands.buffer(), p.buffer, byteOffset, p.size); @@ -1004,7 +1005,7 @@ void VulkanDriver::updateIndexBuffer(Handle ibh, BufferDescriptor void VulkanDriver::updateBufferObject(Handle boh, BufferDescriptor&& bd, uint32_t byteOffset) { - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = mCommands.get(); auto bo = mResourceAllocator.handle_cast(boh); commands.acquire(bo); @@ -1015,7 +1016,7 @@ void VulkanDriver::updateBufferObject(Handle boh, BufferDescript void VulkanDriver::updateBufferObjectUnsynchronized(Handle boh, BufferDescriptor&& bd, uint32_t byteOffset) { - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = mCommands.get(); auto bo = mResourceAllocator.handle_cast(boh); commands.acquire(bo); // TODO: implement unsynchronized version @@ -1180,7 +1181,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP // If that's the case, we need to change the layout of the texture to DEPTH_SAMPLER, which is a // more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for // the non-sampling case. - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = mCommands.get(); VkCommandBuffer const cmdbuffer = commands.buffer(); UTILS_NOUNROLL @@ -1307,7 +1308,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP // Assign a label to the framebuffer for debugging purposes. #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS) - auto const topMarker = mCommands->getTopGroupMarker(); + auto const topMarker = mCommands.getTopGroupMarker(); if (!topMarker.empty()) { DebugUtils::setName(VK_OBJECT_TYPE_FRAMEBUFFER, reinterpret_cast(vkfb), topMarker.c_str()); @@ -1396,7 +1397,7 @@ void VulkanDriver::endRenderPass(int) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("endRenderPass"); - VulkanCommandBuffer& commands = mCommands->get(); + VulkanCommandBuffer& commands = mCommands.get(); VkCommandBuffer cmdbuffer = commands.buffer(); vkCmdEndRenderPass(cmdbuffer); @@ -1449,7 +1450,7 @@ void VulkanDriver::nextSubpass(int) { assert_invariant(renderTarget); assert_invariant(mCurrentRenderPass.params.subpassMask); - vkCmdNextSubpass(mCommands->get().buffer(), VK_SUBPASS_CONTENTS_INLINE); + vkCmdNextSubpass(mCommands.get().buffer(), VK_SUBPASS_CONTENTS_INLINE); mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass, ++mCurrentRenderPass.currentSubpass); @@ -1530,14 +1531,14 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle sbh) { void VulkanDriver::insertEventMarker(char const* string, uint32_t len) { #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - mCommands->insertEventMarker(string, len); + mCommands.insertEventMarker(string, len); #endif } void VulkanDriver::pushGroupMarker(char const* string, uint32_t) { // Turns out all the markers are 0-terminated, so we can just pass it without len. #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - mCommands->pushGroupMarker(string); + mCommands.pushGroupMarker(string); #endif FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START(string); @@ -1545,7 +1546,7 @@ void VulkanDriver::pushGroupMarker(char const* string, uint32_t) { void VulkanDriver::popGroupMarker(int) { #if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS) - mCommands->popGroupMarker(); + mCommands.popGroupMarker(); #endif FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_END(); @@ -1558,7 +1559,7 @@ void VulkanDriver::stopCapture(int) {} void VulkanDriver::readPixels(Handle src, uint32_t x, uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& pbd) { VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast(src); - mCommands->flush(); + mCommands.flush(); mReadPixels.run( srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(), std::move(pbd), @@ -1715,7 +1716,7 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("draw"); - VulkanCommandBuffer* commands = &mCommands->get(); + VulkanCommandBuffer* commands = &mCommands.get(); const VulkanVertexBufferInfo& vbi = *mResourceAllocator.handle_cast(pipelineState.vertexBufferInfo); @@ -1814,7 +1815,7 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) { utils::slog.w << " in material '" << program->name.c_str() << "'"; utils::slog.w << " at binding point " << +binding << utils::io::endl; #endif - texture = mEmptyTexture.get(); + texture = mEmptyTexture; } SamplerParams const& samplerParams = boundSampler->s; @@ -1861,7 +1862,7 @@ void VulkanDriver::bindRenderPrimitive(Handle rph) { FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("bindRenderPrimitive"); - VulkanCommandBuffer* commands = &mCommands->get(); + VulkanCommandBuffer* commands = &mCommands.get(); VkCommandBuffer cmdbuffer = commands->buffer(); const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast(rph); commands->acquire(prim.indexBuffer); @@ -1890,8 +1891,8 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins FVK_SYSTRACE_CONTEXT(); FVK_SYSTRACE_START("draw2"); - VulkanCommandBuffer* commands = &mCommands->get(); - VkCommandBuffer cmdbuffer = commands->buffer(); + VulkanCommandBuffer& commands = mCommands.get(); + VkCommandBuffer cmdbuffer = commands.buffer(); // Bind new descriptor sets if they need to change. // If descriptor set allocation failed, skip the draw call and bail. No need to emit an error @@ -1925,8 +1926,8 @@ void VulkanDriver::dispatchCompute(Handle program, math::uint3 workGr } void VulkanDriver::scissor(Viewport scissorBox) { - VulkanCommandBuffer* commands = &mCommands->get(); - VkCommandBuffer cmdbuffer = commands->buffer(); + VulkanCommandBuffer& commands = mCommands.get(); + VkCommandBuffer cmdbuffer = commands.buffer(); // Set scissoring. // clamp left-bottom to 0,0 and avoid overflows @@ -1953,12 +1954,12 @@ void VulkanDriver::scissor(Viewport scissorBox) { void VulkanDriver::beginTimerQuery(Handle tqh) { VulkanTimerQuery* vtq = mResourceAllocator.handle_cast(tqh); - mTimestamps->beginQuery(&(mCommands->get()), vtq); + mTimestamps->beginQuery(&(mCommands.get()), vtq); } void VulkanDriver::endTimerQuery(Handle tqh) { VulkanTimerQuery* vtq = mResourceAllocator.handle_cast(tqh); - mTimestamps->endQuery(&(mCommands->get()), vtq); + mTimestamps->endQuery(&(mCommands.get()), vtq); } void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept { diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h index d6398f8cd32..98ba9efc721 100644 --- a/filament/backend/src/vulkan/VulkanDriver.h +++ b/filament/backend/src/vulkan/VulkanDriver.h @@ -104,9 +104,9 @@ class VulkanDriver final : public DriverBase { void collectGarbage(); VulkanPlatform* mPlatform = nullptr; - std::unique_ptr mCommands; std::unique_ptr mTimestamps; - std::unique_ptr mEmptyTexture; + + VulkanTexture* mEmptyTexture; VulkanSwapChain* mCurrentSwapChain = nullptr; VulkanRenderTarget* mDefaultRenderTarget = nullptr; @@ -122,6 +122,7 @@ class VulkanDriver final : public DriverBase { // thread. VulkanThreadSafeResourceManager mThreadSafeResourceManager; + VulkanCommands mCommands; VulkanPipelineCache mPipelineCache; VulkanStagePool mStagePool; VulkanFboCache mFramebufferCache; diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 0546ffd04fb..009ca0273ca 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -64,7 +64,8 @@ bool VulkanFboCache::FboKeyEqualFn::operator()(const FboKey& k1, const FboKey& k return true; } -void VulkanFboCache::initialize(VkDevice device) noexcept { mDevice = device; } +VulkanFboCache::VulkanFboCache(VkDevice device) + : mDevice(device) {} VulkanFboCache::~VulkanFboCache() { ASSERT_POSTCONDITION(mFramebufferCache.empty() && mRenderPassCache.empty(), diff --git a/filament/backend/src/vulkan/VulkanFboCache.h b/filament/backend/src/vulkan/VulkanFboCache.h index 82d4dfa53f3..cefdae2995d 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.h +++ b/filament/backend/src/vulkan/VulkanFboCache.h @@ -104,10 +104,9 @@ class VulkanFboCache { bool operator()(const FboKey& k1, const FboKey& k2) const; }; + explicit VulkanFboCache(VkDevice device); ~VulkanFboCache(); - void initialize(VkDevice device) noexcept; - // Retrieves or creates a VkFramebuffer handle. VkFramebuffer getFramebuffer(FboKey config) noexcept; diff --git a/filament/backend/src/vulkan/VulkanSamplerCache.cpp b/filament/backend/src/vulkan/VulkanSamplerCache.cpp index 47be669a3aa..79f8bfbd535 100644 --- a/filament/backend/src/vulkan/VulkanSamplerCache.cpp +++ b/filament/backend/src/vulkan/VulkanSamplerCache.cpp @@ -95,7 +95,8 @@ constexpr inline VkBool32 getCompareEnable(SamplerCompareMode mode) noexcept { return mode == SamplerCompareMode::NONE ? VK_FALSE : VK_TRUE; } -void VulkanSamplerCache::initialize(VkDevice device) { mDevice = device; } +VulkanSamplerCache::VulkanSamplerCache(VkDevice device) + : mDevice(device) {} VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept { auto iter = mCache.find(params); diff --git a/filament/backend/src/vulkan/VulkanSamplerCache.h b/filament/backend/src/vulkan/VulkanSamplerCache.h index a87bee014aa..cb5fe43ce4c 100644 --- a/filament/backend/src/vulkan/VulkanSamplerCache.h +++ b/filament/backend/src/vulkan/VulkanSamplerCache.h @@ -27,7 +27,7 @@ namespace filament::backend { // Simple manager for VkSampler objects. class VulkanSamplerCache { public: - void initialize(VkDevice device); + explicit VulkanSamplerCache(VkDevice device); VkSampler getSampler(SamplerParams params) noexcept; void terminate() noexcept; private: diff --git a/filament/backend/src/vulkan/VulkanStagePool.cpp b/filament/backend/src/vulkan/VulkanStagePool.cpp index 93f434d4afb..4c21104d003 100644 --- a/filament/backend/src/vulkan/VulkanStagePool.cpp +++ b/filament/backend/src/vulkan/VulkanStagePool.cpp @@ -26,10 +26,9 @@ static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS; namespace filament::backend { -void VulkanStagePool::initialize(VmaAllocator allocator, VulkanCommands* commands) noexcept { - mAllocator = allocator; - mCommands = commands; -} +VulkanStagePool::VulkanStagePool(VmaAllocator allocator, VulkanCommands* commands) + : mAllocator(allocator), + mCommands(commands) {} VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) { // First check if a stage exists whose capacity is greater than or equal to the requested size. diff --git a/filament/backend/src/vulkan/VulkanStagePool.h b/filament/backend/src/vulkan/VulkanStagePool.h index 65a67b0ce72..e78aa5ee08e 100644 --- a/filament/backend/src/vulkan/VulkanStagePool.h +++ b/filament/backend/src/vulkan/VulkanStagePool.h @@ -45,7 +45,7 @@ struct VulkanStageImage { // This class manages two types of host-mappable staging areas: buffer stages and image stages. class VulkanStagePool { public: - void initialize(VmaAllocator allocator, VulkanCommands* commands) noexcept; + VulkanStagePool(VmaAllocator allocator, VulkanCommands* commands); // Finds or creates a stage whose capacity is at least the given number of bytes. // The stage is automatically released back to the pool after TIME_BEFORE_EVICTION frames. From 0def77eaa86868b4c127b7c292eba153b7d42b80 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Wed, 13 Mar 2024 17:38:34 -0700 Subject: [PATCH 14/29] Add layerCount to createRenderTarget (#7660) This new parameter indicates whether the render target will be created for multiview. If the value is greater than 1, it tells the render target should be created for multiview. Otherwise, 1 or 0, it creates a single layer render target. --- .../include/backend/TargetBufferInfo.h | 6 ++++- .../include/private/backend/DriverAPI.inc | 1 + filament/backend/src/metal/MetalDriver.mm | 2 +- filament/backend/src/opengl/OpenGLDriver.cpp | 26 ++++++++++++++----- filament/backend/src/opengl/OpenGLDriver.h | 2 +- filament/backend/src/opengl/gl_headers.cpp | 8 ++++-- filament/backend/src/opengl/gl_headers.h | 4 ++- filament/backend/src/vulkan/VulkanDriver.cpp | 2 +- filament/backend/test/test_Blit.cpp | 22 ++++++++-------- filament/backend/test/test_BufferUpdates.cpp | 2 +- filament/backend/test/test_FeedbackLoops.cpp | 2 +- filament/backend/test/test_MRT.cpp | 3 ++- filament/backend/test/test_MipLevels.cpp | 2 +- filament/backend/test/test_ReadPixels.cpp | 8 +++--- filament/backend/test/test_Scissor.cpp | 6 ++--- filament/backend/test/test_StencilBuffer.cpp | 10 +++---- filament/src/RendererUtils.cpp | 16 ++++++++++-- filament/src/ResourceAllocator.cpp | 4 +-- filament/src/ResourceAllocator.h | 2 ++ filament/src/details/RenderTarget.cpp | 4 ++- filament/src/fg/FrameGraphRenderPass.h | 3 ++- filament/src/fg/PassNode.cpp | 4 +-- filament/test/filament_framegraph_test.cpp | 1 + 23 files changed, 91 insertions(+), 49 deletions(-) diff --git a/filament/backend/include/backend/TargetBufferInfo.h b/filament/backend/include/backend/TargetBufferInfo.h index 0f318d6eafe..ce23fc5fd53 100644 --- a/filament/backend/include/backend/TargetBufferInfo.h +++ b/filament/backend/include/backend/TargetBufferInfo.h @@ -32,6 +32,10 @@ struct TargetBufferInfo { // texture to be used as render target Handle handle; + // starting layer index for multiview. This value is only used when the `layerCount` for the + // render target is greater than 1. + uint8_t baseViewIndex = 0; + // level to be used uint8_t level = 0; @@ -80,7 +84,7 @@ class MRT { // this is here for backward compatibility MRT(Handle handle, uint8_t level, uint16_t layer) noexcept - : mInfos{{ handle, level, layer }} { + : mInfos{{ handle, 0, level, layer }} { } }; diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index ac1dc256c8c..680a6bb0136 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -239,6 +239,7 @@ DECL_DRIVER_API_R_N(backend::RenderTargetHandle, createRenderTarget, uint32_t, width, uint32_t, height, uint8_t, samples, + uint8_t, layerCount, backend::MRT, color, backend::TargetBufferInfo, depth, backend::TargetBufferInfo, stencil) diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 8dfbb27c51b..518df89f525 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -376,7 +376,7 @@ void MetalDriver::createRenderTargetR(Handle rth, TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height, - uint8_t samples, MRT color, + uint8_t samples, uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { ASSERT_PRECONDITION(!isInRenderPass(mContext), "createRenderTarget must be called outside of a render pass."); diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 3aebf795809..20b9262f445 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -956,7 +956,7 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer } void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, - GLRenderTarget const* rt, GLenum attachment) noexcept { + GLRenderTarget const* rt, GLenum attachment, uint8_t layerCount) noexcept { #if !defined(NDEBUG) // Only used by assert_invariant() checks below @@ -1094,9 +1094,20 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_CUBE_MAP_ARRAY: #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 - // GL_TEXTURE_2D_MULTISAMPLE_ARRAY is not supported in GLES - glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, + + // TODO: support multiview for iOS and WebGL +#if !defined(__EMSCRIPTEN__) && !defined(IOS) + if (layerCount > 1) { + // if layerCount > 1, it means we use the multiview extension. + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment, + t->gl.id, 0, binfo.baseViewIndex, layerCount); + } else +#endif // !defined(__EMSCRIPTEN__) && !defined(IOS) + { + // GL_TEXTURE_2D_MULTISAMPLE_ARRAY is not supported in GLES + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, t->gl.id, binfo.level, binfo.layer); + } #endif break; default: @@ -1270,6 +1281,7 @@ void OpenGLDriver::createRenderTargetR(Handle rth, uint32_t width, uint32_t height, uint8_t samples, + uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { @@ -1336,7 +1348,7 @@ void OpenGLDriver::createRenderTargetR(Handle rth, if (any(targets & getTargetBufferFlagsAt(i))) { assert_invariant(color[i].handle); rt->gl.color[i] = handle_cast(color[i].handle); - framebufferTexture(color[i], rt, GL_COLOR_ATTACHMENT0 + i); + framebufferTexture(color[i], rt, GL_COLOR_ATTACHMENT0 + i, layerCount); bufs[i] = GL_COLOR_ATTACHMENT0 + i; checkDimensions(rt->gl.color[i], color[i].level); } @@ -1358,7 +1370,7 @@ void OpenGLDriver::createRenderTargetR(Handle rth, // either we supplied only the depth handle or both depth/stencil are identical and not null if (depth.handle && (stencil.handle == depth.handle || !stencil.handle)) { rt->gl.depth = handle_cast(depth.handle); - framebufferTexture(depth, rt, GL_DEPTH_STENCIL_ATTACHMENT); + framebufferTexture(depth, rt, GL_DEPTH_STENCIL_ATTACHMENT, layerCount); specialCased = true; checkDimensions(rt->gl.depth, depth.level); } @@ -1369,13 +1381,13 @@ void OpenGLDriver::createRenderTargetR(Handle rth, if (any(targets & TargetBufferFlags::DEPTH)) { assert_invariant(depth.handle); rt->gl.depth = handle_cast(depth.handle); - framebufferTexture(depth, rt, GL_DEPTH_ATTACHMENT); + framebufferTexture(depth, rt, GL_DEPTH_ATTACHMENT, layerCount); checkDimensions(rt->gl.depth, depth.level); } if (any(targets & TargetBufferFlags::STENCIL)) { assert_invariant(stencil.handle); rt->gl.stencil = handle_cast(stencil.handle); - framebufferTexture(stencil, rt, GL_STENCIL_ATTACHMENT); + framebufferTexture(stencil, rt, GL_STENCIL_ATTACHMENT, layerCount); checkDimensions(rt->gl.stencil, stencil.level); } } diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index 129a34f0f7f..b275309e435 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -302,7 +302,7 @@ class OpenGLDriver final : public DriverBase { void updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb); void framebufferTexture(TargetBufferInfo const& binfo, - GLRenderTarget const* rt, GLenum attachment) noexcept; + GLRenderTarget const* rt, GLenum attachment, uint8_t layerCount) noexcept; void setRasterState(RasterState rs) noexcept; diff --git a/filament/backend/src/opengl/gl_headers.cpp b/filament/backend/src/opengl/gl_headers.cpp index 5e6c46b3411..cb1f022d517 100644 --- a/filament/backend/src/opengl/gl_headers.cpp +++ b/filament/backend/src/opengl/gl_headers.cpp @@ -67,12 +67,14 @@ PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; #ifdef GL_KHR_parallel_shader_compile PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR; #endif +#ifdef GL_OVR_multiview +PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR; +#endif #if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) // 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__ @@ -118,9 +120,11 @@ void importGLESExtensionsEntryPoints() { #ifdef GL_KHR_parallel_shader_compile getProcAddress(glMaxShaderCompilerThreadsKHR, "glMaxShaderCompilerThreadsKHR"); #endif +#ifdef GL_OVR_multiview + getProcAddress(glFramebufferTextureMultiviewOVR, "glFramebufferTextureMultiviewOVR"); +#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 1909c876667..569c8ed2859 100644 --- a/filament/backend/src/opengl/gl_headers.h +++ b/filament/backend/src/opengl/gl_headers.h @@ -151,9 +151,11 @@ extern PFNGLDISCARDFRAMEBUFFEREXTPROC glDiscardFramebufferEXT; #ifdef GL_KHR_parallel_shader_compile extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR; #endif +#ifdef GL_OVR_multiview +extern PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR; +#endif #if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2) extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute; -extern PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR; #endif #endif // __EMSCRIPTEN__ } // namespace glext diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index f65d2ada0ea..b140f43369d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -556,7 +556,7 @@ void VulkanDriver::createDefaultRenderTargetR(Handle rth, int) { void VulkanDriver::createRenderTargetR(Handle rth, TargetBufferFlags targets, uint32_t width, uint32_t height, uint8_t samples, - MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { + uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { UTILS_UNUSED_IN_RELEASE math::vec2 tmin = {std::numeric_limits::max()}; UTILS_UNUSED_IN_RELEASE math::vec2 tmax = {0}; UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0; diff --git a/filament/backend/test/test_Blit.cpp b/filament/backend/test/test_Blit.cpp index e1043e0a7c0..04809884453 100644 --- a/filament/backend/test/test_Blit.cpp +++ b/filament/backend/test/test_Blit.cpp @@ -226,9 +226,9 @@ TEST_F(BackendTest, ColorMagnify) { Handle dstRenderTargets[kNumLevels]; for (uint8_t level = 0; level < kNumLevels; level++) { srcRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR, - kSrcTexWidth >> level, kSrcTexHeight >> level, 1, { srcTexture, level, 0 }, {}, {}); + kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}); dstRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR, - kDstTexWidth >> level, kDstTexHeight >> level, 1, { dstTexture, level, 0 }, {}, {}); + kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {}, {}); } // Do a "magnify" blit from level 1 of the source RT to the level 0 of the destination RT. @@ -301,9 +301,9 @@ TEST_F(BackendTest, ColorMinify) { Handle dstRenderTargets[kNumLevels]; for (uint8_t level = 0; level < kNumLevels; level++) { srcRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR, - kSrcTexWidth >> level, kSrcTexHeight >> level, 1, { srcTexture, level, 0 }, {}, {}); + kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}); dstRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR, - kDstTexWidth >> level, kDstTexHeight >> level, 1, { dstTexture, level, 0 }, {}, {}); + kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {}, {}); } // Do a "minify" blit from level 1 of the source RT to the level 0 of the destination RT. @@ -369,12 +369,12 @@ TEST_F(BackendTest, ColorResolve) { // Create a 4-sample render target with the 4-sample texture. Handle const srcRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, + TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, 0, {{ srcColorTexture }}, {}, {}); // Create a 1-sample render target with the 1-sample texture. Handle const dstRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, + TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 0, {{ dstColorTexture }}, {}, {}); // Prep for rendering. @@ -473,9 +473,9 @@ TEST_F(BackendTest, Blit2DTextureArray) { // Create two RenderTargets. const int level = 0; Handle srcRenderTarget = api.createRenderTarget( TargetBufferFlags::COLOR, - kSrcTexWidth >> level, kSrcTexHeight >> level, 1, { srcTexture, level, kSrcTexLayer }, {}, {}); + kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, kSrcTexLayer }, {}, {}); Handle dstRenderTarget = api.createRenderTarget( TargetBufferFlags::COLOR, - kDstTexWidth >> level, kDstTexHeight >> level, 1, { dstTexture, level, kDstTexLayer }, {}, {}); + kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, kDstTexLayer }, {}, {}); // Do a blit from kSrcTexLayer of the source RT to kDstTexLayer of the destination RT. const int srcLevel = 0; @@ -565,10 +565,10 @@ TEST_F(BackendTest, BlitRegion) { // that this case is handled correctly. Handle srcRenderTarget = api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width, - srcRect.height, 1, {srcTexture, kSrcLevel, 0}, {}, {}); + srcRect.height, 1, 0, {srcTexture, kSrcLevel, 0}, {}, {}); Handle dstRenderTarget = api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel, - kDstTexHeight >> kDstLevel, 1, {dstTexture, kDstLevel, 0}, {}, {}); + kDstTexHeight >> kDstLevel, 1, 0, {dstTexture, kDstLevel, 0}, {}, {}); api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect, SamplerMagFilter::LINEAR); @@ -637,7 +637,7 @@ TEST_F(BackendTest, BlitRegionToSwapChain) { Handle srcRenderTargets[kNumLevels]; for (uint8_t level = 0; level < kNumLevels; level++) { srcRenderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR, - kSrcTexWidth >> level, kSrcTexHeight >> level, 1, { srcTexture, level, 0 }, {}, {}); + kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {}, {}); } // Blit one-quarter of src level 1 to dst level 0. diff --git a/filament/backend/test/test_BufferUpdates.cpp b/filament/backend/test/test_BufferUpdates.cpp index 632acd5d5a0..3207fdae9d9 100644 --- a/filament/backend/test/test_BufferUpdates.cpp +++ b/filament/backend/test/test_BufferUpdates.cpp @@ -216,7 +216,7 @@ TEST_F(BackendTest, BufferObjectUpdateWithOffset) { auto colorTexture = getDriverApi().createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT); auto renderTarget = getDriverApi().createRenderTarget( - TargetBufferFlags::COLOR0, 512, 512, 1, {{colorTexture}}, {}, {}); + TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{colorTexture}}, {}, {}); // Upload uniforms for the first triangle. { diff --git a/filament/backend/test/test_FeedbackLoops.cpp b/filament/backend/test/test_FeedbackLoops.cpp index e726be44b1a..04f72578013 100644 --- a/filament/backend/test/test_FeedbackLoops.cpp +++ b/filament/backend/test/test_FeedbackLoops.cpp @@ -160,7 +160,7 @@ TEST_F(BackendTest, FeedbackLoops) { slog.i << "Level " << int(level) << ": " << (kTexWidth >> level) << "x" << (kTexHeight >> level) << io::endl; renderTargets[level] = api.createRenderTarget( TargetBufferFlags::COLOR, - kTexWidth >> level, kTexHeight >> level, 1, { texture, level, 0 }, {}, {}); + kTexWidth >> level, kTexHeight >> level, 1, 0, { texture, level, 0 }, {}, {}); } // Fill the base level of the texture with interesting colors. diff --git a/filament/backend/test/test_MRT.cpp b/filament/backend/test/test_MRT.cpp index 52f27f742e7..bd29d27e733 100644 --- a/filament/backend/test/test_MRT.cpp +++ b/filament/backend/test/test_MRT.cpp @@ -105,7 +105,8 @@ TEST_F(BackendTest, MRT) { 512, // width 512, // height 1, // samples - {{textureA },{textureB }}, // color + 0, // layerCount + {{textureA },{textureB }}, // color {}, // depth {}); // stencil diff --git a/filament/backend/test/test_MipLevels.cpp b/filament/backend/test/test_MipLevels.cpp index 42b4d4f6957..76497e1b2a4 100644 --- a/filament/backend/test/test_MipLevels.cpp +++ b/filament/backend/test/test_MipLevels.cpp @@ -155,7 +155,7 @@ TEST_F(BackendTest, SetMinMaxLevel) { // Render a white triangle into level 2. // We specify mip level 2, because minMaxLevels has no effect when rendering into a texture. Handle renderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR, 32, 32, 1, + TargetBufferFlags::COLOR, 32, 32, 1, 0, {texture, 2 /* level */, 0 /* layer */}, {}, {}); { RenderPassParams params = {}; diff --git a/filament/backend/test/test_ReadPixels.cpp b/filament/backend/test/test_ReadPixels.cpp index 6c8bd337e38..98d8c41705d 100644 --- a/filament/backend/test/test_ReadPixels.cpp +++ b/filament/backend/test/test_ReadPixels.cpp @@ -279,7 +279,7 @@ TEST_F(ReadPixelsTest, ReadPixels) { // level (at least for OpenGL). renderTarget = getDriverApi().createRenderTarget( TargetBufferFlags::COLOR, t.getRenderTargetSize(), - t.getRenderTargetSize(), t.samples, {{ texture, uint8_t(t.mipLevel) }}, {}, + t.getRenderTargetSize(), t.samples, 0, {{ texture, uint8_t(t.mipLevel) }}, {}, {}); } @@ -318,9 +318,8 @@ TEST_F(ReadPixelsTest, ReadPixels) { // correct mip. RenderPassParams p = params; Handle mipLevelOneRT = getDriverApi().createRenderTarget( - TargetBufferFlags::COLOR, renderTargetBaseSize, renderTargetBaseSize, 1, - {{ texture }}, {}, - {}); + TargetBufferFlags::COLOR, renderTargetBaseSize, renderTargetBaseSize, 1, 0, + {{ texture }}, {}, {}); p.clearColor = {1.f, 0.f, 0.f, 1.f}; getDriverApi().beginRenderPass(mipLevelOneRT, p); getDriverApi().endRenderPass(); @@ -402,6 +401,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) { renderTargetSize, // width renderTargetSize, // height 1, // samples + 0, // layerCount {{ texture }}, // color {}, // depth {}); // stencil diff --git a/filament/backend/test/test_Scissor.cpp b/filament/backend/test/test_Scissor.cpp index ec27e58ce37..985ef9414b8 100644 --- a/filament/backend/test/test_Scissor.cpp +++ b/filament/backend/test/test_Scissor.cpp @@ -113,11 +113,11 @@ TEST_F(BackendTest, ScissorViewportRegion) { // We purposely set the render target width and height to smaller than the texture, to check // that this case is handled correctly. Handle srcRenderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1, + TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1, 0, {srcTexture, kSrcLevel, 0}, {depthTexture, 0, 0}, {}); Handle fullRenderTarget = api.createRenderTarget(TargetBufferFlags::COLOR, - kSrcTexHeight >> kSrcLevel, kSrcTexWidth >> kSrcLevel, 1, + kSrcTexHeight >> kSrcLevel, kSrcTexWidth >> kSrcLevel, 1, 0, {srcTexture, kSrcLevel, 0}, {}, {}); TrianglePrimitive triangle(api); @@ -208,7 +208,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) { (uint32_t)std::numeric_limits::max()}; Handle renderTarget = api.createRenderTarget( - TargetBufferFlags::COLOR, 512, 512, 1, + TargetBufferFlags::COLOR, 512, 512, 1, 0, {srcTexture, 0, 0}, {}, {}); TrianglePrimitive triangle(api); diff --git a/filament/backend/test/test_StencilBuffer.cpp b/filament/backend/test/test_StencilBuffer.cpp index e574bc18e98..aa02830b547 100644 --- a/filament/backend/test/test_StencilBuffer.cpp +++ b/filament/backend/test/test_StencilBuffer.cpp @@ -150,7 +150,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) { auto stencilTexture = api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT); auto renderTarget = getDriverApi().createRenderTarget( - TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, + TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0, {{colorTexture}}, {}, {{stencilTexture}}); RunTest(renderTarget); @@ -174,7 +174,7 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) { auto depthStencilTexture = api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::DEPTH24_STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT); auto renderTarget = getDriverApi().createRenderTarget( - TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, + TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0, {{colorTexture}}, {depthStencilTexture}, {{depthStencilTexture}}); RunTest(renderTarget); @@ -202,10 +202,10 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) { auto depthStencilTextureMSAA = api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::DEPTH24_STENCIL8, 4, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT); auto renderTarget0 = getDriverApi().createRenderTarget( - TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, + TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0, {{}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA}); auto renderTarget1 = getDriverApi().createRenderTarget( - TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, + TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0, {{colorTexture}}, {depthStencilTextureMSAA}, {depthStencilTextureMSAA}); api.startCapture(0); @@ -273,4 +273,4 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) { api.destroyRenderTarget(renderTarget1); } -} // namespace test \ No newline at end of file +} // namespace test diff --git a/filament/src/RendererUtils.cpp b/filament/src/RendererUtils.cpp index a26b9b7b53e..08395f389a1 100644 --- a/filament/src/RendererUtils.cpp +++ b/filament/src/RendererUtils.cpp @@ -57,6 +57,7 @@ FrameGraphId RendererUtils::colorPass( TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR; TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH; TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL; + uint8_t layerCount = 0; data.shadows = blackboard.get("shadows"); data.ssao = blackboard.get("ssao"); @@ -150,7 +151,17 @@ FrameGraphId RendererUtils::colorPass( data.depth = builder.read(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT); data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT); - data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT); + if (engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) { + // Add sampleable usage flag for depth in multiview rendering, othewise it's + // treated as renderbuffer in the backend and crashed. + data.depth = builder.write(data.depth, + FrameGraphTexture::Usage::DEPTH_ATTACHMENT | + FrameGraphTexture::Usage::SAMPLEABLE); + layerCount = engine.getConfig().stereoscopicEyeCount; + } else { + data.depth = builder.write(data.depth, + FrameGraphTexture::Usage::DEPTH_ATTACHMENT); + } /* * There is a bit of magic happening here regarding the viewport used. @@ -172,7 +183,8 @@ FrameGraphId RendererUtils::colorPass( .stencil = data.stencil }, .clearColor = config.clearColor, .samples = config.msaa, - .clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags }); + .layerCount = layerCount, + .clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags}); blackboard["depth"] = data.depth; }, [=, &view, &engine](FrameGraphResources const& resources, diff --git a/filament/src/ResourceAllocator.cpp b/filament/src/ResourceAllocator.cpp index 4eee32aaa10..2990629bcea 100644 --- a/filament/src/ResourceAllocator.cpp +++ b/filament/src/ResourceAllocator.cpp @@ -129,10 +129,10 @@ void ResourceAllocator::terminate() noexcept { RenderTargetHandle ResourceAllocator::createRenderTarget(const char*, TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height, - uint8_t samples, MRT color, TargetBufferInfo depth, + uint8_t samples, uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) noexcept { return mBackend.createRenderTarget(targetBufferFlags, - width, height, samples ? samples : 1u, color, depth, stencil); + width, height, samples ? samples : 1u, layerCount, color, depth, stencil); } void ResourceAllocator::destroyRenderTarget(RenderTargetHandle h) noexcept { diff --git a/filament/src/ResourceAllocator.h b/filament/src/ResourceAllocator.h index 6d854946a9a..5486592fa53 100644 --- a/filament/src/ResourceAllocator.h +++ b/filament/src/ResourceAllocator.h @@ -45,6 +45,7 @@ class ResourceAllocatorInterface { uint32_t width, uint32_t height, uint8_t samples, + uint8_t layerCount, backend::MRT color, backend::TargetBufferInfo depth, backend::TargetBufferInfo stencil) noexcept = 0; @@ -77,6 +78,7 @@ class ResourceAllocator final : public ResourceAllocatorInterface { uint32_t width, uint32_t height, uint8_t samples, + uint8_t layerCount, backend::MRT color, backend::TargetBufferInfo depth, backend::TargetBufferInfo stencil) noexcept override; diff --git a/filament/src/details/RenderTarget.cpp b/filament/src/details/RenderTarget.cpp index f1084a3cf85..4965f3c9641 100644 --- a/filament/src/details/RenderTarget.cpp +++ b/filament/src/details/RenderTarget.cpp @@ -34,6 +34,7 @@ struct RenderTarget::BuilderDetails { uint32_t mWidth{}; uint32_t mHeight{}; uint8_t mSamples = 1; // currently not settable in the public facing API + uint8_t mLayerCount = 0;// currently not settable in the public facing API }; using BuilderType = RenderTarget; @@ -160,7 +161,8 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build FEngine::DriverApi& driver = engine.getDriverApi(); mHandle = driver.createRenderTarget(mAttachmentMask, - builder.mImpl->mWidth, builder.mImpl->mHeight, builder.mImpl->mSamples, mrt, dinfo, {}); + builder.mImpl->mWidth, builder.mImpl->mHeight, builder.mImpl->mSamples, + builder.mImpl->mLayerCount, mrt, dinfo, {}); } void FRenderTarget::terminate(FEngine& engine) { diff --git a/filament/src/fg/FrameGraphRenderPass.h b/filament/src/fg/FrameGraphRenderPass.h index 5ab1ec97d36..e073dcb321a 100644 --- a/filament/src/fg/FrameGraphRenderPass.h +++ b/filament/src/fg/FrameGraphRenderPass.h @@ -47,7 +47,8 @@ struct FrameGraphRenderPass { Attachments attachments{}; Viewport viewport{}; math::float4 clearColor{}; - uint8_t samples = 0; // # of samples (0 = unset, default) + uint8_t samples = 0; // # of samples (0 = unset, default) + uint8_t layerCount = 0; // # of layer (# > 1 = multiview) backend::TargetBufferFlags clearFlags{}; backend::TargetBufferFlags discardStart{}; }; diff --git a/filament/src/fg/PassNode.cpp b/filament/src/fg/PassNode.cpp index 08553721e21..cf76ea35b76 100644 --- a/filament/src/fg/PassNode.cpp +++ b/filament/src/fg/PassNode.cpp @@ -276,8 +276,8 @@ void RenderPassNode::RenderPassData::devirtualize(FrameGraph& fg, name, targetBufferFlags, backend.params.viewport.width, backend.params.viewport.height, - descriptor.samples, - colorInfo,info[0], info[1]); + descriptor.samples, descriptor.layerCount, + colorInfo, info[0], info[1]); } } diff --git a/filament/test/filament_framegraph_test.cpp b/filament/test/filament_framegraph_test.cpp index 32eb3a6ed77..019bcb6776b 100644 --- a/filament/test/filament_framegraph_test.cpp +++ b/filament/test/filament_framegraph_test.cpp @@ -40,6 +40,7 @@ class MockResourceAllocator : public ResourceAllocatorInterface { uint32_t width, uint32_t height, uint8_t samples, + uint8_t layerCount, backend::MRT color, backend::TargetBufferInfo depth, backend::TargetBufferInfo stencil) noexcept override { From 25f017b8834c294cc32c0cfc99618d9364be1976 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 13 Mar 2024 17:31:21 -0700 Subject: [PATCH 15/29] fix missing headers --- filament/backend/src/opengl/OpenGLContext.cpp | 14 ++++ filament/backend/src/opengl/OpenGLContext.h | 21 ++++-- filament/backend/src/opengl/OpenGLDriver.cpp | 73 ++++++++++++++----- filament/backend/src/opengl/OpenGLDriver.h | 30 +++++--- filament/backend/src/opengl/OpenGLProgram.cpp | 13 +++- filament/backend/src/opengl/OpenGLProgram.h | 3 + 6 files changed, 115 insertions(+), 39 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 79231ab3f68..a3ac310c385 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -16,11 +16,25 @@ #include "OpenGLContext.h" +#include "GLUtils.h" +#include "OpenGLTimerQuery.h" + #include +#include + +#include +#include +#include +#include #include +#include #include +#include +#include +#include + // change to true to display all GL extensions in the console on start-up #define DEBUG_PRINT_EXTENSIONS false diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index cce88957698..5cabf57f97b 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -17,27 +17,32 @@ #ifndef TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H #define TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H -#include #include "OpenGLTimerQuery.h" #include -#include -#include - +#include #include -#include "GLUtils.h" +#include "gl_headers.h" + +#include +#include +#include + +#include +#include #include #include #include -#include #include -#include #include +#include +#include + namespace filament::backend { class OpenGLPlatform; @@ -85,7 +90,7 @@ class OpenGLContext final : public TimerQueryFactoryInterface { static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept; explicit OpenGLContext(OpenGLPlatform& platform) noexcept; - ~OpenGLContext() noexcept; + ~OpenGLContext() noexcept final; // TimerQueryInterface ------------------------------------------------------------------------ diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 20b9262f445..2d5c45bed18 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -16,21 +16,54 @@ #include "OpenGLDriver.h" -#include "private/backend/DriverApi.h" - #include "CommandStreamDispatcher.h" +#include "GLUtils.h" #include "OpenGLContext.h" #include "OpenGLDriverFactory.h" #include "OpenGLProgram.h" #include "OpenGLTimerQuery.h" +#include "gl_headers.h" #include + +#include +#include +#include +#include +#include +#include +#include +#include #include +#include -#include +#include "private/backend/Dispatcher.h" +#include "private/backend/DriverApi.h" + +#include +#include #include #include #include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #if defined(__EMSCRIPTEN__) #include @@ -730,23 +763,23 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint // we can't be here -- doesn't matter what we do case SamplerType::SAMPLER_2D: t->gl.target = GL_TEXTURE_2D; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_2D); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D); break; case SamplerType::SAMPLER_3D: t->gl.target = GL_TEXTURE_3D; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_3D); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_3D); break; case SamplerType::SAMPLER_2D_ARRAY: t->gl.target = GL_TEXTURE_2D_ARRAY; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY); break; case SamplerType::SAMPLER_CUBEMAP: t->gl.target = GL_TEXTURE_CUBE_MAP; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP); break; case SamplerType::SAMPLER_CUBEMAP_ARRAY: t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY); break; } @@ -758,7 +791,7 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint // multi-sample texture on GL 3.2 / GLES 3.1 and above t->gl.target = GL_TEXTURE_2D_MULTISAMPLE; t->gl.targetIndex = (uint8_t) - gl.getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE); + OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE); } else { // Turn off multi-sampling for that texture. It's just not supported. } @@ -829,27 +862,27 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t id, switch (target) { case SamplerType::SAMPLER_EXTERNAL: t->gl.target = GL_TEXTURE_EXTERNAL_OES; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_EXTERNAL_OES); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_EXTERNAL_OES); break; case SamplerType::SAMPLER_2D: t->gl.target = GL_TEXTURE_2D; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_2D); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D); break; case SamplerType::SAMPLER_3D: t->gl.target = GL_TEXTURE_3D; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_3D); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_3D); break; case SamplerType::SAMPLER_2D_ARRAY: t->gl.target = GL_TEXTURE_2D_ARRAY; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_ARRAY); break; case SamplerType::SAMPLER_CUBEMAP: t->gl.target = GL_TEXTURE_CUBE_MAP; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP); break; case SamplerType::SAMPLER_CUBEMAP_ARRAY: t->gl.target = GL_TEXTURE_CUBE_MAP_ARRAY; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_CUBE_MAP_ARRAY); break; } @@ -860,7 +893,7 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t id, if (gl.features.multisample_texture) { // multi-sample texture on GL 3.2 / GLES 3.1 and above t->gl.target = GL_TEXTURE_2D_MULTISAMPLE; - t->gl.targetIndex = (uint8_t)gl.getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE); + t->gl.targetIndex = OpenGLContext::getIndexForTextureTarget(GL_TEXTURE_2D_MULTISAMPLE); } else { // Turn off multi-sampling for that texture. It's just not supported. } @@ -901,7 +934,7 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]); GLuint const index = i; - GLint const size = getComponentCount(attribute.type); + GLint const size = (GLint)getComponentCount(attribute.type); GLenum const type = getComponentType(attribute.type); GLboolean const normalized = getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED); GLsizei const stride = attribute.stride; @@ -2494,7 +2527,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level, #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (!gl.isES2()) { - // update the base/max LOD, so we don't access undefined LOD. this allows the app to + // Update the base/max LOD, so we don't access undefined LOD. this allows the app to // specify levels as they become available. if (int8_t(level) < t->gl.baseLevel) { t->gl.baseLevel = int8_t(level); @@ -2595,7 +2628,7 @@ void OpenGLDriver::setCompressedTextureData(GLTexture* t, uint32_t level, #ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2 if (!gl.isES2()) { - // update the base/max LOD, so we don't access undefined LOD. this allows the app to + // Update the base/max LOD, so we don't access undefined LOD. this allows the app to // specify levels as they become available. if (int8_t(level) < t->gl.baseLevel) { t->gl.baseLevel = int8_t(level); @@ -2748,7 +2781,7 @@ TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle tqh, uint return TimerQueryFactoryInterface::getTimerQueryValue(tq, elapsedTime); } -void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority, +void OpenGLDriver::compilePrograms(CompilerPriorityQueue, CallbackHandler* handler, CallbackHandler::Callback callback, void* user) { if (callback) { getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user); diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index b275309e435..6dad88c082b 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -18,31 +18,41 @@ #define TNT_FILAMENT_BACKEND_OPENGL_OPENGLDRIVER_H #include "DriverBase.h" -#include "GLUtils.h" #include "OpenGLContext.h" #include "OpenGLTimerQuery.h" #include "ShaderCompilerService.h" -#include "private/backend/Driver.h" -#include "private/backend/HandleAllocator.h" - #include #include +#include +#include +#include #include #include +#include "private/backend/Driver.h" +#include "private/backend/HandleAllocator.h" + +#include #include -#include +#include #include #include -#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include #ifndef FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB @@ -59,12 +69,14 @@ class OpenGLProgram; class TimerQueryFactoryInterface; class OpenGLDriver final : public DriverBase { - inline explicit OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept; + inline explicit OpenGLDriver(OpenGLPlatform* platform, + const Platform::DriverConfig& driverConfig) noexcept; ~OpenGLDriver() noexcept final; Dispatcher getDispatcher() const noexcept final; public: - static Driver* create(OpenGLPlatform* platform, void* sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept; + static Driver* create(OpenGLPlatform* platform, void* sharedGLContext, + const Platform::DriverConfig& driverConfig) noexcept; class DebugMarker { OpenGLDriver& driver; diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp index aef088f60cf..0d3f7a1a4b8 100644 --- a/filament/backend/src/opengl/OpenGLProgram.cpp +++ b/filament/backend/src/opengl/OpenGLProgram.cpp @@ -16,16 +16,25 @@ #include "OpenGLProgram.h" -#include "BlobCacheKey.h" +#include "GLUtils.h" #include "OpenGLDriver.h" #include "ShaderCompilerService.h" +#include + +#include + #include #include #include #include -#include +#include +#include +#include +#include + +#include namespace filament::backend { diff --git a/filament/backend/src/opengl/OpenGLProgram.h b/filament/backend/src/opengl/OpenGLProgram.h index 5d74fdc827d..155ad78ea77 100644 --- a/filament/backend/src/opengl/OpenGLProgram.h +++ b/filament/backend/src/opengl/OpenGLProgram.h @@ -28,6 +28,9 @@ #include #include +#include +#include + #include #include From b23ee1bce413931a6827dcdffe9f496d186b30a0 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Fri, 15 Mar 2024 10:46:08 -0700 Subject: [PATCH 16/29] gltfio: add extended tangents job (#7666) - TangentsJobExtended extracts data from cgltf accessor and runs geometry::TangentSpaceMesh on the attributes and computes the tangent space. - The /extended folder is meant for running this process. Note that this API might remesh the input and will require corresponding changes that might break previous assumptions. - The general flow of the code is modeled after src/TangentsJob.h - This is not hooked into current code and should have no practical effect on gltfio. --- android/gltfio-android/CMakeLists.txt | 6 + libs/gltfio/CMakeLists.txt | 5 + .../gltfio/src/extended/AssetLoaderExtended.h | 32 + .../src/extended/TangentSpaceMeshWrapper.cpp | 330 +++++++++++ .../src/extended/TangentSpaceMeshWrapper.h | 82 +++ .../src/extended/TangentsJobExtended.cpp | 549 ++++++++++++++++++ .../gltfio/src/extended/TangentsJobExtended.h | 76 +++ 7 files changed, 1080 insertions(+) create mode 100644 libs/gltfio/src/extended/AssetLoaderExtended.h create mode 100644 libs/gltfio/src/extended/TangentSpaceMeshWrapper.cpp create mode 100644 libs/gltfio/src/extended/TangentSpaceMeshWrapper.h create mode 100644 libs/gltfio/src/extended/TangentsJobExtended.cpp create mode 100644 libs/gltfio/src/extended/TangentsJobExtended.h diff --git a/android/gltfio-android/CMakeLists.txt b/android/gltfio-android/CMakeLists.txt index 41b546a92dd..0acc49350ef 100644 --- a/android/gltfio-android/CMakeLists.txt +++ b/android/gltfio-android/CMakeLists.txt @@ -86,6 +86,12 @@ set(GLTFIO_SRCS ${GLTFIO_DIR}/src/Wireframe.cpp ${GLTFIO_DIR}/src/Wireframe.h ${GLTFIO_DIR}/src/downcast.h + ${GLTFIO_DIR}/src/extended/AssetLoaderExtended.h + ${GLTFIO_DIR}/src/extended/TangentsJobExtended.cpp + ${GLTFIO_DIR}/src/extended/TangentsJobExtended.h + ${GLTFIO_DIR}/src/extended/TangentSpaceMeshWrapper.cpp + ${GLTFIO_DIR}/src/extended/TangentSpaceMeshWrapper.h + src/main/cpp/Animator.cpp src/main/cpp/AssetLoader.cpp diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index 2c1532cc163..a638c6b401c 100644 --- a/libs/gltfio/CMakeLists.txt +++ b/libs/gltfio/CMakeLists.txt @@ -52,6 +52,11 @@ set(SRCS src/Wireframe.cpp src/Wireframe.h src/downcast.h + src/extended/AssetLoaderExtended.h + src/extended/TangentsJobExtended.cpp + src/extended/TangentsJobExtended.h + src/extended/TangentSpaceMeshWrapper.cpp + src/extended/TangentSpaceMeshWrapper.h ) # ================================================================================================== diff --git a/libs/gltfio/src/extended/AssetLoaderExtended.h b/libs/gltfio/src/extended/AssetLoaderExtended.h new file mode 100644 index 00000000000..854e87e2a82 --- /dev/null +++ b/libs/gltfio/src/extended/AssetLoaderExtended.h @@ -0,0 +1,32 @@ +/* + * 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 GLTFIO_ASSETLOADEREXTENDED_H +#define GLTFIO_ASSETLOADEREXTENDED_H + +#include + +namespace filament::gltfio { + +// The cgltf attribute is a type and the attribute index +struct Attribute { + cgltf_attribute_type type; // positions, tangents + int index; +}; + +} // namespace filament::gltfio + +#endif // GLTFIO_ASSETLOADEREXTENDED_H diff --git a/libs/gltfio/src/extended/TangentSpaceMeshWrapper.cpp b/libs/gltfio/src/extended/TangentSpaceMeshWrapper.cpp new file mode 100644 index 00000000000..365bd75260a --- /dev/null +++ b/libs/gltfio/src/extended/TangentSpaceMeshWrapper.cpp @@ -0,0 +1,330 @@ +/* + * 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 "TangentSpaceMeshWrapper.h" + +#include + +#include +#include + +namespace filament::gltfio { + +namespace { + +using AuxType = TangentSpaceMeshWrapper::AuxType; +using Builder = TangentSpaceMeshWrapper::Builder; + +struct Passthrough { + static constexpr int POSITION = 256; + static constexpr int UV0 = 257; + static constexpr int NORMALS = 258; + static constexpr int TANGENTS = 259; + static constexpr int TRIANGLES = 260; + + std::unordered_map data; + size_t vertexCount = 0; + size_t triangleCount = 0; +}; + +// Note that the method signatures here match TangentSpaceMesh +struct PassthroughMesh { + explicit PassthroughMesh(Passthrough const& passthrough) + : mPassthrough(passthrough) {} + + void getPositions(float3* data) noexcept { + size_t const nbytes = mPassthrough.vertexCount * sizeof(float3); + std::memcpy(data, mPassthrough.data[Passthrough::POSITION], nbytes); + } + + void getUVs(float2* data) noexcept { + size_t const nbytes = mPassthrough.vertexCount * sizeof(float2); + std::memcpy(data, mPassthrough.data[Passthrough::UV0], nbytes); + } + + void getQuats(short4* data) noexcept { + size_t const nbytes = mPassthrough.vertexCount * sizeof(short4); + std::memcpy(data, mPassthrough.data[Passthrough::TANGENTS], nbytes); + } + + void getTriangles(uint3* data) { + size_t const nbytes = mPassthrough.triangleCount * sizeof(uint3); + std::memcpy(data, mPassthrough.data[Passthrough::TRIANGLES], nbytes); + } + + template + void getAux(AuxType attribute, T data) noexcept { + size_t const nbytes = mPassthrough.vertexCount * sizeof(std::remove_pointer_t); + std::memcpy(data, (T) mPassthrough.data[static_cast(attribute)], nbytes); + } + + size_t getVertexCount() const noexcept { return mPassthrough.vertexCount; } + size_t getTriangleCount() const noexcept { return mPassthrough.triangleCount; } + +private: + Passthrough mPassthrough; +}; + +// Note that the method signatures here match TangentSpaceMesh::Builder +struct PassthroughBuilder { + void vertexCount(size_t count) noexcept { mPassthrough.vertexCount = count; } + + void normals(float3 const* normals) noexcept { + mPassthrough.data[Passthrough::NORMALS] = normals; + } + + void tangents(float4 const* tangents) noexcept { + mPassthrough.data[Passthrough::TANGENTS] = tangents; + } + void uvs(float2 const* uvs) noexcept { mPassthrough.data[Passthrough::UV0] = uvs; } + + void positions(float3 const* positions) noexcept { + mPassthrough.data[Passthrough::POSITION] = positions; + } + + void triangleCount(size_t triangleCount) noexcept { + mPassthrough.triangleCount = triangleCount; + } + + void triangles(uint3 const* triangles) noexcept { + mPassthrough.data[Passthrough::TRIANGLES] = triangles; + } + + void aux(AuxType type, void* data) noexcept { + mPassthrough.data[static_cast(type)] = data; + } + + PassthroughMesh* build() const noexcept { + return new PassthroughMesh(mPassthrough); + } + +private: + Passthrough mPassthrough; + friend struct PassthroughMesh; +}; + +} // anonymous + +#define DO_MESH_IMPL(METHOD, ...) \ + do { \ + if (mTsMesh) { \ + mTsMesh->METHOD(__VA_ARGS__); \ + } else { \ + mPassthroughMesh->METHOD(__VA_ARGS__); \ + } \ + } while (0) + +#define DO_MESH_RET_IMPL(METHOD) \ + do { \ + if (mTsMesh) { \ + return mTsMesh->METHOD(); \ + } else { \ + return mPassthroughMesh->METHOD(); \ + } \ + } while (0) + +struct TangentSpaceMeshWrapper::Impl { + Impl(geometry::TangentSpaceMesh* tsMesh) + : mTsMesh(tsMesh) {} + + Impl(PassthroughMesh* passthroughMesh) + : mPassthroughMesh(passthroughMesh) {} + + ~Impl() { + if (mTsMesh) { + geometry::TangentSpaceMesh::destroy(mTsMesh); + } + if (mPassthroughMesh) { + delete mPassthroughMesh; + } + } + + inline size_t getVertexCount() const noexcept { + DO_MESH_RET_IMPL(getVertexCount); + } + + float3* getPositions() noexcept { + size_t const nbytes = getVertexCount() * sizeof(float3); + auto data = (float3*) malloc(nbytes); + DO_MESH_IMPL(getPositions, data); + return data; + } + + float2* getUVs() noexcept { + size_t const nbytes = getVertexCount() * sizeof(float2); + auto data = (float2*) malloc(nbytes); + DO_MESH_IMPL(getUVs, data); + return data; + } + + short4* getQuats() noexcept { + size_t const nbytes = getVertexCount() * sizeof(short4); + auto data = (short4*) malloc(nbytes); + DO_MESH_IMPL(getQuats, data); + return data; + } + + uint3* getTriangles() { + size_t const nbytes = getTriangleCount() * sizeof(uint3); + auto data = (uint3*) malloc(nbytes); + DO_MESH_IMPL(getTriangles, data); + return data; + } + + template + using is_supported_aux_t = + typename std::enable_if::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value>::type; + template> + T getAux(AuxType attribute) noexcept { + size_t const nbytes = getVertexCount() * sizeof(std::remove_pointer_t); + auto data = (T) malloc(nbytes); + DO_MESH_IMPL(getAux, data); + return data; + } + + inline size_t getTriangleCount() const noexcept { + DO_MESH_RET_IMPL(getTriangleCount); + } + +private: + geometry::TangentSpaceMesh* mTsMesh = nullptr; + PassthroughMesh* mPassthroughMesh = nullptr; +}; + +#undef DO_MESH_IMPL +#undef DO_MESH_RET_IMPL + +#define DO_BUILDER_IMPL(METHOD, ...) \ + do { \ + if (mPassthroughBuilder) { \ + mPassthroughBuilder->METHOD(__VA_ARGS__); \ + } else { \ + mTsmBuilder->METHOD(__VA_ARGS__); \ + } \ + } while (0) + +struct TangentSpaceMeshWrapper::Builder::Impl { + explicit Impl(bool isUnlit) + : mPassthroughBuilder(isUnlit ? std::make_unique() : nullptr), + mTsmBuilder( + !isUnlit ? std::make_unique() : nullptr) {} + + void vertexCount(size_t count) noexcept { DO_BUILDER_IMPL(vertexCount, count); } + void normals(float3 const* normals) noexcept { DO_BUILDER_IMPL(normals, normals); } + void tangents(float4 const* tangents) noexcept { DO_BUILDER_IMPL(tangents, tangents); } + void uvs(float2 const* uvs) noexcept { DO_BUILDER_IMPL(uvs, uvs); } + void positions(float3 const* positions) noexcept { DO_BUILDER_IMPL(positions, positions); } + void triangles(uint3 const* triangles) noexcept { DO_BUILDER_IMPL(triangles, triangles); } + void triangleCount(size_t count) noexcept { DO_BUILDER_IMPL(triangleCount, count); } + + template + void aux(AuxType type, T data) { + DO_BUILDER_IMPL(aux, type, data); + } + + TangentSpaceMeshWrapper* build() { + auto ret = new TangentSpaceMeshWrapper(); + if (mPassthroughBuilder) { + ret->mImpl = new TangentSpaceMeshWrapper::Impl{ mPassthroughBuilder->build() }; + } else { + ret->mImpl = new TangentSpaceMeshWrapper::Impl{ mTsmBuilder->build() }; + } + return ret; + } + +private: + std::unique_ptr mPassthroughBuilder; + std::unique_ptr mTsmBuilder; +}; + +#undef DO_BUILDER_IMPL + +Builder::Builder(bool isUnlit) + : mImpl(new Impl{isUnlit}) {} + + +Builder& Builder::vertexCount(size_t count) noexcept { + mImpl->vertexCount(count); + return *this; +} + +Builder& Builder::normals(float3 const* normals) noexcept { + mImpl->normals(normals); + return *this; +} + +Builder& Builder::tangents(float4 const* tangents) noexcept { + mImpl->tangents(tangents); + return *this; +} + +Builder& Builder::uvs(float2 const* uvs) noexcept { + mImpl->uvs(uvs); + return *this; +} + +Builder& Builder::positions(float3 const* positions) noexcept{ + mImpl->positions(positions); + return *this; +} + +Builder& Builder::triangleCount(size_t triangleCount) noexcept { + mImpl->triangleCount(triangleCount); + return *this; +} + +Builder& Builder::triangles(uint3 const* triangles) noexcept { + mImpl->triangles(triangles); + return *this; +} + +template +Builder& Builder::aux(AuxType type, T data) { + mImpl->aux(type, data); + return *this; +} + +TangentSpaceMeshWrapper* Builder::build() { + return mImpl->build(); +} + +void TangentSpaceMeshWrapper::destroy(TangentSpaceMeshWrapper* mesh) { + assert_invariant(mesh->mImpl); + assert_invariant(mesh); + delete mesh->mImpl; + delete mesh; +} + +float3* TangentSpaceMeshWrapper::getPositions() noexcept { return mImpl->getPositions(); } +float2* TangentSpaceMeshWrapper::getUVs() noexcept { return mImpl->getUVs(); } +short4* TangentSpaceMeshWrapper::getQuats() noexcept { return mImpl->getQuats(); } +uint3* TangentSpaceMeshWrapper::getTriangles() { return mImpl->getTriangles(); } +size_t TangentSpaceMeshWrapper::getVertexCount() const noexcept { return mImpl->getVertexCount(); } + +template +T TangentSpaceMeshWrapper::getAux(AuxType attribute) noexcept { + return mImpl->getAux(attribute); +} + +size_t TangentSpaceMeshWrapper::getTriangleCount() const noexcept { + return mImpl->getTriangleCount(); +} + +} // filament::gltfio diff --git a/libs/gltfio/src/extended/TangentSpaceMeshWrapper.h b/libs/gltfio/src/extended/TangentSpaceMeshWrapper.h new file mode 100644 index 00000000000..6aab1574726 --- /dev/null +++ b/libs/gltfio/src/extended/TangentSpaceMeshWrapper.h @@ -0,0 +1,82 @@ +/* + * 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 GLTFIO_TANGENT_SPACE_MESH_WRAPPER_H +#define GLTFIO_TANGENT_SPACE_MESH_WRAPPER_H + +#include + +#include + +namespace filament::gltfio { + +using namespace math; + +// Wrapper around TangentSpaceMesh because in the case of unlit material, we do not need to go +// through TSM transformation, and we simply passthrough any given input as output. +struct TangentSpaceMeshWrapper { + using AuxType = geometry::TangentSpaceMesh::AuxAttribute; + + struct Builder { + struct Impl; + + Builder(bool isUnlit); + + Builder& vertexCount(size_t count) noexcept; + Builder& normals(float3 const* normals) noexcept; + Builder& tangents(float4 const* tangents) noexcept; + Builder& uvs(float2 const* uvs) noexcept; + Builder& positions(float3 const* positions) noexcept; + Builder& triangleCount(size_t triangleCount) noexcept; + Builder& triangles(uint3 const* triangles) noexcept; + template + Builder& aux(AuxType type, T data); + TangentSpaceMeshWrapper* build(); + + private: + Impl* mImpl; + }; + + explicit TangentSpaceMeshWrapper() = default; + + static void destroy(TangentSpaceMeshWrapper* mesh); + + float3* getPositions() noexcept; + float2* getUVs() noexcept; + short4* getQuats() noexcept; + uint3* getTriangles(); + + template + using is_supported_aux_t = typename std::enable_if< + std::is_same::value || std::is_same::value || + std::is_same::value || std::is_same::value || + std::is_same::value>::type; + template> + T getAux(AuxType attribute) noexcept; + + size_t getVertexCount() const noexcept; + size_t getTriangleCount() const noexcept; + +private: + struct Impl; + Impl* mImpl; + + friend struct Builder::Impl; +}; + +} // namespace filament + +#endif // GLTFIO_TANGENTS_JOB_EXTENDED_H diff --git a/libs/gltfio/src/extended/TangentsJobExtended.cpp b/libs/gltfio/src/extended/TangentsJobExtended.cpp new file mode 100644 index 00000000000..83b9a6edeaa --- /dev/null +++ b/libs/gltfio/src/extended/TangentsJobExtended.cpp @@ -0,0 +1,549 @@ +/* + * 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 "TangentsJobExtended.h" + +#include "AssetLoaderExtended.h" +#include "TangentSpaceMeshWrapper.h" +#include "../GltfEnums.h" +#include "../FFilamentAsset.h" +#include "../Utility.h" + +#include +#include +#include + +#include +#include + +using namespace filament; +using namespace filament::gltfio; +using namespace filament::math; + +namespace { + +constexpr uint8_t POSITIONS_ID = 0; +constexpr uint8_t TANGENTS_ID = 1; +constexpr uint8_t COLORS_ID = 3; +constexpr uint8_t NORMALS_ID = 4; +constexpr uint8_t UV0_ID = 5; +constexpr uint8_t UV1_ID = 6; +constexpr uint8_t WEIGHTS_ID = 7; +constexpr uint8_t JOINTS_ID = 8; +constexpr uint8_t INVALID_ID = 0xFF; + +using POSITIONS_TYPE = float3*; +using TANGENTS_TYPE = float4*; +using COLORS_TYPE = float4*; +using NORMALS_TYPE = float3*; +using UV0_TYPE = float2*; +using UV1_TYPE = float2*; +using WEIGHTS_TYPE = float4*; +using JOINTS_TYPE = ushort4*; + +// Used in template specifier. +#define POSITIONS_T POSITIONS_TYPE, POSITIONS_ID +#define TANGENTS_T TANGENTS_TYPE, TANGENTS_ID +#define COLORS_T COLORS_TYPE, COLORS_ID +#define NORMALS_T NORMALS_TYPE, NORMALS_ID +#define UV0_T UV0_TYPE, UV0_ID +#define UV1_T UV1_TYPE, UV1_ID +#define WEIGHTS_T WEIGHTS_TYPE, WEIGHTS_ID +#define JOINTS_T JOINTS_TYPE, JOINTS_ID + +using DataType = std::variant; +using AttributeDataMap = std::unordered_map; + +// This converts from cgltf attributes to the representation in this file. +inline uint8_t toCode(Attribute attr, UvMap const& uvmap, bool hasUv0) { + switch (attr.type) { + case cgltf_attribute_type_normal: + assert_invariant(attr.index == 0); + return NORMALS_ID; + case cgltf_attribute_type_tangent: + assert_invariant(attr.index == 0); + return TANGENTS_ID; + case cgltf_attribute_type_color: + assert_invariant(attr.index == 0); + return COLORS_ID; + case cgltf_attribute_type_position: + assert_invariant(attr.index == 0); + return POSITIONS_ID; + // This logic is replicating slot assignment in AssetLoaderExtended.cpp + case cgltf_attribute_type_texcoord: { + assert_invariant(attr.index < UvMapSize); + UvSet uvset = uvmap[attr.index]; + switch (uvset) { + case gltfio::UV0: + return UV0_ID; + case gltfio::UV1: + return UV1_ID; + case gltfio::UNUSED: + // If we have a free slot, then include this unused UV set in the VertexBuffer. + // This allows clients to swap the glTF material with a custom material. + if (!hasUv0 && getNumUvSets(uvmap) == 0) { + return UV0_ID; + } + } + utils::slog.w << "Only two sets of UVs are available" << utils::io::endl; + return INVALID_ID; + } + case cgltf_attribute_type_weights: + assert_invariant(attr.index == 0); + return WEIGHTS_ID; + case cgltf_attribute_type_joints: + assert_invariant(attr.index == 0); + return JOINTS_ID; + default: + // Otherwise, this is not an attribute supported by Filament. + return INVALID_ID; + } +} + +// These methods extra and/or transform the data from the cgltf accessors. +namespace data { + +template +inline T get(AttributeDataMap const& data) { + auto iter = data.find(attrib); + if (iter != data.end()) { + return std::get(iter->second); + } + return nullptr; +} + +template +void allocate(AttributeDataMap& data, size_t count) { + assert_invariant(data.find(attrib) == data.end()); + data[attrib] = (T) malloc(sizeof(std::remove_pointer_t) * count); +} + +template +void free(AttributeDataMap& data) { + if (data.find(attrib) == data.end()) { + return; + } + std::free(std::get(data[attrib])); + data.erase(attrib); +} + +constexpr uint8_t UBYTE_TYPE = 1; +constexpr uint8_t USHORT_TYPE = 2; +constexpr uint8_t FLOAT_TYPE = 3; + +template +constexpr uint8_t componentType() { + if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return FLOAT_TYPE; + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return UBYTE_TYPE; + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + return USHORT_TYPE; + } + return 0; +} + +template +constexpr size_t byteCount() { + return sizeof(std::remove_pointer_t); +} + +template +constexpr size_t componentCount() { + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return 2; + } else if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return 3; + } else if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return 4; + } + return 0; +} + +// This method will copy when the input/output types are the same and will do conversion where it +// makes sense. +template +void copy(InDataType in, size_t inStride, OutDataType out, size_t outStride, size_t count) { + uint8_t* inBytes = (uint8_t*) in; + uint8_t* outBytes = (uint8_t*) out; + + if constexpr (componentType() == componentType()) { + if constexpr (componentCount() == 3 && componentCount() == 4) { + for (size_t i = 0; i < count; ++i) { + *((OutDataType) (outBytes + (i * outStride))) = std::remove_pointer_t( + *((InDataType) (inBytes + (i * inStride))), 1); + } + return; + } else if constexpr (componentCount() == componentCount()) { + if (inStride == outStride) { + std::memcpy(out, in, data::byteCount() * count); + } else { + for (size_t i = 0; i < count; ++i) { + *((OutDataType) (outBytes + (i * outStride))) = + std::remove_pointer_t( + *((InDataType) (inBytes + (i * inStride)))); + } + } + return; + } + + PANIC_POSTCONDITION("Invalid component count in conversion"); + } else if constexpr (componentCount() == componentCount()) { + // byte to float conversion + constexpr size_t const compCount = componentCount(); + if constexpr (componentType() == UBYTE_TYPE && + componentType() == FLOAT_TYPE) { + for (size_t i = 0; i < count; ++i) { + for (size_t j = 0; j < compCount; ++j) { + *(((float*) (outBytes + (i * outStride))) + j) = + *(((uint8_t*) (inBytes + (i * inStride))) + j) / 255.0f; + } + } + return; + } else if constexpr (componentType() == UBYTE_TYPE && + componentType() == USHORT_TYPE) { + for (size_t i = 0; i < count; ++i) { + for (size_t j = 0; j < compCount; ++j) { + *(((uint16_t*) (outBytes + (i * outStride))) + j) = + *(((uint8_t*) (inBytes + (i * inStride))) + j); + } + } + return; + } + } + PANIC_POSTCONDITION("Invalid conversion"); +} + +template +void unpack(cgltf_accessor const* accessor, size_t const vertexCount, T out, + bool isMorphTarget = false) { + assert_invariant(accessor->count == vertexCount); + uint8_t const* data = nullptr; + if (accessor->buffer_view->has_meshopt_compression) { + data = (uint8_t const*) accessor->buffer_view->data + accessor->offset; + } else { + data = (uint8_t const*) accessor->buffer_view->buffer->data + + utility::computeBindingOffset(accessor); + } + auto componentType = accessor->component_type; + size_t const inDim = cgltf_num_components(accessor->type); + size_t const outDim = componentCount(); + + if (componentType == cgltf_component_type_r_32f) { + assert_invariant(accessor->buffer_view); + assert_invariant(data::componentType() == FLOAT_TYPE); + size_t const elementCount = outDim * vertexCount; + + if ((isMorphTarget && utility::requiresPacking(accessor)) || + utility::requiresConversion(accessor)) { + cgltf_accessor_unpack_floats(accessor, (float*) out, elementCount); + return; + } else { + if (inDim == 3 && outDim == 4) { + data::copy((float3*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } else { + assert_invariant(inDim == outDim); + data::copy((T) data, accessor->stride, (T) out, data::byteCount(), + vertexCount); + return; + } + } + } else { + assert_invariant(outDim == inDim); + if (componentType == cgltf_component_type_r_8u) { + if (inDim == 2) { + data::copy((ubyte2*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } else if (inDim == 3) { + data::copy((ubyte3*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } else if (inDim == 4) { + data::copy((ubyte4*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } + } else if (componentType == cgltf_component_type_r_16u) { + if (inDim == 2) { + data::copy((ushort2*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } else if (inDim == 3) { + data::copy((ushort3*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } else if (inDim == 4) { + data::copy((ushort4*) data, accessor->stride, (T) out, + data::byteCount(), vertexCount); + return; + } + } + + PANIC_POSTCONDITION("Only ubyte or ushort accepted as input"); + } +} + +template +void unpack(cgltf_accessor const* accessor, AttributeDataMap& data, size_t const vertexCount, + bool isMorphTarget = false) { + assert_invariant(accessor->count == vertexCount); + assert_invariant(data.find(attrib) != data.end()); + + unpack(accessor, vertexCount, data::get(data), isMorphTarget); +} + +template +void add(AttributeDataMap& data, size_t const vertexCount, float3* addition) { + assert_invariant(data.find(attrib) != data.end()); + + T datav = std::get(data[attrib]); + for (size_t i = 0; i < vertexCount; ++i) { + if constexpr(std::is_same_v) { + datav[i] += addition[i].xy; + } else if constexpr(std::is_same_v) { + datav[i] += addition[i]; + } else if constexpr(std::is_same_v) { + datav[i].xyz += addition[i]; + } + } +} + +} // namespace data + +void destroy(AttributeDataMap& data) { + data::free(data); + data::free(data); + data::free(data); + data::free(data); + data::free(data); + data::free(data); + data::free(data); + data::free(data); +} + +} // anonymous namespace + +namespace filament::gltfio { + +void TangentsJobExtended::run(Params* params) { + cgltf_primitive const& prim = *params->in.prim; + int const morphTargetIndex = params->in.morphTargetIndex; + bool const isMorphTarget = morphTargetIndex != kMorphTargetUnused; + bool const isUnlit = prim.material ? prim.material->unlit : false; + + // Extract the vertex count from the first attribute. All attributes must have the same count. + assert_invariant(prim.attributes_count > 0); + auto const vertexCount = prim.attributes[0].data->count; + assert_invariant(vertexCount > 0); + + std::unordered_map accessors; + std::unordered_map morphAccessors; + AttributeDataMap attributes; + + // Extract the accessor per attribute from cgltf into our attributes mapping. + bool hasUV0 = false; + for (cgltf_size aindex = 0; aindex < prim.attributes_count; ++aindex) { + cgltf_attribute const& attr = prim.attributes[aindex]; + cgltf_accessor* accessor = attr.data; + assert_invariant(accessor); + if (auto const attrCode = toCode({attr.type, attr.index}, params->in.uvmap, hasUV0); + attrCode != INVALID_ID) { + hasUV0 = hasUV0 || attrCode == UV0_ID; + accessors[attrCode] = accessor; + } + } + + std::vector morphDelta; + if (isMorphTarget) { + auto const& morphTarget = prim.targets[morphTargetIndex]; + decltype(params->in.uvmap) tmpUvmap; // just a placeholder since we don't consider morph target uvs. + for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) { + cgltf_attribute const& attr = morphTarget.attributes[aindex]; + if (auto const attrCode = toCode({attr.type, attr.index}, tmpUvmap, false); + attrCode != INVALID_ID) { + assert_invariant(accessors.find(attrCode) != accessors.end() && + "Morph target data has no corresponding base vertex data."); + morphAccessors[attrCode] = attr.data; + } + } + morphDelta.resize(vertexCount); + } + using AuxType = TangentSpaceMeshWrapper::AuxType; + TangentSpaceMeshWrapper::Builder tob(isUnlit); + tob.vertexCount(vertexCount); + + // We go through all of the accessors (that we care about) associated with the primitive and + // extra the associated data. For morph targets, we also find the associated morph target offset + // and apply offsets where possible. + for (auto [attr, accessor]: accessors) { + switch (attr) { + case POSITIONS_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + if (auto itr = morphAccessors.find(attr); itr != morphAccessors.end()) { + data::unpack(itr->second, vertexCount, morphDelta.data(), + isMorphTarget); + data::add(attributes, vertexCount, morphDelta.data()); + + // We stash the positions as colors so that they can be retrieved without change + // after the TBN algo, which might have remeshed the input. + data::allocate(attributes, vertexCount); + float4* storage = data::get(attributes); + for (size_t i = 0; i < vertexCount; i++) { + storage[i] = float4{morphDelta[i], 0.0}; + } + tob.aux(AuxType::COLORS, storage); + } + tob.positions(data::get(attributes)); + break; + case TANGENTS_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + if (auto itr = morphAccessors.find(attr); itr != morphAccessors.end()) { + data::unpack(itr->second, vertexCount, morphDelta.data()); + data::add(attributes, vertexCount, morphDelta.data()); + } + tob.tangents(data::get(attributes)); + break; + case NORMALS_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + if (auto itr = morphAccessors.find(attr); itr != morphAccessors.end()) { + data::unpack(itr->second, vertexCount, morphDelta.data()); + data::add(attributes, vertexCount, morphDelta.data()); + } + tob.normals(data::get(attributes)); + break; + case COLORS_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + tob.aux(AuxType::COLORS, data::get(attributes)); + break; + case UV0_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + tob.uvs(data::get(attributes)); + break; + case UV1_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + tob.aux(AuxType::UV1, data::get(attributes)); + break; + case WEIGHTS_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + tob.aux(AuxType::WEIGHTS, data::get(attributes)); + break; + case JOINTS_ID: + data::allocate(attributes, vertexCount); + data::unpack(accessor, attributes, vertexCount); + tob.aux(AuxType::JOINTS, data::get(attributes)); + break; + default: + break; + } + } + + std::unique_ptr unpackedTriangles; + size_t const triangleCount = prim.indices ? (prim.indices->count / 3) : (vertexCount / 3); + unpackedTriangles.reset(new uint3[triangleCount]); + + // TODO: this is slow. We might be able to skip the manual read if the indices are already in + // the right format. + if (prim.indices) { + for (size_t tri = 0, j = 0; tri < triangleCount; ++tri) { + auto& triangle = unpackedTriangles[tri]; + triangle.x = cgltf_accessor_read_index(prim.indices, j++); + triangle.y = cgltf_accessor_read_index(prim.indices, j++); + triangle.z = cgltf_accessor_read_index(prim.indices, j++); + } + } else { + for (size_t tri = 0, j = 0; tri < triangleCount; ++tri) { + auto& triangle = unpackedTriangles[tri]; + triangle.x = j++; + triangle.y = j++; + triangle.z = j++; + } + } + + tob.triangleCount(triangleCount); + tob.triangles(unpackedTriangles.get()); + auto const mesh = tob.build(); + + auto& out = params->out; + out.vertexCount = mesh->getVertexCount(); + + out.triangleCount = mesh->getTriangleCount(); + out.triangles = mesh->getTriangles(); + + if (!isUnlit) { + out.tbn = mesh->getQuats(); + } + + if (isMorphTarget) { + // For morph targets, we need to retrieve the positions, but note that the unadjusted + // positions are stored as colors. + // For morph targets, we use COLORS as a way to store the original positions. + auto data = mesh->getAux(AuxType::COLORS); + out.positions = (float3*) malloc(sizeof(float3) * out.vertexCount); + for (size_t i = 0; i < out.vertexCount; ++i) { + out.positions[i] = data[i].xyz; + } + free(data); + } else { + for (auto [attr, data]: attributes) { + switch (attr) { + case POSITIONS_ID: + out.positions = mesh->getPositions(); + break; + case COLORS_ID: + out.colors = mesh->getAux(AuxType::COLORS); + break; + case UV0_ID: + out.uv0 = mesh->getUVs(); + break; + case UV1_ID: + out.uv1 = mesh->getAux(AuxType::UV1); + break; + case WEIGHTS_ID: + out.weights = mesh->getAux(AuxType::WEIGHTS); + break; + case JOINTS_ID: + out.joints = mesh->getAux(AuxType::JOINTS); + break; + default: + break; + } + } + } + + destroy(attributes); + TangentSpaceMeshWrapper::destroy(mesh); +} + +} // namespace filament::gltfio diff --git a/libs/gltfio/src/extended/TangentsJobExtended.h b/libs/gltfio/src/extended/TangentsJobExtended.h new file mode 100644 index 00000000000..5fb7544f7c5 --- /dev/null +++ b/libs/gltfio/src/extended/TangentsJobExtended.h @@ -0,0 +1,76 @@ +/* + * 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 GLTFIO_TANGENTS_JOB_EXTENDED_H +#define GLTFIO_TANGENTS_JOB_EXTENDED_H + +#include // for UvMap +#include + +#include + +namespace filament::gltfio { + +// Encapsulates a tangent-space transformation, which computes tangents (and maybe transform the +// mesh vertices/indices depending on the algorithm used). Input to this job can be the base mesh +// and attributes, or it could be a specific morph target, where offsets to the base mesh will be +// computed with respect to the morph target index. +struct TangentsJobExtended { + static constexpr int kMorphTargetUnused = -1; + + // The inputs to the procedure. The prim is owned by the client, which should ensure that it + // stays alive for the duration of the procedure. + struct InputParams { + cgltf_primitive const* prim; + int morphTargetIndex = kMorphTargetUnused; + UvMap uvmap; + }; + + // The outputs of the procedure. The results array gets malloc'd by the procedure, so clients + // should remember to free it. + struct OutputParams { + size_t triangleCount = 0; + math::uint3* triangles = nullptr; + + size_t vertexCount = 0; + math::short4* tbn = nullptr; + math::float2* uv0 = nullptr; + math::float2* uv1 = nullptr; + math::float3* positions = nullptr; + math::ushort4* joints = nullptr; + math::float4* weights = nullptr; + math::float4* colors = nullptr; + + bool isEmpty() const { + return !tbn && !uv0 && !uv1 && !positions && !joints && !weights && !colors; + } + }; + + // Clients might want to track the jobs in an array, so the arguments are bundled into a struct. + struct Params { + InputParams in; + OutputParams out; + uint8_t jobType = 0; + }; + + // Performs tangents generation synchronously. This can be invoked from inside a job if desired. + // The parameters structure is owned by the client. + static void run(Params* params); +}; + +} // namespace filament::gltfio + +#endif // GLTFIO_TANGENTS_JOB_EXTENDED_H From dace5fd695f9a8718c9ac27dc8d6e581a49aa2be Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 14 Mar 2024 14:45:48 -0700 Subject: [PATCH 17/29] minor fixes and cleanup to Primitive and BufferInfo factories - both files use consistent names - enforce that the keys are not moved around - don't pass the value (which is 4 bytes) by reference --- filament/src/HwRenderPrimitiveFactory.cpp | 23 ++++++++-------- filament/src/HwRenderPrimitiveFactory.h | 32 ++++++++++++++-------- filament/src/HwVertexBufferInfoFactory.cpp | 3 +- filament/src/HwVertexBufferInfoFactory.h | 13 ++++++--- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/filament/src/HwRenderPrimitiveFactory.cpp b/filament/src/HwRenderPrimitiveFactory.cpp index 1845e9eb780..2c76a3da0a2 100644 --- a/filament/src/HwRenderPrimitiveFactory.cpp +++ b/filament/src/HwRenderPrimitiveFactory.cpp @@ -33,18 +33,17 @@ namespace filament { using namespace utils; using namespace backend; -size_t HwRenderPrimitiveFactory::KeyHash::operator()( - HwRenderPrimitiveFactory::Key const& p) const noexcept { - return utils::hash::combine( - p.params.vbh.getId(), - utils::hash::combine(p.params.ibh.getId(), (size_t)p.params.type)); +size_t HwRenderPrimitiveFactory::Parameters::hash() const noexcept { + return utils::hash::combine(vbh.getId(), + utils::hash::combine(ibh.getId(), + (size_t)type)); } -bool operator==(HwRenderPrimitiveFactory::Key const& lhs, - HwRenderPrimitiveFactory::Key const& rhs) noexcept { - return lhs.params.vbh == rhs.params.vbh && - lhs.params.ibh == rhs.params.ibh && - lhs.params.type == rhs.params.type; +bool operator==(HwRenderPrimitiveFactory::Parameters const& lhs, + HwRenderPrimitiveFactory::Parameters const& rhs) noexcept { + return lhs.vbh == rhs.vbh && + lhs.ibh == rhs.ibh && + lhs.type == rhs.type; } // ------------------------------------------------------------------------------------------------ @@ -67,7 +66,7 @@ auto HwRenderPrimitiveFactory::create(DriverApi& driver, backend::PrimitiveType type) noexcept -> Handle { // see if we already have seen this RenderPrimitive - Key const key{{ vbh, ibh, type }, 1 }; + Key const key({ vbh, ibh, type }); auto pos = mBimap.find(key); // the common case is that we've never seen it (i.e.: no reuse) @@ -77,7 +76,7 @@ auto HwRenderPrimitiveFactory::create(DriverApi& driver, return handle; } - pos->first.pKey->refs++; + ++(pos->first.pKey->refs); return pos->second.handle; } diff --git a/filament/src/HwRenderPrimitiveFactory.h b/filament/src/HwRenderPrimitiveFactory.h index 94ce8f45551..6601a0c25ba 100644 --- a/filament/src/HwRenderPrimitiveFactory.h +++ b/filament/src/HwRenderPrimitiveFactory.h @@ -52,8 +52,11 @@ class HwRenderPrimitiveFactory { backend::VertexBufferHandle vbh; // 4 backend::IndexBufferHandle ibh; // 4 backend::PrimitiveType type; // 4 + size_t hash() const noexcept; }; + friend bool operator==(Parameters const& lhs, Parameters const& rhs) noexcept; + Handle create(backend::DriverApi& driver, backend::VertexBufferHandle vbh, backend::IndexBufferHandle ibh, @@ -63,29 +66,36 @@ class HwRenderPrimitiveFactory { private: struct Key { + // The key should not be copyable, unfortunately due to how the Bimap works we have + // to copy-construct it once. + Key(Key const&) = default; + Key& operator=(Key const&) = delete; + Key& operator=(Key&&) noexcept = delete; + explicit Key(Parameters const& params) : params(params), refs(1) { } Parameters params; mutable uint32_t refs; // 4 bytes + bool operator==(Key const& rhs) const noexcept { + return params == rhs.params; + } }; - struct KeyHash { - size_t operator()(Key const& p) const noexcept; + struct KeyHasher { + size_t operator()(Key const& p) const noexcept { + return p.params.hash(); + } }; - friend bool operator==(Key const& lhs, Key const& rhs) noexcept; - - struct Value { // 4 bytes Handle handle; }; - struct ValueHash { - size_t operator()(Value const& p) const noexcept { - std::hash const h; - return h(p.handle.getId()); + struct ValueHasher { + size_t operator()(Value const v) const noexcept { + return std::hash()(v.handle.getId()); } }; - friend bool operator==(Value const& lhs, Value const& rhs) noexcept { + friend bool operator==(Value const lhs, Value const rhs) noexcept { return lhs.handle == rhs.handle; } @@ -104,7 +114,7 @@ class HwRenderPrimitiveFactory { PoolAllocatorArena mArena; // The special Bimap - Bimap> mBimap; }; diff --git a/filament/src/HwVertexBufferInfoFactory.cpp b/filament/src/HwVertexBufferInfoFactory.cpp index 9219a710da3..44be4301b38 100644 --- a/filament/src/HwVertexBufferInfoFactory.cpp +++ b/filament/src/HwVertexBufferInfoFactory.cpp @@ -22,7 +22,6 @@ #include -#include #include #include #include @@ -66,7 +65,7 @@ auto HwVertexBufferInfoFactory::create(DriverApi& driver, uint8_t attributeCount, backend::AttributeArray attributes) noexcept -> Handle { - Key const key{{ bufferCount, attributeCount, {}, attributes }, 1 }; + Key const key({ bufferCount, attributeCount, {}, attributes }); auto pos = mBimap.find(key); // the common case is that we've never seen it (i.e.: no reuse) diff --git a/filament/src/HwVertexBufferInfoFactory.h b/filament/src/HwVertexBufferInfoFactory.h index 7e745dcb269..c467105b1ad 100644 --- a/filament/src/HwVertexBufferInfoFactory.h +++ b/filament/src/HwVertexBufferInfoFactory.h @@ -67,6 +67,12 @@ class HwVertexBufferInfoFactory { private: struct Key { // 140 bytes + // The key should not be copyable, unfortunately due to how the Bimap works we have + // to copy-construct it once. + Key(Key const&) = default; + Key& operator=(Key const&) = delete; + Key& operator=(Key&&) noexcept = delete; + explicit Key(Parameters const& params) : params(params), refs(1) { } Parameters params; mutable uint32_t refs; // 4 bytes bool operator==(Key const& rhs) const noexcept { @@ -85,13 +91,12 @@ class HwVertexBufferInfoFactory { }; struct ValueHasher { - size_t operator()(Value v) const noexcept { - std::hash const hasher; - return hasher(v.handle.getId()); + size_t operator()(Value const v) const noexcept { + return std::hash()(v.handle.getId()); } }; - friend bool operator==(Value const& lhs, Value const& rhs) noexcept { + friend bool operator==(Value const lhs, Value const rhs) noexcept { return lhs.handle == rhs.handle; } From 0f7cffc407b84d98cbbc9540373507da98b64e33 Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Fri, 15 Mar 2024 15:01:38 -0700 Subject: [PATCH 18/29] Connect multiview components together (#7671) Plumb through multiview configurations to the pipeline so that the engine draws scenes using multiview extension. Users need to prepare shaders compiled with the `multiview` param and set the `stereoscopicType` flag to MULTIVIEW in the Engine::Config to enable multiview feature. In this change, postprocessings for multiview are not yet supported. So we all disable them until they're supported. The debug option `combineMultiviewImages` combines layers as one image, which allows us to check the final result. --- filament/src/RenderPass.cpp | 9 ++++---- filament/src/RenderPass.h | 6 ++--- filament/src/RendererUtils.cpp | 2 ++ filament/src/details/Engine.h | 3 +++ filament/src/details/Renderer.cpp | 38 +++++++++++++++++++++++++++---- samples/CMakeLists.txt | 2 ++ samples/gltf_viewer.cpp | 4 ++++ 7 files changed, 51 insertions(+), 13 deletions(-) diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index de606809703..16489cb098c 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -184,8 +184,7 @@ void RenderPass::appendCommands(FEngine& engine, Command* curr = commands.data(); size_t const commandCount = commands.size(); - auto stereoscopicEyeCount = - renderFlags & IS_STEREOSCOPIC ? engine.getConfig().stereoscopicEyeCount : 1; + auto stereoscopicEyeCount = engine.getConfig().stereoscopicEyeCount; const float3 cameraPosition(mCameraPosition); const float3 cameraForwardVector(mCameraForwardVector); @@ -521,7 +520,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla const bool hasShadowing = renderFlags & HAS_SHADOWING; const bool viewInverseFrontFaces = renderFlags & HAS_INVERSE_FRONT_FACES; - const bool hasStereo = renderFlags & IS_STEREOSCOPIC; + const bool hasInstancedStereo = renderFlags & IS_INSTANCED_STEREOSCOPIC; Command cmdColor; @@ -590,7 +589,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla // soaInstanceInfo[i].count is the number of instances the user has requested, either for // manual or hybrid instancing. Instanced stereo multiplies the number of instances by the // eye count. - if (UTILS_UNLIKELY(hasStereo)) { + if (UTILS_UNLIKELY(hasInstancedStereo)) { cmdColor.primitive.instanceCount = (soaInstanceInfo[i].count * stereoEyeCount) | PrimitiveInfo::USER_INSTANCE_MASK; @@ -624,7 +623,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla cmdDepth.primitive.skinningTexture = skinning.handleSampler; cmdDepth.primitive.morphWeightBuffer = morphing.handle; - if (UTILS_UNLIKELY(hasStereo)) { + if (UTILS_UNLIKELY(hasInstancedStereo)) { cmdColor.primitive.instanceCount = (soaInstanceInfo[i].count * stereoEyeCount) | PrimitiveInfo::USER_INSTANCE_MASK; diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index a2ae834c001..fe6e04cafc9 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -275,9 +275,9 @@ class RenderPass { "Command isn't trivially destructible"); using RenderFlags = uint8_t; - static constexpr RenderFlags HAS_SHADOWING = 0x01; - static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02; - static constexpr RenderFlags IS_STEREOSCOPIC = 0x04; + static constexpr RenderFlags HAS_SHADOWING = 0x01; + static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02; + static constexpr RenderFlags IS_INSTANCED_STEREOSCOPIC = 0x04; // Arena used for commands using Arena = utils::Arena< diff --git a/filament/src/RendererUtils.cpp b/filament/src/RendererUtils.cpp index 08395f389a1..8c63be153eb 100644 --- a/filament/src/RendererUtils.cpp +++ b/filament/src/RendererUtils.cpp @@ -125,7 +125,9 @@ FrameGraphId RendererUtils::colorPass( // depth auto-resolve, in which case we must allocate the depth // buffer with MS and manually resolve it (see "Resolved Depth Buffer" // pass). + .depth = colorBufferDesc.depth, .samples = canAutoResolveDepth ? colorBufferDesc.samples : uint8_t(config.msaa), + .type = colorBufferDesc.type, .format = format, }); if (config.enabledStencilBuffer) { diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 9946edbb701..002e4f25f97 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -588,6 +588,9 @@ class FEngine : public Engine { struct { bool debug_froxel_visualization = false; } lighting; + struct { + bool combine_multiview_images = true; + } stereo; matdbg::DebugServer* server = nullptr; } debug; }; diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 329c3841b3a..493060cd6aa 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -111,6 +111,8 @@ FRenderer::FRenderer(FEngine& engine) : &engine.debug.shadowmap.display_shadow_texture_level_count); debugRegistry.registerProperty("d.shadowmap.display_shadow_texture_power", &engine.debug.shadowmap.display_shadow_texture_power); + debugRegistry.registerProperty("d.stereo.combine_multiview_images", + &engine.debug.stereo.combine_multiview_images); DriverApi& driver = engine.getDriverApi(); @@ -520,7 +522,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // DEBUG: driver commands must all happen from the same thread. Enforce that on debug builds. driver.debugThreading(); - const bool hasPostProcess = view.hasPostProcessPass(); + bool hasPostProcess = view.hasPostProcessPass(); bool hasScreenSpaceRefraction = false; bool hasColorGrading = hasPostProcess; bool hasDithering = view.getDithering() == Dithering::TEMPORAL; @@ -536,7 +538,16 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { auto colorGrading = view.getColorGrading(); auto ssReflectionsOptions = view.getScreenSpaceReflectionsOptions(); auto guardBandOptions = view.getGuardBandOptions(); + const bool isRenderingMultiview = view.hasStereo() && + engine.getConfig().stereoscopicType == backend::StereoscopicType::MULTIVIEW; + // FIXME: This is to override some settings that are not supported for multiview at the moment. + // Remove this when all features are supported. + if (isRenderingMultiview) { + hasPostProcess = false; + msaaOptions.enabled = false; + } const uint8_t msaaSampleCount = msaaOptions.enabled ? msaaOptions.sampleCount : 1u; + if (!hasPostProcess) { // disable all effects that are part of post-processing dofOptions.enabled = false; @@ -700,7 +711,10 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { RenderPass::RenderFlags renderFlags = 0; if (view.hasShadowing()) renderFlags |= RenderPass::HAS_SHADOWING; if (view.isFrontFaceWindingInverted()) renderFlags |= RenderPass::HAS_INVERSE_FRONT_FACES; - if (view.hasStereo()) renderFlags |= RenderPass::IS_STEREOSCOPIC; + if (view.hasStereo() && + engine.getConfig().stereoscopicType == backend::StereoscopicType::INSTANCED) { + renderFlags |= RenderPass::IS_INSTANCED_STEREOSCOPIC; + } RenderPassBuilder passBuilder(commandArena); passBuilder.renderFlags(renderFlags); @@ -977,12 +991,18 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { RenderPass const pass{ passBuilder.build(engine) }; - FrameGraphTexture::Descriptor const desc = { + FrameGraphTexture::Descriptor colorBufferDesc = { .width = config.physicalViewport.width, .height = config.physicalViewport.height, .format = config.hdrFormat }; + // Set the depth to the number of layers if we're rendering multiview. + if (isRenderingMultiview) { + colorBufferDesc.depth = engine.getConfig().stereoscopicEyeCount; + colorBufferDesc.type = backend::SamplerType::SAMPLER_2D_ARRAY; + } + // a non-drawing pass to prepare everything that need to be before the color passes execute fg.addTrivialSideEffectPass("Prepare Color Passes", [=, &js, &view, &ppm](DriverApi& driver) { @@ -990,7 +1010,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { if (colorGradingConfig.asSubpass) { ppm.colorGradingPrepareSubpass(driver, colorGrading, colorGradingConfig, vignetteOptions, - desc.width, desc.height); + colorBufferDesc.width, colorBufferDesc.height); } else if (colorGradingConfig.customResolve) { ppm.customResolvePrepareSubpass(driver, PostProcessManager::CustomResolveOp::COMPRESS); @@ -1008,7 +1028,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { // the color pass itself + color-grading as subpass if needed auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view, - desc, config, colorGradingConfigForColor, pass.getExecutor()); + colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor()); if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) { // this cancels the colorPass() call above if refraction is active. @@ -1152,6 +1172,14 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) { } } + // Debug: combine the array texture for multiview into a single image. + if (UTILS_UNLIKELY(isRenderingMultiview && engine.debug.stereo.combine_multiview_images)) { + input = ppm.debugCombineArrayTexture(fg, blendModeTranslucent, input, xvp, { + .width = vp.width, .height = vp.height, + .format = colorGradingConfig.ldrFormat }, + SamplerMagFilter::NEAREST, SamplerMinFilter::NEAREST); + } + // We need to do special processing when rendering directly into the swap-chain, that is when // the viewRenderTarget is the default render target (mRenderTarget) and we're rendering into // it. diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 027f2f7d6bb..7cd8ce6b9e6 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -50,8 +50,10 @@ file(MAKE_DIRECTORY ${MATERIAL_DIR}) set (MATC_FLAGS ${MATC_BASE_FLAGS}) if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced") set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=instanced) + add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_INSTANCED) elseif (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") set (MATC_FLAGS ${MATC_FLAGS} -PstereoscopicType=multiview) + add_definitions(-DFILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW) endif () foreach (mat_src ${MATERIAL_SRCS}) diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index cedc99fd510..80a06298e71 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -893,6 +893,10 @@ int main(int argc, char** argv) { "d.shadowmap.display_shadow_texture_channel"), 0, 3); ImGui::Unindent(); } +#if defined(FILAMENT_SAMPLES_STEREO_TYPE_MULTIVIEW) + ImGui::Checkbox("Combine Multiview Images", + debug.getPropertyAddress("d.stereo.combine_multiview_images")); +#endif bool debugFroxelVisualization; if (debug.getProperty("d.lighting.debug_froxel_visualization", From ba9cb2fe43f45c407e31fe197aa7e72d0e2810e5 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Sun, 17 Mar 2024 13:12:32 -0700 Subject: [PATCH 19/29] Release Filament 1.51.0 --- README.md | 4 ++-- RELEASE_NOTES.md | 3 +++ android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3bff570f9c5..8a4adea8f24 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.50.6' + implementation 'com.google.android.filament:filament-android:1.51.0' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.50.6' +pod 'Filament', '~> 1.51.0' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e3b32ecc38d..ba94019fceb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created. Instead, if you are authoring a PR for the main branch, add your release note to [NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md). +## v1.51.1 + + ## v1.51.0 - materials: add support for post-lighting mix factor (b/328498606) [⚠️ **New Material Version**] diff --git a/android/gradle.properties b/android/gradle.properties index 5dd1904abe2..535a3002648 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.50.6 +VERSION_NAME=1.51.0 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index e92cedb1b49..513d6c19752 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.6" + spec.version = "1.51.0" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.50.6/filament-v1.50.6-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.0/filament-v1.51.0-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 582f75eec43..2b2c4d8af55 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.50.6", + "version": "1.51.0", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From cf0c1f74dce1dcd82ae6aa88342682d3eceed3f4 Mon Sep 17 00:00:00 2001 From: Benjamin Doherty Date: Sun, 17 Mar 2024 13:12:41 -0700 Subject: [PATCH 20/29] Bump version to 1.51.1 --- README.md | 4 ++-- android/gradle.properties | 2 +- ios/CocoaPods/Filament.podspec | 4 ++-- web/filament-js/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8a4adea8f24..a1dc91cdc6b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.51.0' + implementation 'com.google.android.filament:filament-android:1.51.1' } ``` @@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`: iOS projects can use CocoaPods to install the latest release: ```shell -pod 'Filament', '~> 1.51.0' +pod 'Filament', '~> 1.51.1' ``` ### Snapshots diff --git a/android/gradle.properties b/android/gradle.properties index 535a3002648..685dd260796 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.51.0 +VERSION_NAME=1.51.1 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 513d6c19752..b51a82ab371 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.51.0" + spec.version = "1.51.1" spec.license = { :type => "Apache 2.0", :file => "LICENSE" } spec.homepage = "https://google.github.io/filament" spec.authors = "Google LLC." spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL." spec.platform = :ios, "11.0" - spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.0/filament-v1.51.0-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.1/filament-v1.51.1-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 2b2c4d8af55..7f24bbdf4a6 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.51.0", + "version": "1.51.1", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js", From 780799f30b1aa26e603fee26ade761eb0f3dbd80 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 20 Mar 2024 13:28:09 -0700 Subject: [PATCH 21/29] PlatformEGL::createSwapChain never returns a nullptr anymore in case the swapchain creation fails, it will now return a swapchain with an EGL_NO_SURFACE handle. this will avoid having to nullptr check the pointer in various places and will revert to the previous behavior on failure. FIXES=[329659681] --- .../backend/platforms/OpenGLPlatform.h | 2 +- filament/backend/src/opengl/OpenGLDriver.cpp | 14 ++++ .../src/opengl/platforms/PlatformEGL.cpp | 76 +++++++++---------- 3 files changed, 53 insertions(+), 39 deletions(-) diff --git a/filament/backend/include/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index 565a4641cbc..dec6f47ba74 100644 --- a/filament/backend/include/backend/platforms/OpenGLPlatform.h +++ b/filament/backend/include/backend/platforms/OpenGLPlatform.h @@ -89,7 +89,7 @@ class OpenGLPlatform : public Platform { * @return The driver's SwapChain object. * */ - virtual SwapChain* UTILS_NONNULL createSwapChain( + virtual SwapChain* UTILS_NULLABLE createSwapChain( void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0; /** diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 2d5c45bed18..66ee6685fd0 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1465,6 +1465,13 @@ void OpenGLDriver::createSwapChainR(Handle sch, void* nativeWindow, "createSwapChain(%p, 0x%lx) failed. See logs for details.", nativeWindow, flags); +#if !defined(__EMSCRIPTEN__) + // note: in practice this should never happen on Android + ASSERT_POSTCONDITION(sc->swapChain, + "createSwapChain(%p, 0x%lx) failed. See logs for details.", + nativeWindow, flags); +#endif + // See if we need the emulated rec709 output conversion if (UTILS_UNLIKELY(mContext.isES2())) { sc->rec709 = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE && @@ -1482,6 +1489,13 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, "createSwapChainHeadless(%u, %u, 0x%lx) failed. See logs for details.", width, height, flags); +#if !defined(__EMSCRIPTEN__) + // note: in practice this should never happen on Android + ASSERT_POSTCONDITION(sc->swapChain, + "createSwapChainHeadless(%u, %u, 0x%lx) failed. See logs for details.", + width, height, flags); +#endif + // See if we need the emulated rec709 output conversion if (UTILS_UNLIKELY(mContext.isES2())) { sc->rec709 = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE && diff --git a/filament/backend/src/opengl/platforms/PlatformEGL.cpp b/filament/backend/src/opengl/platforms/PlatformEGL.cpp index fcf4b9c4922..b041473839f 100644 --- a/filament/backend/src/opengl/platforms/PlatformEGL.cpp +++ b/filament/backend/src/opengl/platforms/PlatformEGL.cpp @@ -454,20 +454,7 @@ bool PlatformEGL::isSRGBSwapChainSupported() const noexcept { Platform::SwapChain* PlatformEGL::createSwapChain( void* nativeWindow, uint64_t flags) noexcept { - EGLConfig config = EGL_NO_CONFIG_KHR; - if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) { - config = findSwapChainConfig(flags, true, false); - } else { - config = mEGLConfig; - } - - if (UTILS_UNLIKELY(config == EGL_NO_CONFIG_KHR)) { - // error already logged - return nullptr; - } - Config attribs; - if (ext.egl.KHR_gl_colorspace) { if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) { attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR; @@ -484,16 +471,27 @@ Platform::SwapChain* PlatformEGL::createSwapChain( flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; } - EGLSurface sur = eglCreateWindowSurface(mEGLDisplay, config, - (EGLNativeWindowType)nativeWindow, attribs.data()); - - if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { - logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface"); - return nullptr; + EGLConfig config = EGL_NO_CONFIG_KHR; + if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) { + config = findSwapChainConfig(flags, true, false); + } else { + config = mEGLConfig; } - // this is not fatal - eglSurfaceAttrib(mEGLDisplay, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED); + EGLSurface sur = EGL_NO_SURFACE; + if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) { + sur = eglCreateWindowSurface(mEGLDisplay, config, + (EGLNativeWindowType)nativeWindow, attribs.data()); + + if (UTILS_LIKELY(sur != EGL_NO_SURFACE)) { + // this is not fatal + eglSurfaceAttrib(mEGLDisplay, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED); + } else { + logEglError("PlatformEGL::createSwapChain: eglCreateWindowSurface"); + } + } else { + // error already logged + } SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({ .sur = sur, @@ -508,18 +506,6 @@ Platform::SwapChain* PlatformEGL::createSwapChain( Platform::SwapChain* PlatformEGL::createSwapChain( uint32_t width, uint32_t height, uint64_t flags) noexcept { - EGLConfig config = EGL_NO_CONFIG_KHR; - if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) { - config = findSwapChainConfig(flags, false, true); - } else { - config = mEGLConfig; - } - - if (UTILS_UNLIKELY(config == EGL_NO_CONFIG_KHR)) { - // error already logged - return nullptr; - } - Config attribs = { { EGL_WIDTH, EGLint(width) }, { EGL_HEIGHT, EGLint(height) }, @@ -541,17 +527,28 @@ Platform::SwapChain* PlatformEGL::createSwapChain( flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT; } - EGLSurface sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data()); + EGLConfig config = EGL_NO_CONFIG_KHR; + if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) { + config = findSwapChainConfig(flags, true, false); + } else { + config = mEGLConfig; + } - if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { - logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface"); - return nullptr; + EGLSurface sur = EGL_NO_SURFACE; + if (UTILS_LIKELY(config != EGL_NO_CONFIG_KHR)) { + sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data()); + if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { + logEglError("PlatformEGL::createSwapChain: eglCreatePbufferSurface"); + } + } else { + // error already logged } SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({ .sur = sur, .attribs = std::move(attribs), - .config = config + .config = config, + .flags = flags }); return sc; } @@ -593,6 +590,9 @@ void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain, utils::Invocable preContextChange, utils::Invocable postContextChange) noexcept { + assert_invariant(drawSwapChain); + assert_invariant(readSwapChain); + ContextType type = ContextType::UNPROTECTED; if (ext.egl.EXT_protected_content) { bool const swapChainProtected = isSwapChainProtected(drawSwapChain); From 072562c571ea933d99b7911a619f3168398e73ec Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 21 Mar 2024 16:46:24 -0700 Subject: [PATCH 22/29] Add an option to disable use-after-free checks in the backend BUGS=330403836 --- .../filament-android/src/main/cpp/Engine.cpp | 4 ++- .../com/google/android/filament/Engine.java | 11 ++++++-- filament/backend/include/backend/Platform.h | 5 ++++ .../include/private/backend/HandleAllocator.h | 25 +++++++++++-------- filament/backend/src/HandleAllocator.cpp | 12 ++++++--- filament/backend/src/metal/MetalDriver.mm | 4 ++- filament/backend/src/opengl/OpenGLDriver.cpp | 4 ++- filament/backend/src/vulkan/VulkanDriver.cpp | 2 +- .../src/vulkan/VulkanResourceAllocator.h | 4 +-- filament/include/filament/Engine.h | 5 ++++ filament/src/details/Engine.cpp | 6 +++-- 11 files changed, 58 insertions(+), 24 deletions(-) diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 80409702c37..0cb2c34302c 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -487,7 +487,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu jlong jobSystemThreadCount, jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile, jint stereoscopicType, jlong stereoscopicEyeCount, - jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge) { + jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge, + jboolean disableHandleUseAfterFreeCheck) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; Engine::Config config = { .commandBufferSizeMB = (uint32_t) commandBufferSizeMB, @@ -502,6 +503,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu .stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount, .resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB, .resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge, + .disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck, }; builder->config(&config); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index aee4c2b34aa..bb754d644a9 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -224,7 +224,8 @@ public Builder config(Config config) { config.jobSystemThreadCount, config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile, config.stereoscopicType.ordinal(), config.stereoscopicEyeCount, - config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge); + config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge, + config.disableHandleUseAfterFreeCheck); return this; } @@ -408,6 +409,11 @@ public static class Config { * This value determines for how many frames are texture entries kept in the cache. */ public long resourceAllocatorCacheMaxAge = 2; + + /* + * Disable backend handles use-after-free checks. + */ + public boolean disableHandleUseAfterFreeCheck = false; } private Engine(long nativeEngine, Config config) { @@ -1283,7 +1289,8 @@ private static native void nSetBuilderConfig(long nativeBuilder, long commandBuf long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount, long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile, int stereoscopicType, long stereoscopicEyeCount, - long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge); + long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge, + boolean disableHandleUseAfterFreeCheck); private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal); private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext); private static native long nBuilderBuild(long nativeBuilder); diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index 1d85568a883..f1449795443 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -59,6 +59,11 @@ class UTILS_PUBLIC Platform { * Currently only honored by the GL and Metal backends. */ bool disableParallelShaderCompile = false; + + /** + * Disable backend handles use-after-free checks. + */ + bool disableHandleUseAfterFreeCheck = false; }; Platform() noexcept; diff --git a/filament/backend/include/private/backend/HandleAllocator.h b/filament/backend/include/private/backend/HandleAllocator.h index 53bab210777..578ce5d6f1a 100644 --- a/filament/backend/include/private/backend/HandleAllocator.h +++ b/filament/backend/include/private/backend/HandleAllocator.h @@ -49,7 +49,7 @@ namespace filament::backend { template class HandleAllocator { public: - HandleAllocator(const char* name, size_t size) noexcept; + HandleAllocator(const char* name, size_t size, bool disableUseAfterFreeCheck) noexcept; HandleAllocator(HandleAllocator const& rhs) = delete; HandleAllocator& operator=(HandleAllocator const& rhs) = delete; ~HandleAllocator(); @@ -169,11 +169,13 @@ class HandleAllocator { if (isPoolHandle(handle.getId())) { // check for use after free - uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT; - auto const pNode = static_cast(p); - uint8_t const expectedAge = pNode[-1].age; - ASSERT_POSTCONDITION(expectedAge == age, - "use-after-free of Handle with id=%d", handle.getId()); + if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) { + uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT; + auto const pNode = static_cast(p); + uint8_t const expectedAge = pNode[-1].age; + ASSERT_POSTCONDITION(expectedAge == age, + "use-after-free of Handle with id=%d", handle.getId()); + } } return static_cast(p); @@ -187,7 +189,6 @@ class HandleAllocator { return handle_cast(const_cast&>(handle)); } - private: template @@ -210,8 +211,9 @@ class HandleAllocator { Pool mPool1; Pool mPool2; UTILS_UNUSED_IN_RELEASE const utils::AreaPolicy::HeapArea& mArea; + bool mUseAfterFreeCheckDisabled; public: - explicit Allocator(const utils::AreaPolicy::HeapArea& area); + explicit Allocator(const utils::AreaPolicy::HeapArea& area, bool disableUseAfterFreeCheck); static constexpr size_t getAlignment() noexcept { return MIN_ALIGNMENT; } @@ -237,8 +239,10 @@ class HandleAllocator { // check for double-free Node* const pNode = static_cast(p); uint8_t& expectedAge = pNode[-1].age; - ASSERT_POSTCONDITION(expectedAge == age, - "double-free of Handle of size %d at %p", size, p); + if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) { + ASSERT_POSTCONDITION(expectedAge == age, + "double-free of Handle of size %d at %p", size, p); + } expectedAge = (expectedAge + 1) & 0xF; // fixme if (size <= mPool0.getSize()) { mPool0.free(p); return; } @@ -348,6 +352,7 @@ class HandleAllocator { mutable utils::Mutex mLock; tsl::robin_map mOverflowMap; HandleBase::HandleId mId = 0; + bool mUseAfterFreeCheckDisabled = false; }; } // namespace filament::backend diff --git a/filament/backend/src/HandleAllocator.cpp b/filament/backend/src/HandleAllocator.cpp index bf8e779614c..fb58cb1b588 100644 --- a/filament/backend/src/HandleAllocator.cpp +++ b/filament/backend/src/HandleAllocator.cpp @@ -39,8 +39,10 @@ using namespace utils; template UTILS_NOINLINE -HandleAllocator::Allocator::Allocator(AreaPolicy::HeapArea const& area) - : mArea(area) { +HandleAllocator::Allocator::Allocator(AreaPolicy::HeapArea const& area, + bool disableUseAfterFreeCheck) + : mArea(area), + mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) { // The largest handle this allocator can generate currently depends on the architecture's // min alignment, typically 8 or 16 bytes. @@ -74,8 +76,10 @@ HandleAllocator::Allocator::Allocator(AreaPolicy::HeapArea const& ar // ------------------------------------------------------------------------------------------------ template -HandleAllocator::HandleAllocator(const char* name, size_t size) noexcept - : mHandleArena(name, size) { +HandleAllocator::HandleAllocator(const char* name, size_t size, + bool disableUseAfterFreeCheck) noexcept + : mHandleArena(name, size, disableUseAfterFreeCheck), + mUseAfterFreeCheckDisabled(disableUseAfterFreeCheck) { } template diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 518df89f525..1a87b317717 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -98,7 +98,9 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept : mPlatform(*platform), mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)), - mHandleAllocator("Handles", driverConfig.handleArenaSize) { + mHandleAllocator("Handles", + driverConfig.handleArenaSize, + driverConfig.disableHandleUseAfterFreeCheck) { mContext->driver = this; mContext->device = mPlatform.createDevice(); diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 66ee6685fd0..b6be768f3db 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -205,7 +205,9 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi : mPlatform(*platform), mContext(mPlatform), mShaderCompilerService(*this), - mHandleAllocator("Handles", driverConfig.handleArenaSize), + mHandleAllocator("Handles", + driverConfig.handleArenaSize, + driverConfig.disableHandleUseAfterFreeCheck), mSamplerMap(32), mDriverConfig(driverConfig) { diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index b140f43369d..ce950fbf571 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -209,7 +209,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(), mPlatform->getDevice())), mContext(context), - mResourceAllocator(driverConfig.handleArenaSize), + mResourceAllocator(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck), mResourceManager(&mResourceAllocator), mThreadSafeResourceManager(&mResourceAllocator), mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(), diff --git a/filament/backend/src/vulkan/VulkanResourceAllocator.h b/filament/backend/src/vulkan/VulkanResourceAllocator.h index ac71f0ece09..8563946d484 100644 --- a/filament/backend/src/vulkan/VulkanResourceAllocator.h +++ b/filament/backend/src/vulkan/VulkanResourceAllocator.h @@ -51,8 +51,8 @@ namespace filament::backend { class VulkanResourceAllocator { public: - VulkanResourceAllocator(size_t arenaSize) - : mHandleAllocatorImpl("Handles", arenaSize) + VulkanResourceAllocator(size_t arenaSize, bool disableUseAfterFreeCheck) + : mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck) #if DEBUG_RESOURCE_LEAKS , mDebugOnlyResourceCount(RESOURCE_TYPE_COUNT) { std::memset(mDebugOnlyResourceCount.data(), 0, sizeof(size_t) * RESOURCE_TYPE_COUNT); diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 04d71259b7c..42a7551e577 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -335,6 +335,11 @@ class UTILS_PUBLIC Engine { * This value determines for how many frames are texture entries kept in the cache. */ uint32_t resourceAllocatorCacheMaxAge = 2; + + /* + * Disable backend handles use-after-free checks. + */ + bool disableHandleUseAfterFreeCheck = false; }; diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index 97c22b90c10..a535d5099f5 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -100,7 +100,8 @@ Engine* FEngine::create(Engine::Builder const& builder) { DriverConfig const driverConfig{ .handleArenaSize = instance->getRequestedDriverHandleArenaSize(), .textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize, - .disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile + .disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile, + .disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck }; instance->mDriver = platform->createDriver(sharedContext, driverConfig); @@ -663,7 +664,8 @@ int FEngine::loop() { DriverConfig const driverConfig { .handleArenaSize = getRequestedDriverHandleArenaSize(), .textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize, - .disableParallelShaderCompile = mConfig.disableParallelShaderCompile + .disableParallelShaderCompile = mConfig.disableParallelShaderCompile, + .disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck }; mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig); From dbf0cde330a37a739fcd6426e38b2756fbee9143 Mon Sep 17 00:00:00 2001 From: Ben Doherty Date: Fri, 22 Mar 2024 12:47:56 -0700 Subject: [PATCH 23/29] Metal: track buffer allocations (#7556) --- filament/backend/include/backend/Platform.h | 31 +++++++++++ filament/backend/src/Platform.cpp | 14 +++++ filament/backend/src/metal/MetalBuffer.h | 54 ++++++++++++++++--- filament/backend/src/metal/MetalBuffer.mm | 11 ++-- filament/backend/src/metal/MetalBufferPool.h | 4 +- filament/backend/src/metal/MetalBufferPool.mm | 4 +- filament/backend/src/metal/MetalDriver.mm | 5 ++ filament/backend/src/metal/MetalHandles.mm | 5 +- libs/filamentapp/src/FilamentApp.cpp | 1 + libs/utils/include/utils/ostream.h | 1 + libs/utils/include/utils/sstream.h | 1 + libs/utils/src/ostream.cpp | 4 ++ libs/utils/src/sstream.cpp | 4 ++ libs/utils/test/test_sstream.cpp | 13 +++++ 14 files changed, 136 insertions(+), 16 deletions(-) diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index f1449795443..af99d4e2be5 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -193,9 +193,40 @@ class UTILS_PUBLIC Platform { size_t retrieveBlob(const void* UTILS_NONNULL key, size_t keySize, void* UTILS_NONNULL value, size_t valueSize); + using DebugUpdateStatFunc = utils::Invocable; + + /** + * Sets the callback function that the backend can use to update backend-specific statistics + * to aid with debugging. This callback is guaranteed to be called on the Filament driver + * thread. + * + * @param debugUpdateStat an Invocable that updates debug statistics + */ + void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept; + + /** + * @return true if debugUpdateStat is valid. + */ + bool hasDebugUpdateStatFunc() const noexcept; + + /** + * To track backend-specific statistics, the backend implementation can call the + * application-provided callback function debugUpdateStatFunc to associate or update a value + * with a given key. It is possible for this function to be called multiple times with the + * same key, in which case newer values should overwrite older values. + * + * This function is guaranteed to be called only on a single thread, the Filament driver + * thread. + * + * @param key a null-terminated C-string with the key of the debug statistic + * @param value the updated value of key + */ + void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value); + private: InsertBlobFunc mInsertBlob; RetrieveBlobFunc mRetrieveBlob; + DebugUpdateStatFunc mDebugUpdateStat; }; } // namespace filament diff --git a/filament/backend/src/Platform.cpp b/filament/backend/src/Platform.cpp index db2fd0eafdf..77c85129417 100644 --- a/filament/backend/src/Platform.cpp +++ b/filament/backend/src/Platform.cpp @@ -53,4 +53,18 @@ size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size return 0; } +void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept { + mDebugUpdateStat = std::move(debugUpdateStat); +} + +bool Platform::hasDebugUpdateStatFunc() const noexcept { + return bool(mDebugUpdateStat); +} + +void Platform::debugUpdateStat(const char* key, uint64_t value) { + if (mDebugUpdateStat) { + mDebugUpdateStat(key, value); + } +} + } // namespace filament::backend diff --git a/filament/backend/src/metal/MetalBuffer.h b/filament/backend/src/metal/MetalBuffer.h index 4baccffc2ce..579975d0d6c 100644 --- a/filament/backend/src/metal/MetalBuffer.h +++ b/filament/backend/src/metal/MetalBuffer.h @@ -18,7 +18,6 @@ #define TNT_FILAMENT_DRIVER_METALBUFFER_H #include "MetalContext.h" -#include "MetalBufferPool.h" #include @@ -28,9 +27,50 @@ #include #include +#include namespace filament::backend { +class TrackedMetalBuffer { +public: + TrackedMetalBuffer() noexcept : mBuffer(nil) {} + TrackedMetalBuffer(id buffer) noexcept : mBuffer(buffer) { + if (buffer) { + aliveBuffers++; + } + } + ~TrackedMetalBuffer() { + if (mBuffer) { + aliveBuffers--; + } + } + + TrackedMetalBuffer(TrackedMetalBuffer&&) = delete; + TrackedMetalBuffer(TrackedMetalBuffer const&) = delete; + TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete; + + TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept { + swap(rhs); + return *this; + } + + id get() const noexcept { return mBuffer; } + operator bool() const noexcept { return bool(mBuffer); } + + static uint64_t getAliveBuffers() { return aliveBuffers; } + +private: + void swap(TrackedMetalBuffer& other) noexcept { + id temp = mBuffer; + mBuffer = other.mBuffer; + other.mBuffer = temp; + } + + id mBuffer; + + static std::atomic aliveBuffers; +}; + class MetalBuffer { public: @@ -82,7 +122,7 @@ class MetalBuffer { private: - id mBuffer = nil; + TrackedMetalBuffer mBuffer; size_t mBufferSize = 0; void* mCpuBuffer = nullptr; MetalContext& mContext; @@ -151,7 +191,7 @@ class MetalRingBuffer { // finishes executing. mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions]; assert_invariant(mAuxBuffer); - return {mAuxBuffer, 0}; + return {mAuxBuffer.get(), 0}; } mCurrentSlot = (mCurrentSlot + 1) % mSlotCount; mOccupiedSlots->fetch_add(1, std::memory_order_relaxed); @@ -180,9 +220,9 @@ class MetalRingBuffer { */ std::pair, NSUInteger> getCurrentAllocation() const { if (UTILS_UNLIKELY(mAuxBuffer)) { - return { mAuxBuffer, 0 }; + return { mAuxBuffer.get(), 0 }; } - return { mBuffer, mCurrentSlot * mSlotSizeBytes }; + return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes }; } bool canAccomodateLayout(MTLSizeAndAlign layout) const { @@ -191,8 +231,8 @@ class MetalRingBuffer { private: id mDevice; - id mBuffer; - id mAuxBuffer; + TrackedMetalBuffer mBuffer; + TrackedMetalBuffer mAuxBuffer; MTLResourceOptions mBufferOptions; diff --git a/filament/backend/src/metal/MetalBuffer.mm b/filament/backend/src/metal/MetalBuffer.mm index 4b04e4d5c84..af46027e20d 100644 --- a/filament/backend/src/metal/MetalBuffer.mm +++ b/filament/backend/src/metal/MetalBuffer.mm @@ -15,12 +15,15 @@ */ #include "MetalBuffer.h" +#include "MetalBufferPool.h" #include "MetalContext.h" namespace filament { namespace backend { +std::atomic TrackedMetalBuffer::aliveBuffers = 0; + MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage, size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) { // If the buffer is less than 4K in size and is updated frequently, we don't use an explicit @@ -61,7 +64,7 @@ // Acquire a staging buffer to hold the contents of this update. MetalBufferPool* bufferPool = mContext.bufferPool; const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size); - memcpy(staging->buffer.contents, src, size); + memcpy(staging->buffer.get().contents, src, size); // The blit below requires that byteOffset be a multiple of 4. ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4"); @@ -70,9 +73,9 @@ id cmdBuffer = getPendingCommandBuffer(&mContext); id blitEncoder = [cmdBuffer blitCommandEncoder]; blitEncoder.label = @"Buffer upload blit"; - [blitEncoder copyFromBuffer:staging->buffer + [blitEncoder copyFromBuffer:staging->buffer.get() sourceOffset:0 - toBuffer:mBuffer + toBuffer:mBuffer.get() destinationOffset:byteOffset size:size]; [blitEncoder endEncoding]; @@ -93,7 +96,7 @@ return nil; } assert_invariant(mBuffer); - return mBuffer; + return mBuffer.get(); } void MetalBuffer::bindBuffers(id cmdBuffer, id encoder, diff --git a/filament/backend/src/metal/MetalBufferPool.h b/filament/backend/src/metal/MetalBufferPool.h index 68e056ed404..03688ab3c43 100644 --- a/filament/backend/src/metal/MetalBufferPool.h +++ b/filament/backend/src/metal/MetalBufferPool.h @@ -19,6 +19,8 @@ #include +#include "MetalBuffer.h" + #include #include #include @@ -30,7 +32,7 @@ struct MetalContext; // Immutable POD representing a shared CPU-GPU buffer. struct MetalBufferPoolEntry { - id buffer; + TrackedMetalBuffer buffer; size_t capacity; mutable uint64_t lastAccessed; mutable uint32_t referenceCount; diff --git a/filament/backend/src/metal/MetalBufferPool.mm b/filament/backend/src/metal/MetalBufferPool.mm index 29915d4aacb..3b75c8e85d4 100644 --- a/filament/backend/src/metal/MetalBufferPool.mm +++ b/filament/backend/src/metal/MetalBufferPool.mm @@ -45,12 +45,12 @@ id buffer = [mContext.device newBufferWithLength:numBytes options:MTLResourceStorageModeShared]; ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes); - MetalBufferPoolEntry* stage = new MetalBufferPoolEntry({ + MetalBufferPoolEntry* stage = new MetalBufferPoolEntry { .buffer = buffer, .capacity = numBytes, .lastAccessed = mCurrentFrame, .referenceCount = 1 - }); + }; mUsedStages.insert(stage); return stage; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 1a87b317717..f0e23ccb033 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -20,6 +20,7 @@ #include "metal/MetalDriver.h" #include "MetalBlitter.h" +#include "MetalBufferPool.h" #include "MetalContext.h" #include "MetalDriverFactory.h" #include "MetalEnums.h" @@ -36,6 +37,7 @@ #include #include +#include #include @@ -214,6 +216,9 @@ #if defined(FILAMENT_METAL_PROFILING) os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId); #endif + if (mPlatform.hasDebugUpdateStatFunc()) { + mPlatform.debugUpdateStat("filament.metal.alive_buffers", TrackedMetalBuffer::getAliveBuffers()); + } } void MetalDriver::setFrameScheduledCallback(Handle sch, diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 18d83118253..0d9976211da 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -19,6 +19,7 @@ #include "MetalBlitter.h" #include "MetalEnums.h" #include "MetalUtils.h" +#include "MetalBufferPool.h" #include @@ -770,13 +771,13 @@ void presentDrawable(bool presentFrame, void* user) { PixelBufferDescriptor const& data, const PixelBufferShape& shape) { const size_t stagingBufferSize = shape.totalBytes; auto entry = context.bufferPool->acquireBuffer(stagingBufferSize); - memcpy(entry->buffer.contents, + memcpy(entry->buffer.get().contents, static_cast(data.buffer) + shape.sourceOffset, stagingBufferSize); id blitCommandBuffer = getPendingCommandBuffer(&context); id blitCommandEncoder = [blitCommandBuffer blitCommandEncoder]; blitCommandEncoder.label = @"Texture upload buffer blit"; - [blitCommandEncoder copyFromBuffer:entry->buffer + [blitCommandEncoder copyFromBuffer:entry->buffer.get() sourceOffset:0 sourceBytesPerRow:shape.bytesPerRow sourceBytesPerImage:shape.bytesPerSlice diff --git a/libs/filamentapp/src/FilamentApp.cpp b/libs/filamentapp/src/FilamentApp.cpp index 25f7126f834..82eba7a2316 100644 --- a/libs/filamentapp/src/FilamentApp.cpp +++ b/libs/filamentapp/src/FilamentApp.cpp @@ -674,6 +674,7 @@ FilamentApp::Window::Window(FilamentApp* filamentApp, mSwapChain = mFilamentApp->mEngine->createSwapChain( nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); } + mRenderer = mFilamentApp->mEngine->createRenderer(); // create cameras diff --git a/libs/utils/include/utils/ostream.h b/libs/utils/include/utils/ostream.h index 623cf9c08e2..cde8e75bd27 100644 --- a/libs/utils/include/utils/ostream.h +++ b/libs/utils/include/utils/ostream.h @@ -92,6 +92,7 @@ class UTILS_PUBLIC ostream : protected utils::PrivateImplementation { std::pair grow(size_t s) noexcept; void advance(ssize_t n) noexcept; void reset() noexcept; + size_t length() const noexcept; private: void reserve(size_t newSize) noexcept; diff --git a/libs/utils/include/utils/sstream.h b/libs/utils/include/utils/sstream.h index 8b0c4b4a2a6..605b678e6d3 100644 --- a/libs/utils/include/utils/sstream.h +++ b/libs/utils/include/utils/sstream.h @@ -25,6 +25,7 @@ class sstream : public ostream { public: ostream& flush() noexcept override; const char* c_str() const noexcept; + size_t length() const noexcept; }; } // namespace utils::io diff --git a/libs/utils/src/ostream.cpp b/libs/utils/src/ostream.cpp index 579dc9e36e9..35ffab84347 100644 --- a/libs/utils/src/ostream.cpp +++ b/libs/utils/src/ostream.cpp @@ -267,6 +267,10 @@ void ostream::Buffer::reset() noexcept { size = capacity; } +size_t ostream::Buffer::length() const noexcept { + return curr - buffer; +} + std::pair ostream::Buffer::grow(size_t s) noexcept { if (UTILS_UNLIKELY(size < s)) { size_t const used = curr - buffer; diff --git a/libs/utils/src/sstream.cpp b/libs/utils/src/sstream.cpp index 1c02fd862bb..a3cc80a3a75 100644 --- a/libs/utils/src/sstream.cpp +++ b/libs/utils/src/sstream.cpp @@ -28,4 +28,8 @@ const char* sstream::c_str() const noexcept { return buffer ? buffer : ""; } +size_t sstream::length() const noexcept { + return getBuffer().length(); +} + } // namespace utils::io diff --git a/libs/utils/test/test_sstream.cpp b/libs/utils/test/test_sstream.cpp index fbac6242790..9a832faf2ed 100644 --- a/libs/utils/test/test_sstream.cpp +++ b/libs/utils/test/test_sstream.cpp @@ -144,6 +144,7 @@ TEST(sstream, LargeBuffer) { } EXPECT_EQ(1024 * 1024 * 16, strlen(ss.c_str())); + EXPECT_EQ(1024 * 1024 * 16, ss.length()); } TEST(sstream, LargeString) { @@ -158,6 +159,7 @@ TEST(sstream, LargeString) { ss << filler; EXPECT_EQ(size, strlen(ss.c_str())); + EXPECT_EQ(size, ss.length()); EXPECT_STREQ(filler, ss.c_str()); free(filler); @@ -182,7 +184,18 @@ TEST(sstream, SeveralStrings) { ss << fillerB; EXPECT_EQ(sizeA + sizeB, strlen(ss.c_str())); + EXPECT_EQ(sizeA + sizeB, ss.length()); free(fillerA); free(fillerB); } + +TEST(sstream, length) { + sstream ss; + + EXPECT_EQ(0, ss.length()); + ss << "Hello, world\n"; + EXPECT_EQ(13, ss.length()); + ss << "Foo bar\n"; + EXPECT_EQ(13 + 8, ss.length()); +} From b3ec8b188e3a5bffe3d1121f3f8141ae4c6f8ddb Mon Sep 17 00:00:00 2001 From: Sungun Park Date: Mon, 25 Mar 2024 14:43:04 -0700 Subject: [PATCH 24/29] Add FILAMENT_ENABLE_MULTIVIEW option (#7707) This allows the engine to include multiview shader code for default materials. --- CMakeLists.txt | 7 +++++++ filament/CMakeLists.txt | 7 ++++++- filament/src/details/Engine.cpp | 4 ++++ filament/src/details/Skybox.cpp | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b18c4a5b4ed..ed32949873a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ option(FILAMENT_ENABLE_TSAN "Enable Thread Sanitizer" OFF) option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON) +option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF) + set(FILAMENT_NDK_VERSION "" CACHE STRING "Android NDK version or version prefix to be used when building for Android." ) @@ -541,6 +543,11 @@ if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced" AND NOT FILAMENT_SAMPL message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\" or \"multiview\" ") endif () +# Compiling samples for multiview implies enabling multiview feature as well. +if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview") + set(FILAMENT_ENABLE_MULTIVIEW ON) +endif () + # ================================================================================================== # Material compilation flags # ================================================================================================== diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index 66b78307cc8..b978d2da1bd 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -290,6 +290,11 @@ if (FILAMENT_ENABLE_FEATURE_LEVEL_0) add_definitions(-DFILAMENT_ENABLE_FEATURE_LEVEL_0) endif() +# Whether to include MULTIVIEW materials. +if (FILAMENT_ENABLE_MULTIVIEW) + add_definitions(-DFILAMENT_ENABLE_MULTIVIEW) +endif() + # ================================================================================================== # Definitions # ================================================================================================== @@ -343,7 +348,7 @@ foreach (mat_src ${MATERIAL_SRCS}) endif () list(FIND MATERIAL_MULTIVIEW_SRCS ${mat_src} index) - if (${index} GREATER -1) + if (${index} GREATER -1 AND FILAMENT_ENABLE_MULTIVIEW) string(REGEX REPLACE "[.]filamat$" "_multiview.filamat" output_path_multiview ${output_path}) add_custom_command( OUTPUT ${output_path_multiview} diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index a535d5099f5..b839c0aac0a 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -354,8 +354,12 @@ void FEngine::init() { MATERIALS_DEFAULTMATERIAL_DATA, MATERIALS_DEFAULTMATERIAL_SIZE); break; case StereoscopicType::MULTIVIEW: +#ifdef FILAMENT_ENABLE_MULTIVIEW defaultMaterialBuilder.package( MATERIALS_DEFAULTMATERIAL_MULTIVIEW_DATA, MATERIALS_DEFAULTMATERIAL_MULTIVIEW_SIZE); +#else + assert_invariant(false); +#endif break; } mDefaultMaterial = downcast(defaultMaterialBuilder.build(*const_cast(this))); diff --git a/filament/src/details/Skybox.cpp b/filament/src/details/Skybox.cpp index 7fe6291bc8c..39aca8ef8ca 100644 --- a/filament/src/details/Skybox.cpp +++ b/filament/src/details/Skybox.cpp @@ -129,7 +129,11 @@ FMaterial const* FSkybox::createMaterial(FEngine& engine) { builder.package(MATERIALS_SKYBOX_DATA, MATERIALS_SKYBOX_SIZE); break; case Engine::StereoscopicType::MULTIVIEW: +#ifdef FILAMENT_ENABLE_MULTIVIEW builder.package(MATERIALS_SKYBOX_MULTIVIEW_DATA, MATERIALS_SKYBOX_MULTIVIEW_SIZE); +#else + assert_invariant(false); +#endif break; } } From 61155644d56a6ee17aef3665b4fc3e1566d6176f Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Mon, 25 Mar 2024 22:13:40 +0000 Subject: [PATCH 25/29] gl: v1.51.1 local fix for merge mistake --- filament/backend/src/opengl/OpenGLDriver.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index b6be768f3db..e4668e33306 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1463,9 +1463,6 @@ void OpenGLDriver::createSwapChainR(Handle sch, void* nativeWindow, GLSwapChain* sc = handle_cast(sch); sc->swapChain = mPlatform.createSwapChain(nativeWindow, flags); - ASSERT_POSTCONDITION(sc->swapChain, - "createSwapChain(%p, 0x%lx) failed. See logs for details.", - nativeWindow, flags); #if !defined(__EMSCRIPTEN__) // note: in practice this should never happen on Android @@ -1487,9 +1484,6 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, GLSwapChain* sc = handle_cast(sch); sc->swapChain = mPlatform.createSwapChain(width, height, flags); - ASSERT_POSTCONDITION(sc->swapChain, - "createSwapChainHeadless(%u, %u, 0x%lx) failed. See logs for details.", - width, height, flags); #if !defined(__EMSCRIPTEN__) // note: in practice this should never happen on Android From 1801def1ee580378c009eb9cb36f2ed2bbbc47ff Mon Sep 17 00:00:00 2001 From: Eliza Velasquez Date: Mon, 11 Mar 2024 14:08:42 -0700 Subject: [PATCH 26/29] Allow rendering thread to pause This PR adds a new `pause()` option to the `Engine` `Builder` and a new function `setPaused()` to the `Engine`. While paused, the rendering thread will pause indefinitely for commands as if none are available. As soon as the rendering thread is unpaused, the commands are immediately executed. --- NEW_RELEASE_NOTES.md | 2 ++ .../filament-android/src/main/cpp/Engine.cpp | 13 ++++++++++++ .../com/google/android/filament/Engine.java | 21 +++++++++++++++++++ .../private/backend/CommandBufferQueue.h | 6 +++++- filament/backend/src/CommandBufferQueue.cpp | 16 +++++++++++--- filament/backend/test/BackendTest.cpp | 2 +- filament/backend/test/ComputeTest.cpp | 3 ++- filament/include/filament/Engine.h | 13 ++++++++++++ filament/src/Engine.cpp | 5 +++++ filament/src/details/Engine.cpp | 13 +++++++++++- filament/src/details/Engine.h | 2 ++ 11 files changed, 89 insertions(+), 7 deletions(-) diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 4a1a9c7fa7e..77a7acc5f46 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 + +- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()` diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 0cb2c34302c..2677e2f8fa2 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -391,6 +391,13 @@ Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass, engine->flush(); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Engine_nSetPaused(JNIEnv*, jclass, + jlong nativeEngine, jboolean paused) { + Engine* engine = (Engine*) nativeEngine; + engine->setPaused(paused); +} + // Managers... extern "C" JNIEXPORT jlong JNICALL @@ -520,6 +527,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu builder->sharedContext((void*) sharedContext); } +extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderPaused( + JNIEnv*, jclass, jlong nativeBuilder, jboolean paused) { + Engine::Builder* builder = (Engine::Builder*) nativeBuilder; + builder->paused((bool) paused); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder) { Engine::Builder* builder = (Engine::Builder*) nativeBuilder; 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 bb754d644a9..68f10986548 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 @@ -240,6 +240,18 @@ public Builder featureLevel(FeatureLevel featureLevel) { return this; } + /** + * Sets the initial paused state of the rendering thread. + * + * @param paused Whether to start the rendering thread paused. + * @return A reference to this Builder for chaining calls. + * @warning Experimental. + */ + public Builder paused(boolean paused) { + nSetBuilderPaused(mNativeBuilder, paused); + return this; + } + /** * Creates an instance of Engine * @@ -1195,6 +1207,13 @@ public void flush() { nFlush(getNativeObject()); } + /** + * Pause or resume the rendering thread. + * @warning Experimental. + */ + public void setPaused(boolean paused) { + nSetPaused(getNativeObject(), paused); + } @UsedByReflection("TextureHelper.java") public long getNativeObject() { @@ -1269,6 +1288,7 @@ private static void assertDestroy(boolean success) { private static native void nDestroyEntity(long nativeEngine, int entity); private static native void nFlushAndWait(long nativeEngine); private static native void nFlush(long nativeEngine); + private static native void nSetPaused(long nativeEngine, boolean paused); private static native long nGetTransformManager(long nativeEngine); private static native long nGetLightManager(long nativeEngine); private static native long nGetRenderableManager(long nativeEngine); @@ -1293,5 +1313,6 @@ private static native void nSetBuilderConfig(long nativeBuilder, long commandBuf boolean disableHandleUseAfterFreeCheck); private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal); private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext); + private static native void nSetBuilderPaused(long nativeBuilder, boolean paused); private static native long nBuilderBuild(long nativeBuilder); } diff --git a/filament/backend/include/private/backend/CommandBufferQueue.h b/filament/backend/include/private/backend/CommandBufferQueue.h index 28122452386..92bf7e1488c 100644 --- a/filament/backend/include/private/backend/CommandBufferQueue.h +++ b/filament/backend/include/private/backend/CommandBufferQueue.h @@ -50,12 +50,13 @@ class CommandBufferQueue { size_t mFreeSpace = 0; size_t mHighWatermark = 0; uint32_t mExitRequested = 0; + bool mPaused = false; static constexpr uint32_t EXIT_REQUESTED = 0x31415926; public: // requiredSize: guaranteed available space after flush() - CommandBufferQueue(size_t requiredSize, size_t bufferSize); + CommandBufferQueue(size_t requiredSize, size_t bufferSize, bool paused); ~CommandBufferQueue(); CircularBuffer& getCircularBuffer() noexcept { return mCircularBuffer; } @@ -80,6 +81,9 @@ class CommandBufferQueue { // returns from waitForCommands() immediately. void requestExit(); + // suspend or unsuspend the queue. + void setPaused(bool paused); + bool isExitRequested() const; }; diff --git a/filament/backend/src/CommandBufferQueue.cpp b/filament/backend/src/CommandBufferQueue.cpp index e3e5de045c8..9de07ed0e27 100644 --- a/filament/backend/src/CommandBufferQueue.cpp +++ b/filament/backend/src/CommandBufferQueue.cpp @@ -39,10 +39,11 @@ using namespace utils; namespace filament::backend { -CommandBufferQueue::CommandBufferQueue(size_t requiredSize, size_t bufferSize) +CommandBufferQueue::CommandBufferQueue(size_t requiredSize, size_t bufferSize, bool paused) : mRequiredSize((requiredSize + (CircularBuffer::getBlockSize() - 1u)) & ~(CircularBuffer::getBlockSize() -1u)), mCircularBuffer(bufferSize), - mFreeSpace(mCircularBuffer.size()) { + mFreeSpace(mCircularBuffer.size()), + mPaused(paused) { assert_invariant(mCircularBuffer.size() > requiredSize); } @@ -56,6 +57,15 @@ void CommandBufferQueue::requestExit() { mCondition.notify_one(); } +void CommandBufferQueue::setPaused(bool paused) { + if (paused) { + mPaused = true; + } else { + mPaused = false; + mCondition.notify_one(); + } +} + bool CommandBufferQueue::isExitRequested() const { std::lock_guard const lock(mLock); ASSERT_PRECONDITION( mExitRequested == 0 || mExitRequested == EXIT_REQUESTED, @@ -127,7 +137,7 @@ std::vector CommandBufferQueue::waitForCommands() con return std::move(mCommandBuffersToExecute); } std::unique_lock lock(mLock); - while (mCommandBuffersToExecute.empty() && !mExitRequested) { + while ((mCommandBuffersToExecute.empty() || mPaused) && !mExitRequested) { mCondition.wait(lock); } diff --git a/filament/backend/test/BackendTest.cpp b/filament/backend/test/BackendTest.cpp index 0730236344e..5bb694c974b 100644 --- a/filament/backend/test/BackendTest.cpp +++ b/filament/backend/test/BackendTest.cpp @@ -51,7 +51,7 @@ void BackendTest::init(Backend backend, bool isMobilePlatform) { } BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, - CONFIG_COMMAND_BUFFERS_SIZE) { + CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) { initializeDriver(); } diff --git a/filament/backend/test/ComputeTest.cpp b/filament/backend/test/ComputeTest.cpp index 6ec640dbedd..5808c3b1825 100644 --- a/filament/backend/test/ComputeTest.cpp +++ b/filament/backend/test/ComputeTest.cpp @@ -46,7 +46,8 @@ void ComputeTest::init(Backend backend) { } ComputeTest::ComputeTest() - : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, CONFIG_COMMAND_BUFFERS_SIZE) { + : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, CONFIG_COMMAND_BUFFERS_SIZE, + /*paused=*/false) { } ComputeTest::~ComputeTest() = default; diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 42a7551e577..da936f9b453 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -407,6 +407,13 @@ class UTILS_PUBLIC Engine { */ Builder& featureLevel(FeatureLevel featureLevel) noexcept; + /** + * @param paused Whether to start the rendering thread paused. + * @return A reference to this Builder for chaining calls. + * @warning Experimental. + */ + Builder& paused(bool paused) noexcept; + #if UTILS_HAS_THREADING /** * Creates the filament Engine asynchronously. @@ -832,6 +839,12 @@ class UTILS_PUBLIC Engine { */ void flush(); + /** + * Pause or resume rendering thread. + * @warning Experimental. + */ + void setPaused(bool paused); + /** * Drains the user callback message queue and immediately execute all pending callbacks. * diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 380b7e55ade..ffb85ae70ad 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -295,6 +295,11 @@ utils::JobSystem& Engine::getJobSystem() noexcept { return downcast(this)->getJobSystem(); } +void Engine::setPaused(bool paused) { + ASSERT_PRECONDITION(UTILS_HAS_THREADING, "Pause is meant for multi-threaded platforms."); + downcast(this)->setPaused(paused); +} + DebugRegistry& Engine::getDebugRegistry() noexcept { return downcast(this)->getDebugRegistry(); } diff --git a/filament/src/details/Engine.cpp b/filament/src/details/Engine.cpp index b839c0aac0a..5f7396fb3d5 100644 --- a/filament/src/details/Engine.cpp +++ b/filament/src/details/Engine.cpp @@ -69,6 +69,7 @@ struct Engine::BuilderDetails { Engine::Config mConfig; FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1; void* mSharedContext = nullptr; + bool mPaused = false; static Config validateConfig(const Config* pConfig) noexcept; }; @@ -201,7 +202,8 @@ FEngine::FEngine(Engine::Builder const& builder) : mCameraManager(*this), mCommandBufferQueue( builder->mConfig.minCommandBufferSizeMB * MiB, - builder->mConfig.commandBufferSizeMB * MiB), + builder->mConfig.commandBufferSizeMB * MiB, + builder->mPaused), mPerRenderPassArena( "FEngine::mPerRenderPassAllocator", builder->mConfig.perRenderPassArenaSizeMB * MiB), @@ -1200,6 +1202,10 @@ void FEngine::destroy(FEngine* engine) { } } +void FEngine::setPaused(bool paused) { + mCommandBufferQueue.setPaused(paused); +} + Engine::FeatureLevel FEngine::getSupportedFeatureLevel() const noexcept { FEngine::DriverApi& driver = const_cast(this)->getDriverApi(); return driver.getFeatureLevel(); @@ -1253,6 +1259,11 @@ Engine::Builder& Engine::Builder::sharedContext(void* sharedContext) noexcept { return *this; } +Engine::Builder& Engine::Builder::paused(bool paused) noexcept { + mImpl->mPaused = paused; + return *this; +} + #if UTILS_HAS_THREADING void Engine::Builder::build(Invocable&& callback) const { diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 002e4f25f97..ead937e644d 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -340,6 +340,8 @@ class FEngine : public Engine { void destroy(utils::Entity e); + void setPaused(bool paused); + void flushAndWait(); // flush the current buffer From 0efd94a76996e9355eefa912d5df289b2522fa8c Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 26 Mar 2024 12:51:57 -0700 Subject: [PATCH 27/29] Add missing include to Platform.h (#7709) --- filament/backend/include/backend/Platform.h | 1 + 1 file changed, 1 insertion(+) diff --git a/filament/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index af99d4e2be5..03026dffe6e 100644 --- a/filament/backend/include/backend/Platform.h +++ b/filament/backend/include/backend/Platform.h @@ -23,6 +23,7 @@ #include #include +#include namespace filament::backend { From 66ec81187d3b11a4e269907bd585e44898488c03 Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 26 Mar 2024 13:35:50 -0700 Subject: [PATCH 28/29] vk: delete instead of ref-count EmptyTexture (#7711) --- filament/backend/src/vulkan/VulkanDriver.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index ce950fbf571..d91eeb84b4d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -249,9 +249,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, mStagePool); - // Use resource manager to ref-count placeholder resources. - mResourceManager.acquire(mEmptyTexture); - mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView()); } @@ -317,6 +314,7 @@ void VulkanDriver::terminate() { // are about to be destroyed. mCommands.terminate(); + delete mEmptyTexture; mResourceManager.clear(); mTimestamps.reset(); From 3ed008c0b6e6bff462226441ad34d0f5e2ddd9ef Mon Sep 17 00:00:00 2001 From: Powei Feng Date: Tue, 26 Mar 2024 14:37:13 -0700 Subject: [PATCH 29/29] Bump material version to 51 in MaterialEnums.h --- libs/filabridge/include/filament/MaterialEnums.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/filabridge/include/filament/MaterialEnums.h b/libs/filabridge/include/filament/MaterialEnums.h index 4c4d3da332b..ebe2ccce20e 100644 --- a/libs/filabridge/include/filament/MaterialEnums.h +++ b/libs/filabridge/include/filament/MaterialEnums.h @@ -28,7 +28,7 @@ namespace filament { // update this when a new version of filament wouldn't work with older materials -static constexpr size_t MATERIAL_VERSION = 50; +static constexpr size_t MATERIAL_VERSION = 51; /** * Supported shading models