diff --git a/CMakeLists.txt b/CMakeLists.txt index 91ab2922d83..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." ) @@ -531,6 +533,21 @@ 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 () + +# 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/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/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/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/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index 80409702c37..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 @@ -487,7 +494,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 +510,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); } @@ -518,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 aee4c2b34aa..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 @@ -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; } @@ -239,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 * @@ -408,6 +421,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) { @@ -1189,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() { @@ -1263,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); @@ -1283,8 +1309,10 @@ 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 void nSetBuilderPaused(long nativeBuilder, boolean paused); private static native long nBuilderBuild(long nativeBuilder); } diff --git a/android/gltfio-android/CMakeLists.txt b/android/gltfio-android/CMakeLists.txt index ffac6b8c610..0acc49350ef 100644 --- a/android/gltfio-android/CMakeLists.txt +++ b/android/gltfio-android/CMakeLists.txt @@ -81,9 +81,17 @@ 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 + ${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/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/filament/CMakeLists.txt b/filament/CMakeLists.txt index 25318ec0fae..b978d2da1bd 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}) @@ -285,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 # ================================================================================================== @@ -315,30 +325,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 AND FILAMENT_ENABLE_MULTIVIEW) + 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/backend/include/backend/Platform.h b/filament/backend/include/backend/Platform.h index 1d85568a883..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 { @@ -59,6 +60,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; @@ -188,9 +194,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/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/backend/platforms/OpenGLPlatform.h b/filament/backend/include/backend/platforms/OpenGLPlatform.h index c89684aa708..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; /** @@ -125,30 +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. - * @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 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/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/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/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/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/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/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 8dfbb27c51b..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 @@ -98,7 +100,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(); @@ -212,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, @@ -376,7 +383,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/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/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 2e6193744a1..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 @@ -49,7 +63,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 +285,9 @@ void OpenGLContext::synchronizeStateAndCache(size_t index) noexcept { } } + // the default FBO could be invalid + mDefaultFbo[index].reset(); + contextIndex = index; resetState(); } @@ -751,6 +769,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 its actual name + auto& defaultFboForThisContext = mDefaultFbo[contextIndex]; + if (UTILS_UNLIKELY(!defaultFboForThisContext.has_value())) { + defaultFboForThisContext = GLuint(mPlatform.getDefaultFramebufferObject()); + } + 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..5cabf57f97b 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -17,24 +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; @@ -82,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 ------------------------------------------------------------------------ @@ -148,7 +156,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 +469,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 +530,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 +744,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 cb6958a253a..e4668e33306 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 @@ -172,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) { @@ -730,23 +765,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 +793,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 +864,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 +895,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 +936,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; @@ -956,7 +991,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 +1129,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: @@ -1257,11 +1303,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; @@ -1272,6 +1316,7 @@ void OpenGLDriver::createRenderTargetR(Handle rth, uint32_t width, uint32_t height, uint8_t samples, + uint8_t layerCount, MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { @@ -1338,7 +1383,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); } @@ -1360,7 +1405,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); } @@ -1371,13 +1416,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); } } @@ -1419,6 +1464,13 @@ void OpenGLDriver::createSwapChainR(Handle sch, void* nativeWindow, GLSwapChain* sc = handle_cast(sch); sc->swapChain = mPlatform.createSwapChain(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 && @@ -1433,6 +1485,13 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle sch, GLSwapChain* sc = handle_cast(sch); sc->swapChain = mPlatform.createSwapChain(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 && @@ -1575,11 +1634,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 @@ -2478,7 +2537,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); @@ -2579,7 +2638,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); @@ -2732,7 +2791,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); @@ -2760,13 +2819,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()); } @@ -2854,9 +2913,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()); } @@ -2918,50 +2977,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; } @@ -3681,8 +3738,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..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; @@ -302,7 +314,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; @@ -357,8 +369,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; 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/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 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/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 e20dcdc500a..b041473839f 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,30 +451,10 @@ 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 { - 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)) { - 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; @@ -508,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("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, @@ -532,17 +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)) { - return nullptr; - } - Config attribs = { { EGL_WIDTH, EGLint(width) }, { EGL_HEIGHT, EGLint(height) }, @@ -564,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("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; } @@ -583,29 +557,45 @@ 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; + + assert_invariant(drawSwapChain); + assert_invariant(readSwapChain); + + 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)) { @@ -622,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"); } @@ -746,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; @@ -782,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/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..d91eeb84b4d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -209,10 +209,16 @@ 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(), + 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,13 @@ 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.reset(createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), - mContext, mAllocator, mCommands.get(), mStagePool)); + mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(), + mContext, mAllocator, &mCommands, mStagePool); mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView()); - mBlitter.initialize(mPlatform->getPhysicalDevice(), mPlatform->getDevice(), mAllocator, - mCommands.get()); } VulkanDriver::~VulkanDriver() noexcept = default; @@ -316,8 +312,11 @@ 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(); + + delete mEmptyTexture; + mResourceManager.clear(); + mTimestamps.reset(); mBlitter.terminate(); @@ -346,7 +345,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 +357,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 +385,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 +393,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 +401,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 +499,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 +511,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); } @@ -555,7 +554,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; @@ -607,7 +606,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 +624,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 +635,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 +648,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 +993,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 +1003,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 +1014,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 +1179,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 +1306,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 +1395,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 +1448,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 +1529,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 +1544,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 +1557,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 +1714,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 +1813,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 +1860,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 +1889,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 +1924,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 +1952,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/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/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. 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/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/include/filament/Engine.h b/filament/include/filament/Engine.h index 04d71259b7c..da936f9b453 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; }; @@ -402,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. @@ -827,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/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; } 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 a26b9b7b53e..8c63be153eb 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"); @@ -124,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) { @@ -150,7 +153,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 +185,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/Engine.cpp b/filament/src/details/Engine.cpp index dcf1521055a..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; }; @@ -100,7 +101,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); @@ -200,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), @@ -339,7 +342,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 +350,20 @@ 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: +#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))); float3 dummyPositions[1] = {}; @@ -655,7 +670,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); @@ -1186,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(); @@ -1239,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 9946edbb701..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 @@ -588,6 +590,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/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/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 41b43450f43..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(); @@ -233,8 +235,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) { @@ -521,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; @@ -537,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; @@ -701,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); @@ -978,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) { @@ -991,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); @@ -1009,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. @@ -1153,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/filament/src/details/Skybox.cpp b/filament/src/details/Skybox.cpp index bfdb94b3005..39aca8ef8ca 100644 --- a/filament/src/details/Skybox.cpp +++ b/filament/src/details/Skybox.cpp @@ -120,11 +120,22 @@ 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: +#ifdef FILAMENT_ENABLE_MULTIVIEW + builder.package(MATERIALS_SKYBOX_MULTIVIEW_DATA, MATERIALS_SKYBOX_MULTIVIEW_SIZE); +#else + assert_invariant(false); +#endif + break; + } } auto material = builder.build(engine); return downcast(material); 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 { 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/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::vectormEngine->createSwapChain( nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); } + mRenderer = mFilamentApp->mEngine->createRenderer(); // create cameras 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); diff --git a/libs/gltfio/CMakeLists.txt b/libs/gltfio/CMakeLists.txt index d12f24d2597..a638c6b401c 100644 --- a/libs/gltfio/CMakeLists.txt +++ b/libs/gltfio/CMakeLists.txt @@ -47,9 +47,16 @@ 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 + src/extended/AssetLoaderExtended.h + src/extended/TangentsJobExtended.cpp + src/extended/TangentsJobExtended.h + src/extended/TangentSpaceMeshWrapper.cpp + src/extended/TangentSpaceMeshWrapper.h ) # ================================================================================================== @@ -67,6 +74,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) @@ -83,7 +97,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/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/Animator.cpp b/libs/gltfio/src/Animator.cpp index 99a4269d023..6e53b705c60 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); @@ -394,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, 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/JitShaderProvider.cpp b/libs/gltfio/src/JitShaderProvider.cpp index 22fca3dfe14..b42acda5201 100644 --- a/libs/gltfio/src/JitShaderProvider.cpp +++ b/libs/gltfio/src/JitShaderProvider.cpp @@ -331,6 +331,7 @@ Material* createMaterial(Engine* engine, const MaterialKey& config, const UvMap& MaterialBuilder::TransparencyMode::DEFAULT) .reflectionMode(MaterialBuilder::ReflectionMode::SCREEN_SPACE) .targetApi(filamat::targetApiFromBackend(engine->getBackend())) + .stereoscopicType(engine->getConfig().stereoscopicType) .stereoscopicEyeCount(engine->getConfig().stereoscopicEyeCount); if (!optimizeShaders) { 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 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 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()); +} diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 9b7804e94f3..7cd8ce6b9e6 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -47,13 +47,22 @@ 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) + 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/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", 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",